Error codes
Every code the API returns, what causes it, and whether retrying will help.
Every error has the same shape, whatever went wrong.
| Field | What it is for |
|---|---|
code | Stable and machine-readable. Branch on this, never on the message. |
message | What went wrong, for a human reading a log. |
how_to_fix | What to do about it. Always present: read it before retrying anything. |
Every code
| Status | Code | Cause | Retry? |
|---|---|---|---|
| 400 | bad_request | Malformed JSON, a missing required field, or a URL that is neither media nor a recognised platform. Also a time-coded export of a transcript with no timings. | No, fix the request |
| 401 | unauthorized | Missing, unknown, revoked or expired key. All four are reported identically. | No, fix the key |
| 402 | insufficient_credits | The balance is below what the episode needs. | After topping up |
| 403 | insufficient_scope | The key is valid but lacks a scope this endpoint requires. | No, mint a new key |
| 403 | plan_required | The plan has no API access, or the operation needs write access. | After changing plan |
| 404 | not_found | No such id, or it belongs to another account. Deliberately indistinguishable. | No |
| 422 | source_unavailable | The platform could not give us usable audio, most often a YouTube video with no caption track. | No, the source will not change |
| 429 | rate_limited | Per-minute, per-day or per-operation limit. | Yes, after Retry-After |
| 429 | too_many_in_flight | At the plan’s concurrency cap for transcriptions. | Yes, once one of yours finishes |
| 429 | ai_daily_limit_reached | The account has used its daily AI fair-use allowance. Counts new generations only, fetching a cached one is free and unmetered. Per ACCOUNT, not per key. | Yes, after Retry-After, which is the reset |
| 403 | ai_operation_too_large | The transcript is long enough that this one operation needs more AI units than the plan allows in a whole day. Distinct from the 429 above: the allowance is not spent, it is too small for this episode. | No, waiting does not help. Upgrade, or use a shorter episode |
| 429 | ai_rate_limited | Too many AI generations in one minute. Usually something regenerating in a loop. | Yes, after Retry-After |
| 403 | ai_pass_budget_reached | The episode was paid for with a one-time pass, and the fixed AI budget that pass carries for it is spent. Lifetime, not daily: a pass is bought once, so nothing resets. Anything already generated stays readable. | No, a plan carries a daily allowance |
| 403 | translation_limit_reached | This transcript already holds as many distinct languages as the plan allows. Not plan_required, the plan does include translation. | No, reuse a language, or force |
| 500 | internal_error | Our fault. | Yes, after a few seconds |
| varies | transcription_failed | The job could not be created. The status carries the reason: 400 unreachable audio, 413 a file over the plan’s upload cap, 422 a host that refused us, 500/502 our side or the speech service. | Only on 5xx |
| 502 | source_unavailable | We could not fetch the media. For TikTok this is usually the extraction worker being busy or slow rather than anything about the link. | Sometimes, see below |
| 502 | ai_unavailable | Show notes, chapters or translation failed upstream. A model provider being degraded, not a bad request. | Yes, after a few seconds |
| 503 | service_unavailable | Credit-spending operations are paused system-wide for the rest of the day. Not a limit on your account. | Yes, after Retry-After |
503 is ours, not yours. We have paused every credit-spending operation across all accounts. Upgrading will not lift it and neither will slowing down. Honour Retry-After. Deliberately not a 429, so it cannot be mistaken for your own budget.
Not every 402 is about credits. Two plan limits arrive as 402 rather than 403: an episode over the free plan’s 30-minute cap, and a paid-only format. Topping up clears neither: read message, not just code.
The two 429s are not the same
The distinction most worth encoding in your client.
| `rate_limited` | `too_many_in_flight` | |
|---|---|---|
| Means | You are calling too fast | You have too many transcriptions running |
| Fixed by | Waiting, then slowing down | One of *your* jobs finishing |
| Exponential backoff | Correct | Pointless on its own |
| Right response | Honour Retry-After | Poll ?status=processing, start one as each completes |
Retrying well
- Retry
429,500and network failures. Nothing else: a400will fail identically forever. - Honour
Retry-Afterwhen it is present; it is not a suggestion. - Exponential backoff with jitter after that. Without jitter, everything you deprived of a slot retries in lockstep.
- Cap total attempts. An unbounded retry loop against a permanent error is how an incident becomes an outage.
- Retry
503too, it is a system-wide pause, not your account, andRetry-Aftersays when it lifts. POST /api/v1/transcriptionsis not idempotent: a retry after an unclear failure can start a second transcription and spend credits twice. Check?status=processingbefore retrying a create.
source_unavailable, in detail
| Platform | Status | Why | What to do |
|---|---|---|---|
| YouTube | 422 | No caption track, we never download audio. | Use a video with captions, or pass the audio as audio_url. |
| TikTok | 502 | The extraction worker was at capacity, or the download outran the request budget. | Retry once. If it keeps failing the video is private, removed or region-locked. Pass the audio as audio_url. |
| Spotify | 422 | Spotify Originals and Exclusives have no public RSS feed, and a deleted or region-locked episode has no fetchable audio. | Try another episode from the show, or pass a direct .mp3 as audio_url. |
| Apple Podcasts | 422 | The episode parsed, but its feed no longer offers that enclosure. | Try another episode, or pass the audio directly. |
A 500 carries no detail on purpose: error text is a place internals leak. Retry after a few seconds; if it persists, mail support@podcaststotext.com with the endpoint, the approximate time, and the id if you have one.