Skip to content

Endpoints

Public liveness probe.

Terminal window
curl -s https://api.factum.pyragogy.org/health
{"status":"ok","service":"factum-parse"}

Public operational status. The response includes service version, uptime and, when the DB is reachable, job counters and aggregate cost information.

Terminal window
curl -s https://api.factum.pyragogy.org/status

Upload one FatturaPA XML or invoice PDF. The endpoint returns ingestion metadata; it does not return the parsed envelope in DocumentResponse.

Auth: X-API-Key (header).

Terminal window
curl -X POST 'https://api.factum.pyragogy.org/v1/uploads' \
-H 'X-API-Key: YOUR_API_KEY' \
-F 'file=@./invoice.xml;type=application/xml'

The only multipart form field is file. The filename is supplied as multipart metadata, not as a separate filename field.

Campo Tipo Descrizione
job_id string (UUID) ID del job per polling /v1/uploads/{job_id}
doc_type string Tipo rilevato (es. fattura_pa_xml, invoice_pdf, generic)
content_hash string SHA-256 del contenuto (whitespace-collapsed per XML)
size_bytes integer Dimensione file in byte
mime_type string MIME type rilevato
deduplicated boolean true se il documento era già processato (cache hit)
status enum done | requires_vision | queued | failed | expired
message string | null Messaggio contestuale (es. errore di validazione)

For PDF ingestion, status is currently requires_vision and the response message points to /v1/parse for semantic extraction.

{
"job_id": "3f2b5e6a-0000-4000-8000-000000000004",
"doc_type": "fattura_pa_xml",
"content_hash": "a1b2c3d4e5f6...",
"size_bytes": 2841,
"mime_type": "text/xml",
"deduplicated": false,
"status": "done",
"message": null
}
Status Meaning
400 Empty file
401 Missing/invalid X-API-Key
413 Body/file exceeds 10 MiB limit (max_upload_bytes)
415 Unsupported document/content type (non-XML/PDF)
422 FatturaPA XML detected but deterministic parsing fails, PDF without text layer (< 80 chars), or PDF > 50 pages
500 Unexpected server/storage/database failure

Upload errors use RFC 7807 application/problem+json.

Poll an upload job.

Terminal window
curl -s 'https://api.factum.pyragogy.org/v1/uploads/JOB_ID' \
-H 'X-API-Key: YOUR_API_KEY'

A missing job returns 404 as RFC 7807 application/problem+json.

The response remains DocumentResponse; the parsed DocumentEnvelope is not embedded in this response model.

Parse either an uploaded job or direct text. The backend schema is ParseRequest.

Auth: X-API-Key (header).

Importante: job_id e text sono XOR — esattamente uno dei due deve essere presente.

Campo Tipo Obbligatorio Descrizione
job_id UUID | null XOR con text Job ID da /v1/uploads
text string | null XOR con job_id Testo diretto. Zero-retention: mai su disco/DB
doc_type "auto" | "fattura" | "f24" | "generico" No (default: "auto") Hint di tipo documento
async_mode boolean No (default: false) true → 202 Accepted. Non valido con text
callback_url URL HTTPS | null No Webhook per async. Solo con async_mode=true

Vincoli di validazione (422):

  • text < 80 caratteri → rifiutato (layer di testo assente)
  • text > 200.000 caratteri → rifiutato (RIZZO_PII_MAX_TEXT_CHARS)
  • async_mode=true + text → 422 (testo è stateless, solo in RAM)
  • callback_url senza async_mode=true → 422
  • job_id e text entrambi presenti o entrambi assenti → 422 (XOR)
Terminal window
curl -X POST 'https://api.factum.pyragogy.org/v1/parse' \
-H 'X-API-Key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"text":"Fattura n. 2026/001. Cedente: ACME S.r.l. Totale documento: 1220,00 EUR","doc_type":"auto","async_mode":false}'

Python · httpx

import httpx
payload = {
"text": "Fattura n. 2026/001. Cedente: ACME S.r.l. Totale documento: 1220,00 EUR",
"doc_type": "auto",
"async_mode": False,
}
response = httpx.post(
"https://api.factum.pyragogy.org/v1/parse",
headers={"X-API-Key": "YOUR_API_KEY"},
json=payload,
)
response.raise_for_status()
print(response.json())

TypeScript · fetch

const response = await fetch("https://api.factum.pyragogy.org/v1/parse", {
method: "POST",
headers: {
"X-API-Key": process.env.FACTUM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "Fattura n. 2026/001. Cedente: ACME S.r.l. Totale documento: 1220,00 EUR",
doc_type: "auto",
async_mode: false,
}),
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
const result = await response.json();

Go · net/http

package main
import (
"bytes"
"fmt"
"net/http"
)
func main() {
body := []byte(`{"text":"Fattura n. 2026/001. Totale documento: 1220,00 EUR","doc_type":"auto","async_mode":false}`)
req, _ := http.NewRequest("POST", "https://api.factum.pyragogy.org/v1/parse", bytes.NewReader(body))
req.Header.Set("X-API-Key", "YOUR_API_KEY")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer res.Body.Close()
fmt.Println(res.Status)
}

PHP · cURL

<?php
$ch = curl_init('https://api.factum.pyragogy.org/v1/parse');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-API-Key: ' . getenv('FACTUM_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'text' => 'Fattura n. 2026/001. Totale documento: 1220,00 EUR',
'doc_type' => 'auto',
'async_mode' => false,
]),
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
if ($response === false) throw new RuntimeException(curl_error($ch));
curl_close($ch);
echo $response;
Terminal window
curl -X POST 'https://api.factum.pyragogy.org/v1/parse' \
-H 'X-API-Key: YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"job_id":"3f2b5e6a-0000-4000-8000-000000000004","doc_type":"auto","async_mode":false}'
{
"job_id": "3f2b5e6a-0000-4000-8000-000000000004",
"doc_type": "fattura",
"async_mode": true,
"callback_url": "https://example.com/factum/callback"
}
Campo Tipo Descrizione
job_id string UUID del job
status "done" | "failed" | "queued" Stato elaborazione
document_type string Tipo documento rilevato (es. "fattura")
provider_used string Provider LLM ("deepseek-v3", "gpt-4o-mini", "")
prompt_version string Versione del prompt template
confidence float | null Confidence score 0.0–1.0 (1.0 = deterministico)
cost_usd float Costo LLM in USD (0.0 per deterministico)
tokens integer Token LLM consumati
fallbacks string[] Fallback LLM attivati
content_hash string SHA-256 canonico del documento
result DocumentEnvelope | null Contenuto strutturato v2 (null su errore)
error string | null Messaggio errore se status: failed
{
"job_id": "3f2b5e6a-0000-4000-8000-000000000004",
"status": "done",
"document_type": "invoice",
"provider_used": "fast-path",
"prompt_version": "fast-path-v1",
"confidence": 1.0,
"cost_usd": 0.0,
"tokens": 0,
"fallbacks": [],
"content_hash": "a1b2c3d4...",
"result": {
"schema_version": "2.0",
"document_type": "invoice",
"meta": {
"cost_eur": 0.0,
"confidence": 1.0,
"provider": "fast-path",
"prompt_version": "fast-path-v1"
},
"payload": {
"kind": "invoice",
"dati_trasmissione": {},
"cedente_prestatore": {},
"cessionario_committente": {},
"corpi": []
}
},
"error": null
}

result is null on error. Direct text is stateless: it does not create a ParseJob.

Status Meaning
401 Missing/invalid X-API-Key
404 Referenced job_id does not exist
422 Invalid source (XOR violation), text bounds (< 80 or > 200K chars), extraction rejection
500 Unexpected parsing/provider/privacy-sidecar error
503 Required Rizzo privacy sidecar unavailable; fail-closed

DocumentResponse and ParseResponse are not interchangeable. The former describes ingestion state; the latter carries the structured DocumentEnvelope v2 when parsing produces a result.