Errors
Every error from the Video API endpoints uses the same JSON shape:
{
"error": {
"code": "...",
"message": "..."
}
}The HTTP status tells you the class of the problem; error.code is a stable string you can branch on in code. Always match on error.code rather than parsing error.message.
Error codes
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A required parameter is missing or a value is invalid |
| 400 | invalid_callback_url | callback_url is not HTTPS or points to a private network (blocked by SSRF checks) |
| 400 | references_conflict | frame_images and input_references were sent in the same request (mutually exclusive) |
| 400 | too_many_references | input_references exceeds the limits (images > 9 / audio > 3 / video > 1) |
| 400 | cancel_not_supported | The upstream provider cannot interrupt this job |
| 400 | cancel_failed | The job is already in a terminal state and cannot be cancelled |
| 401 | unauthorized / invalid_api_key | Authentication failed |
| 401 | upstream_auth_failed | Upstream provider authentication failed |
| 402 | insufficient_credits | Not enough balance to accept the job |
| 404 | not_found | The job does not exist, or you do not have access to it |
| 404 | model_not_found | The requested model does not exist |
| 429 | rate_limited | Too many requests (polling rate limit or upstream rate limit) |
| 502 | upstream_error / route_error | Upstream provider error / routing failure |
| 500 | internal_error | Unexpected internal error |
references_conflict and too_many_references are request-body validation errors — see Create video for the exact limits. cancel_not_supported and cancel_failed are described on Cancel video. not_found is returned for both an unknown job and a job owned by a different API key; the two cases are intentionally indistinguishable.
real_person preprocessing errors
When real_person: true image preprocessing fails because of the supplied image, the API returns HTTP 400 with error.code: "invalid_request". The end of error.message includes one of these stable reason tokens and identifies the failing reference, for example input_references[0]:
| Reason token | What to fix |
|---|---|
bad_data_uri | The data: URI is malformed or its base64 payload is invalid |
download_failed | The image could not be downloaded because of a connection, timeout, or access-control failure |
unreachable | The image URL returned a non-success HTTP status |
not_image | The downloaded content is not a supported, decodable image |
too_large | The image exceeds the size, pixel, or per-request processing limit |
{
"error": {
"code": "invalid_request",
"message": "real_person image processing failed — input_references[0]: ... (not_image)"
}
}Branch on error.code in application logic. Use the reason token and reference location to correct the input or for diagnostics.
402 Insufficient credits
insufficient_credits is returned only when you create a job (POST /v1/videos). When pre-authorization is enabled, ofox estimates the cost from the requested resolution and duration before accepting the job. If the estimate exceeds your balance, the request is rejected immediately — no job record is created and nothing is charged.
{ "error": { "code": "insufficient_credits", "message": "..." } }insufficient_credits is the only 402 code the Video API returns. Handle this single code, top up your balance, and retry.
429 Rate limiting
rate_limited covers both the per-key polling limit on GET /v1/videos/{id} and upstream rate limits. When you hit the polling limit, the response carries a Retry-After header:
HTTP/1.1 429 Too Many Requests
Retry-After: 1
Content-Type: application/json
{ "error": { "code": "rate_limited", "message": "..." } }Respect the Retry-After header and back off before retrying. If you are polling frequently, switch to webhook notifications so ofox pushes the terminal state to you instead — this avoids the polling limit entirely. Job creation (POST) and cancellation (DELETE) are not affected by the polling limit.