3 API3.2
Soumettre du JSON : POST /invoices
JSON canonique ou export d’un ERP.
- Dans le bac à sable
En termes simples
Cet appel soumet une facture : si elle passe tous les contrôles, le service la met en file d’attente ; sinon, la réponse nomme le champ en échec et rien n’est mis en file d’attente.
POST /invoices contrôle la facture comme un essai à blanc. Si elle passe, le service la met en file d’attente et répond 202 avec un identifiant. Si un contrôle échoue, la réponse est 422 et rien n’est mis en file d’attente. L’appel nécessite une clé dotée de la portée submit et un en-tête Idempotency-Key.
La requête
| Élément | Signification |
|---|---|
En-tête Idempotency-Key | Obligatoire, de 8 à 100 caractères. |
invoice_ref | Obligatoire. L’identifiant du document propre à l’ERP, jusqu’à 100 caractères. |
route | Facultatif. L’un des cinq canaux. S’il est omis, le service le choisit d’après la facture (les pays du vendeur et de l’acheteur, le profil et les identifiants d’accès stockés du client). Si aucune règle ne convient, la réponse est 422, EI-ROUTE-UNDECIDED ou EI-ROUTE-PEPPOL-UNKNOWN. |
document | Une facture dans le modèle canonique. Envoyez ceci ou un export avec un connector, pas les deux. |
export, connector | Un export d’ERP tel que l’ERP l’a écrit, avec connector à business-central ou sap-b1. Le service le mappe avec la version de mapping active du connecteur, ou avec la mapping_version que vous indiquez (voir versions de mapping). Un constat dans le mapping donne 422 et nomme le champ de l’ERP. L’export est conservé avec la facture. |
formats | Facultatif. Indiquez-en un au plus : le document qui est construit, contrôlé et envoyé. |
environment | Facultatif. Si vous l’envoyez, il doit correspondre à l’environnement de votre clé, sinon la réponse est 400. |
client | Facultatif. La clé propre d’un client peut l’omettre ou indiquer son propre client ; tout autre client reçoit 403. Les plafonds des réseaux sont comptés par client (voir limites). |
Le corps est limité à 5 Mo. En production, un export n’est lu que si les réglages du connecteur du client contiennent les coordonnées propres du vendeur et de paiement du client ; sans elles, la réponse est 422, connector-settings-missing, avec ce qui manque. Le bac à sable le lit avec les coordonnées d’exemple du connecteur.
Une facture acceptée
L’exemple est submit-de.json. Location contient l’URL de la facture, et links pointe vers la facture et ses événements. L’état est queued : rien n’a été envoyé.
curl -X POST "https://api-sandbox-eu.eurinvoice.com/invoices" \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-2026-0001" \
--data-binary @submit-de.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/invoices"))
.header("Authorization", "Bearer <your-api-key>")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "order-2026-0001")
.POST(HttpRequest.BodyPublishers.ofFile(Path.of("submit-de.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/invoices', {
method: 'POST',
headers: {
Authorization: 'Bearer <your-api-key>',
'Content-Type': 'application/json',
'Idempotency-Key': 'order-2026-0001',
},
body: await readFile('submit-de.json'),
});
console.log(response.status);
console.log(await response.text());{
"links": {
"self": "/invoices/inv_936a93e38de84e7b0a1d7681",
"events": "/invoices/inv_936a93e38de84e7b0a1d7681/events"
},
"id": "inv_936a93e38de84e7b0a1d7681",
"state": "queued"
}Une facture qui échoue à un contrôle
Le même appel sans le nom du vendeur, submit-de-missing-seller-name.json. La réponse est un document de problème avec une liste errors. Chaque erreur est un constat semblable à ceux d’un rapport d’essai à blanc. Les lignes surlignées sont le code et le champ à corriger.
curl -X POST "https://api-sandbox-eu.eurinvoice.com/invoices" \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-2026-0002" \
--data-binary @submit-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/invoices"))
.header("Authorization", "Bearer <your-api-key>")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "order-2026-0002")
.POST(HttpRequest.BodyPublishers.ofFile(Path.of("submit-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/invoices', {
method: 'POST',
headers: {
Authorization: 'Bearer <your-api-key>',
'Content-Type': 'application/json',
'Idempotency-Key': 'order-2026-0002',
},
body: await readFile('submit-de-missing-seller-name.json'),
});
console.log(response.status);
console.log(await response.text());{
"type": "https://eurinvoice.com/problems/validation-failed",
"title": "The invoice did not pass the checks",
"errors": [
{
"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."
},
{
"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."
}
],
"status": 422
}Idempotence
- La même clé avec le même corps renvoie à nouveau la première réponse, avec le même identifiant.
- La même clé avec un corps différent donne
409. - Les réponses stockées comprennent aussi les
422. Envoyez une facture corrigée avec une nouvelle clé. - Une réponse stockée est conservée aussi longtemps que les données du client.
- Une deuxième requête avec une clé dont la première requête est encore en cours reçoit
409,request-in-progress. Une clé dont la requête n’a jamais reçu de réponse, parce que le serveur s’est arrêté, est libérée au bout de 15 minutes.
curl -X POST "https://api-sandbox-eu.eurinvoice.com/invoices" \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-2026-0001" \
--data-binary @submit-de.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/invoices"))
.header("Authorization", "Bearer <your-api-key>")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "order-2026-0001")
.POST(HttpRequest.BodyPublishers.ofFile(Path.of("submit-de.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/invoices', {
method: 'POST',
headers: {
Authorization: 'Bearer <your-api-key>',
'Content-Type': 'application/json',
'Idempotency-Key': 'order-2026-0001',
},
body: await readFile('submit-de.json'),
});
console.log(response.status);
console.log(await response.text());{
"links": {
"self": "/invoices/inv_936a93e38de84e7b0a1d7681",
"events": "/invoices/inv_936a93e38de84e7b0a1d7681/events"
},
"id": "inv_936a93e38de84e7b0a1d7681",
"state": "queued"
}curl -X POST "https://api-sandbox-eu.eurinvoice.com/invoices" \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-2026-0001" \
--data-binary @submit-de-changed.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/invoices"))
.header("Authorization", "Bearer <your-api-key>")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "order-2026-0001")
.POST(HttpRequest.BodyPublishers.ofFile(Path.of("submit-de-changed.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/invoices', {
method: 'POST',
headers: {
Authorization: 'Bearer <your-api-key>',
'Content-Type': 'application/json',
'Idempotency-Key': 'order-2026-0001',
},
body: await readFile('submit-de-changed.json'),
});
console.log(response.status);
console.log(await response.text());{
"type": "https://eurinvoice.com/problems/idempotency-conflict",
"title": "This Idempotency-Key was already used with a different body",
"status": 409
}Un document déjà envoyé
Un document identique à un document actif, pour le même client et le même canal, est de nouveau accepté sous une nouvelle clé. La réponse contient duplicate_of, l’identifiant de la première facture, et rien de nouveau n’est mis en file d’attente.
curl -X POST "https://api-sandbox-eu.eurinvoice.com/invoices" \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-2026-0003" \
--data-binary @submit-de.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/invoices"))
.header("Authorization", "Bearer <your-api-key>")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "order-2026-0003")
.POST(HttpRequest.BodyPublishers.ofFile(Path.of("submit-de.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/invoices', {
method: 'POST',
headers: {
Authorization: 'Bearer <your-api-key>',
'Content-Type': 'application/json',
'Idempotency-Key': 'order-2026-0003',
},
body: await readFile('submit-de.json'),
});
console.log(response.status);
console.log(await response.text());{
"links": {
"self": "/invoices/inv_936a93e38de84e7b0a1d7681",
"events": "/invoices/inv_936a93e38de84e7b0a1d7681/events"
},
"id": "inv_936a93e38de84e7b0a1d7681",
"state": "queued",
"duplicate_of": "inv_936a93e38de84e7b0a1d7681"
}Réponses
| Statut | Signification |
|---|---|
202 | Acceptée et mise en file d’attente, ou doublon d’une facture active. |
400 | Le corps n’est pas un JSON valide, un champ est erroné, ou l’en-tête Idempotency-Key est absent ou ne compte pas de 8 à 100 caractères. |
401 | Aucune clé, ou une clé inconnue. |
403 | La clé n’a pas la portée submit, ou indique un autre client (forbidden). |
409 | La clé a été utilisée avec un corps différent (idempotency-conflict), ou sa première requête est encore en cours (request-in-progress). |
413 | Le corps dépasse 5 Mo (payload-too-large). |
422 | La facture a échoué à un contrôle. errors indique le problème et qui le corrige. |
429 | Trop de requêtes pour la clé. Attendez Retry-After secondes. |
503 | Une autre requête pour le même document est encore en cours de stockage (busy). Rien n’a été écrit et la clé peut être réutilisée. Réessayez après Retry-After secondes. |
Attention
Après un 202, vous ne renvoyez jamais la facture. Les erreurs passagères côté réseau donnent lieu à de nouvelles tentatives selon notre calendrier, et un rejet définitif va sur la liste de suivi.