Skip to main content

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โ€‹

RuleWhat it means for you
UBL onlyThe 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 stringThe XML travels in the document field of a JSON body. There is no raw-XML, base64 or multipart variant.
You name the receiverreceiver_scheme and receiver_id are required. GoRoute does not read them from the XML.
You name the document typedocument_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 youLeave sender_scheme/sender_id out and GoRoute uses your organisation's first active participant.
Sent as submittedNo 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"}
}
FieldRequiredNotes
documentYesThe UBL XML as a string, without an SBDH envelope (GoRoute's Access Point adds that). Empty or whitespace-only is refused.
receiver_scheme, receiver_idYesThe buyer's Peppol identifier, e.g. 0208 + 0123456789. Use the same value as the buyer's cbc:EndpointID in your XML.
document_typeNoDefaults to the BIS 3.0 Invoice identifier shown above. Must start with urn: and contain ::.
process_idNoDefaults to urn:fdc:peppol.eu:2017:poacc:billing:01:1.0. Must start with urn:.
sender_scheme, sender_idNoYour participant. Must be a participant of your organisation (or of one it manages); see Sender entitlement.
metadataNoFree-form object stored with the transaction.
webhook_urlNoA 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"])
These are GoRoute's own checks, not the official Schematron

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 with 202 and "Document already accepted (idempotent replay)"; the document is not sent twice. The same key with a different body is refused with 422 IDEMPOTENCY_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 the existing_transaction_id. That 409 carries no error_code field โ€” check error.

Details: Sending an invoice โ†’ Idempotency.

Errorsโ€‹

StatusCodeMeaning
400INVALID_REQUESTThe document failed validation (up to five [code] message items in message), no sender could be found, or the sender is not entitled (see below).
400INVALID_IDEMPOTENCY_KEYThe key is longer than 255 characters.
401MISSING_API_KEY / INVALID_API_KEY / EXPIRED_API_KEYCheck the X-API-Key header.
403INSUFFICIENT_PERMISSION / INSUFFICIENT_SCOPEThe key needs the send scope.
409duplicate_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.
422IDEMPOTENCY_KEY_REUSEDSame key, different body, within 24 hours.
422PLATFORM_ONLY_DOCUMENT_TYPETax-reporting and MLS document types are sent by GoRoute itself, never by a tenant.
429RATE_LIMIT_MINUTE / RATE_LIMIT_DAYWait 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โ€‹

  1. transaction.queued fires as soon as the document is accepted.
  2. transaction.delivered fires when the recipient's Access Point confirms receipt; transaction.retrying and transaction.failed cover the other outcomes.
  3. GET /api/v1/transactions/{transaction_id} returns the current status (queued, submitted, accepted, delivered, failed, retrying, held or issued) with the delivery timestamps and any error_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.

Next stepsโ€‹