Skip to content

E-invoicing Compliance

Check an invoice against a published e-invoicing ruleset, get the XML, or get a PDF with the XML inside it.

What this is. A growing number of jurisdictions require invoices to be machine-readable and to satisfy a published rule set — not a PDF that happens to contain the right words. This API builds that document and holds it to the actual rules, so you find out before you send rather than from a counterparty weeks later.

What this is not. We do not transmit. Getting a document onto the Peppol network is the Access Point role — membership, certificates, accreditation — and we are not one. We make the document correct; your access point, or your customer's, moves it. Factur-X needs no network at all: it is emailed like any other PDF.


Profiles

A profile is a ruleset a document is built and validated against. There is no default: a document valid under one can be rejected by another, so the choice is the question rather than something to guess at.

profile Syntax Rules run Where it is used
factur_x_en16931 CII, inside a PDF/A-3 EN 16931 1.3.16 France and Germany, by agreement rather than mandate
peppol_bis_billing_3 UBL EN 16931 1.3.16 + Peppol BIS Billing 3.0.20 The Peppol network — EU public sector, NHS England, and the UK from April 2029
xrechnung_ubl UBL EN 16931 1.3.16 + XRechnung 3.0 German public authorities
xrechnung_cii CII EN 16931 1.3.16 + XRechnung 3.0 German public authorities

XRechnung appears twice because it is defined in both syntaxes, and they are not the same document. Ask the receiver which one they take.

GET /api/v1/reference/tax-categories, /tax-schemes and /unit-codes return the coded lists these rules require. See Reference Data.

Peppol is not the EU

Italy clears through SdI, France through its PDP model, Poland through KSeF. These are national platforms, not Peppol, and each is a separate implementation rather than a profile. peppol_bis_billing_3 covers an Italian invoice sent over Peppol, which is not the same thing as FatturaPA's domestic mandate.

US invoices

EN 16931 is a European VAT standard. Its rules test the tax scheme against the literal string VAT (BR-47, BR-48, BR-CO-17), and the US has no VAT — sales tax is state and local, with no invoice-format requirement attached. There is nothing here for a US-only business to comply with, and the API will say so rather than relabelling a sales tax. For that business the PDF is the invoice.


Validate Compliance

POST /api/v1/documents/validate-compliance

Checks a document and returns everything both tiers had to say about it. Nothing is stored, no PDF is produced, and it does not count against your render quota — so you can check every invoice you are about to send.

curl https://invoicepdfs.com/api/v1/documents/validate-compliance \
  -H "Authorization: Bearer inv_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "document_type": "invoice",
    "profile": "peppol_bis_billing_3",
    "data": { ... }
  }'

A document that satisfies its profile:

{
  "data": {
    "profile": "factur_x_en16931",
    "ruleset_version": "EN16931-CII 1.3.16",
    "valid": true,
    "in_scope": true,
    "fully_checked": true,
    "rulesets": [
      {"id": "semantic",     "label": "Mandatory fields", "version": "",       "ran": true, "reason": null},
      {"id": "en16931-cii",  "label": "EN 16931 (CII)",   "version": "1.3.16", "ran": true, "reason": null}
    ],
    "violations": []
  }
}

One that does not:

{
  "data": {
    "profile": "peppol_bis_billing_3",
    "ruleset_version": "EN16931-UBL 1.3.16 + Peppol BIS Billing 3.0.20",
    "valid": false,
    "in_scope": true,
    "fully_checked": true,
    "violations": [
      {
        "rule": "BT-10",
        "path": "buyer_reference",
        "message": "Peppol BIS Billing 3.0 requires a buyer reference unless an order reference is present, and this document carries neither.",
        "severity": "fatal",
        "ruleset": "semantic"
      }
    ]
  }
}

Reading the answer

There are four fields that mean different things, and conflating them is the usual mistake.

valid — nothing found would get this document rejected. It is not "no findings": both EN 16931 and Peppol grade a large share of their rules as advisory, and a document reported invalid over a recommendation would be a verdict nobody could use. Only severity: "fatal" counts.

fully_checked — every ruleset that applies actually ran. This is a separate question from the verdict, and the one people miss. The authoritative tier executes the published Schematron artefacts; if that toolchain is unavailable, the document is still checked against the mandatory-field rules and valid can come back true — but on a materially weaker basis. A ruleset that could not run is never reported as a pass. Check fully_checked before you treat valid: true as an answer, and see rulesets[].ran and .reason for which one and why.

in_scope — whether these rules mean anything for this document at all. false for an invoice with no party in a country that uses them: a verdict of "an access point would reject this" names a rejection that cannot happen. It never changes valid — it tells you whether the verdict is worth having.

ruleset_version — which artefact versions ran. These are published documents that get revised, so "which rules did this pass?" is answerable only with the version beside it.

Severity

severity Meaning
fatal A receiver is entitled to reject the document. Sets valid: false.
warning Advisory. Worth reading, not a rejection.

rule is the identifier the standard uses — BT-10 from the mandatory-field tier, PEPPOL-EN16931-R003 from Schematron — which is what a rejection notice from an access point quotes back at you. path locates it: a field of your request from the first tier, an XPath into the generated XML from the second.


Try it

Get the XML

POST /api/v1/documents/xml

The document as UBL or CII, in the syntax its profile is expressed in. Stateless — send the data, get the XML.

GET /api/v1/documents/{document_id}/xml

The same, for a document you have stored.

This is the file you hand to an access point. We do not send it for you.


Try it
Try it

Factur-X: the PDF with the XML inside

Pass format: "facturx_pdf" instead of "pdf" on any render call and you get a PDF/A-3 carrying factur-x.xml — a human-readable invoice and a machine-readable one, in a single file that needs no network and no counterparty agreement. It is emailed like any other PDF.

curl https://invoicepdfs.com/api/v1/documents/doc_01ABC/renders \
  -H "Authorization: Bearer inv_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"format": "facturx_pdf"}'
curl https://invoicepdfs.com/api/v1/documents/render \
  -H "Authorization: Bearer inv_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "document_type": "invoice",
    "data": { ... },
    "output": {"format": "facturx_pdf"}
  }'

This path fails closed. The document is checked before it is rendered, and a fatal violation means no PDF is produced — you get 422 with code: "compliance_failed" and every violation in details, not just the first. A file that claims EN 16931 conformance and does not have it is worse than no file, because the claim travels with it.

The embedded XML is the exact bytes that were validated, not a second serialization of the same document.

What the render records

A render made this way carries what it was held to:

{
  "data": {
    "id": "rnd_01ABC",
    "format": "pdf",
    "compliance": {
      "profile": "factur_x_en16931",
      "ruleset_version": "EN16931-CII 1.3.16",
      "fully_checked": true,
      "advisories": []
    },
    "download_url": "https://invoicepdfs.com/api/v1/renders/rnd_01ABC/download?token=..."
  }
}

compliance is null on a plain pdf render — no ruleset was applied, so there is no claim. Its presence is how you tell a hybrid PDF from an ordinary one.

It is read back from the record, not recomputed, so GET /api/v1/renders/{render_id} answers with the version the document was actually checked against rather than whatever the rulesets say today. fully_checked means here exactly what it means above: false says the authoritative tier did not run for the file you shipped.

advisories are the non-fatal findings the render proceeded past. An empty list means nothing was raised.


Which call do I want?

Question Call
Will this be accepted, before I commit to anything? POST /documents/validate-compliance
I need the XML to hand to my access point POST /documents/xml or GET /documents/{id}/xml
I need one file to email that satisfies Factur-X any render with format: "facturx_pdf"
What was this file held to? compliance on the render