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
| Supadata | YTAPI | Notes |
|---|---|---|
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=en | languages=en (alias lang) | YTAPI also accepts the lang alias, so this often needs no change |
text=true | format=text | Pure concatenated text |
text=false (default) | format=segments (default) | Timestamped segments |
mode=native | track_policy=manual_first (default) | Creator-uploaded captions preferred |
mode=generate | track_policy=asr_first | Auto-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
| Supadata | YTAPI | |
|---|---|---|
| Transcript cost | 1 credit (native) | 1 credit |
| "No captions" response | HTTP 206 transcript-unavailable — still billed 1 credit | 4xx captions_disabled — not billed |
| Failed calls | Varies | 0 credits, always |
| Credit expiry | Monthly reset, no rollover | Never expires |
Two things to fix in your error handling:
- 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. - 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-keyheader - Change
url=tovideo_id=(extract the ID) - Map
text=true/falsetoformat=text/segments - Divide
offset/durationby 1000, renameoffset→start - Unwrap
{content}→ readsegmentsdirectly - 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.