Error Handling
Every failed request returns a structured JSON error you can act on programmatically. This page covers the response envelope, the error categories, and how to handle them in code.
status for the HTTP outcome, branch on errorCode for the category, and you have handled every error the API can return.
The error envelope
Every error response, on every endpoint, contains the same top-level fields:
| Field | Type | Description |
|---|---|---|
| type | string | RFC 7807 type URI for the error class. |
| title | string | Short, human-readable summary of the error class. |
| status | int | The HTTP status code, repeated in the body for convenience. |
| errorCode | int | The InvoiceXML category code. This is the one value to branch on. |
| detail | string | Longer human-readable explanation, safe to show to end users. |
| errors | array | An array of finding objects, one per validation error. Present on validation failures, and identical in shape whether the failure is XML validation (4001) or model validation (4002). |
| warnings | array | Non-blocking findings, same object shape as a 4001 finding. Advisory only, they never cause a request to fail. |
Error codes
The errorCode identifies the category of error. Branch on it to decide how to handle the response. The errors column shows what, if anything, the errors field carries for that code.
| errorCode | Name | Description | errors |
|---|---|---|---|
| 4000 | General Error | An unclassified processing error. Check title and detail for information. |
none |
| 4001 | XML Validation | The invoice XML violates EN 16931 Schematron business rules (e.g. BR-01, BR-CO-15). Each violation is a finding object. | errors[] |
| 4002 | Model Validation | One or more request fields are missing or malformed before processing could start. | errors[] |
| 4003 | File Too Large | The uploaded file exceeds the 20 MB size limit. | none |
| 4004 | Unsupported Format | The uploaded file type is not supported for that endpoint. | none |
| 4005 | Extraction Failed | The API could not extract invoice data from the PDF. The document may not be an invoice, or the file may be corrupted. | none |
| 4006 | Rate Limited | You have exceeded the request rate limit for your plan. Retry after the period in the Retry-After header. |
none |
| 4007 | Quota Exceeded | Your monthly request quota has been reached. Upgrade your plan or wait for the next billing cycle. | none |
Example responses
The envelope is identical in all of them. Both validation categories (4001 and 4002) use the very same errors array of finding objects, so you handle them with one code path. Every other error code carries no errors field at all.
XML validation, errorCode 4001
The generated or uploaded XML violates EN 16931 Schematron rules. errors is an array of finding objects, one per violated rule.
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "Validation Failed",
"status": 400,
"errorCode": 4001,
"detail": "The invoice contains 2 validation error(s).",
"errors": [
{
"rule": "BR-01",
"line": null,
"message": "The invoice is missing a specification identifier (BT-24).",
"btCodes": ["BT-24"],
"fields": ["specificationId"],
"raw": "[BR-01] An invoice shall have a specification identifier."
},
{
"rule": "BR-25",
"line": 3,
"message": "Line item 3: each invoice line must have an item name (BT-153).",
"btCodes": ["BT-153"],
"fields": ["lines[2].item.name"],
"raw": "[BR-25] Each Invoice line shall contain the Item name."
}
],
"warnings": []
}
Each finding is self-describing. Use message for end-user display and raw for debugging or logs. The btCodes and fields arrays let you map a finding back to a form input.
| Field | Type | Description |
|---|---|---|
| rule | string | The validated rule identifier, e.g. BR-01. |
| line | int | null | 1-based invoice line number, or null for a document-level finding. |
| message | string | Plain-language explanation, safe to show end users. |
| btCodes | string[] | EN 16931 Business Term codes the rule references. |
| fields | string[] | Dotted JSON paths into the invoice the finding maps to. Line-scoped paths use a zero-based index (line 3 maps to lines[2]). |
| raw | string | Verbatim validator output, including the rule id, layer and XPath location. |
fields or btCodes to the matching input, mark it invalid, and show the friendly message beside it. The user is taken straight to the field that needs fixing, with no XPath decoding.
Model validation, errorCode 4002
A request field is missing or malformed, so processing never started. errors is the exact same flat array of finding objects as 4001. rule and raw are null (model validation has no Schematron rule), while btCodes and fields are resolved from the field path, so the finding maps to the offending input by both BT code and JSON path.
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errorCode": 4002,
"valid": false,
"detail": "The request failed validation with 2 error(s).",
"errors": [
{
"rule": null,
"line": null,
"message": "The seller name is required.",
"btCodes": ["BT-27"],
"fields": ["seller.name"],
"raw": null
},
{
"rule": null,
"line": 1,
"message": "Quantity must be greater than zero.",
"btCodes": ["BT-129"],
"fields": ["lines[0].quantity"],
"raw": null
}
],
"warnings": []
}
Other errors, errorCode 4000 and 4003 to 4007
File size, unsupported format, extraction, rate limit and quota errors carry no errors field. The title and detail tell you what happened.
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "File Too Large",
"status": 400,
"errorCode": 4003,
"detail": "The uploaded file exceeds the 20 MB size limit."
}
warnings array uses the exact same finding shape as 4001 errors, but it is advisory. A response can be successful and still carry warnings. Only entries in errors make a request fail.
Handling errors in code
Parse the JSON body and branch on errorCode. Because both validation categories share the same errors array, they need just one branch between them; every other code falls through to a generic handler.
const response = await fetch(apiUrl, { method: 'POST', body: formData });
if (!response.ok) {
const problem = await response.json();
switch (problem.errorCode) {
case 4001: // XML validation, Schematron rule violations
case 4002: // Model validation, malformed request fields
// Both return the same flat array of finding objects.
problem.errors.forEach(f => {
const where = f.line != null ? `line ${f.line}` : 'document';
console.error(`${where}: ${f.message}`);
// f.fields and f.btCodes map the finding to a form input
});
break;
default: // 4000, 4003 to 4007: no errors array, use title + detail
console.error(`${problem.title}: ${problem.detail}`);
}
}