API reference

Submit invoice data, receive a compliant Factur-X PDF or XML.

← Back to the form

Authentication

Every protected endpoint requires an API key. Keys are minted by an administrator (CLI php bin/users.php mint <user_id>, or via /admin) and look like inv_…. The plaintext value is shown once at creation.

Send the key in either of two headers:

Authorization: Bearer inv_xxxxxxxxxxxx
X-Api-Key: inv_xxxxxxxxxxxx

The browser form on / also accepts an API key (stored in localStorage if you tick the box) and sends it as Authorization: Bearer ….

POST /invoice

Builds the CII XML, optionally wraps it as a PDF/A-3, and returns the file.

Query parameters

NameValuesDefaultNotes
profilebasicwl, basic, en16931, extendedbasicMINIMUM is intentionally rejected (structural constraints).
formatpdf, xmlderived from Accept, else pdfOverrides the Accept header.

Request bodies

Content-TypeBody
application/jsonThe invoice as a JSON object (see /example.json).
text/csvA sectioned CSV (see /example.csv).
multipart/form-dataFile field file with a .csv / .json / .xlsx upload; or text fields json / csv.
application/vnd.openxmlformats-officedocument.spreadsheetml.sheetReserved; currently returns 501.

Responses

StatusContent-TypeBody
200application/pdfPDF/A-3 with embedded CII XML. Content-Disposition: attachment.
200application/xmlCII XML (already validated against the official XSD).
4xx / 5xxapplication/json{"error": "..."} — see Errors.

Input schemas

JSON

The JSON body is the invoice object passed straight into the builder. Download /example.json for a complete sample. Top-level fields:

{
  "number":           "INV-2026-0042",     // required
  "issue_date":       "2026-05-05",        // required (YYYY-MM-DD or YYYYMMDD)
  "due_date":         "2026-06-04",
  "currency":         "EUR",
  "type_code":        "380",               // UNTDID 1001; 380 = commercial invoice
  "buyer_reference":  "PO-9981",
  "order_reference":  "CMD-77",
  "payment_terms":    "Paiement à 30 jours net.",
  "prepaid":          0,

  "seller": { /* party */ },               // required
  "buyer":  { /* party */ },               // required

  "lines": [
    {
      "name":         "...",               // required
      "description":  "...",               // only kept in EN16931/EXTENDED
      "quantity":     1,                   // required
      "unit_price":   1800.00,             // required
      "unit_code":    "C62",               // UN/CEFACT, default C62 (each)
      "vat_rate":     20.0,                // required
      "vat_category": "S"                  // UNTDID 5305, default S
    }
  ],

  "notes": [
    "Free-form note",
    { "subject_code": "PMT", "content": "..." }  // UNTDID 4451 SubjectCode
  ],

  "payment_means": { "type_code": "30", "iban": "FR76…" }
}

Party shape:

{
  "name":              "Atelier Lumen SARL",  // required
  "vat_id":            "FR43123456789",
  "legal_id":          "123456789",
  "legal_id_scheme":   "0002",        // 0002 = SIRET, 0009 = SIREN
  "electronic_address":        "facturation@atelier-lumen.fr",
  "electronic_address_scheme": "EM",  // EM = email; 0088 = GLN; …

  "address": {
    "line1":    "12 rue des Tisserands",
    "line2":    null,
    "postcode": "75011",
    "city":     "Paris",
    "country":  "FR"
  }
}

Sectioned CSV

One file = one invoice. Lines starting with # open a section. Two shapes:

Key/value sections (header, seller, buyer, payment_means): second row is the literal header field,value, then key,value pairs. Dotted keys (e.g. address.line1) build nested objects.

Tabular sections (lines, notes): second row is the column header, each subsequent row is one entity. Empty cells are dropped.

#header
field,value
number,INV-2026-0042
issue_date,2026-05-05
currency,EUR

#seller
field,value
name,Atelier Lumen SARL
vat_id,FR43123456789
address.line1,12 rue des Tisserands
address.postcode,75011
address.city,Paris
address.country,FR

#buyer
field,value
name,Maison Dubois SAS
...

#lines
name,description,quantity,unit_price,unit_code,vat_rate,vat_category
Audit ergonomique,Audit UX,1,1800.00,C62,20.0,S
Atelier de design,,4,650.00,HUR,20.0,S

#notes
subject_code,content
,Merci pour votre confiance.
PMT,"Tout retard de paiement..."

#payment_means
field,value
type_code,30
iban,FR7630006000011234567890189

Unknown sections produce a 400. Download /example.csv for the canonical template.

Profiles

profileURNNotes
basicwlurn:factur-x.eu:1p0:basicwlHeader-only ("Without Lines"). Useful for summary invoices.
basicurn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basicDefault. Line items, per-rate VAT, no product descriptions.
en16931urn:cen.eu:en16931:2017Full EN 16931. Line descriptions allowed.
extendedurn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extendedSuperset of EN 16931 with additional fields.
minimumReturns 400 — has structural constraints that need a separate code path.

Errors

StatusMeaning
400Bad input: missing field, malformed JSON, unknown CSV section, unknown profile.
401Missing or invalid API key. WWW-Authenticate: Bearer realm="invox".
403Caller is authenticated but not authorized (e.g. non-admin hitting /admin).
404No such route.
405Method not allowed.
415Unsupported Content-Type / file format.
501Format is recognized but not implemented yet (e.g. XLSX upload).
500Unexpected server error. Check error_log for the trace.

Error bodies are JSON: {"error": "human-readable message"}.

curl examples

JSON → PDF

curl -sS -X POST http://127.0.0.1:8000/invoice \
     -H "Authorization: Bearer inv_..." \
     -H "Content-Type: application/json" \
     -H "Accept: application/pdf" \
     --data @invoice.json \
     -o invoice.pdf

JSON → XML

curl -sS -X POST 'http://127.0.0.1:8000/invoice?format=xml' \
     -H "Authorization: Bearer inv_..." \
     -H "Content-Type: application/json" \
     --data @invoice.json \
     -o invoice.xml

CSV upload → PDF

curl -sS -X POST 'http://127.0.0.1:8000/invoice?profile=en16931&format=pdf' \
     -H "X-Api-Key: inv_..." \
     -F "file=@invoice.csv;type=text/csv" \
     -o invoice.pdf

Raw CSV body → PDF

curl -sS -X POST http://127.0.0.1:8000/invoice \
     -H "Authorization: Bearer inv_..." \
     -H "Content-Type: text/csv" \
     --data-binary @invoice.csv \
     -o invoice.pdf