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}/delivereddeletes both immediately. An authenticated job answerspurged: falseand 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
- Plan limits — per-minute (
rpm) and per-day budgets for job submission and upload-link creation. - Account limits — your account can carry its own per-minute/per-day budget
set by the operator, which replaces the plan's. It is counted per account,
not per key: the studio,
POST /jobs/*and the/r/<slug>gateway all draw on the same counters, so extra keys don't buy extra budget. - Per-key daily cap — each key's
max_per_day, shared betweenPOST /jobs/upload,POST /jobs/apiand the/r/<slug>gateway. - Anonymous traffic (no API key) — separate, stricter per-IP limits.
- No per-IP limit for authenticated callers — once you send a key (or are signed in), every guard counts against your account, never your address. A shared office IP, a NAT, a mobile carrier CGNAT or several parallel uploads from one machine never spend each other's budget, and the anonymous abuse block can never apply to you.
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.