Errors
Errors use HTTP status codes and an application/problem+json body with a stable code. Match on code, not on the text of detail.
Error format
Every error body has type, title, status, detail, code and request_id (RFC 9457). Every response, error or not, carries an X-Request-Id header: quote it when you contact us.
HTTP/1.1 402 Payment Required
Content-Type: application/problem+json
X-Request-Id: req_8Zt3NwQ1pL5m
{
"type": "https://zilapi.com/errors/insufficient_credit",
"title": "Insufficient credit",
"status": 402,
"detail": "Available credit is below this job's cost.",
"code": "insufficient_credit",
"request_id": "req_8Zt3NwQ1pL5m"
}Error codes
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 422, 400 | invalid_request | A field is missing or has a bad value, the JSON is malformed, or a cursor is invalid. detail names the field, for example size_bytes: …. | Correct the request. Retrying the same request will fail the same way. |
| 401 | unauthorized | The Authorization header is missing, or the key is unknown or revoked. | Send Authorization: Bearer zil_live_… (or zil_test_…) with an active key from the dashboard. |
| 402 | insufficient_credit | Your available credit (unexpired credit minus what running jobs hold) is below this job's exact cost. Nothing started and nothing was charged. | Add credit in the dashboard, or wait for running jobs to finish, then create the job again. |
| 403 | forbidden | This key or account is not allowed to do this. | Check that you are using the right key and account. |
| 403 | insufficient_scope | The key lacks a scope this endpoint needs; detail names it (files:write, jobs:write, jobs:read or credits:read). | Use a key that has the scope, or create one. See Authentication. |
| 403 | account_suspended | The account is suspended. New jobs are refused; jobs already running finish. | Email support@zilapi.com. |
| 403 | account_frozen | The account is frozen while a card payment dispute is open. | Email support@zilapi.com to resolve the dispute. |
| 404 | not_found | No such file, job or model on this account in this mode. Test keys never see live objects, and live keys never see test ones. | Check the id, and use a key of the same mode (live or test) that created the object. |
| 405 | method_not_allowed | The path exists but not with this HTTP method. | Check the method in the API reference. |
| 409 | file_not_ready | The file is still uploading or validating, or it was rejected, so a job cannot use it yet. | Poll GET /v1/files/{id} until status is ready. A rejected file never becomes ready. |
| 409 | file_in_use | You tried to delete a file that a queued or processing job is using. | Wait for the job to finish (or cancel it while it is queued), then delete the file. |
| 409 | upload_expired | /complete came after the upload window (6 hours from POST /v1/files) closed. | Start a new upload with POST /v1/files. |
| 409 | job_not_cancelable | The job has already started processing. Only queued jobs can be canceled. | Let it finish. It is charged only if it succeeds. |
| 409 | job_not_succeeded | You asked for the output of a job that has not succeeded (yet). | Poll GET /v1/jobs/{id} until status is succeeded. Failed and canceled jobs have no output. |
| 409 | idempotency_key_reused | This Idempotency-Key was already used with a different request body, or an earlier request with it did not record a result. | Use a new key for each distinct request. Reuse a key only to retry the identical request. |
| 410 | output_expired | The result was deleted: outputs are kept for 7 days after the job succeeds. | Download results within 7 days. To get it again, run a new job on a new upload. |
| 413 | file_too_large | The file is over 5 GB (5,368,709,120 bytes), over 25 MB for a test key, or over the model's size limit. | Compress or split the audio. Use a live key for files over 25 MB. |
| 413 | request_too_large | The request body itself is too large. File bytes never go through the API; they go to the upload URL. | Send file bytes to upload.url, not to the API. |
| 415 | unsupported_media_type | content_type is not an audio (or audio-in-video) type we accept. | Send the file's real type, for example audio/wav, audio/mpeg, audio/mp4, audio/flac or audio/ogg. |
| 422 | invalid_audio | The upload is not audio we can decode. The file becomes rejected (error.code on the file). | Check the file plays locally and that content_type matches it, then upload it again. |
| 422 | audio_too_long | The audio is longer than 6 hours (21,600 s), or longer than the model allows. | Split the recording into parts under 6 hours. |
| 422 | model_not_available | The model id is unknown, not live, or has no price. | Pick a model from GET /v1/models, or leave model out to use the task's default. |
| 429 | rate_limited | Over the per-key limit: 120 creates or 1,200 reads per minute (and per IP for public endpoints). | Wait Retry-After seconds, then retry. See Rate limits. |
| 429 | quota_exceeded | An upload quota was reached: 10 open uploads, retained input bytes, or validations per hour. Retry-After comes with the hourly limit. | Finish or delete open uploads, delete files you no longer need, or wait Retry-After seconds. |
| 500 | internal_error | Something went wrong on our side. On a job, error.code is internal_error and you are not charged. | Retry with the same Idempotency-Key. If it keeps happening, email us the request_id. |
| 502 | provider_error | An upstream service failed: the GPU provider (as a failed job's error.code) or the payment provider. | Create the job again. Failed jobs are never charged. |
| 503 | service_unavailable | A dependency (storage, the rate-limit store) is briefly unavailable. | Retry after Retry-After seconds, with the same Idempotency-Key for creates. |
Handling errors
- Retry
429,500,502and503afterRetry-Afterseconds (or with exponential backoff when there is none). For aPOST, retry with the sameIdempotency-Key. - Do not retry other
4xxerrors unchanged: fix the request first. The exception is409 file_not_ready: wait until the file is ready. - Only a succeeded job is ever charged. A failed request or a failed job costs nothing.
Retry on 429 and 503 with Retry-After
# --retry honours Retry-After on 429 and 503
curl -s --retry 5 --retry-max-time 120 https://api.zilapi.com/v1/jobs/$JOB_ID -H "Authorization: Bearer $ZILAPI_KEY" -D headers.txt
grep -i '^ratelimit-' headers.txt # RateLimit-Limit: 1200, RateLimit-Remaining: 1199, RateLimit-Reset: 1