Back to blog

Migration · Supadata · YouTube API

Migrating from Supadata to YTAPI: A Field Guide for Transcript Pipelines

Switching your YouTube transcript pipeline from Supadata to YTAPI.dev. Covers auth, parameter mapping, the milliseconds-to-seconds gotcha, response reshaping, and billing differences.

· 3 min read · Platform Team

If you're running a transcript pipeline on Supadata and evaluating alternatives, this guide covers the mechanical differences you need to handle when migrating to YTAPI.dev. Most of the work is a thin translation layer — the concepts map 1:1.

Before you switch, the measured comparison shows how the three APIs behave on uncached videos, and pricing compared works out what each costs at your volume.

1. Authentication: zero changes

Supadata authenticates with an x-api-key header. YTAPI accepts the same header (header names are case-insensitive), plus Authorization: Bearer as an alternative:

# Supadata
curl -H "x-api-key: YOUR_KEY" "https://api.supadata.ai/v1/transcript?url=..."

# YTAPI — same header works
curl -H "x-api-key: YOUR_KEY" "https://api.ytapi.dev/v1/transcripts?video_id=..."
# or
curl -H "Authorization: Bearer YOUR_KEY" "https://api.ytapi.dev/v1/transcripts?video_id=..."

2. Endpoint and parameter mapping

SupadataYTAPINotes
GET /v1/transcript?url=GET /v1/transcripts?video_id=YTAPI takes a video ID, not a URL. Extract the 11-char ID from your URL, or keep passing the URL and parse it client-side
lang=enlanguages=en (alias lang)YTAPI also accepts the lang alias, so this often needs no change
text=trueformat=textPure concatenated text
text=false (default)format=segments (default)Timestamped segments
mode=nativetrack_policy=manual_first (default)Creator-uploaded captions preferred
mode=generatetrack_policy=asr_firstAuto-generated captions preferred
mode=auto(default behavior)YTAPI falls back automatically
chunkSize=2000—No equivalent; chunk client-side if you relied on it

Minimal migration diff:

# Before (Supadata)
resp = requests.get(
    "https://api.supadata.ai/v1/transcript",
    headers={"x-api-key": KEY},
    params={"url": video_url, "lang": "en", "text": False},
)
segments = resp.json()["content"]

# After (YTAPI)
video_id = video_url.split("v=")[-1].split("&")[0]  # or use urllib.parse
resp = requests.get(
    "https://api.ytapi.dev/v1/transcripts",
    headers={"x-api-key": KEY},
    params={"video_id": video_id, "lang": "en"},
)
segments = resp.json()["segments"]

3. The big gotcha: milliseconds vs seconds

This is the one semantic difference that will silently corrupt your data if you miss it. Supadata returns timestamps in milliseconds with an offset field; YTAPI returns seconds (float) with a start field:

// Supadata: text=false
{ "content": [{ "text": "hello", "offset": 1250, "duration": 2000, "lang": "en" }] }

// YTAPI: format=segments
{ "segments": [{ "text": "hello", "start": 1.25, "duration": 2.0 }] }

Translation: start = offset / 1000, duration = duration / 1000. If your downstream code does arithmetic on timestamps (clip boundaries, subtitle rendering, alignment), divide by 1000 at the boundary.

4. Response envelope

Supadata wraps everything in {content, lang, availableLangs}. YTAPI returns the payload directly with richer metadata:

// YTAPI format=segments response (top-level fields)
{
  "video_id": "dQw4w9WgXcQ",
  "language": "en",
  "track_kind": "manual",
  "duration_seconds": 213.5,
  "has_word_level": false,
  "segments": [{ "text": "...", "start": 0.0, "duration": 4.12 }]
}

availableLangs maps to YTAPI's video info endpoint: GET /v1/videos/{id}/basic-info returns available_languages — and the lookup is free.

5. Billing and error handling differences

SupadataYTAPI
Transcript cost1 credit (native)1 credit
"No captions" responseHTTP 206 transcript-unavailable — still billed 1 credit4xx captions_disabled — not billed
Failed callsVaries0 credits, always
Credit expiryMonthly reset, no rolloverNever expires

Two things to fix in your error handling:

  1. Stop treating 206 as success. On YTAPI, missing captions is a proper 4xx (captions_disabled / language_not_found) and costs you nothing. You can drop the "was I billed for an empty transcript?" accounting.
  2. Error shape changes from {error, message, details, documentationUrl} to {error: {code, message}} with snake_case codes (invalid_request, unauthorized, rate_limited, insufficient_credits).

6. Async jobs

If you used Supadata's 202 + jobId polling for large videos or AI-generated transcripts, note that YTAPI's single-transcript endpoint is synchronous. For bulk work, use the batch API instead: POST /v1/batch with up to 100 tasks, then poll GET /v1/batch/{id} — each task reports its own HTTP status, so partial failures don't fail the job.

Checklist

  • Swap base URL and keep the x-api-key header
  • Change url= to video_id= (extract the ID)
  • Map text=true/false to format=text/segments
  • Divide offset/duration by 1000, rename offset → start
  • Unwrap {content} → read segments directly
  • Replace 206 handling with 4xx handling (and enjoy not being billed for it)
  • Update error parsing to {error: {code, message}}

FAQ

Can I run both APIs while I migrate?

Yes. Both use an API key in a header, so you can call both for the same videos, compare the output, and switch traffic over gradually. YTAPI's 200 free credits are enough to check your own sample first.

What about videos without captions?

Supadata can generate a transcript with AI for those; YTAPI returns a 404 instead, which isn't billed. If you rely on AI transcripts for some videos, keep Supadata for those, or send them to your own speech-to-text.

Do I need to change my timestamp handling?

Yes. Supadata returns milliseconds and YTAPI returns seconds, so divide by 1,000 (section 3). It's the one change that breaks silently if you miss it.