Send XML You Already Generate
If your software already writes the e-invoice XML, GoRoute only has to deliver it.
POST /api/v1/documents takes your document as it is, routes it over Peppol and reports the
outcome. GoRoute does not rewrite, re-render or sign it: the XML you send is the XML the
recipient receives.
This page lists the rules that matter when the XML comes from your own code. For a working call in Python, Node.js, C# and Java, see Client libraries.
The rules in one tableโ
| Rule | What it means for you |
|---|---|
| UBL only | The root element must be UBL Invoice or CreditNote (or one of the UBL business documents). CII is refused, not converted โ see below. |
| XML as a JSON string | The XML travels in the document field of a JSON body. There is no raw-XML, base64 or multipart variant. |
| You name the receiver | receiver_scheme and receiver_id are required. GoRoute does not read them from the XML. |
| You name the document type | document_type defaults to the Peppol BIS 3.0 Invoice. A credit note, XRechnung, PINT or any other customisation must pass its own value. |
| Sender defaults to you | Leave sender_scheme/sender_id out and GoRoute uses your organisation's first active participant. |
| Sent as submitted | No signature, QR code or attachment is added. Anything the recipient needs must already be in your XML. |
The requestโ
POST /api/v1/documents
X-API-Key: your_api_key
Content-Type: application/json
Idempotency-Key: INV-2026-00123
{
"receiver_scheme": "0208",
"receiver_id": "0123456789",
"document_type": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1",
"process_id": "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0",
"document": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Invoice xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\" ...>...</Invoice>",
"metadata": {"your_reference": "order-8812"}
}
| Field | Required | Notes |
|---|---|---|
document | Yes | The UBL XML as a string, without an SBDH envelope (GoRoute's Access Point adds that). Empty or whitespace-only is refused. |
receiver_scheme, receiver_id | Yes | The buyer's Peppol identifier, e.g. 0208 + 0123456789. Use the same value as the buyer's cbc:EndpointID in your XML. |
document_type | No | Defaults to the BIS 3.0 Invoice identifier shown above. Must start with urn: and contain ::. |
process_id | No | Defaults to urn:fdc:peppol.eu:2017:poacc:billing:01:1.0. Must start with urn:. |
sender_scheme, sender_id | No | Your participant. Must be a participant of your organisation (or of one it manages); see Sender entitlement. |
metadata | No | Free-form object stored with the transaction. |
webhook_url | No | A URL to notify for this document only. |
A new document is answered with 202 Accepted:
{
"transaction_id": "5f1cโฆ",
"status": "queued",
"message": "Document queued for delivery",
"created_at": "2026-10-01T09:30:00Z",
"idempotency_key": "INV-2026-00123"
}
Credit notes and other document typesโ
The document type is not inferred from your XML. A UBL CreditNote sent without its own
document_type goes out under the Invoice identifier, and the recipient's Access Point may
refuse it. Pick the value from the root element before you send:
from lxml import etree
INVOICE = ("urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##"
"urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1")
CREDIT_NOTE = ("urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2::CreditNote##"
"urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1")
root = etree.fromstring(xml_bytes)
document_type = CREDIT_NOTE if etree.QName(root).localname == "CreditNote" else INVOICE
The two values above are for Peppol BIS Billing 3.0. A document with a different
cbc:CustomizationID โ XRechnung, a PINT jurisdiction, self-billing โ needs the matching
document type identifier: see Document types and the
country guides. The UBL post-award documents (orders,
despatch advice and the rest) are the exception: GoRoute sets their document type from the
document itself, see Business documents.
CII is refusedโ
ZUGFeRD, Factur-X and XRechnung can be written in UN/CEFACT CII syntax
(rsm:CrossIndustryInvoice). GoRoute does not convert CII to UBL. A CII document is refused
with HTTP 400:
Document validation failed with 1 errors: [INVALID_ROOT] Root element must be 'Invoice' or 'CreditNote', got 'CrossIndustryInvoice'
Convert it to UBL first, or let GoRoute build the invoice from data instead. Both routes are in ZUGFeRD, Factur-X and XRechnung CII.
Validate before you sendโ
POST /api/v1/documents/validate runs the same checks the send path runs, without sending.
It needs the invoices:read permission and answers 200 even when the document is invalid;
read valid and issues[].
response = requests.post(
f"{BASE_URL}/api/v1/documents/validate",
headers={"X-API-Key": API_KEY, "Content-Type": "application/json"},
json={"document": invoice_xml},
timeout=30,
)
result = response.json()
if not result["valid"]:
for issue in result["issues"]:
print(issue["severity"], issue["code"], issue["message"], issue["location"])
For raw XML, GoRoute checks the root element, the header, both parties, the tax and monetary totals, the lines and a set of business rules. It does not run the XSD or the official CEN / Peppol / KoSIT Schematron on your XML (UBL post-award documents excepted). Keep running your library's own validation, or the official validators, as part of your build.
Retries and duplicatesโ
- Send an
Idempotency-Key(up to 255 characters, kept for 24 hours). Repeating the same key with the same body returns the original transaction with202and"Document already accepted (idempotent replay)"; the document is not sent twice. The same key with a different body is refused with 422IDEMPOTENCY_KEY_REUSED. - Without a key, GoRoute derives one from the document itself. Re-sending the identical
document replays; sending different content under an invoice number you already used is
refused with 409 and
"error": "duplicate_invoice_number"plus theexisting_transaction_id. That 409 carries noerror_codefield โ checkerror.
Details: Sending an invoice โ Idempotency.
Errorsโ
| Status | Code | Meaning |
|---|---|---|
| 400 | INVALID_REQUEST | The document failed validation (up to five [code] message items in message), no sender could be found, or the sender is not entitled (see below). |
| 400 | INVALID_IDEMPOTENCY_KEY | The key is longer than 255 characters. |
| 401 | MISSING_API_KEY / INVALID_API_KEY / EXPIRED_API_KEY | Check the X-API-Key header. |
| 403 | INSUFFICIENT_PERMISSION / INSUFFICIENT_SCOPE | The key needs the send scope. |
| 409 | duplicate_invoice_number (in error) | Same invoice number, different content, no idempotency key. |
| 422 | (FastAPI field errors) | Missing receiver, empty document, malformed document_type or process_id. |
| 422 | IDEMPOTENCY_KEY_REUSED | Same key, different body, within 24 hours. |
| 422 | PLATFORM_ONLY_DOCUMENT_TYPE | Tax-reporting and MLS document types are sent by GoRoute itself, never by a tenant. |
| 429 | RATE_LIMIT_MINUTE / RATE_LIMIT_DAY | Wait for Retry-After. |
Sender entitlement. If you name a sender, it must be an active participant of your
organisation or of an organisation beneath it. Otherwise the send is refused (as
INVALID_REQUEST on this endpoint) with the message "Sender โฆ is not an active participant
of this organization or of an organization it manages". See
For software vendors.
After the sendโ
transaction.queuedfires as soon as the document is accepted.transaction.deliveredfires when the recipient's Access Point confirms receipt;transaction.retryingandtransaction.failedcover the other outcomes.GET /api/v1/transactions/{transaction_id}returns the currentstatus(queued,submitted,accepted,delivered,failed,retrying,heldorissued) with the delivery timestamps and anyerror_code/error_message.
A raw-XML transaction has no invoice data behind it, so resend is not available for it
(400 NO_INVOICE_DATA); use retry instead. See Tracking.