Core API

Validator API

Validate e-invoices against official KoSIT schemas and Schematron rules. Get detailed error messages with correction suggestions.

POST/api/v1/invoice/{countryCode}/validate
API key required

Validation only works with a valid bearer key. Without one the endpoint answers 401.

Check before you send

Catch format errors before the invoice reaches the recipient — with error message and correction hint.

KoSIT-compliant

Official Schematron rules from KoSIT (Coordination Office for IT Standards)

37 Countries

Support for AE, AT, AU, BE, BG, CH, CY, CZ, DE, DK, EE, ES, FI, FR, GB, GR, HR, HU, IE, IS, IT, JP, LI, LT, LU, LV, MT, NL, NO, NZ, OM, PL, PT, RO, SE, SI, SK

All Formats

XRechnung, ZUGFeRD, Factur-X, Peppol UBL and national formats (FatturaPA, ebInterface, Facturae, ISDOC, NAV, myDATA, KSeF FA(3), eFactura/CIUS-RO, CIUS-PT, QR-Bill) are supported — the full table is further down.

Example

bash
1curl -X POST 'https://service.invoice-api.xhub.io/api/v1/invoice/de/validate' \
2 -H 'Authorization: Bearer sk_test_xxx' \
3 -H 'Content-Type: application/json' \
4 -d '{
5 "invoice": {
6 "invoiceNumber": "RE-2026-001",
7 "type": "invoice",
8 "issueDate": "2026-01-15",
9 "dueDate": "2026-02-15",
10 "currency": "EUR",
11 "seller": {
12 "name": "Meine Firma GmbH",
13 "street": "Musterstraße 1",
14 "city": "Berlin",
15 "postalCode": "10115",
16 "countryCode": "DE",
17 "vatId": "DE123456789"
18 },
19 "buyer": {
20 "name": "Kunde AG",
21 "street": "Kundenweg 42",
22 "city": "München",
23 "postalCode": "80331",
24 "countryCode": "DE",
25 "vatId": "DE987654321"
26 },
27 "items": [{
28 "position": 1,
29 "description": "Beratung",
30 "quantity": 10,
31 "unit": "HUR",
32 "unitPrice": 150.00,
33 "taxRate": 19,
34 "netAmount": 1500.00,
35 "taxAmount": 285.00,
36 "grossAmount": 1785.00
37 }],
38 "subtotal": 1500.00,
39 "total": 1785.00,
40 "taxSummary": [{ "taxRate": 19, "netAmount": 1500.00, "taxAmount": 285.00 }],
41 "paymentTerms": { "dueDays": 30 }
42 }
43 }'

Validate one format

POST/api/v1/invoice/{countryCode}/{format}/validate

Next to the country-wide route there is one route per format. It generates the invoice in exactly that format and runs the result through that format's XSD and Schematron — instead of through those of every format the country has.

Country-wide route

/api/v1/invoice/{countryCode}/validate

Checks every format the country has, plus the country's own business rules. For DE the invoice above yields eleven entries in results[] — XRechnung and ten ZUGFeRD profiles — and the codes DE_REF_002 and DE_SELLER_CONTACT_001 in errors[] (measured on 2026-09-15).

Format route

/api/v1/invoice/{countryCode}/{format}/validate

Checks exactly one format. For ZUGFeRD and Factur-X every profile is generated and checked, which is why results[] carries several entries there. The country's business rules do not run here: valid refers to the generated XML alone.

format path parameter

The spec knows these values, always lowercase:

ebinterfaceebInterfacefacturaeFacturaefacturxFactur-XfatturapaFatturaPAhr-fiskHR-FISKisdocISDOCksefKSeFmydatamyDATAnavNAVpdfPDFpeppol-ublPeppol UBLqr-billQR-BillublUBLxrechnungXRechnungzugferdZUGFeRD

Which of them a country actually carries as a validatable XML format depends on the country — the formats endpoint returns the list per country. A value the country does not carry is rejected with 400: /de/pdf/validate answers "Format 'pdf' is not a validatable XML format for country DE" (measured on 2026-09-15).

Request

bash
1curl -X POST 'https://service.invoice-api.xhub.io/api/v1/invoice/de/zugferd/validate' \
2 -H 'Authorization: Bearer sk_test_xxx' \
3 -H 'Content-Type: application/json' \
4 -d '{
5 "invoice": {
6 "invoiceNumber": "RE-2026-001",
7 "type": "invoice",
8 "issueDate": "2026-01-15",
9 "dueDate": "2026-02-15",
10 "currency": "EUR",
11 "seller": {
12 "name": "Meine Firma GmbH",
13 "street": "Musterstraße 1",
14 "city": "Berlin",
15 "postalCode": "10115",
16 "countryCode": "DE",
17 "vatId": "DE123456789"
18 },
19 "buyer": {
20 "name": "Kunde AG",
21 "street": "Kundenweg 42",
22 "city": "München",
23 "postalCode": "80331",
24 "countryCode": "DE",
25 "vatId": "DE987654321"
26 },
27 "items": [{
28 "position": 1,
29 "description": "Beratung",
30 "quantity": 10,
31 "unit": "HUR",
32 "unitPrice": 150.00,
33 "taxRate": 19,
34 "netAmount": 1500.00,
35 "taxAmount": 285.00,
36 "grossAmount": 1785.00
37 }],
38 "subtotal": 1500.00,
39 "total": 1785.00,
40 "taxSummary": [{ "taxRate": 19, "netAmount": 1500.00, "taxAmount": 285.00 }],
41 "paymentTerms": { "dueDays": 30 }
42 }
43 }'

Response

The same invoice as above, sent to /de/zugferd/validate — the service's answer of 2026-09-15, unchanged:

json
1{
2 "valid": true,
3 "results": [
4 { "format": "zugferd-2.4-minimum", "valid": true, "errors": [] },
5 { "format": "zugferd-2.4-en16931", "valid": true, "errors": [] },
6 { "format": "zugferd-2.4-basic", "valid": true, "errors": [] },
7 { "format": "zugferd-2.4-basic-wl", "valid": true, "errors": [] },
8 { "format": "zugferd-2.4-extended", "valid": true, "errors": [] },
9 { "format": "zugferd-2.5-minimum", "valid": true, "errors": [] },
10 { "format": "zugferd-2.5-en16931", "valid": true, "errors": [] },
11 { "format": "zugferd-2.5-basic", "valid": true, "errors": [] },
12 { "format": "zugferd-2.5-basic-wl", "valid": true, "errors": [] },
13 { "format": "zugferd-2.5-extended", "valid": true, "errors": [] }
14 ]
15}

Sent to /de/xrechnung/validate the same invoice yields valid: false, one entry in results[] (xrechnung-3.0.2-ubl) and the rule codes PEPPOL-EN16931-R020, BR-DE-1, BR-DE-6, BR-DE-7 and BR-DE-15. Each entry there carries location (XPath) and severity; the same findings also appear flat in errors[].

Auth and quota as on the country-wide route: a bearer key is required (401 without it), and the call is bound to the key's monthly quota — once that is exhausted this route answers 429 as well. Plan it like a generation call: the check generates the document internally.

Supported Countries

The country code is specified as part of the URL: /api/v1/invoice/{countryCode}/validate

DE

Germany

AT

Austria

CH

Switzerland

FR

France

IT

Italy

ES

Spain

NL

Netherlands

BE

Belgium

PL

Poland

PT

Portugal

AE

United Arab Emirates

AU

Australia

BG

Bulgaria

CY

Cyprus

CZ

Czech Republic

DK

Denmark

EE

Estonia

FI

Finland

GB

United Kingdom

GR

Greece

HR

Croatia

HU

Hungary

IE

Ireland

IS

Iceland

JP

Japan

LI

Liechtenstein

LT

Lithuania

LU

Luxembourg

LV

Latvia

MT

Malta

NO

Norway

NZ

New Zealand

OM

Oman

RO

Romania

SE

Sweden

SI

Slovenia

SK

Slovakia

= Available

Germany

/api/v1/invoice/de/validate

Austria

/api/v1/invoice/at/validate

France

/api/v1/invoice/fr/validate

Italy

/api/v1/invoice/it/validate

Switzerland

/api/v1/invoice/ch/validate

Spain

/api/v1/invoice/es/validate

Request Parameters

Path Parameter

ParameterTypeRequiredDescription
countryCodestringISO 3166-1 alpha-2 country code. Available (37): AE, AT, AU, BE, BG, CH, CY, CZ, DE, DK, EE, ES, FI, FR, GB, GR, HR, HU, IE, IS, IT, JP, LI, LT, LU, LV, MT, NL, NO, NZ, OM, PL, PT, RO, SE, SI, SK.

Headers & Body

ParameterTypeRequiredDescription
AuthorizationheaderBearer token with sk_test_* or sk_live_* API key
Content-Typeheaderapplication/json
invoiceobject (body)The invoice object to validate (JSON). The endpoint accepts a JSON body with an invoice field only — no raw XML, no file upload.

Authentication

Use your API key in the Authorization header: for test environment or for production.Bearer sk_test_... / Bearer sk_live_...

Response

Valid Invoice

200 OK
json
1{
2 "valid": true,
3 "errors": [],
4 "warnings": []
5}

Invalid Invoice

200 OK
json
1{
2 "valid": false,
3 "errors": [
4 {
5 "code": "BR-DE-1",
6 "message": "Eine Rechnung (INVOICE) muss Angaben zu "PAYMENT INSTRUCTIONS" (BG-16) enthalten.",
7 "field": "/Invoice/cac:PaymentMeans"
8 },
9 {
10 "code": "BR-DE-15",
11 "message": "Das Element "Buyer reference" (BT-10) muss übermittelt werden.",
12 "field": "/Invoice/cbc:BuyerReference"
13 }
14 ],
15 "warnings": [
16 {
17 "code": "MISSING_OPTIONAL",
18 "message": "Delivery date is recommended",
19 "field": "deliveryDate"
20 }
21 ]
22}

Error Responses

400 Bad Request
json
1{
2 "error": "Bad Request",
3 "message": "Invoice data is required"
4}
401 Unauthorized
json
1{
2 "error": "UNAUTHORIZED",
3 "message": "Invalid or missing API key"
4}
500 Internal Server Error
json
1{
2 "error": "INTERNAL_ERROR",
3 "message": "An unexpected error occurred"
4}

Response Fields

valid

true / false

errors

Array of errors. Each error contains code, message and optionally field.

warnings

Array of warnings. Same schema as errors, but not critical issues.

Schema for Errors/Warnings

FieldTypeDescription
codestringError code (e.g. BR-DE-01, BR-16)
messagestringHuman-readable error description
fieldstring (optional)XPath or field name where the error occurred

Supported Formats

FormatSchemaValidierung
XRechnung 3.0.2EN16931 CII/UBLKoSIT 3.0.2 Schematron
XRechnung 3.0EN16931 CII/UBLKoSIT 3.0 Schematron
ZUGFeRD 2.5EN16931 CIIEN16931 Schematron
ZUGFeRD 2.4EN16931 CIIEN16931 Schematron
ZUGFeRD 2.3EN16931 CIIEN16931 Schematron
Factur-X 1.09EN16931 CIIEN16931 Schematron
Factur-X 1.08EN16931 CIIEN16931 Schematron
Factur-X 1.0EN16931 CIIEN16931 Schematron
UBL (Peppol BIS 3.0)EN16931 UBLPeppol BIS 3.0 / EN16931
ebInterfaceebInterface XSD (AT)ebInterface
FatturaPA 1.2.3FatturaPA XSD (IT)SDI-Schema
FacturaeFacturae XSD (ES)Facturae XSD
ISDOCISDOC XSD (CZ)ISDOC XSD
NAVNAV XSD (HU)NAV-Online XSD
myDATAmyDATA XSD (GR)myDATA XSD
KSeF FA(3)KSeF UBL-Slot (PL)KSeF FA(3) XSD
eFactura (CIUS-RO)EN16931 UBLCIUS-RO
CIUS-PTEN16931 UBLCIUS-PT
QR-BillSwiss QR-Bill (CH/LI)QR-Bill Spezifikation

Common Validation Errors

BR-DE-1

Payment instructions missing: an invoice must contain payment information (BG-16)

Solution: Set paymentMethods (e.g. bank_transfer) together with seller.bankAccount including the IBAN

BR-DE-15

Buyer reference missing: the element Buyer reference (BT-10) must be provided

Solution: Set countrySpecific.buyerReference — for public-sector buyers the Leitweg ID in countrySpecific.leitwegId

BR-DE-17

Invalid invoice type: the invoice type code (BT-3) must be 326, 380, 384, 389, 381, 875, 876 or 877

Solution: Set invoice.type to a permitted value (invoice = 380); proforma is not allowed in XRechnung

BR-16

No invoice line: an invoice must contain at least one line (BG-25)

Solution: Add at least one entry to items

BR-CO-10

Sum of line item net amounts does not equal invoice net

Solution: Check the sum of all items[].quantity * items[].unitPrice

Full error list: Error Handling Documentation

On both validate routes the number of invoice line items is capped per plan, exactly as when generating — above the cap the API answers with HTTP 400 and TOO_MANY_LINE_ITEMS: Limits in the Creator API

Validation and your quota

The validation call itself is not metered against your monthly quota — but it is bound to it: once the quota is exhausted, validation answers 429 as well. Keep that in mind when validating in CI/CD pipelines or automated tests.

Validation in Playground

The Playground lets you try validation without an API key of your own — the request runs on our key there and is rate-limited per IP. The endpoint itself always requires a bearer key.

Go to Playground →