Errors
All API errors share the same envelope:
json
{
"error": {
"code": "rate_limited",
"message": "request rate exceeded (60/min)",
"details": { "retry_after_seconds": 12 }
}
}code is a stable machine-readable identifier; message is a friendly string suitable for surfacing in logs but not for branching. details is endpoint-specific — see the table below.
HTTP status codes
| Status | Meaning |
|---|---|
| 200 | OK. |
| 201 | Resource created (file finalized, key issued). |
| 202 | Accepted — conversion queued. |
| 204 | No content (delete, revoke). |
| 302 | Redirect to a presigned download URL (/v1/conversions/{id}/output). |
| 400 | Bad request — validation, unsupported format, unknown preset. |
| 401 | Missing / invalid / revoked API key. |
| 403 | Forbidden — wrong scope, free tier, banned account. |
| 404 | Resource not found OR not yours. |
| 413 | Payload too large (file exceeds tier or scanner cap). |
| 422 | Magic-byte mismatch or malware detected. |
| 429 | Rate limited; honour Retry-After. |
| 503 | Maintenance mode — try again shortly. |
| 5xx | Server error — safe to retry once with jittered backoff. |
Error code reference
| Code | Status | Retryable | Notes |
|---|---|---|---|
unauthorized | 401 | No | Missing / invalid / revoked key. |
forbidden | 403 | No | Wrong scope, free tier, banned. |
not_found | 404 | No | Includes "doesn't belong to caller". |
rate_limited | 429 | After Retry-After | Per-key request bucket. |
conversion_rate_limited | 429 | After Retry-After | Per-key conversion bucket. |
file_too_large | 400 / 413 | No | Tier cap. |
mime_mismatch | 422 | No | Declared MIME ≠ detected MIME. |
malware_detected | 422 | No | ClamAV hit; object deleted, audit log written. |
scan_size_limit_exceeded | 413 | No | File exceeds clamd StreamMaxLength. |
unsupported_pair | 400 | No | No conversion path from source → target. |
unknown_preset | 400 | No | Preset not allowed for this pair. |
bulk_too_large | 400 | No | Exceeds tier.bulk_max. |
not_ready | 400 | Yes (poll) | Hit /output before completed. |
not_cancellable | 400 | No | Conversion already terminal. |
unknown_scope | 400 | No | Scope name not recognised at create time. |
Worker-side conversion failures
If a conversion finishes with status: 'failed', look at error_code in the response body:
| Field | Meaning | Should retry? |
|---|---|---|
malware | ClamAV hit during finalize. | No. |
unsupported | Worker tool can't read the input. | No. |
timeout | Wall-clock timeout in the worker. | Maybe — try a smaller input or different preset. |
tool_error | Worker tool returned a fatal error (libreoffice crash, etc.). | No. |
permanent | Catch-all for terminal worker errors after retries. | No. |
transient | Worker exhausted its 2 auto-retries with retryable errors (network, OOM). | Yes — create a new conversion. |
There's no manual "force retry" on the public API; if you want one, create a new conversion with the same input + preset.
