Skip to content

Errors & Rate Limits

Factum Parse uses standard HTTP codes and application/problem+json for structured API errors.

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)

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
{
"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: 45
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 45

500 — 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
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 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: 45
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 45

Operational 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-Key or, in its absence, client IP (with XFF anti-spoofing).

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.

  • 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.