Peppol e-invoicing for developers. Send compliant invoices with one API call.
getpeppr is a developer-first API gateway for the Peppol e-invoicing network. You send JSON — we handle UBL XML, BIS 3.0 validation, and network delivery.
npm install @getpeppr/sdkimport { Peppol } from "@getpeppr/sdk";
const peppol = new Peppol({ apiKey: "sk_sandbox_..." });
const invoice = await peppol.invoices.send({
number: "INV-2026-001",
to: { name: "Globex NV", peppolId: "0208:BE0987654321", street: "Rue de la Loi 200", city: "Brussels", postalCode: "1000", country: "BE" },
lines: [{ description: "Consulting", quantity: 1, unitPrice: 1000, vatRate: 21 }],
});
console.log(invoice.status); // "submitted"curl -X POST https://api.getpeppr.dev/v1/invoices/send \
-H "Authorization: Bearer sk_sandbox_abc123..." \
-H "Content-Type: application/json" \
-d '{
"number": "INV-2026-001",
"to": {
"name": "Globex NV",
"peppolId": "0208:BE0987654321",
"street": "Rue de la Loi 200",
"city": "Brussels",
"postalCode": "1000",
"country": "BE"
},
"lines": [{
"description": "Consulting",
"quantity": 1,
"unitPrice": 1000,
"vatRate": 21
}]
}'# Install once
npm install -g @getpeppr/cli
# Authenticate (sandbox by default)
getpeppr login --key sk_sandbox_abc123... --sandbox
# Send a JSON file and watch delivery status
getpeppr send invoice.json --watch
# Or synthesize a quick test invoice from flags
getpeppr send --to 0208:BE0987654321 --amount 1000 --desc "Consulting"The CLI also handles offline validation (getpeppr validate), scaffolding (getpeppr init), UBL conversion (getpeppr convert), and Peppol Directory lookups (getpeppr lookup).
Full documentation is available at getpeppr.dev/docs.
| Guide | Description |
|---|---|
| Quick Start | Installation, first invoice, configuration |
| Onboarding Setup | Legal entity and Peppol identity setup |
| Authentication | API keys, environments, rate limits |
| Sandbox | Sandbox limitations, quotas, and going to production |
| Send an Invoice | Sending, attachments, allowances, delivery |
| Credit Notes | Correcting and cancelling invoices |
| Listing Invoices | Browsing your sent invoices |
| Validation | Client-side and server-side validation before sending |
| Contacts & Directory | Contact management and Peppol Directory lookup |
| Document Status | Tracking delivery lifecycle |
| Webhooks | Real-time event notifications |
| Platform & Multi-Tenant | Sending on behalf of sub-tenants, legal entity lifecycle, and platform webhooks |
| Error Handling | Error types, status codes, retries |
| CLI | Send invoices, manage credentials, validate, scaffold, convert, and lookup — all from the terminal |
| Type Definitions | TypeScript interface reference |
Import the collection into Postman for interactive API exploration.
- Open Postman → Import → select
postman/getpeppr.postman_collection.json - Set collection variables:
base_url→https://api.getpeppr.devapi_key→ your API key (e.g.sk_sandbox_abc123...)
| Language | Directory | Description |
|---|---|---|
| TypeScript | examples/typescript/ |
Full SDK examples |
| Python | examples/python/ |
HTTP examples with requests |
| cURL | examples/curl/ |
Command-line examples |
Send an invoice, track its delivery, and export it:
send → track status → export (PDF, XML, JSON)
| Method | Endpoint | SDK Method | Description |
|---|---|---|---|
POST |
/v1/invoices/send |
invoices.send() |
Validate, create, and send in one step |
GET |
/v1/invoices |
invoices.list() |
List invoices (paginated) |
GET |
/v1/invoices/:id |
invoices.getStatus() |
Get invoice details and delivery status |
GET |
/v1/invoices/:id/as/:format |
invoices.getAs() |
Export as PDF, XML, or JSON |
Invoices are immutable after submission — there are no drafts, updates, or deletes. To correct an invoice, send a credit note.
"submitted" → "delivered" → "accepted" → "paid"
→ "rejected"
→ "failed"
| Method | Endpoint | SDK Method | Description |
|---|---|---|---|
POST |
/v1/invoices/send |
invoices.send() |
Send a credit note (isCreditNote: true, must include invoiceReference) |
| Method | Endpoint | SDK Method | Description |
|---|---|---|---|
GET |
/v1/contacts |
contacts.list() |
List contacts (paginated) |
GET |
/v1/contacts/:id |
contacts.get() |
Get contact details |
POST |
/v1/contacts |
contacts.create() |
Create a contact |
PUT |
/v1/contacts/:id |
contacts.update() |
Update a contact |
DELETE |
/v1/contacts/:id |
contacts.delete() |
Delete a contact |
| Method | Endpoint | SDK Method | Description |
|---|---|---|---|
GET |
/v1/bank-accounts |
bankAccounts.list() |
List bank accounts (paginated) |
GET |
/v1/bank-accounts/:id |
bankAccounts.get() |
Get bank account details |
POST |
/v1/bank-accounts |
bankAccounts.create() |
Create a bank account |
PUT |
/v1/bank-accounts/:id |
bankAccounts.update() |
Update a bank account |
DELETE |
/v1/bank-accounts/:id |
bankAccounts.delete() |
Delete a bank account |
| Method | Endpoint | SDK Method | Description |
|---|---|---|---|
GET |
/v1/transports/types |
transports.listTypes() |
List available transport types |
GET |
/v1/transports |
transports.list() |
List configured transports |
GET |
/v1/transports/:code |
transports.get() |
Get transport details |
POST |
/v1/transports |
transports.create() |
Create a transport |
PUT |
/v1/transports/:code |
transports.update() |
Update a transport |
DELETE |
/v1/transports/:code |
transports.delete() |
Delete a transport |
Note: Transports are managed automatically by the Peppol network. These endpoints return static transport configurations — creating, updating, or deleting transports has no effect on invoice delivery routing.
| Method | Endpoint | SDK Method | Description |
|---|---|---|---|
GET |
/v1/directory/:scheme/:id |
directory.lookup() |
Lookup Peppol participant (enriched: name, country, capabilities, VAT, contacts, website) |
GET |
/v1/directory/search |
directory.search() |
Search Peppol Directory by name, country, or VAT |
Convenience method: directory.searchByVat(vatNumber) — searches by VAT number (country prefix stripped server-side).
| Method | Endpoint | SDK Method | Description |
|---|---|---|---|
POST |
/v1/validate |
peppol.validate() |
Validate invoice (client-side rules, BIS 3.0 + country-specific) |
POST |
/v1/validate/server |
invoices.validateServer() |
Gateway-side validation with SDK checks, UBL generation status, and offline Peppol business rules |
| Method | Endpoint | SDK Method | Description |
|---|---|---|---|
GET |
/v1/events |
events.list() |
List usage events (paginated) |
Verify incoming webhook signatures using HMAC-SHA256:
import { webhooks } from "@getpeppr/sdk";
const event = await webhooks.constructEvent(
rawBody, // raw request body string
req.headers["getpeppr-signature"],
process.env.WEBHOOK_SECRET
);
console.log(event.type); // e.g. "invoice.sent"| Event | Description |
|---|---|
invoice.sent |
Invoice successfully delivered to recipient's access point |
invoice.accepted |
Recipient accepted the invoice |
invoice.refused |
Recipient rejected the invoice |
invoice.error |
Delivery failed (final state) |
invoice.registered |
Cleared by tax authority (e.g., KSA, PT) |
invoice.received |
Receipt acknowledged by recipient |
invoice.paid |
Payment confirmed by recipient |
legal_entity.registered |
Platform sub-tenant reached a verified or active state |
legal_entity.verification_failed |
Platform sub-tenant registry verification failed |
legal_entity.awaiting_authz |
Platform sub-tenant authorisation email is awaiting customer action |
inbound.invoice.received |
An invoice addressed to your Legal Entity was received from the Peppol network (pilot — contact support to enable) |
inbound.creditnote.received |
A credit note addressed to your Legal Entity was received from the Peppol network (pilot — contact support to enable) |
test.ping |
Test event sent during endpoint setup |
* |
Wildcard — subscribes to all event types |
See the full Webhooks guide for payload shapes, the Getpeppr-Signature format, and retry behaviour.
When a supplier on the Peppol network sends an invoice or credit note to one of your Legal Entities, getpeppr stores the document and dispatches an inbound.invoice.received or inbound.creditnote.received event. This is a pilot feature — contact support@getpeppr.dev to enable it for your account.
Not to be confused with
invoice.received— that outbound event means a document you sent was acknowledged by the recipient's access point. Theinbound.*events mean a document was sent to you by a third party.
Delivery is at-least-once: deduplicate on data.receivedDocumentId, the stable idempotency key for inbound events (one received document, one id, however many deliveries). The UBL XML is embedded in the payload as base64 (data.document.content) for documents up to 512 KB; larger documents set content to null with contentOmittedReason: "size". There is no retrieval API for received documents yet — one is planned for a future release.
Verify that a recipient is registered on the Peppol network before sending:
// Non-blocking mode — sends even if recipient not found (omit for no validation)
const invoice = await peppol.invoices.send(data, { validateRecipient: "warn" });
// Strict mode — rejects with 422 if recipient not found
const invoice = await peppol.invoices.send(data, { validateRecipient: "strict" });Also available via the x-validate-recipient header in REST calls.
Automatically paginate through all results:
for await (const invoice of peppol.invoices.listAll()) {
console.log(invoice.number);
}Also available on contacts.listAll(), bankAccounts.listAll(), and events.listAll().
Note:
invoices.list()returns outbound invoice submissions (invoices you sent). Inbound documents (invoices and credit notes sent to you) are delivered through theinbound.*webhook events (pilot — contact support to enable); a retrieval API for received documents is planned for a future release.
Send multiple invoices concurrently:
const result = await peppol.invoices.sendBatch(invoices, {
concurrency: 5,
stopOnError: false,
});
console.log(`${result.succeeded.length} sent, ${result.failed.length} failed`);Wait for an invoice to reach a target status:
const final = await peppol.invoices.waitFor(invoice.id, "accepted", {
interval: 2000, // poll every 2s
timeout: 60_000, // give up after 60s
});MIT — see LICENSE.
Built by Zero Loop Labs.