Send an invoice via the API

Send an e-invoice via the SalesInvoice endpoint: upload in any supported format, automatic transformation, routing and status tracking.

Sending an e-invoice via the PSB API is the most commonly used endpoint. You send an XML document to the API in the format your software produces, and the PSB takes care of validation, automatic transformation to the format the recipient expects, routing and delivery through the appropriate network.

Endpoint
POST /api/v1/{partyId}/salesInvoice/send

The {partyId} in the URL is the Peppol identifier of the sending organisation (the supplier). The request contains the XML document as the body (content-type application/xml). You can submit in any supported format: UBL 2.1, NLCIUS, BIS Billing V3, PINT, CII, XRechnung, Factur-X, FatturaPA, ebInterface, Svefaktura, DICO, SETU and more. The PSB auto-detects the document format, validates the document and automatically transforms it to the format the recipient expects.

Basic flow
  1. Upload: send the XML document to the endpoint
  2. Validation: the PSB validates the document against the XSD schema and business rules
  3. Routing: the PSB looks up how to reach the recipient via SML/SMP
  4. Delivery: the document is delivered through the appropriate channel (Peppol, email, or another network)
  5. Status update: you receive a webhook notification with the delivery status

Important: the PSB determines the recipient based on the EndpointID in your XML document. If this element is missing or contains an incorrect identifier, the delivery fails with the status InvoiceSentError. Ensure that your source system fills in the correct EndpointID. More about identifiers and routing can be found in the article on Peppol identifiers.

Idempotency: preventing duplicate uploads

The PSB supports idempotency via the X-EConnect-DocumentId request header. By including your own UUID with each upload request, you prevent duplicate processing:

X-EConnect-DocumentId: 550e8400-e29b-41d4-a716-446655440000

If you send the same documentId again, the API returns 409 Conflict, letting the sender know the document has already been processed.

Important: Always use a UUID/GUID as the documentId. Never use the invoice number, as this causes issues when you want to resend a corrected version of the same invoice.

Checking routing

Before sending an invoice, you can use queryRecipientParty to check whether and how the recipient is reachable:

POST /api/v1/{partyId}/salesInvoice/queryRecipientParty

In the request body you pass a list of identifiers, for example ["0106:12345678"], or an object with partyIds and metaAttributes. Optional query parameters are ?preferredDocumentTypeId and ?includeOptions.

The response contains information about:

  • Whether the recipient is registered on Peppol
  • Which document types the recipient can receive
  • Which Access Point the recipient is reachable through
  • Which channel the PSB will use for delivery

For advanced lookups including the Access Point URL and the AP certificate, a separate route is available:

GET /api/v1/peppol/deliveryOption?partyIds={id}&documentFamily=Invoice&isCredit=false
Error handling

When delivery fails, the PSB automatically applies a retry mechanism:

  • Up to 8 attempts spread over approximately 35 hours
  • Only for 5xx server errors (temporary errors on the receiving end)
  • An InvoiceSentRetry event is published for each attempt
  • On permanent failure, you receive an InvoiceSentError event via your webhook

Common errors when sending:

ErrorCauseSolutionValidation error (4xx)Document does not comply with the standardValidate the document using the Validate API. Note: 4xx errors are not retried; the document remains in InvoiceSentError as its final state.Invalid payload — '{value}' is not a valid xs:decimal on an amount fieldThe source system serialises an amount (for example cbc:TaxInclusiveAmount, cbc:PriceAmount, cbc:LineExtensionAmount) in scientific notation, such as -1.336061E6 instead of -1336061.00. UBL amount fields are of type xs:decimal and the XML Schema type does not allow exponent notation; only fixed decimal notation with at most 2 decimals is valid. Typically occurs for large or negative amounts that are internally serialised as double.Adjust the source system so amounts are written as plain decimal numbers (no E notation, no thousands separators). Report the issue to the vendor of the sending software package; eConnect cannot fix this because the value is in the supplied XML.Recipient not foundIdentifier not registered in PeppolCheck via queryRecipientParty409 ConflictA document with this documentId has already been processedNo action needed, the document has already been sentHTTP 500 for large payloadRequest exceeds the 24 MB web server limit (including overhead)Reduce embedded PDF attachments; account for ~33% base64 overhead

Note: SI-UBL 1.2 (Simpler Invoicing 1.2) has not been allowed on the Peppol network since 1 January 2024. Invoices in this format result in an InvoiceSentError. Use NLCIUS (SI-UBL 2.0) or Peppol BIS Billing V3. Check the CustomizationID in your XML document if you encounter this error.

Response

A successful upload returns 200 OK with the document ID in the PSB. Use this ID to track the document status via the API or via webhooks.

No DELETE on salesInvoice: final state after a 4xx error

Unlike purchaseInvoice, the salesInvoice endpoint has no DELETE. This is a deliberate choice, because a sent or rejected sales invoice is an audit-relevant life event: the document remains traceable in the audit trail, even after delivery has failed.

Customers sometimes ask whether a rejected sales invoice can be stopped or deleted, for example after they have already credited the invoice internally. The answer is:

  • Validation error (4xx, for example Invalid payload): the PSB places the document in the final state InvoiceSentError. No retry is executed (only 5xx errors are retried). The document is not delivered to the recipient after all. No action is needed on the PSB; the document is in its final state and is retained for 90 days for audit purposes.
  • No DELETE endpoint needed: because the document is no longer active and is not retried, it does not need to be explicitly deleted to stop delivery.
  • Send a correction via a new document: if the original invoice was incorrect and had already been (partially) delivered, use a credit note or correction invoice according to the standard accounting flow. The original documentId remains as a reference in the audit trail.

This difference in lifecycle between salesInvoice (no DELETE) and purchaseInvoice (with DELETE) stems from the role: an outgoing invoice is a commercial action recorded by the sender that must be legally traceable; an incoming invoice can be removed from the recipient's own system after successful download.

ViDA and CTC reporting

When sending an invoice, the PSB also handles fiscal reporting. Under the ViDA directive (VAT in the Digital Age), the PSB automatically generates a DRR (Digital Reporting Requirement) from the invoice data and submits it to the relevant tax authority. As an integrator you do not need to do anything extra: the DRR is derived from the same invoice you submit through the regular endpoint. Status messages and CTC reporting messages are included in the document price.

Advanced options
Forcing a channel

By default, the PSB automatically selects the best channel. With the ?channel={hookId} query parameter, you can force a specific delivery channel (e.g. a specific network or email fallback).

Including attachments

Attachments (PDF, images) can be embedded as base64 in the UBL document as AdditionalDocumentReference. The PSB processes embedded attachments correctly and delivers them together with the invoice.

Note: The send endpoint accepts a maximum of 24 MB per request (including HTTP overhead). With base64-encoded attachments, the payload grows by approximately 33%, so an 18 MB PDF results in a request of approximately 24 MB. Payloads that exceed the limit are rejected by the web server with HTTP 500 (not 413), before application validation takes place. Reduce large attachments or send them via a separate channel.

Tip: The current Peppol BIS Billing 3.0 standard supports a maximum of one OrderReference per invoice. If an invoice relates to multiple orders, multiple invoices must be sent.

Tip: Some recipients report errors for invoices with an XML namespace prefix (e.g. <urn:Invoice> instead of <Invoice>). Both forms are technically valid XML according to the W3C specification. If a recipient cites this as a reason for rejection, it is not a valid refusal within the Peppol network. The recipient must use an XML parser that correctly handles both prefixed and default namespaces.

Frequently asked questions
What format do I submit my invoice in?

You can submit in any format the PSB supports: UBL 2.1, NLCIUS, BIS Billing V3, PINT, CII, XRechnung, Factur-X, FatturaPA, ebInterface, Svefaktura, DICO, SETU and more. Post the XML document as the body to POST /api/v1/{partyId}/salesInvoice/send with Content-Type: application/xml. The PSB auto-detects the format, validates it and transforms it to the format the recipient expects. The recipient is determined via the EndpointID in your XML.

How do I prevent duplicate submissions and what does 409 Conflict mean?

Include the header X-EConnect-DocumentId with a unique UUID for every upload. If you repeat the same documentId, the API responds with 409 Conflict, confirming that the document has already been processed. Never use the invoice number as the documentId, because when resending a corrected version you need a new ID.

How do I track the delivery status and what happens with temporary errors?

After a successful upload, the API returns a document ID in the PSB that you can use to track the status via the API or webhooks. For temporary 5xx errors on the receiving end, the PSB publishes an InvoiceSentRetry event for each attempt and retries up to 8 times over approximately 35 hours; if delivery permanently fails, you receive an InvoiceSentError event.


Want to test whether your document is valid first? Use the Validate API.

Try it in the API