⚡ Certified Peppol Access Point · POP000991

Peppol API Integration
for ERPs, SaaS and Finance Systems

One REST API to send and receive compliant e-invoices across the Peppol network and 40+ countries. Certified Access Point, SMP, Schematron validation, PINT support, AS4 delivery — all behind a single X-API-Key header.

✓Peppol BIS 3.0 + PINT
✓UBL 2.1 or JSON input
✓Signed webhooks
✓ISO 27001-aligned

▶ Watch: one JSON invoice, every format (3:22)

POST /peppol-api/api/v1/documents
curl -X POST https://app.goroute.ai/peppol-api/api/v1/documents \
  -H "X-API-Key: $GOROUTE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: INV-2026-0412" \
  -d '{
    "receiver_scheme": "0208",
    "receiver_id": "0987654394",
    "document": "<Invoice xmlns=...>...</Invoice>"
  }'

HTTP/1.1 202 Accepted
{
  "transaction_id": "1d5c8f92-3b47-4c2e-9a61-0f7d2e8b4c15",
  "status": "queued",
  "message": "Document queued for delivery"
}

What is a Peppol API?

A Peppol API is a web interface, run by a certified Peppol Access Point, that lets your software send and receive e-invoices on the Peppol network with ordinary HTTPS calls. You post an invoice and read back its delivery status. The Access Point handles the AS4 messaging, certificates, validation and receiver discovery that Peppol requires.

Peppol itself has no central sending API. The network is a set of certified Access Points that exchange documents with each other, plus a directory (the SML and the SMPs) that says which Access Point serves which business. OpenPeppol publishes the rules and a public directory you can search, but an invoice only travels from one Access Point to another. So every Peppol API you can send through is an Access Point's own interface, and the questions worth asking are how it authenticates you, how you test against it, how you find a receiver and how it tells you an invoice arrived. The rest of this page answers each of those for GoRoute's API.

How do I connect my ERP to Peppol through an API?

Post the invoice to one REST endpoint with an API key. GoRoute's certified Access Point (POP000991) converts it to UBL, validates it against the profile the receiver's country requires, signs it, looks the recipient up in the SMP and delivers it over AS4. Your ERP never speaks Peppol itself.

There are two ways in. If your system already produces a Peppol BIS 3.0 UBL file, send it as a string to POST /api/v1/documents with the receiver's scheme and identifier, as in the example above. GoRoute transmits that document exactly as you sent it. If your system holds invoice data but no UBL, send JSON to POST /api/v1/invoices and GoRoute validates it, renders the UBL and queues it. Both return 202 Accepted with a transaction_id, because delivery happens afterwards, not during your call.

That is the job of this page. Three neighbouring questions have their own pages: whether to build Peppol into a product you sell is answered on Peppol for software vendors; validating invoices and filing them with a tax authority is invoice validation and tax reporting; and reselling e-invoicing under a commercial agreement is the partner program.

What is Peppol API integration?

Peppol API integration is the process of connecting a business system — an ERP such as SAP S/4HANA or Microsoft Business Central, an accounting package such as Odoo or Zoho Books, or a SaaS billing platform such as Stripe or Zuora — to the Peppol network via a certified Access Point exposed as a REST API.

Instead of implementing AS4 messaging, G3 PKI certificates, EN 16931 Schematron validation and SMP/SML discovery yourself, you POST a UBL or JSON document to a single endpoint. GoRoute's certified Access Point (POP000991) handles conversion, validation, signing, routing and delivery to any business registered to receive on Peppol, whichever Access Point that business uses.

The same API also covers DBNAlliance in the United States, Finvoice in Finland, and national CTC systems including Fawtara Oman, KSeF Poland, SDI Italy, XRechnung Germany and MyInvois Malaysia (ZATCA Saudi Arabia is in development) — so a single integration unlocks 40+ countries.

Core capabilities of the Peppol API

Send invoices

POST a finished UBL 2.1 document, or JSON that GoRoute renders to BIS 3.0 or PINT for the receiver's country.

Receive invoices

A signed webhook tells you a document arrived; fetch the original UBL XML with one call.

Validate

Four-layer validation: UBL XSD → business rules → eDEC code lists → CEN/BIS + PINT Schematron.

SMP lookup

Check any Peppol participant and the document types it accepts with GET /api/v1/participants/lookup.

PINT jurisdictions

A-NZ, Oman, Japan, Malaysia, Singapore, EU — auto-detected from CustomizationID.

CTC clearance

Fawtara (Oman), KSeF (Poland), SDI (Italy), MyInvois (Malaysia), FIRS (Nigeria). ZATCA (Saudi) is in development.

Delivery status

Poll a transaction or subscribe to HMAC-signed webhooks for queued, delivered, retrying, failed and received.

Any language

Plain HTTPS and JSON, nothing to install. An OpenAPI 3.0 document and a Postman collection generate a client if you want one.

Multi-tenant

Every key is bound to one organisation. Ideal for white-label and multi-subsidiary deployments.

How do Peppol API keys work?

Every request carries one API key in the X-API-Key header. Test keys start sk_test_ and live keys start pk_live_, so the two cannot be confused in a config file. A key belongs to exactly one organisation, and every call it makes is scoped to that organisation: there is no organisation ID to pass.

Create a key in the dashboard under Settings → API Keys, or with POST /api/v1/api-keys once you hold one. The secret is shown once, in the response that creates it, and never again. Each key carries scopes, so an integration that only sends invoices does not also need the power to manage webhooks:

Scope What it allows
sendSend documents over Peppol
readRead transactions, participants and lookups
writeCreate and update participants, delete transactions
adminManage webhooks and API keys; satisfies every other scope

A new key from the API gets send and read unless you ask for more. Keys can carry an expiry date, or none. To rotate without downtime, create the new key, deploy it, confirm it works, then revoke the old one with DELETE /api/v1/api-keys/{id}. Revocation is permanent.

Errors name their cause in a top-level error_code: MISSING_API_KEY, INVALID_API_KEY or EXPIRED_API_KEY with HTTP 401, INSUFFICIENT_SCOPE with 403, and a 429 with a Retry-After header when you pass your plan's rate limit. Limits apply per organisation, not per key. The full rules are in the authentication guide.

How do I test a Peppol API integration before going live?

Use the test environment. It is a separate host, on the Peppol test network, where nothing you send is a real invoice and nothing is billed. Its data is separate too: an organisation or participant created in test does not exist in production. The free 14-day developer sandbox gives you a test key and a registered test participant, provisioned within 24 hours on business days, with no credit card.

First, ask the host which environment it is. GET /health needs no key and answers "environment": "test" or "production". Then send to the fixed sandbox receivers. Each one always behaves the same way, so you can prove your code handles success and each kind of failure:

Receiver Outcome
9999:test-successDelivered
9999:test-recipient-not-foundFailed, RECIPIENT_NOT_FOUND
9999:test-as4-failureFailed, AS4_DELIVERY_FAILED
9999:test-validation-failureFailed, VALIDATION_FAILED
9999:test-timeoutRetrying, then failed, TIMEOUT

test-timeout is the one to test last: it moves through retrying several times before it settles, which is where duplicate records on your side usually show up. A sandbox receiver sent from production is refused with SANDBOX_NOT_ALLOWED, so a misconfigured deployment is never handed a fake success.

The sandbox simulates delivery; it does not apply a country's invoice rules. To check a document against those, call the validation endpoint. The complete list of receivers is on Test Identifiers, and what OpenPeppol's own testbed is for is explained in Peppol test bed explained.

Integrate in three steps

1

Get a sandbox key

Apply on the developer page. A test key and a test Peppol participant are provisioned within 24 hours on business days — no credit card.

2

POST your first invoice

Send UBL to /api/v1/documents or JSON to /api/v1/invoices. Validation errors come back on the call, so you fix the mapping before going live.

3

Go live

Request production access from the dashboard; the live key usually follows within 24 hours. Register your production participant, switch keys and exchange real invoices over AS4.

How do I look up a receiver's Peppol ID through the API?

Call GET /api/v1/participants/lookup with the identifier as one value in scheme:value form. The answer says whether the network confirmed the participant, its country, and the document types it can receive. Check that list before sending a credit note or an order, not only an invoice.

curl "https://app.goroute.ai/peppol-api/api/v1/participants/lookup?peppol_id=0208:0987654394" \
  -H "X-API-Key: $GOROUTE_KEY"

{
  "found": true,
  "participant_id": "0208:0987654394",
  "country": "BE",
  "capabilities": [
    "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",
    "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"
  ]
}

Read "found": false as "not confirmed", not as "not registered". The same answer comes back when a directory fails to respond, so treat it as a reason to hold and retry, not as proof the customer's identifier is wrong. A lookup that is not found is not an HTTP error: it answers 200.

Two related calls cover what this one leaves out. GET /api/v1/lookup/participant returns the business name and the Access Point behind each document type. GET /api/v1/public/lookup/{scheme}/{identifier} needs no key at all, for a web page that shows a result inline. If you are not sure which identifier a customer is registered under, which Peppol identifier is yours explains the schemes country by country.

How do I track delivery status through the Peppol API?

Every send returns 202 with status queued and a transaction_id. From there, either poll GET /api/v1/transactions/{transaction_id} or let a webhook tell you. There is no synchronous send mode, so do not write code that waits on the first response. A transaction moves through these states:

Status Meaning
queuedAccepted by the API, waiting to be processed
submittedHanded to the AS4 sender
acceptedThe receiving Access Point acknowledged the AS4 message
deliveredFinal success
retryingA temporary failure; GoRoute will try again
failedTerminal failure, with an error code saying why

To be told rather than poll, register a URL with POST /api/v1/webhooks and choose events. The ones a sender needs are transaction.queued, transaction.delivered, transaction.retrying and transaction.failed. A failed event carries the error_code, the message and the retry count.

Each delivery is signed. The X-Webhook-Signature header holds a timestamp t and an HMAC-SHA256 value v1, computed with your webhook secret over the timestamp, a full stop and the raw request body. Verify the raw bytes before parsing the JSON, or the digest will not match.

Retries are safe if you send an Idempotency-Key header: a repeat within 24 hours returns the first transaction_id and its current status, and nothing is sent twice. Field-by-field detail is in the webhook events reference. If a shop platform rather than your own code reports a failure, start with what to do when an e-invoice was not delivered.

Receiving inbound invoices

When a document addressed to you arrives from the Peppol network, GoRoute posts a transaction.received event to your webhook. The event identifies the document; fetch the original UBL XML with GET /api/v1/transactions/{transaction_id}/document and archive it as your jurisdiction requires.

POST https://your-app.example.com/hooks/peppol
X-Webhook-Signature: t=1791797462,v1=1f8ac10f23c5...
X-Event-Type: transaction.received
Content-Type: application/json

{
  "id": "evt_4d5e6f708192a3b4c5d6e7f809122334",
  "event_type": "transaction.received",
  "created_at": "2026-10-12T09:31:02.123456",
  "data": {
    "transaction_id": "550e8400-e29b-41d4-a716-446655440000",
    "direction": "received",
    "status": "delivered",
    "sender_peppol_id": "0208:0123456749",
    "receiver_peppol_id": "0208:0987654394",
    "document_type": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##...",
    "message_id": "phase4-msg-abc123",
    "received_at": "2026-10-12T09:31:01+00:00"
  }
}

Worked example: sending a Belgian invoice under scheme 0208

Since 1 January 2026, a Belgian VAT-liable business invoicing another must send a structured e-invoice in Peppol BIS Billing 3.0 over Peppol, and every such business must be able to receive one, as the Belgian government's e-invoicing site sets out. Belgian companies are usually addressed by their enterprise number (KBO/BCE) under Peppol scheme 0208; the VAT number under scheme 9925 is also in use. No Belgian accreditation is needed beyond Peppol certification. Here is the whole flow through the API.

1. Confirm the buyer can receive

Look the buyer up as 0208: plus its ten-digit enterprise number, as in the lookup example above. If 0208 is not confirmed, try the buyer's VAT number under 9925 before concluding anything. The enterprise number has a mod-97 check on its last two digits, so validate it in your own system before you call. The numbers on this page pass that check but are illustrations: replace them with your own and your customer's.

2. Send the invoice as JSON

curl -X POST https://app.goroute.ai/peppol-api/api/v1/invoices \
  -H "X-API-Key: $GOROUTE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: BE-2026-0412" \
  -d '{
    "invoice": {
      "document_type": "invoice",
      "number": "BE-2026-0412",
      "issue_date": "2026-10-12",
      "due_date": "2026-11-11",
      "currency": "EUR",
      "seller": {
        "name": "Voorbeeld NV",
        "peppol_id": "0208:0123456749",
        "vat_number": "BE0123456749",
        "address": { "street": "Koningsstraat 1", "city": "Brussel",
                     "postal_code": "1000", "country": "BE" }
      },
      "buyer": {
        "name": "Klant BV",
        "peppol_id": "0208:0987654394",
        "vat_number": "BE0987654394",
        "address": { "street": "Meir 10", "city": "Antwerpen",
                     "postal_code": "2000", "country": "BE" }
      },
      "lines": [
        { "description": "Consulting, October 2026", "quantity": 10,
          "unit": "HUR", "unit_price": 100.00,
          "tax_rate": 21, "tax_category": "standard" }
      ],
      "totals": { "net_amount": 1000.00, "tax_amount": 210.00,
                  "payable_amount": 1210.00 }
    }
  }'

GoRoute validates the data, renders a Peppol BIS Billing 3.0 UBL invoice and answers 202 with "status": "queued". To check the same body without sending it, post it to /api/v1/invoices/validate first. Belgium's reduced rates of 12% and 6% go in as the standard category with the lower percentage; they are not separate codes.

3. Watch it arrive

The transaction.delivered webhook, or a poll that reads delivered, closes the loop. One Belgian rule is enforced for you: asking GoRoute to deliver a Belgian seller's invoice by email instead is refused with EMAIL_NOT_ALLOWED, because a PDF by email no longer counts between Belgian businesses.

For the mandate itself, see Belgium e-invoicing compliance. For why 0208 matters more than the VAT number, read the Belgian enterprise number on Peppol.

Validation before transmission

Every outbound invoice passes through four sequential validation layers. A Schematron error rejects the document before AS4 transmission; only warnings are allowed to pass.

1

UBL 2.1 XSD

Structural validation of the OASIS UBL 2.1 schema using lxml.

2

Business rules

Cross-field checks on totals, line calculations, tax bases, and currency consistency.

3

eDEC code lists

Participant schemes, document types, process IDs and country codes — synced from OpenPeppol.

4

Schematron (CEN/BIS + PINT)

CEN-EN16931-UBL + PEPPOL-EN16931-UBL baseline for EU flows, or PINT OM / A-NZ / SG / MY / JP / EU for non-EU jurisdictions, compiled to XSLT via Saxon-HE 12.4.

Peppol API integration FAQ

Where do I get a Peppol API key?

From the GoRoute dashboard under Settings → API Keys, or with POST /api/v1/api-keys once you hold a key. Test keys start sk_test_ and live keys start pk_live_. The secret is shown once, when the key is created.

Is there a free Peppol API sandbox?

Yes. The developer sandbox is free for 14 days and needs no credit card. It gives you a test key and a registered test participant, provisioned within 24 hours on business days, plus fixed 9999:test- receivers that simulate success and each kind of failure.

How do I check a Peppol invoice's delivery status through the API?

Poll GET /api/v1/transactions/{transaction_id}, or subscribe to the transaction.delivered, transaction.retrying and transaction.failed webhooks. A send always starts as queued; delivered is final success and failed is terminal, with an error code.

Which Peppol identifier does a Belgian company use?

Usually its ten-digit enterprise number (KBO/BCE) under scheme 0208, written as 0208:0123456749. The VAT number under scheme 9925 is also in use, so if a 0208 lookup is not confirmed, try 9925 before concluding the company is not on Peppol.

Where is the Peppol API documentation?

At docs.goroute.ai: a quickstart, the authentication and environment guides, an endpoint reference generated from the OpenAPI 3.0 document, a Postman collection, and a webhook events reference.

How long does a Peppol API integration take?

Typical REST-API integrations are live in 1–2 weeks. Full ERP connector deployments (SAP, Microsoft, Oracle) take 2–4 weeks. The sandbox is provisioned within 24 hours on business days.

What is the difference between an Access Point and an SMP?

An Access Point (AP) sends and receives AS4 messages. An SMP (Service Metadata Publisher) stores each participant's capability profile so other APs can discover where to route messages. GoRoute operates both a certified AP and a certified SMP under seat POP000991.

Do I need my own Peppol participant ID?

Yes — each legal entity needs its own participant ID (typically based on VAT number, ABN, NZBN, UEN, etc.). GoRoute registers and maintains your participant ID in the SMP and SML as part of the integration.

Can I white-label the Peppol API?

Yes. Our Hosted Infrastructure product deploys a complete AP + SMP under your brand, with your own G3 certificates and domain, in about four weeks.

What are the rate limits?

Limits apply per organisation and come from the plan: 120 requests a minute and 5,000 a day on the free, starter and professional plans, 300 and 20,000 on business, and 1,000 and 100,000 on enterprise. The developer sandbox allows 30 a minute. A key's own limit can only lower its plan's.

Where is data stored?

In encrypted PostgreSQL on AWS (ISO 27001 regions). Retention matches the sender's legal jurisdiction (typically 5–10 years). GDPR and HIPAA compliant.

Which ERPs have pre-built connectors?

SAP (S/4HANA, Business ByDesign, ECC), Microsoft (Dynamics 365, Business Central), Oracle, Odoo, Sage, Zoho, Certinia, Stripe, Zuora and Tally.

Ready to integrate Peppol?

Start with the documentation, or talk it through with us. Free 14-day sandbox. Certified Access Point. Global coverage.

Prefer to watch first? The tutorials show recorded ERP integrations end to end. Next read: how to choose a Peppol Access Point, or one API for multi-country e-invoicing. Plans and per-invoice prices are on the pricing page.