Errori & Rate Limit
Factum Parse usa codici HTTP standard e application/problem+json per gli errori
strutturati dell’API.
Matrice degli stati
Sezione intitolata “Matrice degli stati”| Status | Significato | Causa tipica |
|---|---|---|
200 |
Successo | Risposta normale di parse/upload/status |
202 |
Accettato | Parse asincrono (async_mode=true) |
400 |
Richiesta malformata | Body upload vuoto, JSON malformato |
401 |
Non autorizzato | X-API-Key mancante o non valida (fail-closed, constant-time) |
404 |
Non trovato | job_id upload inesistente |
413 |
Payload troppo grande | Body/file oltre 10 MiB (max_upload_bytes / max_body_bytes) |
415 |
Tipo media/documento non supportato | File non supportato (non XML/PDF) |
422 |
Entità non processabile | XML FatturaPA non valido, limiti text non rispettati, violazione XOR, conflitto async/text |
429 |
Troppe richieste | Rate-limit middleware, quando attivo |
500 |
Errore interno | Errore imprevisto provider/privacy/database/runtime |
503 |
Servizio non disponibile | Sidecar Rizzo non raggiungibile (blocco privacy fail-closed) |
Formato RFC 7807
Sezione intitolata “Formato RFC 7807”Tutti gli errori business restituiscono Content-Type: application/problem+json:
{ "type": "about:blank", "title": "Tipo di file non supportato", "status": 415, "detail": "contenuto non riconosciuto: attesi PDF o FatturaPA XML", "instance": "7c6f2d1c-..."}| Campo | Tipo | Descrizione |
|---|---|---|
type |
string | URI del tipo di problema ("about:blank" per default) |
title |
string | Titolo leggibile dell’errore |
status |
integer | HTTP status code |
detail |
string | Descrizione specifica del problema |
instance |
string | null | Request-ID o UUID generato |
Esempi di errore (RFC 7807)
Sezione intitolata “Esempi di errore (RFC 7807)”400 — Bad Request (JSON malformato)
Sezione intitolata “400 — Bad Request (JSON malformato)”{ "type": "about:blank", "title": "Richiesta malformata", "status": 400, "detail": "Il corpo della richiesta non è un JSON valido: Expecting value: line 1 column 1 (char 0)", "instance": "a1b2c3d4-..."}401 — Unauthorized (API Key mancante o non valida)
Sezione intitolata “401 — Unauthorized (API Key mancante o non valida)”{ "type": "about:blank", "title": "Non autorizzato", "status": 401, "detail": "X-API-Key mancante o non valida", "instance": null}Il confronto usa secrets.compare_digest (constant-time). Il body non rivela se la chiave era solo sbagliata vs. formattata male.
422 — Unprocessable Entity (Schema documentale non riconosciuto)
Sezione intitolata “422 — Unprocessable Entity (Schema documentale non riconosciuto)”{ "type": "about:blank", "title": "Richiesta non processabile", "status": 422, "detail": "serve esattamente una sorgente: job_id oppure text", "instance": "b2c3d4e5-..."}429 — Too Many Requests (Rate limit superato)
Sezione intitolata “429 — Too Many Requests (Rate limit superato)”{ "type": "about:blank", "title": "Troppe richieste", "status": 429, "detail": "Rate limit superato. Attendi 45 secondi.", "instance": "c3d4e5f6-..."}Header inclusi:
Retry-After: 45X-RateLimit-Limit: 60X-RateLimit-Remaining: 0X-RateLimit-Reset: 45500 — Internal Server Error (Errore computazionale non recuperabile)
Sezione intitolata “500 — Internal Server Error (Errore computazionale non recuperabile)”{ "type": "about:blank", "title": "Errore interno del server", "status": 500, "detail": "Errore imprevisto durante l'elaborazione del documento", "instance": "d4e5f6a7-..."}503 — Service Unavailable (Sidecar Rizzo non raggiungibile)
Sezione intitolata “503 — Service Unavailable (Sidecar Rizzo non raggiungibile)”{ "type": "about:blank", "title": "Servizio non disponibile", "status": 503, "detail": "Servizio di pseudonimizzazione non raggiungibile (fail-closed)", "instance": "e5f6a7b8-..."}Il sistema è fail-closed: se il sidecar Rizzo non è disponibile, la richiesta viene rifiutata con 503. Mai testo scoperto all’LLM.
Dettaglio 422 — violazioni ParseRequest (testo diretto)
Sezione intitolata “Dettaglio 422 — violazioni ParseRequest (testo diretto)”| Condizione | detail |
|---|---|
text < 80 caratteri |
Testo troppo corto: minimo 80 caratteri |
text > 200.000 caratteri |
Testo troppo lungo: massimo 200.000 caratteri |
async_mode=true + text |
text supporta solo elaborazione sincrona (payload in RAM) |
job_id e text entrambi |
serve esattamente una sorgente: job_id oppure text |
job_id e text entrambi assenti |
serve esattamente una sorgente: job_id oppure text |
callback_url senza async_mode=true |
callback_url richiede async_mode=true |
Dettaglio 422 — violazioni upload
Sezione intitolata “Dettaglio 422 — violazioni upload”| Condizione | detail |
|---|---|
| PDF senza layer di testo (< 80 char) | scanned_document: il PDF non contiene testo estraibile |
| PDF > 50 pagine | Il PDF supera il limite massimo di 50 pagine |
| XML non conforme a FatturaPA | XML non valido o non conforme al formato FatturaPA |
| Content-Type non supportato | contenuto non riconosciuto: attesi PDF o FatturaPA XML |
Rate limiting
Sezione intitolata “Rate limiting”Il rate limiting è configurabile tramite impostazioni backend
(app/services/ratelimit.py, header X-Tier, valori free | basic | pro,
default free). Non trattare le vecchie tabelle dei tier come un contratto API
pubblico stabile a meno che la configurazione di deployment e il middleware non
siano stati riverificati.
Limiti correnti configurati:
| Tier | Limite | Finestra |
|---|---|---|
free |
60 req/min | 1 min |
basic |
100 req/min | 1 min |
pro |
1000 req/min | 1 min |
Quando il rate limiting è attivo, 429 Too Many Requests include header di rate limit:
Retry-After: 45X-RateLimit-Limit: 60X-RateLimit-Remaining: 0X-RateLimit-Reset: 45Note operative:
- Il rate limit è fail-open: se Redis è irraggiungibile (
RATE_LIMIT_REQUIRED=false), le richieste passano (non blocca il servizio).RATE_LIMIT_REQUIRED=true→ Redis giù = 503. - Gira prima dell’auth (ASGI middleware): richieste non autenticate consumano quota.
- Il retry loop
wait_ready()(10 tentativi, 2s caduno) garantisce Redis connesso prima che il server inizi a servire. - Chiave di rate-limit:
X-API-Keyo, in assenza, IP del client (con anti-spoofing XFF).
Request ID — stato attuale
Sezione intitolata “Request ID — stato attuale”POST /v1/uploads accetta:
X-Request-Id: 7c6f2d1c-...Il valore è usato come campo instance RFC 7807. Se assente, il router upload
genera un UUID per l’istanza errore.
Importante: il backend non implementa correntemente un header X-Request-ID
globale in risposta su tutte le route. La documentazione non finge quindi che tale
header sia universalmente disponibile. Un futuro middleware globale di request-ID
dovrebbe essere trattato come funzionalità backend e testato prima di modificare
questa pagina.
Strategia client
Sezione intitolata “Strategia client”400/401/404/415/422: correggi la richiesta; retry ciechi sono generalmente sbagliati.429: retry con backoff secondo la politica di rate-limit del deployment.500: retry solo quando l’operazione è sicura da ripetere; preserva il contesto della richiesta.503: trattalo come un fallimento del confine di privacy; retry dopo backoff piuttosto che bypassare il sidecar.
