FeaturesHow it worksPricingBlog
All articles
EngineeringMay 5, 2025 · 10 min read

YouTube Data API: Auto-Sync Shorts to Your Website

The Core Challenge: YouTube Has No isShort Flag

The YouTube Data API v3 does not expose a dedicated field to identify Shorts. There is no `video.isShort = true`. This surprises every developer who tries to build a Shorts sync system.

The practical workaround: filter by duration ≤ 60 seconds. Duration is available for any video via `contentDetails.duration`. For videos you own, `fileDetails` adds aspect ratio — but that requires OAuth as the channel owner.

The Sync Architecture

For **public channels**:

channelId
  → channels.list(part=contentDetails) → uploadsPlaylistId
  → playlistItems.list(part=snippet) → [videoId, ...]
  → videos.list(part=contentDetails, id=id1,id2,...) → filter duration ≤ 60s
  → store metadata in database

For **authenticated channels** (owner connects via OAuth):

Same flow, plus `videos.list(part=fileDetails)` → filter `fileDetails.videoStreams[0].aspectRatio < 1` (portrait = < 1.0) for perfect accuracy.

Quota Management

YouTube API v3 gives you 10,000 units/day free. A channel with 100 Shorts costs ~4 units per full sync — enough for thousands of workspaces before needing a quota increase.

**Don't poll.** Polling burns quota and introduces delay. Use webhooks instead.

PubSubHubbub: Real-Time Detection

YouTube implements WebSub (PubSubHubbub). Subscribe and receive a POST within seconds of any new video being published.

Subscribe:

POST https://pubsubhubbub.appspot.com/subscribe

hub.callback=https://yourapp.com/api/youtube/webhook
hub.topic=https://www.youtube.com/xml/feeds/videos.xml?channel_id=CHANNEL_ID
hub.mode=subscribe
hub.lease_seconds=864000

Verify (YouTube sends a GET with hub.challenge):

export async function GET(req: Request) {
  const { searchParams } = new URL(req.url)
  const challenge = searchParams.get("hub.challenge")
  if (challenge) return new Response(challenge, { status: 200 })
  return new Response("Bad Request", { status: 400 })
}

Handle new video pings (POST with Atom XML):

export async function POST(req: Request) {
  const body = await req.text()
  const videoId = extractVideoId(body)
  // Enqueue job: fetch details, check if Short, add to feed
  await enqueueSync({ videoId })
  return new Response("OK", { status: 200 })
}

Subscriptions expire every 10 days. Re-subscribe with a daily cron:

SELECT cron.schedule('resubscribe-webhooks', '0 6 * * *',
  $$SELECT resubscribe_expiring_channels()$$);

Result

A Short published on YouTube appears in your embed widget within ~30 seconds — no polling, ~2 API units consumed, well within the free quota even at hundreds of connected channels. This is exactly how EmbedShorts works.

Ready to add Shorts to your site?

Free plan available. No credit card. 2-minute setup.

Start for free →