9. Errors
| 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 names the reason (e.g. font_size: Input should be less than or equal to 12); check service_slug / operation_key / accepts against /studio/tools |
401 |
missing or invalid API key | check the X-Api-Key header |
402 |
not enough credits | top up |
403 |
not allowed | admin-only action |
404 |
not found | service, job, or session id is wrong |
409 |
conflict | job already finished, or upload link already used |
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 — e.g. an unsafe webhook_url |
429 |
rate limited | honor the Retry-After header (seconds) |
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.