Errors & Rate Limits
Factum Parse uses standard HTTP codes and application/problem+json for structured API errors.
Status Matrix
Section titled “Status Matrix”| Status | Meaning | Typical Cause |
|---|---|---|
200 |
Success | Normal parse/upload/status response |
202 |
Accepted | Async parse (async_mode=true) |
400 |
Bad request | Empty upload body, malformed JSON |
401 |
Unauthorized | Missing or invalid X-API-Key (fail-closed, constant-time) |
404 |
Not found | Non-existent upload job_id |
413 |
Payload too large | Body/file exceeds 10 MiB (max_upload_bytes / max_body_bytes) |
415 |
Unsupported media/document type | Unsupported file (not XML/PDF) |
422 |
Unprocessable entity | Invalid FatturaPA XML, text limit violations, XOR violation, async/text conflict |
429 |
Too many requests | Rate-limit middleware, when active |
500 |
Internal error | Unexpected provider/privacy/database/runtime error |
503 |
Service unavailable | Rizzo sidecar unreachable (privacy fail-closed) |
RFC 7807 Format
Section titled “RFC 7807 Format”All business errors return Content-Type: application/problem+json:
{ "type": "about:blank", "title": "Unsupported File Type", "status": 415, "detail": "unrecognized content: expected PDF or FatturaPA XML", "instance": "7c6f2d1c-..."}| Field | Type | Description |
|---|---|---|
type |
string | Problem type URI ("about:blank" by default) |
title |
string | Human-readable error title |
status |
integer | HTTP status code |
detail |
string | Problem-specific description |
instance |
string | null | Request-ID or generated UUID |
Error Samples (RFC 7807)
Section titled “Error Samples (RFC 7807)”400 — Bad Request (malformed JSON)
Section titled “400 — Bad Request (malformed JSON)”{ "type": "about:blank", "title": "Bad Request", "status": 400, "detail": "The request body is not valid JSON: Expecting value: line 1 column 1 (char 0)", "instance": "a1b2c3d4-..."}401 — Unauthorized (missing or invalid API Key)
Section titled “401 — Unauthorized (missing or invalid API Key)”{ "type": "about:blank", "title": "Unauthorized", "status": 401, "detail": "Missing or invalid X-API-Key", "instance": null}Uses secrets.compare_digest (constant-time). The body does not reveal whether the key was wrong vs. malformed.
422 — Unprocessable Entity (unrecognized document schema)
Section titled “422 — Unprocessable Entity (unrecognized document schema)”{ "type": "about:blank", "title": "Unprocessable Request", "status": 422, "detail": "exactly one source is required: job_id or text", "instance": "b2c3d4e5-..."}429 — Too Many Requests (rate limit exceeded)
Section titled “429 — Too Many Requests (rate limit exceeded)”{ "type": "about:blank", "title": "Too Many Requests", "status": 429, "detail": "Rate limit exceeded. Wait 45 seconds.", "instance": "c3d4e5f6-..."}Includes headers:
Retry-After: 45X-RateLimit-Limit: 60X-RateLimit-Remaining: 0X-RateLimit-Reset: 45500 — Internal Server Error (unrecoverable computational error)
Section titled “500 — Internal Server Error (unrecoverable computational error)”{ "type": "about:blank", "title": "Internal Server Error", "status": 500, "detail": "Unexpected error during document processing", "instance": "d4e5f6a7-..."}503 — Service Unavailable (Rizzo sidecar unreachable)
Section titled “503 — Service Unavailable (Rizzo sidecar unreachable)”{ "type": "about:blank", "title": "Service Unavailable", "status": 503, "detail": "Pseudonymization service unreachable (fail-closed)", "instance": "e5f6a7b8-..."}The system is fail-closed: if the Rizzo sidecar is unavailable, the request is rejected with 503. Clear text never reaches the LLM.
422 Detail — ParseRequest violations (direct text)
Section titled “422 Detail — ParseRequest violations (direct text)”| Condition | detail |
|---|---|
text < 80 characters |
Text too short: minimum 80 characters |
text > 200,000 characters |
Text too long: maximum 200,000 characters |
async_mode=true + text |
text only supports synchronous processing (RAM payload) |
Both job_id and text |
exactly one source is required: job_id or text |
Neither job_id nor text |
exactly one source is required: job_id or text |
callback_url without async_mode=true |
callback_url requires async_mode=true |
422 Detail — Upload violations
Section titled “422 Detail — Upload violations”| Condition | detail |
|---|---|
| PDF without text layer (< 80 chars) | scanned_document: PDF does not contain extractable text |
| PDF > 50 pages | PDF exceeds maximum page limit of 50 |
| XML not conforming to FatturaPA | Invalid XML or not conforming to FatturaPA format |
| Unsupported Content-Type | unrecognized content: expected PDF or FatturaPA XML |
Rate Limiting
Section titled “Rate Limiting”Rate limiting is backend-configurable (app/services/ratelimit.py, X-Tier header, values free | basic | pro, default free). Do not treat old tier tables as a stable API contract unless the deployment configuration and middleware have been re-verified.
Current configured limits:
| Tier | Limit | Window |
|---|---|---|
free |
60 req/min | 1 min |
basic |
100 req/min | 1 min |
pro |
1000 req/min | 1 min |
When rate limiting is active, 429 Too Many Requests includes rate-limit headers:
Retry-After: 45X-RateLimit-Limit: 60X-RateLimit-Remaining: 0X-RateLimit-Reset: 45Operational notes:
- Rate limiting is fail-open: if Redis is unreachable (
RATE_LIMIT_REQUIRED=false), requests pass through (does not block the service).RATE_LIMIT_REQUIRED=true→ Redis down = 503. - Runs before auth (ASGI middleware): unauthenticated requests consume quota.
wait_ready()retry loop (10 attempts, 2s each) ensures Redis is connected before the server starts serving.- Rate-limit key:
X-API-Keyor, in its absence, client IP (with XFF anti-spoofing).
Request ID — Current Status
Section titled “Request ID — Current Status”POST /v1/uploads accepts:
X-Request-Id: 7c6f2d1c-...The value is used as the RFC 7807 instance field. If absent, the upload router generates a UUID for the error instance.
Important: the backend does not currently implement a global X-Request-ID response header across all routes. This documentation therefore does not pretend such a header is universally available. A future global request-ID middleware should be treated as a backend feature and tested before modifying this page.
Client Strategy
Section titled “Client Strategy”400/401/404/415/422: fix the request; blind retries are generally wrong.429: retry with backoff according to the deployment’s rate-limit policy.500: retry only when the operation is safe to repeat; preserve request context.503: treat as a privacy boundary failure; retry after backoff rather than bypassing the sidecar.
