Developers
Build on the Peppol API
Get a sandbox key with one request, send a first invoice as JSON, then go live on the same API.
Quickstart
A sent invoice in three steps
You need a terminal and an email address. The sandbox needs no card and no approval.
Start for free →Step 1: Sign up and get a key
Send an email address to
POST /v1/signup. The response contains a sandbox key that works immediately. It shows the key one time only, so keep it in a safe place.Sign up curl -X POST https://api.peppol.sh/v1/signup \ -H "Content-Type: application/json" \ -d '{"email": "you@example.com"}' # 201 Created # { # "id": "acc_...", # "email": "you@example.com", # "status": "active", # "api_key": "ps_test_..." # }You can also make keys in the dashboard.
Step 2: Send a sandbox invoice
First create the company that sends. You do this one time for each sender.
Create the company export PEPPOL_API_KEY="ps_test_..." curl -X POST https://sandbox.peppol.sh/v1/companies \ -H "Authorization: Bearer $PEPPOL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Acme BV", "country": "BE", "company_registration_id": "0123456749", "peppol_id": "0208:0123456749" }' # 201 Created, with the id of the company: com_...Then send the invoice as JSON, with the
company_idof the response. The API validates the invoice, converts it to Peppol BIS 3.0 UBL and puts it in the queue. A sandbox document does not go on the live Peppol network.Send the invoice curl -X POST https://sandbox.peppol.sh/v1/documents \ -H "Authorization: Bearer $PEPPOL_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: acme-INV-2026-001" \ -d '{ "company_id": "com_...", "number": "INV-2026-001", "issue_date": "2026-03-01", "due_date": "2026-03-31", "from": { "name": "Acme BV", "tax_id": "BE0123456749" }, "to": { "name": "Globex NV", "tax_id": "BE0987654394" }, "lines": [ { "description": "API integration services", "quantity": 1, "unit_price": 500, "tax_rate": 21 } ] }' # 202 Accepted, with the id of the document: doc_...This is the minimum body. The API reference gives the optional fields, such as addresses, payment details and attachments.
Step 3: Get the result
Sending is asynchronous. Register a webhook and the API posts the result to your URL, or read the status of the document. Webhooks work in the sandbox too.
Register a webhook or read the status # A webhook for the result. The response has the signing secret. curl -X POST https://sandbox.peppol.sh/v1/webhooks \ -H "Authorization: Bearer $PEPPOL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/hooks/peppol", "events": ["document.delivered", "document.failed"] }' # Or read the status: queued, sending, delivered or failed. curl "https://sandbox.peppol.sh/v1/documents/doc_...?company_id=com_..." \ -H "Authorization: Bearer $PEPPOL_API_KEY"The webhook documentation gives the events, the payloads and the signature check.
Environments
Sandbox and live
The same API on two hosts. The prefix of the key selects the environment.
Sandbox
Key prefix: ps_test_https://sandbox.peppol.sh/v1Test delivery. Documents do not go on the live Peppol network. Free.
Live
Key prefix: ps_live_https://api.peppol.sh/v1The live Peppol network. Needs a verified workspace and credits.
A key works on one host only. A ps_test_ key on api.peppol.sh, or a ps_live_ key on sandbox.peppol.sh, returns 403 with code wrong_environment.
To go live, a person verifies the workspace one time. Then you create a ps_live_ key and change the base URL. Live documents use prepaid credits: see pricing.
SDK
TypeScript SDK
Typed from the OpenAPI spec. For Node.js 18 or later, Bun, Deno and Cloudflare Workers.
@peppol-sh/sdk on GitHubnpm i @peppol-sh/sdkimport { Peppol } from "@peppol-sh/sdk";
const peppol = new Peppol({
apiKey: process.env.PEPPOL_API_KEY!,
baseUrl: "https://sandbox.peppol.sh", // omit for live
});
const invoice = await peppol.documents.send({
company_id: "com_...",
number: "INV-2026-001",
// the same fields as the JSON body of step 2
});
const current = await peppol.documents.get(invoice.id!, {
company_id: "com_...",
});
console.log(current.status); // queued | sending | delivered | failedThere is no SDK for other languages at this time. Use the REST API directly, or generate a client from the OpenAPI spec.
Limits
Rate limits
Counted for each API key, in windows of one minute.
- Sandbox key
- 60 requests per minute
- Live key
- 120 requests per minute
- Signup
- 10 for each IP address in one hour
Each response gives the budget in the RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers. Above the limit, the response is 429 with code rate_limit_exceeded and a Retry-After header in seconds. The public lookup endpoints have their own limits for each hour, given in the API reference.
Reference
Reference and resources
The full API, the machine-readable files and the policies.
API reference
Each endpoint, with request and response examples.
OpenAPI spec
The OpenAPI 3.1 document, for code generators and agents.
Webhooks
Events, payloads, signature check, retries and rotation.
Authentication guide
How to get, use and revoke an API key.
TypeScript SDK
Source, changelog and issues of @peppol-sh/sdk on GitHub.
Deprecation policy
API versions, notice periods and current notices.
llms.txt
The site index for AI agents.
A question about an integration? Email hello@peppol.sh. The contact page tells you what to put in a support request.
Contact and support