3 API3.2
Przesłanie JSON: POST /invoices
Kanoniczny JSON lub eksport z ERP.
- W sandboxie
Prostymi słowami
To wywołanie przesyła fakturę: jeśli przejdzie wszystkie kontrole, usługa umieszcza ją w kolejce, a jeśli nie, odpowiedź wskazuje pole, które nie przeszło kontroli, i nic nie trafia do kolejki.
POST /invoices sprawdza fakturę tak jak przebieg próbny. Jeśli faktura przejdzie kontrole, usługa umieszcza ją w kolejce i zwraca 202 z identyfikatorem. Jeśli kontrola się nie powiedzie, odpowiedzią jest 422 i nic nie trafia do kolejki. Wywołanie wymaga klucza z zakresem submit i nagłówka Idempotency-Key.
Żądanie
| Część | Znaczenie |
|---|---|
Nagłówek Idempotency-Key | Wymagany, od 8 do 100 znaków. |
invoice_ref | Wymagane. Własny identyfikator dokumentu w ERP, maksymalnie 100 znaków. |
route | Opcjonalne. Jeden z pięciu kanałów przesyłania. Jeśli go brak, usługa wybiera kanał na podstawie faktury (kraje sprzedawcy i nabywcy, profil oraz zapisane dane dostępowe klienta). Jeśli żadna reguła nie pasuje, odpowiedzią jest 422, EI-ROUTE-UNDECIDED lub EI-ROUTE-PEPPOL-UNKNOWN. |
document | Jedna faktura w modelu kanonicznym. Należy wysłać to pole albo export z connector, nie oba naraz. |
export, connector | Eksport z ERP w takiej postaci, w jakiej zapisało go ERP, z connector ustawionym na business-central lub sap-b1. Usługa mapuje go według aktywnej wersji mapowania konektora albo według podanego mapping_version (zob. wersje mapowania). Ustalenie w mapowaniu daje odpowiedź 422 ze wskazanym polem ERP. Eksport jest przechowywany razem z fakturą. |
formats | Opcjonalne. Można podać najwyżej jeden: dokument, który jest budowany, sprawdzany i wysyłany. |
environment | Opcjonalne. Jeśli zostanie wysłane, musi odpowiadać środowisku klucza; w przeciwnym razie odpowiedź to 400. |
client | Opcjonalne. Klucz klienta może pominąć to pole albo podać własnego klienta; każdy inny klient daje odpowiedź 403. Limity sieci są liczone osobno dla każdego klienta (zob. limity). |
Treść jest ograniczona do 5 MB. W produkcji eksport jest odczytywany tylko wtedy, gdy ustawienia konektora klienta zawierają własne dane sprzedawcy i płatności klienta; bez nich odpowiedzią jest 422, connector-settings-missing, ze wskazaniem, czego brakuje. Sandbox odczytuje eksport z przykładowymi danymi konektora.
Przyjęta faktura
Przykład to submit-de.json. Location zawiera adres URL faktury, a links zawiera odnośniki do faktury i jej zdarzeń. Stan to queued: nic nie zostało wysłane.
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"
}Faktura, która nie przechodzi kontroli
To samo wywołanie bez nazwy sprzedawcy, submit-de-missing-seller-name.json. Odpowiedzią jest dokument opisu problemu z listą errors. Każdy błąd ma postać ustalenia takiego jak w raporcie z przebiegu próbnego. Wyróżnione wiersze to kod i pole do poprawienia.
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
}Idempotencja
- Ten sam klucz z tą samą treścią zwraca ponownie pierwszą odpowiedź, z tym samym identyfikatorem.
- Ten sam klucz z inną treścią kończy się odpowiedzią
409. - Zapisane odpowiedzi obejmują też
422. Poprawioną fakturę należy wysłać z nowym kluczem. - Zapisana odpowiedź jest przechowywana tak długo, jak dane klienta.
- Drugie żądanie z kluczem, którego pierwsze żądanie jeszcze trwa, kończy się odpowiedzią
409,request-in-progress. Klucz, którego żądanie nigdy nie otrzymało odpowiedzi, bo serwer się zatrzymał, zostaje zwolniony po 15 minutach.
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
}Dokument wysłany już wcześniej
Dokument identyczny z aktywnym, dla tego samego klienta i kanału, zostaje ponownie przyjęty pod nowym kluczem. Odpowiedź zawiera duplicate_of, identyfikator pierwszej faktury, a nic nowego nie trafia do kolejki.
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"
}Odpowiedzi
| Status | Znaczenie |
|---|---|
202 | Przyjęta i umieszczona w kolejce albo duplikat aktywnej faktury. |
400 | Treść nie jest poprawnym JSON, pole jest błędne albo brakuje Idempotency-Key lub jego długość nie mieści się w zakresie od 8 do 100 znaków. |
401 | Brak klucza lub nieznany klucz. |
403 | Klucz nie ma zakresu submit albo wskazuje innego klienta (forbidden). |
409 | Klucz został użyty z inną treścią (idempotency-conflict) albo jego pierwsze żądanie jeszcze trwa (request-in-progress). |
413 | Treść przekracza 5 MB (payload-too-large). |
422 | Faktura nie przeszła kontroli. errors podaje, co jest nie tak i kto ma to poprawić. |
429 | Zbyt wiele żądań dla klucza. Należy odczekać liczbę sekund podaną w Retry-After. |
503 | Inne żądanie dotyczące tego samego dokumentu jest jeszcze zapisywane (busy). Nic nie zostało zapisane, a klucza można użyć ponownie. Należy ponowić po liczbie sekund podanej w Retry-After. |
Ostrzeżenie
Po 202 nigdy nie wysyła się faktury ponownie. Przy błędach po stronie sieci, które mogą ustąpić, ponawiamy wysyłkę według naszego harmonogramu, a trwałe odrzucenie trafia na listę do obsługi.