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. Response includes service version, uptime, and when DB is reachable, job counters.

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

Upload a FatturaPA XML or invoice PDF. Returns ingestion metadata; 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 field is file. Filename comes from multipart metadata, not a separate field.

Field Type Description
job_id string (UUID) Job ID for polling /v1/uploads/{job_id}
doc_type string Detected type (e.g. fattura_pa_xml, invoice_pdf, generic)
content_hash string SHA-256 of content (whitespace-collapsed for XML)
size_bytes integer File size in bytes
mime_type string Detected MIME type
deduplicated boolean true if document was already processed (cache hit)
status enum done | requires_vision | queued | failed | expired
message string | null Contextual message (e.g. validation error)

For PDFs, status is requires_vision and the message directs to /v1/parse for semantic extraction.

Status Meaning
400 Empty file
401 Missing or invalid X-API-Key
413 Body/file exceeds 10 MiB limit (max_upload_bytes)
415 Unsupported document/content type (not XML/PDF)
422 FatturaPA XML detected but deterministic parsing failed, PDF without text layer (< 80 chars), or PDF > 50 pages
500 Unexpected server/storage/database error

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 non-existent job returns 404 as RFC 7807 application/problem+json.

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

Process an uploaded job or direct text. Backend schema is ParseRequest.

Auth: X-API-Key (header).

Important: job_id and text are XOR — exactly one must be present.

Field Type Required Description
job_id UUID | null XOR with text Job ID from /v1/uploads
text string | null XOR with job_id Direct text. Zero-retention: never on disk/DB
doc_type "auto" | "fattura" | "f24" | "generico" No (default: "auto") Document type hint
async_mode boolean No (default: false) true → 202 Accepted. Not valid with text
callback_url HTTPS URL | null No Webhook for async. Only with async_mode=true

Validation constraints (422):

  • text < 80 characters → rejected (no text layer)
  • text > 200,000 characters → rejected (RIZZO_PII_MAX_TEXT_CHARS)
  • async_mode=true + text → 422 (text is stateless, RAM-only)
  • callback_url without async_mode=true → 422
  • Both or neither job_id and text → 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":"Invoice no. 2026/001. Supplier: ACME S.r.l. Total: € 1,220.00.","doc_type":"auto","async_mode":false}'

Python · httpx

import httpx
payload = {
"text": "Invoice no. 2026/001. Supplier: ACME S.r.l. Total: € 1,220.00.",
"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

// Replace with your actual API key
const API_KEY = "YOUR_API_KEY";
const response = await fetch("https://api.factum.pyragogy.org/v1/parse", {
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "Invoice no. 2026/001. Supplier: ACME S.r.l. Total: € 1,220.00.",
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":"Invoice no. 2026/001. Supplier: ACME S.r.l. Total: € 1,220.00.","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
$apiKey = 'YOUR_API_KEY'; // Sostituisci con la tua chiave
$ch = curl_init('https://api.factum.pyragogy.org/v1/parse');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'X-API-Key: ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'text' => 'Invoice no. 2026/001. Supplier: ACME S.r.l. Total: € 1,220.00.',
'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"
}
Field Type Description
job_id string Job UUID
status "done" | "failed" | "queued" Processing status
document_type string Detected document type (e.g. "invoice")
provider_used string LLM provider ("deepseek-v3", "gpt-4o-mini", "")
prompt_version string Prompt template version
confidence float | null Confidence score 0.0–1.0 (1.0 = deterministic)
cost_usd float LLM cost in USD (0.0 for deterministic)
tokens integer LLM tokens consumed
fallbacks string[] Activated LLM fallbacks
content_hash string Canonical SHA-256 of the document
result DocumentEnvelope | null Structured content v2 (null on error)
error string | null Error message if status: failed

result is null on error. Direct text is stateless: no ParseJob is created.

Status Meaning
401 Missing or invalid X-API-Key
404 Referenced job_id not found
422 Invalid source (XOR violation), text limits (< 80 or > 200K chars), extraction rejected
500 Unexpected parsing/provider/privacy error
503 Rizzo sidecar unreachable; 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.

Webhook for Lemon Squeezy self-service license management. This endpoint does not require an API key; HMAC-SHA256 signature in the X-Signature header serves as implicit authentication.

Does not generate requestBody in OpenAPI (uses Starlette/FastAPI request: Request to read raw body, required for HMAC verification).

Event Action
subscription_created Creates API key (active status)
order_created Creates API key (active status)
subscription_updated Updates tier and rate limit
subscription_cancelled Revokes keys (revoked status)
subscription_expired Revokes keys (revoked status)
subscription_resumed Reactivates keys (active status)
Tier Limit
starter 60 requests/minute
pro 300 requests/minute
custom 1000 requests/minute
Terminal window
# Simulate a subscription_created event
LEMON_SECRET="your_webhook_secret"
PAYLOAD='{"meta":{"event_name":"subscription_created"},"data":{"attributes":{"user_email":"user@example.com","variant_name":"starter","first_subscription":{"license_key":{"key":"ls_xxxxxxxxx"}}}}}'
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$LEMON_SECRET" | awk '{print $2}')
curl -X POST 'https://api.factum.pyragogy.org/v1/webhooks/lemonsqueezy' \
-H 'Content-Type: application/json' \
-H "X-Signature: $SIGNATURE" \
-d "$PAYLOAD"
Status Meaning
200 Event processed (even if ignored)
400 Invalid JSON payload
401 Missing or invalid HMAC signature
503 LEMONSQUEEZY_WEBHOOK_SECRET not configured
{
"status": "created",
"subscription_id": "sub_xxxxxxx",
"api_key": "factum_xxxxxxxxxxxxxxxxxxxx"
}