{
  "openapi": "3.1.0",
  "info": {
    "title": "Factum Parse API",
    "version": "0.1.0",
    "description": "**Factum Parse** — Parsing documentale ibrido per fatture italiane con routing asimmetrico (XML deterministico senza token / LLM con pseudonimizzazione PII).\n\n- **Zero Data Retention (ZDR)**: elaborazione in RAM, zero scritture su disco.\n- **Fail-closed auth**: ogni richiesta richiede `X-API-Key` valida.\n- **Schema `DocumentEnvelope v2.0`**: output validato Pydantic con campi normalizzati secondo normativa italiana.\n- **Rizzo PII sidecar**: 22 categorie di dati personali mascherati prima dell'invio a LLM.\n\nDistribuito tramite [Zyla API Hub](https://zylalabs.com/api-marketplace/legal+%26+compliance/factum+parse+api/13523).",
    "contact": {
      "name": "Factum Parse — Zyla Marketplace",
      "url": "https://zylalabs.com/api-marketplace/legal+%26+compliance/factum+parse+api/13523"
    }
  },
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "health"
        ],
        "summary": "Health check",
        "operationId": "health_health_get",
        "responses": {
          "200": {
            "description": "API funzionante",
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "type": "object",
                  "title": "Response Health Health Get"
                }
              }
            }
          }
        },
        "description": "Endpoint pubblico per health check (nessuna autenticazione richiesta).",
        "security": []
      }
    },
    "/status": {
      "get": {
        "tags": [
          "status"
        ],
        "summary": "Status page (public)",
        "operationId": "status_page_status_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatusPage"
                }
              }
            }
          }
        },
        "description": "Endpoint pubblico per stato del servizio (versione, uptime, engine). Nessuna autenticazione richiesta.",
        "security": []
      }
    },
    "/v1/uploads": {
      "post": {
        "tags": [
          "ingest"
        ],
        "summary": "Upload document",
        "operationId": "upload_v1_uploads_post",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "X-Request-Id",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Request-Id"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_upload_v1_uploads_post"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentResponse"
                }
              }
            }
          },
          "413": {
            "description": "Payload troppo grande"
          },
          "415": {
            "description": "Tipo di file non supportato"
          },
          "422": {
            "description": "XML non valido per FatturaPA"
          }
        },
        "description": "Carica un documento (FatturaPA XML o fattura PDF) in multipart/form-data. Restituisce metadata di ingestion con job_id per polling."
      }
    },
    "/v1/uploads/{job_id}": {
      "get": {
        "tags": [
          "ingest"
        ],
        "summary": "Poll upload result",
        "operationId": "get_upload_v1_uploads__job_id__get",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid",
              "title": "Job Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "description": "Polling del risultato di un upload. Restituisce DocumentResponse con lo stato corrente del job."
      }
    },
    "/v1/parse": {
      "post": {
        "tags": [
          "parse"
        ],
        "summary": "Parse",
        "operationId": "parse_v1_parse_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ParseRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParseResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ]
      }
    },
    "/v1/webhooks/lemonsqueezy": {
      "post": {
        "tags": [
          "webhooks"
        ],
        "summary": "Lemon Squeezy webhook",
        "description": "Webhook per self-service licensing. Lemon Squeezy invia eventi del ciclo di vita dell'abbonamento (subscription_created, subscription_updated, subscription_cancelled, subscription_expired, subscription_resumed). Richiede header X-Signature con HMAC-SHA256.",
        "operationId": "lemonsqueezy_webhook_v1_webhooks_lemonsqueezy_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "type": "object",
                  "title": "Response Lemonsqueezy Webhook V1 Webhooks Lemonsqueezy Post"
                }
              }
            }
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "schemas": {
      "DocumentEnvelope": {
        "properties": {
          "schema_version": {
            "type": "string",
            "const": "2.0",
            "title": "Schema Version",
            "default": "2.0"
          },
          "document_type": {
            "type": "string",
            "enum": [
              "invoice",
              "generic"
            ],
            "title": "Document Type"
          },
          "meta": {
            "$ref": "#/components/schemas/EnvelopeMeta"
          },
          "payload": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/InvoicePayload"
              },
              {
                "$ref": "#/components/schemas/GenericPayload"
              }
            ],
            "title": "Payload",
            "discriminator": {
              "propertyName": "kind",
              "mapping": {
                "generic": "#/components/schemas/GenericPayload",
                "invoice": "#/components/schemas/InvoicePayload"
              }
            }
          }
        },
        "type": "object",
        "required": [
          "document_type",
          "payload"
        ],
        "title": "DocumentEnvelope",
        "description": "Wrapper uninificato del risultato di parsing (v2, unico formato wire).\n\nNiente dualità v1/v2: questa è la forma unica; i vecchi record v1 vengono\nconvertiti on-read (vedi `app/services/envelope.py`)."
      },
      "DocumentResponse": {
        "properties": {
          "job_id": {
            "type": "string",
            "title": "Job Id",
            "examples": [
              "3f2b5e6a-0000-4000-8000-000000000004"
            ]
          },
          "doc_type": {
            "type": "string",
            "title": "Doc Type",
            "examples": [
              "fattura_pa_xml"
            ]
          },
          "content_hash": {
            "type": "string",
            "title": "Content Hash",
            "examples": [
              "a1b2c3d4e5f6..."
            ]
          },
          "size_bytes": {
            "type": "integer",
            "title": "Size Bytes",
            "examples": [
              2841
            ]
          },
          "mime_type": {
            "type": "string",
            "title": "Mime Type",
            "examples": [
              "text/xml"
            ]
          },
          "deduplicated": {
            "type": "boolean",
            "title": "Deduplicated",
            "default": false
          },
          "status": {
            "type": "string",
            "title": "Status",
            "examples": [
              "done"
            ]
          },
          "message": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Message"
          }
        },
        "type": "object",
        "required": [
          "job_id",
          "doc_type",
          "content_hash",
          "size_bytes",
          "mime_type",
          "status"
        ],
        "title": "DocumentResponse",
        "description": "Esito di POST /v1/uploads e GET /v1/uploads/{id}."
      },
      "EnvelopeMeta": {
        "properties": {
          "confidence": {
            "type": "number",
            "title": "Confidence",
            "default": 1.0
          },
          "provider": {
            "type": "string",
            "title": "Provider",
            "default": ""
          },
          "prompt_version": {
            "type": "string",
            "title": "Prompt Version",
            "default": ""
          }
        },
        "type": "object",
        "title": "EnvelopeMeta",
        "description": "Metadati di parsing, fuori dal payload (stabili fra i tipi documento)."
      },
      "GenericPayload": {
        "properties": {
          "kind": {
            "type": "string",
            "const": "generic",
            "title": "Kind",
            "default": "generic"
          },
          "content": {
            "additionalProperties": true,
            "type": "object",
            "title": "Content"
          }
        },
        "additionalProperties": true,
        "type": "object",
        "title": "GenericPayload",
        "description": "Documento non-fattura: contenuto libero, tipizzazione minima (YAGNI)."
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "InvoicePayload": {
        "properties": {
          "kind": {
            "type": "string",
            "const": "invoice",
            "title": "Kind",
            "default": "invoice"
          },
          "dati_trasmissione": {
            "additionalProperties": true,
            "type": "object",
            "title": "Dati Trasmissione"
          },
          "cedente_prestatore": {
            "additionalProperties": true,
            "type": "object",
            "title": "Cedente Prestatore"
          },
          "cessionario_committente": {
            "additionalProperties": true,
            "type": "object",
            "title": "Cessionario Committente"
          },
          "corpi": {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array",
            "title": "Corpi"
          }
        },
        "additionalProperties": true,
        "type": "object",
        "title": "InvoicePayload",
        "description": "Fattura: shape canonica FatturaPA (deterministico) o estrazione strutturata.\n\nI campi sono dict grezzi valid come trasportati dal parser FatturaPA:\nil payload NON li normalizza (fedeltà, vedi doc del modulo)."
      },
      "ParseRequest": {
        "properties": {
          "job_id": {
            "anyOf": [
              {
                "type": "string",
                "format": "uuid"
              },
              {
                "type": "null"
              }
            ],
            "title": "Job Id",
            "description": "ID del job restituito da POST /v1/uploads (obbligatorio se no `text`)",
            "examples": [
              "3f2b5e6a-0000-4000-8000-000000000004"
            ]
          },
          "text": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Text",
            "description": "Testo del documento da analizzare (zero-retention, reso in RAM)",
            "examples": [
              "Fattura n. 2026/001 ..."
            ]
          },
          "doc_type": {
            "type": "string",
            "enum": [
              "auto",
              "fattura",
              "f24",
              "generico"
            ],
            "title": "Doc Type",
            "default": "auto"
          },
          "async_mode": {
            "type": "boolean",
            "title": "Async Mode",
            "default": false
          },
          "callback_url": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "format": "uri"
              },
              {
                "type": "null"
              }
            ],
            "title": "Callback Url",
            "description": "Webhook HTTPS per il completamento async"
          }
        },
        "type": "object",
        "title": "ParseRequest",
        "description": "Richiesta di parsing: da job (upload PDF/XML) o da testo diretto.\n\n- `job_id`: job di origine (PDF vettoriale o XML già `done`).\n- `text` : input testuale first-class (zero-retention: mai su disco/DB).\n- `doc_type` : hint di tipo (auto = inferito).\n- `async_mode` : True → risposta 202 Accepted, lavoro in background.\n  Con `text` → 422: il testo esiste solo in RAM, nessuna coda consumabile."
      },
      "ParseResponse": {
        "properties": {
          "job_id": {
            "type": "string",
            "title": "Job Id",
            "examples": [
              "3f7522e6b-0000-4000-8000-000000000004"
            ]
          },
          "status": {
            "type": "string",
            "title": "Status",
            "examples": [
              "done"
            ]
          },
          "document_type": {
            "type": "string",
            "title": "Document Type",
            "examples": [
              "fattura"
            ]
          },
          "confidence": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Confidence",
            "examples": [
              0.97
            ]
          },
          "content_hash": {
            "type": "string",
            "title": "Content Hash",
            "default": "",
            "examples": [
              "a1b2c3d4...64caratteri sha256"
            ]
          },
          "result": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DocumentEnvelope"
              },
              {
                "type": "null"
              }
            ]
          },
          "error": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Error"
          }
        },
        "type": "object",
        "required": [
          "job_id",
          "status",
          "document_type"
        ],
        "title": "ParseResponse",
        "description": "Esito di /v1/parse (sincrono) con risultato validato.\n\nPR 2 / A1: per i flussi LLM (`text` e PDF) il `result` è presente SOLO in\nquesta risposta HTTP; il DB registra solo metadati anonimi (`content_hash`,\nstato). `content_hash` è l'hash canonico del documento (testo\nwhitespace-collapsed o sha256 del file lato upload): mai il contenuto."
      },
      "StatusPage": {
        "properties": {
          "status": {
            "type": "string",
            "title": "Status",
            "examples": [
              "ok"
            ]
          },
          "service": {
            "type": "string",
            "title": "Service",
            "examples": [
              "factum-parse"
            ]
          },
          "version": {
            "type": "string",
            "title": "Version",
            "examples": [
              "0.1.0"
            ]
          },
          "engine": {
            "type": "string",
            "title": "Engine",
            "examples": [
              "ready"
            ]
          },
          "uptime_seconds": {
            "type": "number",
            "title": "Uptime Seconds",
            "examples": [
              120.5
            ]
          }
        },
        "type": "object",
        "required": [
          "status",
          "service",
          "version",
          "engine",
          "uptime_seconds"
        ],
        "title": "StatusPage",
        "description": "GET /status — uptime, versione, engine. Pubblica (Zyla/clienti)."
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          },
          "input": {
            "title": "Input"
          },
          "ctx": {
            "type": "object",
            "title": "Context"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Autenticazione fail-closed. Ogni richiesta DEVE includere l'header `X-API-Key` con una chiave valida. Ottieni la chiave su [Zyla Marketplace](https://zylalabs.com/api-marketplace/legal+%26+compliance/factum+parse+api/13523)."
      }
    }
  },
  "servers": [
    {
      "url": "https://api.factum.pyragogy.org",
      "description": "Produzione (EU — Hetzner eu-central)"
    }
  ],
  "externalDocs": {
    "description": "Documentazione completa",
    "url": "https://docs.factum.pyragogy.org"
  },
  "tags": [
    {
      "name": "Parse",
      "description": "Parsing documentale ibrido (XML deterministico / LLM con PII masking)"
    },
    {
      "name": "Health",
      "description": "Endpoint di monitoring / health check"
    },
    {
      "name": "status",
      "description": "Status page pubblica (versione, uptime, engine)"
    },
    {
      "name": "ingest",
      "description": "Upload e polling documentale (multipart/form-data)"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ]
}
