Errors
All errors return a JSON body with this shape:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Per-minute limit exceeded.",
"details": { ... }
}
}The HTTP status code is the source of truth; code is a stable
machine-readable identifier you can switch on.
Status code map
| Status | Meaning | Retry? |
|---|---|---|
| 400 | Bad request (malformed body, missing field) | No — fix client. |
| 401 | Missing / invalid API key | No. |
| 403 | Authenticated but lacking scope | No. |
| 404 | Resource not found, or not in your org | No. |
| 409 | Duplicate resource / version conflict | No (or after refetching). |
| 422 | Validation failed | No — inspect details. |
| 425 | Too early (key not active yet) | After expires_at. |
| 429 | Rate limit exceeded | Yes, honour Retry-After. |
| 500 | Server error | Yes, with backoff. |
| 502/503/504 | Upstream unavailable | Yes, with backoff. |
The SDKs retry the "Yes" rows automatically (max_retries=5 by default,
exponential backoff with jitter, capped at 8 s).
Error codes
code | Meaning |
|---|---|
unauthorized | API key missing or wrong. |
expired_api_key | Key past expires_at. |
insufficient_scope | Key lacks the scope this endpoint requires. |
unsupported_format | Uploaded file is not an image or PDF. |
file_too_large | >15 MB. |
low_confidence | Extraction succeeded but confidence under threshold. |
rate_limit_exceeded | See retry_after. |
quota_exhausted | Monthly plan quota fully consumed (HTTP 402). |
idempotency_conflict | Same Idempotency-Key reused with different body. |
validation_error | Request body failed validation; see details. |
server_error | Generic 5xx — already retried. |
Request IDs
Every response (success or error) carries X-Request-Id. Forward it to
support — we can pull the full trace from logs in under 30 s.
Programmatic handling
from oligon_receipts import RateLimitError, AuthError, ValidationError
try:
client.extract(file=path)
except RateLimitError as e:
print("slow down:", e.retry_after)
except AuthError:
rotate_key()
except ValidationError as e:
print("server says:", e.body["error"]["details"])