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 → Content → YouTube.
1. Get a YouTube Data API key
Section titled “1. Get a YouTube Data API key”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.
- 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).
- Enable “YouTube Data API v3” for it.
- Credentials → Create credentials → API key.
2. Connect it
Section titled “2. Connect it”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.
3. Schedule it
Section titled “3. Schedule it”Scheduled jobs → New → YouTube videos to cards.
| Input | Meaning |
|---|---|
| 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 run | How 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.
What you get per video
Section titled “What you get per video”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 columnsourceUrl=https://www.youtube.com/watch?v=<id>— the field the card’s play button reads, so the video plays inline inyoutube-nocookie.com- the video’s
maxresthumbnail as its link preview - tags
from:youtubeandyoutube:<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.
Why runs stay cheap
Section titled “Why runs stay cheap”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.
Notifications
Section titled “Notifications”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.
Troubleshooting
Section titled “Troubleshooting”| The run says | What 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 created | Expected once the backfill is done — the cursor is at your newest video. |
Private and unlisted videos
Section titled “Private and unlisted videos”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.
