ChannelText API
Version 1.0.0 · Base URL https://channeltext.com/api/v1 · OpenAPI 3.1 document
Get transcripts of YouTube videos, playlists and whole channels programmatically. The API uses the same prepaid balance, jobs and files as the website.
Authentication: create an API key on your account page and send it as Authorization: Bearer <key>. The key is shown once; only its prefix is kept visible. Revoked keys get 401 unauthorized.
Billing: $0.008 per delivered transcript, from your prepaid balance. Before a job starts, the cost of up to maxVideos transcripts is reserved (capped by your balance, so it never goes negative). When the job ends you are charged exactly delivered x $0.008 and the rest is released. Videos without captions, blocked, unavailable or over the limit are free. Below $0.008 a job is refused with 402 insufficient_balance.
Idempotency: send an Idempotency-Key header (any string up to 255 characters) with POST /jobs. Repeating the same key with the same body returns the original job (200, header Idempotent-Replayed: true) without starting or charging anything new. The same key with a different body returns 409 idempotency_conflict. Keys are scoped to your account and do not expire. A request that was refused (4xx) does not use up its key.
Rate limit: 60 requests in a burst per API key, refilled at 60 per minute. Over the limit you get 429 rate_limited with a Retry-After header (seconds). Every response to a valid key carries X-RateLimit-Limit and X-RateLimit-Remaining.
Asking again for the same URLs: a job with exactly the same URL set as an earlier job of this account continues from what this account already received. For a channel or playlist with maxVideos, it takes the next videos not yet received (the next older ones) and charges them like any delivered transcript; it does not return the earlier videos again (field-tested 2026-09-26: the same channel with maxVideos 5, asked twice, gave 10 different videos, 5 charged each time). Videos already delivered that are listed again (expected when you ask again for a single video, or for a list without a cap that has no new videos; fixture-tested, not yet field-tested) come back as unchanged rows, with no text and no charge, whatever the language. Their text stays in the earlier job: find it with GET /jobs (your jobs, newest first) and read GET /jobs/{id}/results. This is what makes "update this channel" cheap. A different URL set (another spelling of the channel, or one more URL) is a new set: its videos are fetched and charged again.
Errors: every error is JSON { "error": { "code": "...", "message": "..." } }; job_in_progress also has jobId. Codes are stable; messages may change.
Endpoints
POST /jobs Start a job
Starts fetching the transcripts of one or more video, playlist or channel URLs. Responds as soon as the job has started; poll GET /jobs/{id} until state is no longer queued, running or cancelling. At most 500 videos per job.
Parameters
Idempotency-Key(header) Makes retries safe: the same key and body return the original job instead of starting a new one.
Body (JSON)
urls(required) Up to 50 YouTube video, Shorts, playlist or channel URLs (or video ids). A string may hold several, one per line.languageCaption language code such as en, es, pt-BR;original= the video's own language. Falls back to the original language.formatsFile types to produce. Text and timed segments are always included so /results can return them.maxVideosMost videos to process (newest first for channels). Reserve and charge are capped by this and by your balance.publishedAfterOnly videos published on or after this date (YYYY-MM-DD or ISO date-time).publishedBeforeOnly videos published on or before this date (YYYY-MM-DD or ISO date-time).
Responses
200Replay of an earlier request with the same Idempotency-Key and body: the original job, nothing new started or charged. HeaderIdempotent-Replayed: true.201Job started. Money for up toinput.maxVideostranscripts is reserved.400invalid_requestorinvalid_url401unauthorized402insufficient_balance409idempotency_conflictorjob_in_progress413invalid_request(body over 64 KB)415invalid_request(Content-Type is not application/json)429rate_limited(see the Retry-After header)500internal_error503server_busy(see the Retry-After header)
curl -X POST https://channeltext.com/api/v1/jobs \
-H "Authorization: Bearer $CT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: my-channel-2026-09-26" \
-d '{"urls": ["https://www.youtube.com/@somechannel/videos"], "maxVideos": 20, "formats": ["text", "srt"]}'GET /jobs Your jobs, newest first
Every job of this account (web, API and MCP), newest first, paginated with offset and limit. Use it to find an earlier job again, for example the one that delivered the videos a later request returned as unchanged.
Parameters
offset(query)limit(query)
Responses
200One page of jobs.400invalid_request(offset or limit)401unauthorized429rate_limited(see the Retry-After header)500internal_error
curl "https://channeltext.com/api/v1/jobs?limit=20" \
-H "Authorization: Bearer $CT_API_KEY"GET /jobs/{id} Job status and progress
Parameters
id(path, required) Job id returned by POST /jobs.
Responses
200The job.401unauthorized404not_found429rate_limited(see the Retry-After header)500internal_error
curl https://channeltext.com/api/v1/jobs/JOB_ID \
-H "Authorization: Bearer $CT_API_KEY"GET /jobs/{id}/results Transcripts and download links
Per-video text and timed segments, plus download links for each file and the whole job. Works while the job is running (videos delivered so far). Paginated with offset and limit.
Parameters
id(path, required) Job id returned by POST /jobs.offset(query)limit(query)
Responses
200Results page.400invalid_request(offset or limit)401unauthorized404not_found429rate_limited(see the Retry-After header)500internal_error
curl "https://channeltext.com/api/v1/jobs/JOB_ID/results?limit=50" \
-H "Authorization: Bearer $CT_API_KEY"GET /jobs/{id}/files/{file} Download a file
zip (every file plus all-transcripts.txt and report.csv), all.txt (all texts in one file), report.csv (one line per video, including failures), or one video's file as <index>.txt, <index>.srt, <index>.vtt or <index>.json (timed segments). Use the links in the results rather than building them.
Parameters
id(path, required) Job id returned by POST /jobs.file(path, required)
Responses
200The file, as an attachment.401unauthorized404not_found429rate_limited(see the Retry-After header)500internal_error
curl -OJ https://channeltext.com/api/v1/jobs/JOB_ID/files/zip \
-H "Authorization: Bearer $CT_API_KEY"GET /balance Prepaid balance
Money reserved for running jobs is already subtracted.
Responses
200The balance.401unauthorized429rate_limited(see the Retry-After header)500internal_error
curl https://channeltext.com/api/v1/balance \
-H "Authorization: Bearer $CT_API_KEY"GET /openapi.json This document
Responses
200OpenAPI 3.1 document.
curl https://channeltext.com/api/v1/openapi.jsonError codes
| Code | HTTP | Meaning |
|---|---|---|
invalid_request | 400 | The body, a parameter or a header is malformed or out of range (also 413 for a body over 64 KB and 415 for a body that is not JSON). The message says which field. |
invalid_url | 400 | One of the URLs is not a YouTube video, playlist or channel URL. Nothing was started or charged. |
unauthorized | 401 | The Authorization header is missing, or the API key is unknown or revoked. |
insufficient_balance | 402 | Your balance does not cover one transcript ($0.008). Top up on the website; nothing was started. |
not_found | 404 | No such job or file for this API key's account. Jobs of other accounts are reported the same way. |
idempotency_conflict | 409 | This Idempotency-Key was already used with a different request body. |
job_in_progress | 409 | A job with the same URL set is still running for this account. The error carries its `jobId`: poll that job instead of starting another. |
rate_limited | 429 | Too many requests for this API key. Wait the number of seconds in the Retry-After header. |
internal_error | 500 | Unexpected server error. Retry later; job creation is safe to retry with the same Idempotency-Key. |
server_busy | 503 | The server is running its maximum number of jobs. Retry after the Retry-After header. |