9. Errors

Anonymous runs are deleted on delivery. A job submitted without a key or a token (the open web studio) keeps its source and its result only until the caller says they have the file: POST /api/v1/jobs/{id}/delivered deletes both immediately. An authenticated job answers purged: false and is untouched — its files live until the storage lifecycle expires them.

Status Meaning What to do
400 bad request — unknown operation, invalid file URL, a parameter the operation refused, or a file of the wrong kind the detail is a string that names the reason. A bad param names the field and refunds the charge: could not start the job — quality: Input should be less than or equal to 100 (charge refunded). Wrong kind names both: this operation works on video files — the file you sent looks like image. Unknown key: unknown operation 'mute'. Malformed params: params must be a JSON object: …. Check service_slug / operation_key / accepts against /studio/tools
401 missing or invalid API key {"detail":"missing API key"} when the header is absent, {"detail":"invalid API key"} when it is wrong — check X-Api-Key (or Authorization: Bearer ca_live_…, which is accepted identically). Billing endpoints instead want a panel Bearer token and answer {"detail":"missing bearer token"}
402 not enough bonuses top up
403 not allowed an admin-only action, or a payment on an account where billing isn't switched on yet: POST /billing/checkout can return {"detail":"payments are not enabled for this account yet"}
404 not found service, job, or session id is wrong
409 conflict job already finished, or upload link already used. Billing uses it for account-state refusals, all with an object detail: buying a different plan while a term runs, {"code":"subscription_required"} from POST /billing/checkout when packs are add-ons and you hold no plan, and a refund request the order cannot take — {"code":"nothing_left"}, window_closed, already_open, fully_refunded, not_settled, or refunds_disabled when self-service refunds are switched off (write to support instead)
410 upload link expired mint a new one
413 file too large over MAX_UPLOAD_GB or your declared limit
415 file type not allowed check allowed_mime_types on the link
422 validation error pydantic detail array (detail[].loc / .msg) — e.g. an unsafe webhook_url (host '127.0.0.1' resolves to a private/reserved address), or GET /jobs?limit= outside 1..200
429 rate limited, or too many jobs in flight honor the Retry-After header (seconds). detail says which: the per-minute / per-day counters, or your plan's in-flight cap (open jobs plus undelivered batch files); the cap clears the moment a job ends
502 / 503 upstream error / temporarily unavailable retry with exponential backoff

Maintenance windows

During planned maintenance every submitting endpoint (/studio/run, /studio/batch, POST /jobs*, and starting a bridge upload) answers 503 with a machine-readable detail:

{ "detail": { "code": "maintenance", "message": "Back around 18:00 CET" } }

Reads keep working, so a job submitted before the window can still be polled and its result downloaded. Treat code: "maintenance" as "retry later, don't resubmit in a loop" — the message is written for humans and can be shown as-is.

Check ahead of time without submitting anything:

curl -s https://mp.dvocorp.com/api/v1/public/config
# -> {"maintenance":false,"maintenance_message":"", "support":{...}, ...}

Reaching a human

POST /api/v1/public/feedback sends a message straight to our team chat. Send your API key with it (or use the form in the web app — anonymous calls from scripts are refused with 401, same rule as the free studio):

curl -s -X POST https://mp.dvocorp.com/api/v1/public/feedback \
  -H "X-Api-Key: $API_KEY" -H 'Content-Type: application/json' \
  -d '{"message":"circle op fails on 4K input","contact":"[email protected]","error":"job failed: upstream 500"}'
# -> {"ok":true}

message is required (10–2000 chars); contact, page and error are optional context.

This mailbox is a human, not a webhook, so it is tightly limited:

Limit Anonymous (web form) Signed in
Gap between messages 15 min per IP 2 min per account
Per day 5 per IP 20 per account
Per network / hour 10 per /24 (or /64) —

Plus a global hourly ceiling, and content rules: at most 2 links, no exact duplicate of a message you already sent that day. Answers are 400 (message rejected — too short, too many links, duplicate), 429 (Retry-After tells you the wait in seconds), or 503 (feedback delivery is not configured — use the contacts from /public/config instead).

Rate limits

All of them return 429 with Retry-After.

Queue delay

Limits decide whether a request is accepted; the plan also decides when the accepted work starts. On a throttled plan a submit still returns 201 and is charged normally, but the job stays queued for a configured hold — and may additionally wait until higher-priority work has finished, up to a fixed cap.

There is no separate status or error for this, and nothing to handle: poll GET /api/v1/jobs/{id} (or wait for the webhook) until done / failed, and size your client timeouts for minutes rather than seconds. If you need guaranteed fast starts, ask about a higher tier.