Lifecycle of a Spanish invoice: how PDF, Extract, FacturaE and VeriFactu fit together

A Spanish invoice isn't a single event; it's a process with four distinct compliance checkpoints. Each checkpoint has its own format, its own recipient, and its own deadline. Here's how they map to each other, and how the same JSON payload powers all four.

Invoice lifecycle and billing document
Photo by Abhinav Arya on Unsplash

Four stages, four requirements

Most software treats invoice compliance as a single checkbox. In Spain, it's actually four separate obligations that apply in different combinations depending on who you're selling to:

Stage What it is Who it applies to Deadline
Visual invoice (PDF) Human-readable invoice sent to the customer All businesses Immediately on issuance
VeriFactu registration Real-time record of the invoice sent to AEAT All businesses (planned Oct 2028, pending approval) Near real-time at issuance
FacturaE delivery Signed XML invoice delivered to FACe or buyer system B2G invoices + voluntary private exchange Required for public sector payment
Received invoice extraction Parsing and validating invoices received from suppliers Any business receiving invoices Ongoing

These stages are not always sequential, and not all of them apply to every invoice. A B2C sale needs PDF and VeriFactu. A public sector sale needs PDF, VeriFactu, and FacturaE. Received invoices are a separate flow entirely — they're incoming, not outgoing.


Stage 1: Visual invoice with Kreyo PDF

Every invoice issuance starts with a document the customer can read. In Spain, the legal minimum is that the invoice contains a specific set of fields defined in the Reglamento de Facturación (RD 1619/2012): issuer details, recipient details, date, concept, base amounts, VAT rates, and totals.

Kreyo PDF takes a JSON payload describing the invoice and returns a PDF. The PDF layout handles Spanish tax fields (IVA, IRPF retention, surcharge of equivalence), multi-rate breakdowns, and localized formatting for amounts and dates. The same InvoiceData object that describes the invoice for PDF generation is the input for VeriFactu registration — you don't maintain two representations.

curl -X POST https://api.kreyo.io/pdf/v1/invoice \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "issuer": { "name": "Empresa Ejemplo S.L.", "nif": "B12345678", ... },
    "recipient": { "name": "Cliente S.A.", "nif": "A87654321", ... },
    "number": "2026-00042",
    "date": "2026-05-09",
    "lines": [{ "description": "Consulting", "quantity": 1, "unitPrice": 1000, "vatRate": 21 }]
  }'
# Returns: application/pdf

Stage 2: VeriFactu registration

From October 2028 (planned, pending regulatory approval), every issued invoice must be registered with AEAT in near-real-time via the VeriFactu system. The registration happens in parallel with sending the PDF to the customer — it's not a separate step the user initiates.

Kreyo VeriFactu takes the same InvoiceData, builds the RegistroFacturacion XML, computes the SHA-256 hash chain, signs with the client's certificate, and submits via mTLS. The response includes AEAT's confirmation code, which can be printed as a QR on the invoice PDF if required.

curl -X POST https://api.kreyo.io/verifactu/v1/register \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "invoice": { ...same InvoiceData... },
    "certificate": { "pfxBase64": "...", "password": "..." }
  }'
# Returns: { "status": "Correcto", "aeatCode": "...", "hash": "A4C8E2B1..." }

Stage 3: FacturaE delivery (B2G)

If the customer is a public administration (a ministry, a local government, a state-owned company), you're legally required to deliver the invoice in FacturaE 3.2.2 format, signed with XAdES-EPES, via the FACe platform (or the customer's own supplier portal if they have one).

This is a separate act from VeriFactu registration. VeriFactu notifies AEAT that the invoice was issued. FacturaE delivers the invoice to the customer's accounts payable system and triggers their payment process. An invoice that is VeriFactu-registered but not FacturaE-delivered will not be paid by the administration.

Kreyo FacturaE takes the same InvoiceData, generates the FacturaE 3.2.2 XML, signs it with XAdES-EPES using the client's certificate, and returns the signed XML ready for delivery to FACe.

curl -X POST https://api.kreyo.io/facturae/v1/generate \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "invoice": { ...same InvoiceData... },
    "certificate": { "pfxBase64": "...", "password": "..." }
  }'
# Returns: { "signedXml": "", "validationStatus": "Valid" }

Stage 4: Received invoice extraction with Kreyo Extract

The previous three stages all deal with invoices you issue. Extract handles invoices you receive — from suppliers, contractors, service providers.

Received invoices come in every format imaginable: PDFs (digital-native or scanned), photos taken with a phone, Excel spreadsheets, hand-formatted emails. Extract uses a dual pipeline (Vision LLM for structured extraction, OCR for numeric cross-verification) and returns a validated InvoiceData object with a compliance score indicating how many fiscal rules the invoice passes.

The output is the same InvoiceData format used by the other three products, which means an extracted invoice can be fed directly into VeriFactu (if you're a collaborative social scheme reseller managing someone else's compliance) or into PDF (to generate a compliant copy of a non-compliant original).

curl -X POST https://api.kreyo.io/extract/v1/invoice \
  -H "Authorization: Bearer sk_live_..." \
  -F "file=@invoice.pdf"
# Returns: { "invoice": { ...InvoiceData... }, "complianceScore": 94, "warnings": [...] }

Why the shared data model matters

The four stages are implemented as four separate API endpoints — separate billing, separate docs, separate integrations. But they share a single InvoiceData JSON schema as the common language.

This means:

For ERPs, this matters because it means integrating Kreyo is a single integration surface. You don't wire up four separate vendors — you wire up one JSON schema and the rest follows.