Errors
Errors are returned as JSON with the HTTP status code carrying the category.
Most bodies have an error field naming the class of problem and a message
field written for a person. Some add fields that let you recover without
guessing.
400 Malformed JSON
The body could not be parsed.
{
"error": "Invalid JSON",
"message": "Request body must be valid JSON"
}400 Failed validation
The body parsed but did not match the schema. details names every field that
failed, so you can map errors back to your form or payload.
{
"error": "Validation error",
"message": "Invalid request parameters",
"details": [
{ "field": "inputUrl", "message": "Invalid input URL" },
{ "field": "preset", "message": "Invalid input" }
]
}Invalid input means the field was missing entirely. Invalid input URL means
the value was present but is not a valid absolute URL.
400 Unknown preset
The preset identifier does not exist. Every valid identifier is listed, so you never have to look one up to recover.
{
"error": "Invalid preset",
"message": "Unknown preset: nope",
"availablePresets": [
"mp4_264_240p", "mp4_264_480p", "mp4_264_720p", "mp4_264_1080p",
"mp4_265_1080p_efficient", "hls_abr_264_basic", "hls_abr_264_standard"
]
}Despite the field name, availablePresets here is every preset the service
knows, not the subset your plan can use.
400 Operation not allowed in this state
Returned by cancellation when the job has already finished.
{
"error": "Invalid operation",
"message": "Cannot cancel job with status: completed. Only queued or processing jobs can be cancelled."
}401 Unauthorized
Missing header, malformed header, unrecognised prefix, unknown key, or revoked key. All five produce the same body.
{
"error": "Unauthorized",
"message": "Valid API key required. Use format: Bearer spk_xxx or Bearer sk_xxx"
}Not retryable. Check the key rather than the request.
403 Preset not on your plan
The preset exists, but your plan does not include it. This body tells you what you can use instead and what you would need to upgrade to.
{
"error": "PRESET_NOT_ALLOWED",
"message": "The \"hls_abr_265_efficient_4k\" preset is not available for your Starter plan",
"code": "PRESET_NOT_ALLOWED",
"availablePresets": [
"mp4_264_240p", "mp4_264_480p", "mp4_264_720p", "hls_abr_264_basic",
"audio_aac_podcast", "util_thumbnails", "util_subs_sidecar_basic"
],
"requiredPlan": "enterprise",
"yourPlan": "free",
"upgradeMessage": "Upgrade to enterprise plan to access this preset"
}Here availablePresets really is your plan's list. Note that yourPlan is the
plan identifier while the message uses the display name, so a free account
reads free in one field and Starter in the other.
404 Not found
The job does not exist, or it belongs to another account. The two cases are deliberately not distinguished.
{
"error": "Not found",
"message": "Job not found or you do not have access to it"
}410 Output gone
The job completed, but its output can no longer be signed for download. This happens for jobs stored before the current storage layout. The file is not recoverable; run the job again.
{
"message": "The output for this job is no longer available for download"
}429 Quota exhausted
You have used every job in this month's allowance. The counter resets at the start of the calendar month.
{
"error": "Usage limit exceeded",
"message": "Monthly limit of 10 jobs reached",
"usage": {
"current": 10,
"limit": 10,
"plan": "Free"
}
}Retrying will not help until the month rolls over or you change plan. This is a quota, not a rate limit; there is no per-minute throttle to back off from.
500 Server error
Unexpected. The requestId is a fresh UUID for this failure; quote it when you
contact support.
{
"error": "Internal server error",
"message": "An unexpected error occurred while processing your request",
"requestId": "0e2f9c14-7b3a-4d61-b9c8-5a1e7f2d3c40"
}Safe to retry with backoff.
The download-url exception
GET /jobs/{id}/download-url predates the conventions above and returns a bare
message with no error field.
{ "message": "Job not found" }{ "message": "Output file is not available or job is not completed" }If you parse errors generically, read message first and treat error as
optional.
Failures that are not HTTP errors
A job can be accepted with 201 and still fail later, because the source is
only fetched and inspected by the worker. Those failures appear as
status: "failed" with a human-readable errorMessage on the job, and they
trigger a webhook if you have one configured.
errorMessage contains | Cause | Retried by the worker |
|---|---|---|
DURATION_EXCEEDED | Source is longer than your plan allows | No |
| Download or network wording | Source URL was unreachable or refused | Yes, up to 3 attempts |
| FFmpeg wording | Source could not be decoded with this preset | Yes, up to 3 attempts |
See Handling failures for what to do about each.