Quickstart
A complete end-to-end flow through the Agent API: create a test key, set up a client organization (including address), enroll it in the Peppol sandbox, issue an invoice, send it over Peppol, track its status, and receive a webhook. All in the sandbox, no credits, a real Peppol TEST network.
Prerequisites
| Item | Value |
|---|---|
| Base URL | https://api.efaktura.sk |
| Authentication | The X-API-Key header, plus X-Organization-Id for operations on an organization |
| Test key | efk_pk_test_… |
X-Organization-Id is always the company on whose behalf you call the API. When sending, that is the sender (the supplier); for a self-billed invoice, the buyer who issues it. You never set the recipient via org_id; it is derived from the buyer on the invoice (customer.dic).efk_pk_test_… = sandbox (Peppol TEST network, no credits), efk_pk_live_… = production.Integration flow
POST /v1/agent/peppol/connector/send): it validates, reserves credit, and sends in one go, with idempotency and opt-in auto-fix. For bulk sending from a spreadsheet, use CSV ingest. The step-by-step flow below is the universal foundation.In the partner portal, under API keys, create a sandbox key. It's shown only once, so store it in a secret store. A partner key (efk_pk_*) can create client organizations; it's tied to your partner account.
A partner key creates a white-label child organization. In this call you don't send X-Organization-Id: the organization doesn't exist yet. The org:provision scope is required.
street, city, postalCode) and dic + ic_dph right away. Without a complete seller address, Peppol sending fails on SK validation rules (BT-35/37/38). More in the Invoices section.ICO=$(printf '%08d' $(( (RANDOM<<15 | RANDOM) % 100000000 )))
DIC=2$(printf '%09d' $(( (RANDOM<<15 | RANDOM) % 1000000000 )))
curl -X POST https://api.efaktura.sk/v1/agent/organizations \
-H "X-API-Key: efk_pk_test_..." \
-H "Content-Type: application/json" \
-d @- <<EOF
{
"name": "Tatra Servis s.r.o.",
"ico": "$ICO",
"dic": "$DIC",
"ic_dph": "SK$DIC",
"address": {
"street": "Hlavná 1",
"city": "Bratislava",
"postalCode": "81101",
"country": "SK"
}
}
EOFThe sandbox is shared by all partners, so the IČO and DIČ are generated at random: sample values may already be used by another partner and the API would return 409. In your own code use your own made-up IČO and DIČ.
Response (201 Created, or 200 if an existing organization was reused):
{
"data": {
"org_id": "0a2c1f3e-…",
"partner_id": "7b8d…",
"slug": "tatra-servis-sro",
"reused": false,
"status": "caka_na_token",
"peppol_sandbox": null
}
}The sandbox behaves like production: it does not register the organization on the Peppol TEST network by itself. Always enroll in step 3. Accounts registered before 02.10.2026 keep a lenient sandbox until 01.11.2026 that registers the organization automatically (the peppol_sandbox field and the SANDBOX_AUTO_ENROLL warning) unless they switch on “Sandbox like production” in the portal.
org_id is the value you send in X-Organization-Id from now on. You can get the organization's detail (including participant_id and peppol_status) via GET /v1/agent/organizations/{org_id}.
Always call enroll when you onboard a client, the same way in the sandbox and in production. It registers the organization's DIČ as a Peppol participant (in the sandbox in the TEST SMP as 9915:<DIČ>). The invoice:send scope and the X-Organization-Id header are required. Send verificationTokenHex: in production it is the Financial Administration verification code (more in Going live), in the sandbox any hex is enough, for example a1b2c3. Without a token the sandbox refuses the enroll like production (the lenient sandbox of older accounts passes with a warning until 01.11.2026). The differences and the test tokens are in Sandbox vs. production.
curl -X POST https://api.efaktura.sk/v1/agent/peppol/enroll \
-H "X-API-Key: efk_pk_test_..." \
-H "X-Organization-Id: 0a2c1f3e-…" \
-H "Content-Type: application/json" \
-d '{ "verificationTokenHex": "a1b2c3" }'{
"data": {
"status": "active",
"registration_id": "…",
"participant_id": "9915:2123456789"
}
}The organization's DIČ must have 10 digits, otherwise enroll returns VALIDATION_ERROR.
You issue an invoice via POST /v1/agent/invoices (scope invoice:create). The seller is derived from the organization, the buyer from customer (or an existing customer_id). For the invoice to be sendable over Peppol, the buyer must have a complete address and (for a company) ico/dic/ic_dph.
curl -X POST https://api.efaktura.sk/v1/agent/invoices \
-H "X-API-Key: efk_pk_test_..." \
-H "X-Organization-Id: 0a2c1f3e-…" \
-H "Content-Type: application/json" \
-d '{
"customer": {
"name": "Customer s.r.o.",
"ico": "87654321",
"dic": "2010101010",
"ic_dph": "SK2010101010",
"address": {
"street": "Obchodná 5",
"city": "Košice",
"postalCode": "04001",
"country": "SK"
},
"email": "fakturacia@odberatel.sk"
},
"items": [
{
"description": "Service work",
"quantity": 2,
"unit": "hod",
"unit_price": 50,
"vat_rate": 23
}
],
"payment_method": "bank_transfer",
"due_days": 14
}'Response (201 Created), then save the id:
{
"id": "b1f0d7a2-…",
"invoice_number": "2026001",
"status": "issued",
"customer": { "id": "…", "name": "Customer s.r.o." },
"items": [ … ],
"subtotal": "100.00",
"vat_total": "23.00",
"total": "123.00",
"currency": "EUR",
"issue_date": "2026-06-22",
"due_date": "2026-07-06",
"pdf_url": "https://…",
"created_at": "2026-06-22T10:00:00.000Z"
}Sending queues the invoice (scope invoice:send). A test key routes the send through the sandbox AP on the real Peppol TEST network, no credits.
curl -X POST https://api.efaktura.sk/v1/agent/peppol/send/b1f0d7a2-… \
-H "X-API-Key: efk_pk_test_..." \
-H "X-Organization-Id: 0a2c1f3e-…"Response (202 Accepted):
{
"data": {
"invoice_id": "b1f0d7a2-…",
"job_id": "peppol-send:b1f0d7a2-…",
"status": "queued"
}
}POST /v1/agent/peppol/send/batch with the body { "invoiceIds": ["…", "…"] } (1–200 IDs).You check transmission status via GET /v1/agent/peppol/status/{invoiceId} (scope invoice:read). The endpoint's actual states are: QUEUED, SENDING, SENT, DEFERRED, ERROR; if nothing has been sent yet, a synthetic not_sent value is returned. (DELIVERED is a webhook event state, not a polling-endpoint state.) Details in Receiving and statuses.
curl https://api.efaktura.sk/v1/agent/peppol/status/b1f0d7a2-… \
-H "X-API-Key: efk_pk_test_..." \
-H "X-Organization-Id: 0a2c1f3e-…"{
"data": {
"invoice_id": "b1f0d7a2-…",
"state": "SENT",
"error_message": null,
"receiver_identifier": "9915:2010101010",
"document_id": "…",
"updated_at": "2026-06-22T10:00:05.000Z"
}
}Instead of polling, we recommend webhooks. Register an HTTPS endpoint in the portal and react to peppol.document.sent / peppol.document.delivered / peppol.document.failed, or to an inbound document via peppol.document.received. Every delivery is HMAC-signed: verify the signature and deduplicate by X-Webhook-Id. The full picture is in Webhooks.
{
"event": "peppol.document.delivered",
"timestamp": "2026-06-22T10:00:08.000Z",
"data": {
"invoiceId": "b1f0d7a2-…",
"invoiceNumber": "2026001",
"documentType": "invoice",
"mode": "test",
"state": "DELIVERED",
"messageId": "…",
"transactionId": "…",
"orgId": "0a2c1f3e-…"
}
}Next steps
Before sending, you can pre-validate your UBL XML (Validation). Once the flow is verified in the sandbox, move on to Going live: production enroll requires a Financial Administration verification token and partner activation.