Skip to content

YouTube channel videos

Point the platform at a YouTube channel and every upload becomes a card on your board — title, thumbnail, and an inline player. Leave the schedule running and a newly published video shows up on its own.

There is no separate “videos” screen: a video is an entity: 'link' Task whose sourceUrl is the watch URL, which is exactly what a link you paste by hand is. That one decision is why the feature needs no new route, table, or client — the board already knows how to render and play it, the universal /tasks/$id editor already edits it, and the tag filters already slice it.

Sidebar: Business → ContentYouTube.

Reading a channel’s uploads is public data, so this uses a plain API key — not OAuth. There are no scopes to grant and nothing for a user to consent to.

  1. In the Google Cloud console, pick a project (the one behind your Gmail OAuth app is fine — enabling an API is per-project and independent of the OAuth consent screen).
  2. Enable “YouTube Data API v3” for it.
  3. Credentials → Create credentials → API key.

Channels → Connect an account → YouTube (channel videos). Host youtube.com, a channel handle for the account label, and paste the key into YouTube Data API key.

The key is stored encrypted on a youtube:// channel and read per run. Setting a YOUTUBE_* environment variable does nothing — like notion:// and gmail://, credentials live on the channel row, not in env.

One connected youtube:// channel serves every YouTube channel you schedule: the key authenticates the API, while which YouTube channel to read is a per-schedule input.

Scheduled jobs → New → YouTube videos to cards.

InputMeaning
Channel (required)An @handle, the channel name, its UC… id, or any youtube.com channel URL — /channel/UC…, /@handle, and the legacy /user/… and /c/… forms all resolve.
Videos per runHow many uploads one run may import. Default 25, hard ceiling 200.

Pick a cron (remember schedules fire in UTC). Hourly is plenty — the warm path is cheap, see below.

The first run backfills up to Videos per run; every run after that adds only what published since. Raising the cap later does not reach further back — the cursor has already moved past those videos. To re-backfill a channel, delete its row from ingestion_watermarks (source = 'youtube') and run again.

Each card is built the same way a pasted link is (buildLinkCreate), so it carries:

  • entity: 'link', sub-status captured → the board’s To do column
  • sourceUrl = https://www.youtube.com/watch?v=<id> — the field the card’s play button reads, so the video plays inline in youtube-nocookie.com
  • the video’s maxres thumbnail as its link preview
  • tags from:youtube and youtube:<channel>

youtube is a reserved tag prefix, so the per-channel tag does not spawn its own Inbox menu — the Business → Content entry is the single way in. Filter to one channel with /tasks?tags=youtube:<channel>, and hide videos from the unfiltered board with notTags=from:youtube.

The uploads playlist is newest-first and a video’s publish date never changes, so one cursor per (schedule, channel) is enough: the reader stops paging at the first upload at or before it. A warm run costs two quota units — one channels.list, one playlistItems.list — against a default budget of 10,000 per day.

Two guards stop duplicates, and they guard different things. The watermark is the cheap one (don’t fetch what we’ve seen); a batched idempotency lookup on the watch URL is the correct one, because the cursor can’t help a cold re-run after the watermark is cleared. The cursor advances to the newest imported publish date — never to now — so it can’t drift against YouTube’s clock or lose a video published mid-run, and it advances only after the cards are written.

A new video is the notification: it arrives as a fresh todo card, the same way calendarToTasks and captureComments work. There is no email or push path, and no WebSub/PubSubHubbub subscription — real push would need a public unauthenticated callback, lease renewal every ~5 days, and HMAC verification.

If you want mail as well, chain a dispatch step to email://${setting:notificationEmail} in a custom flow.

The run saysWhat to do
“requires a connected youtube:// channel carrying a YouTube Data API key”No youtube:// channel, or its apiKey secret is empty. See step 2.
“the API key is restricted to HTTP referrers…”The trap in step 1. Change Application restrictions to None or IP addresses.
“YouTube rejected the API key…”The key is wrong, or YouTube Data API v3 isn’t enabled for its project.
“No YouTube channel found for ’…’”The handle/URL doesn’t resolve. A watch URL names a video, not a channel — use the channel’s own page.
“daily quota is exhausted”10,000 units/day, shared across everything using that project. It resets at midnight Pacific; the next scheduled run picks up where it left off.
Runs fine, 0 createdExpected once the backfill is done — the cursor is at your newest video.

Out of scope. An API key sees only public uploads, which is the whole published catalogue for a normal channel. Private/unlisted videos, drafts, and analytics need OAuth with youtube.readonly — a different credential model, not a setting on this youtube:// channel.