Bulk jobs
Use bulk jobs when one request needs many transcripts. Submit a channel or playlist URL (or a list of video IDs), poll for progress, then download the results as a ZIP. Supports cancellation with prorated refund.
Read endpoints also see jobs created in the website and MCP, including standalone comments collections. Check kind before interpreting results. POST /jobs creates transcript jobs only; standalone comments collections are started in the website.
Lifecycle
- Submit: POST a URL or video_ids array with an Idempotency-Key. The API lists the source before charging and returning a
job_id; a large source can take time to resolve. - Poll: GET
/jobs/:idevery 2 to 5 seconds untilstatusis terminal. - Inspect (optional): GET
/jobs/:id/videosfor per-video status and transcripts. - Export: GET
/jobs/:id/exportto download all completed transcripts as a ZIP. - Cancel (optional): DELETE
/jobs/:idat any time before terminal state to halt processing. Refunds every video that didn't deliver a successful transcript: still-pending videos plus any that had failed during processing but weren't yet refunded.
List recent jobs
/api/v1/jobs| Name | Type | Description |
|---|---|---|
limit | number | Default 10, maximum 50. |
cursor | string | next_cursor from the previous list response (a job UUID). Omit on page one. Not interchangeable with the per-video cursor. |
curl -H "Authorization: Bearer yvt_live_..." \
"https://api.youtubevideotranscript.io/api/v1/jobs?limit=10"Returns total, jobs, and next_cursor. Jobs are newest first, from the account's last 100 days by creation time, across the website, API, and MCP. Each row contains the status fields below plus a nullable title from its first video. The detail-only message is not included in list rows. Stop at a null cursor or an empty page. An exact full final page can return a cursor followed by an empty page. Invalid UUID cursors return 400.
Submit a job
/api/v1/jobsHeaders
Authorization: Bearer yvt_live_...
Idempotency-Key: <required>
Content-Type: application/jsonBody (URL form)
{
"url": "https://www.youtube.com/@channelname",
"format": "txt"
}Body (video IDs form)
For power users who already enumerated the channel themselves.
{
"video_ids": ["abc12345678", "def12345678"],
"format": "txt"
}Body fields
| Name | Type | Description |
|---|---|---|
include_comments | boolean | Default false. Reserve one extra credit per video for the first Top comments page, fetched only after its transcript succeeds. Empty or failed comments are refunded and do not fail the transcript. |
url | string | A YouTube channel, playlist, or video URL. Same parser as /resolve. Either url or video_ids is required. |
video_ids | string[] | Explicit list of 11-char video IDs. Either url or video_ids is required. |
format | string | Default export format. Can be overridden per-call at export time. See Export formats. Defaults to txt. |
Plan limits
With include_comments: true, 100 videos reserve 200 credits: 100 for transcripts and up to 100 for comments. One Top page usually contains about 20 comments; no additional pages or replies are fetched. Unused credits are refunded when the job finishes or is canceled. Existing plan caps and Idempotency-Key protection still apply.
| Plan | Max active bulk jobs | Max videos per job |
|---|---|---|
| Free | 0 (bulk denied) | None |
| Starter | 1 | 500 |
| Pro | 2 | 2,000 |
| Business | 3 | 10,000 |
Channel and playlist resolution stops once the request exceeds your plan's per-job video cap. Very large channels can take a few minutes to resolve, so keep the same Idempotency-Key when retrying the same submission.
REST does not silently clip a channel or playlist to your balance or plan cap. An oversized source returns 413; insufficient credits return 402, with no job charge. This differs from MCP's channel reservation flow. Use video_ids to submit an explicit subset. After a network failure or timeout with an unknown outcome, retry with the same key and body to recover the original response. An explicit active-job-cap rejection instead requires a new key after capacity is available; see rejected submissions.
Response 201 Created
{
"job_id": "9f3a8b2e-1d4c-4e7a-b9c1-2f6e8d5a3c7b",
"status": "queued",
"total_videos": 487,
"include_comments": false,
"credits_reserved": 487,
"status_url": "/api/v1/jobs/9f3a8b2e-1d4c-4e7a-b9c1-2f6e8d5a3c7b",
"results_url": "/api/v1/jobs/9f3a8b2e-1d4c-4e7a-b9c1-2f6e8d5a3c7b/videos"
}Errors
| Status | Type | When |
|---|---|---|
| 400 | bad_request | Missing Idempotency-Key; both url and video_ids missing; invalid format; URL unrecognized; bad video_id in array; resolution returned zero videos |
| 402 | insufficient_quota | Not enough credits for the resolved video count |
| 403 | forbidden | Free plan |
| 409 | idempotency_conflict | Same Idempotency-Key reused with a different body, or concurrent retry while first is in flight |
| 413 | request_too_large | Exceeded the per-job video cap for your plan |
| 429 | rate_limited | Either per-minute rate limit OR active-bulk-job cap reached. Check error.message to disambiguate. Per-minute: wait for Retry-After and keep the key. Active-job cap: wait for capacity, then use a new key because this rejection is cached. |
| 503 | service_degraded | Bulk job submissions are temporarily unavailable. Retry in a few minutes. Sync transcript requests are unaffected. |
Poll status
/api/v1/jobs/:idThis endpoint is intended for polling. A reasonable interval is every 2 to 5 seconds while status is processing.
Response 200
{
"id": "9f3a8b2e-1d4c-...",
"kind": "transcripts",
"comments_pages": 0,
"comments_count": 0,
"message": null,
"status": "processing",
"format": "txt",
"source_url": "https://www.youtube.com/@channelname",
"source_type": "channel",
"total_videos": 487,
"videos_completed": 342,
"videos_failed": 5,
"include_comments": false,
"comments_completed": 0,
"created_at": "2026-05-21T12:34:56.789Z",
"updated_at": "2026-05-21T12:39:12.456Z",
"status_url": "/api/v1/jobs/...",
"results_url": "/api/v1/jobs/.../videos"
}Status values
resolving: discovering the source for a website or MCP job. Not terminal; the video count may still be provisional.pending: accepted, processing has not started yet.processing: at least one video has started.completed: processing ended. Transcript jobs have finished all their items; comments collections may have stopped at their credit limit with partial results. Readmessageand coverage.canceled: canceled through the website or API.failed: job ended without completing all videos.
Do not infer completion from counters alone. Use the status: comments jobs can leave videos skipped, so completed plus failed can be smaller than total_videos even at a terminal state.
Kinds and comments counters
kind: "transcripts": a transcript collection, optionally with Top comment samples.include_comments: whether that transcript job reserved comment samples.comments_completed: successful nonempty comment samples saved alongside transcripts.kind: "comments": a standalone collection; no transcripts were fetched.comments_pagesandcomments_count: paid nonempty pages and saved comment rows in a standalone collection.message: nullable job-level error or stopping explanation, available on the detail endpoint.
Errors
404 not_found if the job doesn't exist or doesn't belong to your account.
Per-video results
/api/v1/jobs/:id/videosCursor-paginated per-video status, oldest first, with optional saved transcripts and comment samples. For standalone comments jobs this endpoint reports video status but not the collected comment rows; download those through export.
Query parameters
| Name | Type | Description |
|---|---|---|
cursor | string | Opaque cursor from the previous response's next_cursor. Omit on the first call. |
limit | number | Default 100, max 500. |
status | string | Filter to one status: pending, processing, completed, failed, or skipped (standalone comments work left uncollected at cancellation or a budget stop). |
include | string | Pass transcript to include per-video transcript arrays inline. Otherwise the transcript field is absent from each row. Pass comments for saved comments and errors, or transcript,comments for both. Reading saved results does not fetch again or charge credits. |
Response 200
{
"videos": [
{
"video_id": "abc12345678",
"title": "Video Title",
"status": "completed",
"language": "en",
"error_message": null,
"transcript": [{ "text": "...", "start": 0.0, "duration": 1.5 }]
},
{
"video_id": "def12345678",
"title": null,
"status": "failed",
"language": null,
"error_message": "Video def12345678 is not playable: This video is unavailable"
}
],
"next_cursor": "eyJ0IjoiMjAyNi0wNS0..."
}next_cursor: null means the last page.
With include=comments, each row also has nullable comments and comments_error. A saved sample is{ total_comments, comments: [...] }; its rows use the public comment fields without reply-continuation tokens. An empty successful sample has an empty array; a failed or absent sample is null. Readback does not fetch or charge again.
Export as ZIP
/api/v1/jobs/:id/exportDownload all completed transcripts as a ZIP archive. Only available after the job reaches a terminal state. Failed videos are excluded; only completed rows are included.
Jobs created with include_comments: true also contain a comments/VIDEO_ID.json file for each completed transcript. It contains the saved Top page, an empty comments array, or a comments_error if fetching failed. Comments stay JSON regardless of the selected transcript format. The website also returns a ZIP when downloading one selected video from a comments-enabled job.
Query parameters
| Name | Type | Description |
|---|---|---|
format | string | Override the job's stored format. One of txt, srt, json, csv, docx, markdown. An unrecognized value falls back to the job's stored format. Use /transcript/:id if you want strict validation. |
Standalone comments ZIP
For kind: "comments", this same export route returns comments-<8-char-job-id>.zip. The format parameter is ignored: both JSON and CSV are included. The ZIP contains manifest.json plus VIDEO_ID.comments.json and VIDEO_ID.comments.csv for each video with saved comments, including partial results from failed or skipped videos. There are no transcript files. If discovery attached no videos, export returns 404.
The manifest records options, the credit limit, credits used, saved comment count, stopping message, and each video's status and has_comments flag. A completed job can be budget-limited; check this manifest rather than assuming all comments were collected.
Transcript ZIP response 200
Content-Type: application/zipContent-Disposition: attachment; filename="transcripts-<8-char-job-id>.zip"- Each entry in the ZIP is
<sanitized-title-or-videoId>.<ext>. Duplicate titles get(2),(3), etc. suffixes. - The JSON shape inside the ZIP differs from the sync transcript response. See Export formats.
Errors
| Status | Type | When |
|---|---|---|
| 404 | not_found | Job doesn't exist, doesn't belong to you, or has zero completed transcripts (for a transcript job), or no discovered videos (for a standalone comments job) |
| 409 | conflict | Job is still processing. It must reach a terminal state first |
| 410 | gone | Export expired (100 days after job became terminal). |
Cancel
/api/v1/jobs/:idCancel a nonterminal job of either kind. A transcript job refunds undelivered transcripts and unused comment reservations. A standalone comments job refunds its unused page budget. Already saved results remain available for export.
Response 200
{
"id": "9f3a8b2e-...",
"status": "canceled",
"total_videos": 487,
"videos_completed": 342,
"videos_failed": 145,
"refunded": 145
}refunded is the total credits returned to your original billing cycle by this cancellation. It counts credits, not videos: optional comments can increase it. In the transcript-only example above, 342 completed, 5 failed early in processing, 140 were still pending, so cancel refunds 5 + 140 = 145. Refunds from an expired billing cycle do not top up the new cycle.
- Job becomes downloadable via
/exportimmediately. Partial results are available. - A video already being fetched may continue in the background, but it is not included in the canceled job unless it had already completed.
- Two concurrent DELETE calls serialize cleanly: only the winning call returns
refunded > 0. The other returns 409.
Errors
| Status | Type | When |
|---|---|---|
| 404 | not_found | Job doesn't exist or doesn't belong to you |
| 409 | conflict | Job is already in a terminal state (completed, failed, or canceled) |
Full example
KEY=yvt_live_...
HOST=https://api.youtubevideotranscript.io
# Submit
JOB=$(curl -sS -H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"url": "https://www.youtube.com/@channelname", "format": "txt"}' \
"$HOST/api/v1/jobs")
JOB_ID=$(echo "$JOB" | jq -r .job_id)
# Poll until done
while true; do
STATUS=$(curl -sS -H "Authorization: Bearer $KEY" "$HOST/api/v1/jobs/$JOB_ID" | jq -r .status)
echo "status=$STATUS"
case "$STATUS" in
completed|failed|canceled) break ;;
esac
sleep 5
done
# Download
curl -OJ -H "Authorization: Bearer $KEY" "$HOST/api/v1/jobs/$JOB_ID/export"