Documentation
API documentation
normbill is a headless e-invoicing API: you POST invoice JSON, you get back a compliant XRechnung or ZUGFeRD / Factur-X document — validated before it reaches you. When validation fails, every finding maps back to a field in your request with a plain-language message and a concrete fix.
Authentication
Every request carries an API key as a bearer token. You get keys in the dashboard right after signing up — no sales call.
Authorization: Bearer sk_test_… # or sk_live_… in productionRequests without a valid key are rejected with 401 unauthorized.
Your first invoice
POST /v1/invoices/generate takes a JSON invoice and returns the generated XML plus a validation report. This payload is complete and passes validation as-is:
curl https://api.normbill.com/v1/invoices/generate \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"format": "xrechnung-3.0",
"invoice": {
"number": "INV-2026-0001",
"issue_date": "2026-06-15",
"due_date": "2026-07-15",
"currency": "EUR",
"buyer_reference": "04011000-1234512345-06",
"seller": {
"name": "Muster Lieferant GmbH",
"vat_id": "DE123456789",
"address": {
"country": "DE",
"city": "Berlin",
"postal_code": "10115"
},
"contact": {
"name": "Erika Muster",
"phone": "+49 30 1234567",
"email": "billing@lieferant.example"
}
},
"buyer": {
"name": "Beispiel Kunde AG",
"address": {
"country": "DE",
"city": "Hamburg",
"postal_code": "20095"
},
"contact": {
"email": "ap@kunde.example"
}
},
"payment": {
"iban": "DE89370400440532013000"
},
"lines": [
{
"name": "Beratungsleistung",
"qty": 10,
"unit_price": 100,
"vat": 19
}
]
}
}'A successful response (HTTP 200):
{
"format": "xrechnung-3.0",
"profile": "XRechnung 3.0.2 (UBL 2.1)",
"xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Invoice …",
"validation": {
"valid": true,
"tier": "schematron",
"counts": { "errors": 0, "warnings": 0 },
"issues": []
}
}Amounts you omit are computed for you (line totals, VAT breakdown, grand total), and sensible defaults are applied — for example currency defaults to EUR and the payment means code is derived from the IBAN. Field names are snake_case.
When validation fails: the 422 report
If the document is not valid, the API responds with HTTP 422 and does not return an invalid document. The body is an RFC 9457 problem-details object carrying a full report. Each issue tells you the rule that fired, the path in your request JSON, what is wrong, and how to fix it:
{
"type": "https://normbill.com/docs/errors/validation-failed",
"title": "The invoice is not valid",
"status": 422,
"detail": "1 error(s), 0 warning(s). Each issue includes the JSON path and a suggested fix.",
"report": {
"valid": false,
"format": "xrechnung-3.0",
"profile": "XRechnung 3.0.2 (UBL 2.1)",
"tier": "schematron",
"counts": {
"errors": 1,
"warnings": 0
},
"issues": [
{
"severity": "error",
"rule": "BR-DE-15",
"path": "invoice.buyer_reference",
"message": "Buyer reference (BT-10) is missing. XRechnung requires it; public-sector buyers in Germany use their Leitweg-ID here.",
"fix": "Set invoice.buyer_reference. Leitweg-ID format: 04011000-1234512345-06.",
"docs_url": "https://normbill.com/docs/rules/br-de-15",
"location": "/ubl:Invoice/cbc:BuyerReference"
}
]
}
}report.tier— how far validation got:structural(normbill's own pre-flight rules),schema(XSD), orschematron(the authoritative KoSIT rule set).issues[].path— the field in your request JSON, e.g.invoice.buyer_referenceorinvoice.lines[0].vat. It isnullwhen a finding cannot be mapped back to an input field.issues[].docs_url— a per-rule page under/docs/rules/<rule-id>.
Quotas and rate-limit headers
Plans include a monthly document allowance (see pricing). Only successful generate and parse calls consume it — failed attempts never count, and /v1/invoices/validate has its own free fair-use allowance of five times your document quota. Every response reports where you stand:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Your monthly allowance in the current period. |
X-RateLimit-Remaining | Documents left this period. |
X-Quota-Warning | Present from 80% usage, so you are warned well before the limit. |
Exhausting the allowance returns 429 quota-exceeded with an upgrade link. Metered overage for paid plans is rolling out — once enabled, extra documents are billed per invoice instead of rejected, capped at 2× your base fee by default.
Retries are safe: POST /v1/invoices/generate accepts an optional Idempotency-Key header (valid for 24 hours). A retry with the same key and body returns the same document and never consumes quota twice.
Test vs. live keys
sk_test_keys have their own allowance of 1000 requests per month and are never billed — they do not count against your plan.- Documents generated with a test key carry a visible note marking them as test invoices, so they can't be mistaken for legal documents.
sk_live_keys count against the plan quota and produce production documents.
API reference
The whole surface is three endpoints — small on purpose. Full request/response schemas live in the OpenAPI spec; every non-2xx response is an RFC 9457 problem-details body (see the error reference).
Invoice JSON in, validated document out. Formats: xrechnung-3.0 (UBL, authoritative KoSIT validation), peppol-bis-3.0 (UBL, BIS Billing 3.0 CIUS), zugferd-2.x / facturx-1.0 (CII embedded in a hybrid PDF/A-3, returned base64 in the pdf field — paid plans). Non-XRechnung formats are validated at the structural tier; the report's tier field states this.
curl https://api.normbill.com/v1/invoices/generate \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"format": "xrechnung-3.0",
"invoice": {
"number": "INV-2026-0001",
"issue_date": "2026-06-15",
"due_date": "2026-07-15",
"currency": "EUR",
"buyer_reference": "04011000-1234512345-06",
"seller": {
"name": "Muster Lieferant GmbH",
"vat_id": "DE123456789",
"address": {
"country": "DE",
"city": "Berlin",
"postal_code": "10115"
},
"contact": {
"name": "Erika Muster",
"phone": "+49 30 1234567",
"email": "billing@lieferant.example"
}
},
"buyer": {
"name": "Beispiel Kunde AG",
"address": {
"country": "DE",
"city": "Hamburg",
"postal_code": "20095"
},
"contact": {
"email": "ap@kunde.example"
}
},
"payment": {
"iban": "DE89370400440532013000"
},
"lines": [
{
"name": "Beratungsleistung",
"qty": 10,
"unit_price": 100,
"vat": 19
}
]
}
}'Returns 200 with xml (+ pdf for ZUGFeRD / Factur-X) and the validation report, or 422 with the full report when the document is invalid.
Validates XML you already have — from your own system or a supplier. UBL 2.1 / XRechnung documents run through the authoritative KoSIT validation; CII (ZUGFeRD / Factur-X) documents are checked at the structural EN 16931 tier (tier 1) today. The report's tier field states which one ran.
curl https://api.normbill.com/v1/invoices/validate \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "<?xml version=\"1.0\"?><Invoice …>…</Invoice>" }'A valid document returns the report with valid: true; an invalid one returns the same 422 shape as above — every failed rule with its ID, severity, message and a suggested fix.
The receiving side of the mandate: maps an inbound e-invoice back to the normbill invoice model. Both syntaxes are accepted and auto-detected — UBL 2.1 (XRechnung) and CII / UN-CEFACT D16B (ZUGFeRD, Factur-X); the response's format field states which was detected.
curl https://api.normbill.com/v1/invoices/parse \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "<?xml version=\"1.0\"?><ubl:Invoice …>…</ubl:Invoice>" }'A successful response (HTTP 200):
{
"format": "xrechnung-3.0",
"profile": "XRechnung 3.0.2 (UBL 2.1)",
"invoice": {
"number": "INV-2026-0001",
"issue_date": "2026-06-15",
"seller": { "name": "Muster Lieferant GmbH", "vat_id": "DE123456789", … },
"lines": [ … ]
}
}Error reference
Every non-2xx response is an RFC 9457 problem-details body whose type URL links to a documented error:
| Error | Status | Meaning |
|---|---|---|
invalid-request | 400 | Invalid request body |
bad-request | 400 | Bad request |
payload-too-large | 413 | Payload too large |
unauthorized | 401 | Unauthorized |
validation-failed | 422 | The invoice is not valid |
unparseable-document | 400 | The document could not be parsed |
plan-limit | 403 | Feature not included in your plan |
quota-exceeded | 429 | Monthly quota exceeded |
rate-limited | 429 | Too many requests (burst rate limit) |
overage-cap-reached | 429 | Overage spending cap reached |
validate-fair-use | 429 | Validation fair-use allowance exhausted |
idempotency-key-invalid | 400 | Invalid Idempotency-Key header |
idempotency-key-reuse | 422 | Idempotency key reused with a different body |
idempotency-conflict | 409 | Concurrent request with the same idempotency key |
pdf-unavailable | 503 | PDF rendering temporarily unavailable |
validator-unavailable | 503 | Validation service temporarily unavailable |
not-ready | 503 | Service not ready |
Rule reference
Validation findings link to per-rule pages with the mapped JSON field and a concrete fix. The curated rules (grown from real traffic — the KoSIT validator checks many more):
| Rule | What it checks |
|---|---|
BR-DE-1 | Payment instructions (BG-16) are missing. XRechnung requires them. |
BR-DE-2 | Seller contact details (BG-6) are missing. XRechnung requires a contact person or department. |
BR-DE-3 | Seller city (BT-37) is missing. |
BR-DE-4 | Seller postal code (BT-38) is missing. |
BR-DE-5 | Seller contact name (BT-41) is missing. |
BR-DE-6 | Seller contact phone number (BT-42) is missing. |
BR-DE-7 | Seller contact email (BT-43) is missing. |
BR-DE-8 | Buyer city (BT-52) is missing. |
BR-DE-9 | Buyer postal code (BT-53) is missing. |
BR-DE-10 | A deliver-to address (BG-15) is present but its city (BT-77) is missing — XRechnung requires it. |
BR-DE-11 | A deliver-to address (BG-15) is present but its post code (BT-78) is missing — XRechnung requires it. |
BR-DE-15 | Buyer reference (BT-10) is missing. XRechnung requires it; public-sector buyers in Germany use their Leitweg-ID here. |
BR-DE-16 | Seller VAT identifier (BT-31) or tax registration number (BT-32) is required. |
BR-DE-17 | This invoice type code is not recommended in XRechnung. |
BR-DE-18 | A payment-terms line starting with # is not a valid Skonto segment — XRechnung requires the exact format #SKONTO#TAGE=n#PROZENT=n.nn# (optionally #BASISBETRAG=n.nn#), one per line. |
BR-DE-23-a | Payment means is a credit transfer (code 30/58) but no payment account (IBAN, BT-84) is given. |
BR-DE-23-b | Payment means is a credit transfer (code 30/58), so direct-debit information (BG-19) must not be present. |
BR-DE-25-a | Payment means is a SEPA direct debit (code 49/59) but direct-debit information (BG-19) is missing. |
BR-DE-25-b | Payment means is a SEPA direct debit (code 49/59), so a payee account (BG-17) must not be present. |
BR-DE-26 | A corrected invoice (type_code 384) should reference the invoice it corrects (BT-25). |
BR-DE-27 | The seller contact phone number (BT-42) should contain at least three digits. |
BR-DE-30 | Direct-debit information (BG-19) is present but the creditor identifier (BT-90) is missing. |
BR-DE-31 | Direct-debit information (BG-19) is present but the debited account identifier (BT-91) is missing. |
PEPPOL-EN16931-R061 | A direct-debit payment (means code 49/59) must carry a mandate reference (BT-89). |
BR-CO-10 | The sum of line net amounts (BT-106) does not match the invoice lines. |
BR-CO-13 | Supplied totals.net does not equal the sum of line nets minus document allowances plus document charges. |
BR-CO-14 | Supplied totals.tax does not match the computed VAT breakdown. |
BR-CO-15 | Supplied totals.gross does not equal net + tax. |
BR-CO-16 | The amount due (BT-115) would be negative: paid_amount (BT-113) exceeds the invoice total with VAT (BT-112). |
BR-CO-25 | An amount is due but neither a due date (BT-9) nor payment terms (BT-20) are present. |
BR-DEC-01 | A document-level allowance amount (BT-92) has more than 2 decimal places. |
BR-DEC-02 | A document-level allowance base amount (BT-93) has more than 2 decimal places. |
BR-DEC-05 | A document-level charge amount (BT-99) has more than 2 decimal places. |
BR-DEC-06 | A document-level charge base amount (BT-100) has more than 2 decimal places. |
BR-DEC-12 | totals.net (BT-109) has more than 2 decimal places — EN 16931 allows at most 2. |
BR-DEC-13 | totals.tax (BT-110) has more than 2 decimal places — EN 16931 allows at most 2. |
BR-DEC-14 | totals.gross (BT-112) has more than 2 decimal places — EN 16931 allows at most 2. |
BR-DEC-24 | A line-level allowance amount (BT-136) has more than 2 decimal places. |
BR-DEC-25 | A line-level allowance base amount (BT-137) has more than 2 decimal places. |
BR-DEC-27 | A line-level charge amount (BT-141) has more than 2 decimal places. |
BR-DEC-28 | A line-level charge base amount (BT-142) has more than 2 decimal places. |
BR-S-02 | The invoice has standard-rated (S) lines, which require the seller VAT identifier (BT-31) or tax registration number (BT-32). |
BR-S-05 | A standard-rated (S) line must have a VAT rate greater than zero. |
BR-Z-05 | A zero-rated (Z) line must have a VAT rate of exactly 0. |
BR-E-05 | A VAT-exempt (E) line must have a VAT rate of 0. |
BR-AE-05 | A reverse-charge (AE) line must have a VAT rate of 0. |
BR-AE-02 | Reverse charge (AE) requires a seller VAT identifier (BT-31) or tax registration number (BT-32), and the buyer VAT identifier (BT-48). |
BR-27 | The item net price (BT-146) must not be negative. |
BR-E-10 | Lines use VAT category "E" (exempt) but no exemption reason (BT-120) or reason code (BT-121) is given. |
BR-AE-10 | Lines use VAT category "AE" (reverse charge) but no exemption reason (BT-120) or reason code (BT-121) is given. |
BR-IC-10 | Lines use VAT category "K" (intra-community supply) but no exemption reason (BT-120) or reason code (BT-121) is given. |
BR-G-10 | Lines use VAT category "G" (export outside the EU) but no exemption reason (BT-120) or reason code (BT-121) is given. |
BR-O-10 | Lines use VAT category "O" (not subject to VAT) but no exemption reason (BT-120) or reason code (BT-121) is given. |
BR-O-05 | A line "not subject to VAT" (O) must not carry a VAT rate. |
BR-O-11 | VAT category "O" (not subject to VAT) cannot be mixed with other VAT categories on the same invoice (BR-O-11..14). |
BR-32 | A document-level allowance needs a VAT category (BT-95); it cannot be inherited because the lines use more than one VAT category/rate. |
BR-37 | A document-level charge needs a VAT category (BT-102); it cannot be inherited because the lines use more than one VAT category/rate. |
BR-33 | A document-level allowance must state why it is granted (BT-97/BT-98). |
BR-38 | A document-level charge must state what it is for (BT-104/BT-105). |
BR-42 | A line-level allowance must state why it is granted (BT-139/BT-140). |
BR-44 | A line-level charge must state what it is for (BT-144/BT-145). |
NB-AC-MISMATCH | An allowance/charge amount does not equal base_amount × percent / 100 — the KoSIT validator rejects this (PEPPOL-EN16931-R040). |
NB-LEITWEG-FORMAT | buyer_reference does not look like a Leitweg-ID. German B2G platforms (ZRE/OZG-RE) reject malformed Leitweg-IDs. For B2B invoices any buyer reference is fine — ignore this warning then. |
NB-EADDR-SELLER | Seller electronic address (BT-34) is missing — XRechnung requires one for both parties. |
NB-EADDR-BUYER | Buyer electronic address (BT-49) is missing — XRechnung requires one for both parties. |
NB-IBAN-CHECKSUM | The IBAN fails its checksum — this is almost certainly a typo. |
NB-VAT-RATE | A line has neither a VAT rate nor a VAT category — the category cannot be determined. |
NB-PAYMENT-MEANS-UNSUPPORTED | Card-payment means codes (48/54/55) need card information (BG-18) that normbill cannot express yet — KoSIT would reject the invoice (BR-DE-24-a). |
NB-VAT-ID-FORMAT | A German VAT identifier (BT-31/BT-48) must be "DE" followed by exactly 9 digits. EN 16931 and the KoSIT validator only check the ISO country prefix, not the national length, so a German id with the wrong number of digits still passes tier 3 — normbill catches it here. |
NB-VALIDATOR-REJECT | The official KoSIT validator rejected this document without a mappable rule finding — typically an XML schema (XSD) violation. |
BR-CO-26 | The seller carries none of: VAT identifier (BT-31), legal registration identifier (BT-30) or seller identifier (BT-29) — at least one is required so the buyer can identify the supplier. The German tax number (BT-32) alone does NOT satisfy this rule. |
BR-IC-11 | An intra-community supply (VAT category K) must state the actual delivery date (BT-72) or an invoicing period (BG-14). |
BR-IC-12 | An intra-community supply (VAT category K) must state the deliver-to country (BT-80). |
BR-29 | The invoicing period end date (BT-74) must not be earlier than its start date (BT-73). |
BR-30 | An invoice line period end date (BT-135) must not be earlier than its start date (BT-134). |
PEPPOL-EN16931-R110 | An invoice line period start date must lie within the document invoicing period. |
PEPPOL-EN16931-R111 | An invoice line period end date must lie within the document invoicing period. |
BR-IC-05 | An intra-community supply (K) line must have a VAT rate of exactly 0. |
BR-G-05 | An export (G) line must have a VAT rate of exactly 0. |
BR-CL-04 | The invoice currency code (BT-5) is not part of ISO 4217. |
BR-CL-14 | A country code in the invoice is not an ISO 3166-1 alpha-2 code. |
BR-CL-16 | The payment means code (BT-81) is not a UNTDID 4461 code. |
BR-CL-19 | An allowance reason code (BT-98 document level, BT-140 line level) is not part of the UNCL 5189 subset EN 16931 permits. |
BR-CL-20 | A charge reason code (BT-105 document level, BT-145 line level) is not a UNTDID 7161 code. |
BR-CL-22 | A VAT exemption reason code (BT-121) is not part of the VATEX code list. |
BR-CL-25 | An electronic address scheme (BT-34 seller, BT-49 buyer — the schemeID on cbc:EndpointID) is not part of the EAS code list. |
Interactive API reference
The full request/response schemas are published as an OpenAPI 3.1 document generated directly from the runtime contracts — the reference cannot drift from what the API actually validates:
- Interactive reference — every schema, browsable, with request builders
- /openapi.json — the raw OpenAPI spec, ready for codegen (also served by the API at
https://api.normbill.com/openapi.json)
Questions, missing docs, or a rule that needs a better explanation? Email support@normbill.com.