3 API3.1
Dry run: POST /validate
Runs the same checks as submit. Stores and sends nothing.
- In the sandbox
In plain words
A dry run checks an invoice the way a real submission would and sends nothing, so a partner can find every problem before a client's invoice goes anywhere.
POST /validate runs the same checks as a submit and answers with a report. It queues nothing and sends nothing. The call needs a key with the submit scope. The body is JSON, up to 5 MB.
Tip
Start here when you map a new client.
The request
| Field | Meaning |
|---|---|
invoice_ref | Required. The ERP's own document id, up to 100 characters. Echoed in every status event. |
route | Required. DE-XRECHNUNG, PEPPOL, FR-PA, PL-KSEF or RO-EFACTURA. |
document | One invoice in the canonical model. Send this or an export, not both. |
export, connector | An ERP export as the ERP wrote it, with connector set to business-central or sap-b1. Send this or a document, not both. On a route other than Germany the report starts with a mapping layer. mapping_version picks a version other than the live one (see mapping versions). |
formats | The documents to build where the route allows a choice. Germany: any of xrechnung-ubl (the default), xrechnung-cii and zugferd; KoSIT runs once per format, and the PDF also runs Mustang and veraPDF. France: ubl, cii or facturx. |
environment | Optional. It must match the environment of your key. |
erp_totals | The totals your ERP computed: payable_amount, currency and optionally tax_amount, as strings. If they differ from the totals the service computes from the lines, the report has a finding EI-TOTALS-MISMATCH on erp_totals.payable_amount. |
The full field list is in the reference.
An invoice that passes
The sample request is validate-de-ok.json, an invented German invoice. The report has valid: true, one document with its SHA-256, and four layers that passed.
curl -X POST "https://api-sandbox-eu.eurinvoice.com/validate" \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
--data-binary @validate-de-ok.jsonimport java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Path;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api-sandbox-eu.eurinvoice.com/validate"))
.header("Authorization", "Bearer <your-api-key>")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofFile(Path.of("validate-de-ok.json")))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}import { readFile } from 'node:fs/promises';
const response = await fetch('https://api-sandbox-eu.eurinvoice.com/validate', {
method: 'POST',
headers: {
Authorization: 'Bearer <your-api-key>',
'Content-Type': 'application/json',
},
body: await readFile('validate-de-ok.json'),
});
console.log(response.status);
console.log(await response.text());{
"valid": true,
"route": "DE-XRECHNUNG",
"documents": [
{
"sha256": "e21ab9d5097022bea30bfa9f9fe0a4c7ca9afdbd672b0ce2f9150461db773625",
"kind": "xrechnung-ubl",
"content_base64": "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgi... (7,092 characters, shortened for these docs)"
}
],
"layers": [
{
"findings": [],
"passed": true,
"layer": "schema"
},
{
"findings": [],
"passed": true,
"layer": "mapping"
},
{
"findings": [],
"passed": true,
"layer": "pre-check"
},
{
"findings": [],
"passed": true,
"layer": "kosit"
}
]
}An invoice that fails
The same invoice without the seller's name, validate-de-missing-seller-name.json. A dry run still answers 200. valid is false, and each finding names the field and who fixes it. The two highlighted lines are the ones to look at: the catalogue code, and the field in the client's data.
curl -X POST "https://api-sandbox-eu.eurinvoice.com/validate" \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
--data-binary @validate-de-missing-seller-name.jsonimport java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Path;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api-sandbox-eu.eurinvoice.com/validate"))
.header("Authorization", "Bearer <your-api-key>")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofFile(Path.of("validate-de-missing-seller-name.json")))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}import { readFile } from 'node:fs/promises';
const response = await fetch('https://api-sandbox-eu.eurinvoice.com/validate', {
method: 'POST',
headers: {
Authorization: 'Bearer <your-api-key>',
'Content-Type': 'application/json',
},
body: await readFile('validate-de-missing-seller-name.json'),
});
console.log(response.status);
console.log(await response.text());{
"valid": false,
"route": "DE-XRECHNUNG",
"layers": [
{
"findings": [
{
"code": "EI-SCHEMA",
"field": "seller.name",
"related": [
{
"code": "Art.226(5)",
"source": "pre-check"
}
],
"fix_hint": "Read the JSON path in the error and correct the client mapping.",
"who_fixes": "us",
"source": "schema",
"message": "We could not read this invoice from your export. We are correcting our mapping; if a field is missing in the ERP we will tell you which one."
}
],
"passed": false,
"layer": "schema"
},
{
"findings": [],
"passed": true,
"layer": "mapping"
},
{
"findings": [
{
"code": "Art.226(5)",
"field": "seller.name",
"related": [
{
"code": "EI-SCHEMA",
"source": "schema"
}
],
"fix_hint": "Complete the party's name and address in the master data.",
"who_fixes": "erp",
"source": "pre-check",
"message": "A company name or street address is missing. Complete the company or customer record in the ERP."
}
],
"passed": false,
"layer": "pre-check"
}
]
}Reading the report
validistrueonly when every layer passed.layersrun in order:schema,mapping,pre-check, then the route's own validators, named for what ran (for exampleKoSIT-XRechnung-3.0.2).- Each finding has a catalogue
code, thefield,who_fixes(usorerp), afix_hint, amessageand thesourcelayer. Errors and the catalogue lists every code. documentslists what was built, with its SHA-256 and, for a file of 2 MiB or less, its bytes incontent_base64. It is present when the invoice is valid.
Other answers
| Status | Meaning |
|---|---|
200 | A report, valid or not. |
400 | The request cannot be read: not JSON, a field is wrong, or both document and export are present. |
401 | No key, or an unknown key. |
403 | The key does not have the submit scope. |
413 | The body is over 5 MB (payload-too-large). |
422 | A number past the limits, such as one over 40 characters (EI-SCHEMA, naming the field). Every other finding comes in the 200 report. |
429 | Too many requests for the key. Wait for Retry-After seconds. |
503 | Every validator is busy (busy). Retry after Retry-After seconds. |
curl -X POST "https://api-sandbox-eu.eurinvoice.com/validate" \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
--data-binary @not-json.txtimport java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Path;
public class Example {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder(URI.create("https://api-sandbox-eu.eurinvoice.com/validate"))
.header("Authorization", "Bearer <your-api-key>")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofFile(Path.of("not-json.txt")))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}import { readFile } from 'node:fs/promises';
const response = await fetch('https://api-sandbox-eu.eurinvoice.com/validate', {
method: 'POST',
headers: {
Authorization: 'Bearer <your-api-key>',
'Content-Type': 'application/json',
},
body: await readFile('not-json.txt'),
});
console.log(response.status);
console.log(await response.text());{
"detail": "The request is not valid JSON.",
"type": "https://eurinvoice.com/problems/bad-request",
"title": "The request could not be read",
"status": 400
}