API
API errors and limits
The error object, HTTP statuses, every error code with its CLI exit code, rate limits, quotas, upload limits and how to retry safely.
On this page
Errors come in two shapes. A request that is refused gets an HTTP error with a snake_case code. A job that fails later carries the engine's error, with an upper-case code. The same upper-case codes are used by the CLI and the MCP server. A code that means "out of minutes" in one place means it everywhere.
The error object
A refused request
A request that cannot be accepted gets an HTTP status and this body:
{ "error": { "code": "source_auth_required", "message": "Instagram needs a login to read this video.", "engineCode": "SOURCE_AUTH_REQUIRED", "hint": "Download the video or its audio yourself and upload the file instead.", "retryable": false, "stage": "resolve" }}| Field | Description |
|---|---|
code | A stable snake_case code. Branch on this. The table below lists them. |
message | A sentence for a person. Do not parse it. |
engineCode | The engine's code, when the problem is one of those in Error codes. |
hint | What to do next, when there is one. Written to be shown to users. |
retryable, stage | As in a job's error, when the engine raised it. |
The hint in this example tells people to download the file and upload it.
Some errors carry more fields: minutesNeeded and minutesRemaining on quota_exceeded and anonymous_limit, and upload_id when a multipart request was refused after the file arrived. A Retry-After header says how many seconds to wait on a 429 or 503.
A failed job
A job that fails, and the error event that ends its stream, carry the engine's error:
{ "code": "SOURCE_BLOCKED", "message": "YouTube refused the download (HTTP 403).", "retryable": true, "scope": "job", "stage": "download", "hint": "Update yt-dlp: brew upgrade yt-dlp"}| Field | Description |
|---|---|
code | A stable, upper-case code. Branch on this. |
message | A sentence for a person. Do not parse it. |
retryable | Whether trying the same request again can help. |
scope | job when only this input failed. account when the account has a problem, such as no minutes. account errors will repeat for every job until someone acts. |
stage | Where it happened: resolve, download, extract, transcribe, synthesize and the others in The JSON protocol. |
hint | What to do next. Written to be shown to users. |
status, retryAfterMs | The upstream HTTP status and wait, when there was one. |
For a job, the error is in error on GET /v1/jobs/:id. A canceled job has the code CANCELLED. Error messages never contain keys or credentials. When Scribiz's own connection to the model service fails or runs out of quota, you see INTERNAL or RATE_LIMIT with a hint to try again, never an account problem of yours.
HTTP statuses
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request | The body, a field or an id is not valid | Fix the request. The message says what. |
| 400 | length_mismatch | The upload was shorter than its Content-Length | Send it again |
| 401 | invalid_api_key, unauthorized | No key, or a bad one | Fix the key |
| 402 | quota_exceeded | Out of minutes | Add minutes, or wait for the next period |
| 403 | insufficient_scope, session_required | The key's scope or kind does not allow it | Use a key with the right scope, or a session |
| 403 | account_required, turnstile_required, turnstile_failed | Anonymous use does not allow it | Sign in, or send a Turnstile token |
| 403 | not_owner | Only the creator of a job can cancel or delete it | Do not retry |
| 404 | not_found, upload_not_found | No such job or upload, or it belongs to someone else | Do not retry |
| 409 | job_not_ready, upload_in_use, key_limit_reached | The thing is in the wrong state | Wait, or fix the state |
| 410 | job_expired, upload_expired | The result or the upload has expired | Run it again |
| 411 | length_required | An upload needs a Content-Length | Send one |
| 413 | video_too_long, body_too_large | Over the length limit of the plan, or the upload is over 100 MB | Send less |
| 415 | unsupported_media, unsupported_media_type | The file is not media Scribiz can read | Send another file |
| 422 | url_unsupported, source_unavailable, source_live, source_auth_required, input_unsupported, idempotency_key_reused and others | The link or the input cannot be used | Fix the request. A source error carries engineCode and a hint. |
| 429 | rate_limited, anonymous_limit, too_many_in_flight, too_many_uploads, too_many_streams | A limit was reached | Wait for Retry-After, then retry |
| 451 | blocked_content | The video was removed after a takedown request | Do not retry |
| 500, 502, 503, 504 | internal_error, server, network, timeout, busy, not_configured and others | Something failed on our side or upstream | Retry with a growing pause |
| 501 | billing_not_configured | This is not open for purchase on this server | Do not retry |
A source error from the engine becomes the lower-case form of its code with the engine code beside it: source_unavailable with engineCode: "SOURCE_UNAVAILABLE". Its status is 422 for a problem with the link, 413 for a video that is too long, and 502 to 504 for a problem upstream.
Error codes
These are the engine's codes. Jobs, the event stream, the CLI and MCP use them as written. The API's own refusals use the snake_case form with the engine code beside it. Every code, whether a retry can help, and the exit code the CLI uses. See Commands for the exit codes.
The input
| Code | Meaning | Retry | Exit |
|---|---|---|---|
INPUT_NOT_FOUND | The file or upload does not exist | No | 4 |
INPUT_UNSUPPORTED | The file cannot be read as audio or video | No | 4 |
INPUT_CORRUPT | The file is damaged | No | 4 |
NO_AUDIO_STREAM | There is no audio. Watch can still run | No | 4 |
URL_UNSUPPORTED | The link is not one Scribiz can use | No | 4 |
The source
| Code | Meaning | Retry | Exit |
|---|---|---|---|
SOURCE_UNAVAILABLE | Private, deleted or restricted in a region | No | 4 |
SOURCE_AUTH_REQUIRED | The site needs a login | No | 4 |
SOURCE_BLOCKED | The site refused a server: a 403, a bot check | Only if another way is left | 4 |
SOURCE_LIVE | A live stream | No | 4 |
SOURCE_TOO_LONG | Over your plan's length limit | No | 4 |
TOOL_MISSING | ffmpeg, ffprobe or yt-dlp is not installed | No | 6 |
The service
| Code | Meaning | Retry | Exit |
|---|---|---|---|
AUTH | The credential was rejected | No | 3 |
QUOTA | Out of minutes, or the model service's quota is used up | No | 3 |
RATE_LIMIT | Slow down. Honor Retry-After | Yes | 5 |
SERVER | The model service had an error | Yes | 5 |
NETWORK | A connection failed | Yes | 5 |
TIMEOUT | A request took too long | Yes | 5 |
TOO_LARGE | A request body was over a limit | No | 5 |
CONTENT_BLOCKED | The model service declined to process the content | No | 5 |
PROVIDER_PROTOCOL | The model service answered in a way Scribiz did not expect | No | 5 |
INCOMPLETE_OUTPUT | Part of the answer was missing. Scribiz repairs it itself, and only reports INCOMPLETE_COVERAGE if a gap remains | Yes | 5 |
The run
| Code | Meaning | Retry | Exit |
|---|---|---|---|
CANCELLED | You or the host canceled the run | No | 130 |
COST_LIMIT | The estimate was above --max-cost | No | 1 |
INTERNAL | A bug. Please report it | No | 1 |
Retrying safely
- Retry
429,5xx, timeouts and dropped connections. Do not retry other4xxresponses: the same request will fail the same way. - Honor
Retry-Afteron a429or503. Without it, wait 1, 2, 4 and 8 seconds with a little random jitter, and stop after a few tries. - Send an
Idempotency-KeywithPOST /v1/context. Then a retry cannot create a second job or a second charge. - A job that failed with
retryable: truecan be started again with a new request. A failed job keeps its audio for the retry, so work that already finished, such as audio pieces that were transcribed, is not paid for twice. - Scribiz retries on its own, inside a job, for network errors, timeouts and rate limits. A job reports
failedonly after that. - A job that is interrupted, for example by a restart of the service, is queued again on its own, up to three times. Its stream shows a
requeuedevent.
Limits
| Limit | Anonymous | Free | Pro |
|---|---|---|---|
| Listen and Watch minutes | 10 per day | 30 per month | 600 per month |
| Caption jobs | 30 per day | 0.1 minutes per minute of video, from your minutes | 0.1 minutes per minute of video, from your minutes |
| Questions | 5 per day | 0.1 minutes each | 0.1 minutes each |
| Longest video | 15 minutes | 2 hours | 6 hours |
| Running jobs at once | 1 | 2 | 2 |
| Jobs waiting at once | 1 | 3 | 3 |
| Result kept for | 24 hours | 30 days | 30 days |
Mac Lifetime accounts have the free plan's limits and a one-time 300 minutes.
| Limit | Value |
|---|---|
| Requests that create work (jobs, uploads, questions) | 6 per minute anonymous, 30 per minute for an account |
| Upload size | 100 MB |
| An upload is kept | 1 hour |
| Uploads waiting to be used | 3 anonymous, 10 for an account |
| Event streams open at once | 8 |
| Event stream keep-alive | Every 15 seconds |
Body of POST /v1/context | 64 KB |
| Jobs waiting across the service | 200. The next is 503 with busy. |
| API keys per account | 10 |
| Concurrent MCP runs per key | 2 |
Plan limits are the same ones shown on the pricing page and in Minutes and billing. For a larger input, cut the audio yourself and upload that, or use the CLI, which cuts the audio on your machine. With your own Gemini key it has no limit from Scribiz. Signed in to an account, it uses that account's minutes.
Usage and metering
Hosted jobs are charged in minutes of source video times the multiplier of what ran: captions 0.1 (and, without an account, 30 lookups a day), Listen 1, Watch 1, Both 2, a question 0.1, a stored result 0. The charge is made when the job ends, from what ran. A canceled or failed job is not charged. Two callers who share one run of the same video each pay the minutes, and the upstream cost is recorded once.
Scribiz measures usage from what the model service reports it processed, never from a number the client sends. So there is no header to set, and no way to under-report. See Minutes and billing.
When the hosted tool is busy
If anonymous use has spent the day's budget, Listen and Watch are switched off for anonymous callers for a while. A request that needs them answers 503 with busy, and Auto falls back to captions when captions exist: the 202 carries degraded. Captions keep working. Signed-in accounts are not affected. GET /v1/status shows the state under free_tier.listen_watch. The MCP server without a key has its own daily limit across all callers. It cannot use up the web tool's, and the web tool cannot use up its.
When the queue is full, or the disk is nearly full, new jobs and uploads also answer 503 with busy. Retry after the Retry-After wait.
Checked against the Scribiz build on 2026-10-05.