Reference

Error Handling

Understand API errors and learn how to handle them in your application. All errors follow a consistent format with helpful details.

Error Response Format

All API errors are returned in the following JSON format:

json
1{
2 "error": "VALIDATION_FAILED",
3 "message": "Die Rechnung entspricht nicht dem XRechnung 3.0.2 Schema"
4}

error

Machine-readable error code for programmatic handling

message

Human-readable error description

429 Quota Exceeded Response

When quota is exceeded, the response includes an additional quota object with usage details.

json
1{
2 "error": "QUOTA_EXCEEDED",
3 "message": "Usage limit reached for pdf:de:generate",
4 "quota": {
5 "current": 100,
6 "limit": 100,
7 "period": "monthly"
8 }
9}

quota.current

Current usage count

quota.limit

Usage limit

quota.period

Quota period type, e.g. monthly or yearly — never a calendar label. Absent when the rejection belongs to no plan quota.

HTTP Status Codes

400
Bad Request

Invalid request data (JSON, XML, missing required fields)

401
Unauthorized

Missing or invalid API key

403
Forbidden

API key has no permission for this resource

404
Not Found

Resource (e.g. invoice) not found

422
Unprocessable Entity

Compliance check failed — the request JSON was valid, but an EN 16931 / KoSIT rule fired. The rule (e.g. BR-61) is listed in errors[].

429
Too Many Requests

Rate limit reached — the response carries **no** `quota` object. Retry with backoff.

429
Too Many Requests (QUOTA_EXCEEDED)

Monthly quota exhausted (`error: "QUOTA_EXCEEDED"`). Only this case carries a `quota` object; retrying does not help.

500
Internal Server Error

Server error, please try again

API Error Codes

CodeHTTPDescriptionSolution
Bad Request400The request body or a path parameter is invalid — the most common error string of the API.Read `message` — it names the field. For format errors, check the path token: `xrechnung`, `zugferd`, `pdf`, `facturx`, `ubl`, `peppol-ubl`, `qr-bill`, `fatturapa`, `facturae`, `ebinterface`, `isdoc`, `nav`, `mydata`, `ksef` — lower case and without a version number.
INVALID_REQUEST400Invalid input on the VeriFactu route (`POST /api/v1/invoice/es/verifactu-qr`) — the only endpoint that returns this code.Check the VeriFactu fields against `/docs/api/verifactu-qr`. On every other route the same case is called `Bad Request`.
FORMAT_NOT_DETECTED400Format detection was not confident enough (confidence < 50) — applies to `POST /api/v1/invoice/parse` and `POST /api/v1/invoice/convert`.Call the explicit parse endpoint with country and format: `POST /api/v1/invoice/{countryCode}/{format}/parse`. The response also carries `detection` with the intermediate result.
COUNTRY_NOT_DETECTED400The format was detected, the country was not (`POST /api/v1/invoice/parse`).Call the parse endpoint with a country path: `POST /api/v1/invoice/{countryCode}/{format}/parse`.
TOO_MANY_LINE_ITEMS400The invoice carries more entries in `items[]` than the plan allows — applies to generating, both validate routes and converting, not to parsing. `lineItems.count` and `lineItems.limit` name the counted number and the cap.Split the document into several invoices or change plan (Free 25, Starter 100, Premium 1,000, Enterprise individual). Across all plans an absolute ceiling of 15,000 line items per invoice applies. Rejected means: no invoice produced, no call billed.
UNAUTHORIZED401API key is missing or invalidCheck the Authorization: Bearer <key> header
FORBIDDEN403API key has no permissionCheck if the key is enabled for this endpoint
Not Found404Resource not foundCheck the ID or endpoint path
PARSE_FAILED422The source document was detected but could not be read (HTTP 422). The individual errors are in `errors[]`.Inspect `errors[]`. Most common cause: a PDF without embedded XML, or a truncated upload.
CONVERSION_FAILED422The source could not be converted into a conformant target document (HTTP 422). `complianceErrors` names the rules that failed.Read `complianceErrors` and `conversionWarnings` — usually a field the target format requires is missing from the source.
QUOTA_EXCEEDED429Monthly quota exhaustedUpgrade your plan or wait until the end of the month
Internal Server Error500Internal server errorTry again, contact support if it persists

XRechnung Validation Errors (BR-DE)

These errors come from KoSIT Schematron validation and are returned in errors[] / warnings[]:

An entry in `errors[]` or `warnings[]` has exactly three fields: code, message, field. There is no `details` object. `MISSING_FIELD` and `MISSING_OPTIONAL` are values of `errors[].code` and `warnings[].code`, not top-level error codes.

BR-DE-1

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

Field: paymentMethods

BR-DE-15

Buyer reference missing: the element Buyer reference (BT-10) must be provided — for public-sector invoices this carries the Leitweg ID. Its shape: 2–12 digits of coarse addressing, optionally 0–30 digits of fine addressing, 2 check digits, separated by `-` (example: `04011000-12345-34`).

Field: 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

Field: type

BR-16

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

Field: items

BR-CO-10

Sum of line items does not equal invoice net amount

Field: items[].quantity * items[].unitPrice

BR-CO-13

Invoice net total calculated wrongly: BT-109 = sum of line amounts minus allowances plus charges

Field: totals

BR-S-08

VAT category taxable amount for category S is wrong: BT-116 does not match the sum of the lines at that rate

Field: totals / items[].taxRate

BR-CL-10

Invalid scheme identifier: an identifier's schemeID must come from the ISO 6523 list

Field: PartyIdentification/ID/@schemeID

BR-61

Credit transfer without a payee account: if paymentMethods is a credit transfer (BT-81 = 30/58), the payment account identifier (BT-84, IBAN) must be present

Field: seller.bankAccount.iban

BR-CL-23

Unit of measure not a code: items[].unit must be a UN/ECE Rec 20 code (C62 piece, HUR hour, KGM, MTR, DAY) — no free text

Field: items[].unit

Full list: KoSIT XRechnung Specification

Retry Strategy

Implement retries for temporary errors (429, 5xx):

typescript
1async function createInvoice(data, retries = 3) {
2 for (let i = 0; i < retries; i++) {
3 try {
4 const response = await fetch('https://service.invoice-api.xhub.io/api/v1/invoice/de/xrechnung/generate', {
5 method: 'POST',
6 headers: {
7 'Authorization': 'Bearer sk_live_...',
8 'Content-Type': 'application/json'
9 },
10 body: JSON.stringify(data)
11 });
12 
13 if (response.status === 429) {
14 const body = await response.json();
15 // Only the quota rejection carries a `quota` object. Retrying it is
16 // pointless — the monthly allowance is gone until the period rolls over.
17 if (body.quota) throw new Error(body.message);
18 // Rate limit: no Retry-After header is sent, so back off exponentially.
19 await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
20 continue;
21 }
22 
23 if (response.status >= 500) {
24 // Server error - retry with exponential backoff
25 await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000));
26 continue;
27 }
28 
29 const result = await response.json();
30 
31 if (!response.ok) {
32 // Client error - don't retry, handle the error
33 throw new Error(result.message);
34 }
35 
36 return result;
37 } catch (error) {
38 if (i === retries - 1) throw error;
39 }
40 }
41}

Best Practices

Log Errors

Always log the response header x-request-id. There is no `requestId` field in the response body. With the header we can trace the request for support inquiries.

Retry Only on 5xx/429

Client errors (4xx) won't be fixed by retries. Only retry on server or rate limit errors.

Exponential Backoff

For retries: double the wait time after each failed attempt (1s, 2s, 4s, ...) to avoid overloading the server.

Tell the two 429s apart

No `Retry-After` header is sent. Instead, check whether the 429 response carries a quota object: with the object the quota is exhausted (upgrade, or wait for the period to roll over); without it the rate limit fired (retry with exponential backoff).

Support

For recurring errors or unclear error messages, contact us at support@xhub.io with the response header x-request-id.