Errors
Every non-2xx response uses a single envelope. The HTTP status carries the coarse category; the code field carries the precise, stable identifier that SDKs switch on.
The error envelope#
The body of any error is always just the envelope below, with no extra top-level keys (no data, no success: false). The same envelope is used by REST, MCP, and webhook surfaces.
{
"error": {
"code": "JOB_NOT_FOUND",
"message": "No job with id 'job_x9k' exists for this tenant.",
"request_id": "req_01HBXY4Z2N3K1QHTRACE6789",
"details": null,
"retry_after_ms": null,
"doc_url": "https://docs.lirovo.ai/errors/job-not-found"
}
}error.coderequiredStable UPPER_SNAKE_CASE identifier. The only field clients should branch on.
error.messagerequiredHuman-readable, end-user-safe text. Never contains stack traces or secrets.
error.request_idrequiredSame value as the x-lirovo-request-id response header.
error.detailsPer-code structured context (for example the offending id or validation issues).
error.retry_after_msWhen set, wait at least this many ms before retrying. Mirrors the Retry-After header on 429 / 503.
error.doc_urlPermalink to the public error reference for the code.
Error codes#
There are 36 codes as of v1.1, grouped by category. The list below summarizes the real categories and the codes in each rather than reproducing the full per-code details shapes. Each code maps to exactly one HTTP status.
Authentication and authorization (401 / 403)
MISSING_AUTHORIZATION(401),INVALID_API_KEY(401),EXPIRED_API_KEY(401)INSUFFICIENT_SCOPE(403),TENANT_SUSPENDED(403)
Resource not found (404)
JOB_NOT_FOUND,SCHEMA_NOT_FOUND,DESTINATION_NOT_FOUND,EXTRACTION_NOT_FOUND,API_KEY_NOT_FOUNDJOB_RESULT_NOT_READY,JOB_GRAPH_NOT_FOUND
The API never leaks cross-tenant existence: an unknown id and another tenant's id return the same envelope.
Validation (400 / 422)
INVALID_REQUEST(400),INVALID_JSON(400)UNSUPPORTED_SOURCE(422),SOURCE_BLOCKED(422, SSRF screening),SCHEMA_INVALID(422),SCHEMA_TOO_LARGE(422),VIDEO_TOO_LONG(422),CONFLICTING_SCHEMA_REF(422)IDEMPOTENCY_KEY_CONFLICT(422),IDEMPOTENCY_KEY_ORPHANED(422)
Rate limiting and quotas (429)
RATE_LIMITED,MINUTES_QUOTA_EXCEEDED,STORAGE_QUOTA_EXCEEDED,CONCURRENT_JOBS_LIMIT,COST_QUOTA_EXCEEDED
State and semantics (409 / 422)
JOB_ALREADY_TERMINAL(409),JOB_NOT_TERMINAL(409),DUPLICATE_API_KEY_NAME(409),DESTINATION_UNREACHABLE(422)
Server-side (5xx)
INTERNAL_ERROR(500),UPSTREAM_TIMEOUT(504),MODEL_UNAVAILABLE(503),STORAGE_UNAVAILABLE(503),MAINTENANCE(503)
Retry guidance#
The SDK enforces these defaults; the same rules apply to hand-rolled clients.
- 429 with
retry_after_ms(RATE_LIMITED,CONCURRENT_JOBS_LIMIT,COST_QUOTA_EXCEEDED): wait exactlyretry_after_ms, then retry. Max 5 attempts. - 429 without
retry_after_ms(MINUTES_QUOTA_EXCEEDED,STORAGE_QUOTA_EXCEEDED): do not auto-retry. Surface to the user. - 500, 503, 504: exponential backoff (500ms, 1s, 2s, 4s, 8s), max 5 attempts.
- 400, 401, 403, 404, 409, 422: never auto-retry. The request itself is wrong.
X-Lirovo-Request-Id matching error.request_id. On 429 and 503 with a retry hint, a Retry-After header (whole seconds) accompanies retry_after_ms.