Skip to content

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.

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 →
  1. 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.

  2. 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_id of 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.

  3. 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.

Sandbox and live

The same API on two hosts. The prefix of the key selects the environment.

  • Sandbox

    https://sandbox.peppol.sh/v1

    Test delivery. Documents do not go on the live Peppol network. Free.

    Key prefix: ps_test_
  • Live

    https://api.peppol.sh/v1

    The live Peppol network. Needs a verified workspace and credits.

    Key prefix: ps_live_

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.

TypeScript SDK

Typed from the OpenAPI spec. For Node.js 18 or later, Bun, Deno and Cloudflare Workers.

@peppol-sh/sdk on GitHub
Install
npm i @peppol-sh/sdk
Send and read a document
import { 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 | failed

There is no SDK for other languages at this time. Use the REST API directly, or generate a client from the OpenAPI spec.

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 and resources

The full API, the machine-readable files and the policies.

A question about an integration? Email hello@peppol.sh. The contact page tells you what to put in a support request.

Contact and support