For SaaS, ERP and platforms
Embed Peppol e-invoicing into your product
You integrate once and serve an unlimited number of your customers. Our own certified Slovak Peppol Access Point, REST API, signed webhooks, a public sandbox and white-label included. This page is the complete technical guide: from registration through company onboarding to sending and receiving documents.
Architecture
Integration model: one token, N of your customers
The model is proxy / multi-tenant: your backend holds one API token (vpt_…) exclusively on the server, and each of your customers is one company (tenant) with us. All calls are made by your server. The token never belongs in a browser or a mobile app. Which company a call serves is set for documents by the X-Peppol-Participant-Id header, and for company management directly by the company ID in the path.
1 · Portal REST API
Tenant management: company creation, sending verification, webhooks, document lists, downloads of XML/PDF/receipts, exports. Auth: Authorization: Bearer vpt_… directly.
2 · SAPI-SK 1.0
The national standardised interface for the documents themselves: sending (including batches), receiving, delivery status. Auth: OAuth2 client_credentials (client_id = the token's UUID, client_secret = the vpt_… token).
3 · Webhooks to you
On a received / sent invoice we call your URL with an HMAC-signed POST, with retries and a dead-letter queue (per tenant, with its own secret).
your SaaS (backend) Verteco Peppol AP world
─────────────────── ───────────────── ─────
POST /api/v1/companies ──────────────▶ tenant created
customer: selection on the FS portal (eID) ─▶ Slovak Financial Administration
◀── FS webhook: company active + SMP reg.
POST /sapi/document/send ─────────────▶ validation → AS4 ─────────────────────▶ Peppol network (recipient)
◀── MLS receipt (delivered/rejected)
◀── webhook invoice.received ────────── invoice received from the Peppol network ◀── senderpeppolParticipantId); sending: after verification of the verification token (the company is sending-verified). Sending is fail-closed with us (the Financial Administration’s recommendation to providers, § 76a para. 2 of the VAT Act): without a verified verification token the API returns 403 sending_not_verified. Details in step 2.Step 0
Account and API key: registering for our endpoints
- 1. Create an account at /register (e-mail + password, free, no commitment).
- 2. In the dashboard open API tokens (
/dashboard/tokens) and create a token. The format isvpt_+ 40 hex characters; the plaintext is shown only once on creation (we store only the SHA-256 hash). Along with the token, note down its UUID as well. It also serves as the OAuth2client_idfor SAPI-SK. - 3. The token has the same access as your account: it works for all companies you manage (multi-tenant without additional keys). Revoking the token in the portal immediately stops portal calls as well as the issuing of new SAPI tokens.
Verifying that the key works:
curl https://peppol.verteco.digital/api/v1/auth/me -H 'Authorization: Bearer vpt_8f2a…'
# → 200 { "id": "…", "email": "dev@vasa-saas.sk", … }POST /api/v1/tokens → DELETE /api/v1/tokens/{id}). SAPI access tokens already issued run until they expire (max 15 minutes).Step 1
Customer onboarding = creating a company via the API
Create a company for each customer with a single call. Store the returned id with your tenant. It is the key for all further calls.
curl -X POST https://peppol.verteco.digital/api/v1/companies \
-H 'Authorization: Bearer vpt_8f2a…' \
-H 'Content-Type: application/json' \
-d '{
"ico": "12345678",
"dic": "SK1234567890",
"legalName": "Customer Company s.r.o.",
"street": "Example Street 12",
"postalCode": "010 01",
"city": "Žilina",
"iban": "SK31 1200 0000 1987 4263 7541"
}'
# → 201 { "id": "9fa8fd81-…", "status": "pending_verification", "peppolParticipantId": null, … }
# (the IČO/DIČ above are examples; use the customer's real data)| Field | Rules |
|---|---|
| ico | 8 digits; required on creation; immutable once set (400 ico_immutable); duplicate → 409 ico_taken |
| dic | VAT ID / tax ID in the format SK + 10 digits (for non-VAT payers "SK" + tax ID); required; immutable once set (it is the company's Peppol identity) |
| legalName | required, max 500 characters; locked after verification by the Financial Administration (400 company_verified_locked) |
| street / city / postalCode | optional (max 255 / 128 / 16 characters) |
| country | optional, 2 characters, default "SK" |
| iban | optional, max 34 characters; pre-filled into invoices created in our web interface |
| registeredAddress | optional, max 1000 characters (an alternative to the structured address) |
Tip: pre-fill the company data from the Register of Legal Entities (our own onboarding form uses the same API):
curl "https://peppol.verteco.digital/api/v1/lookup/company?ico=53412834" -H 'Authorization: Bearer vpt_8f2a…'
# → 200 { "name": "Verteco digital services, s. r. o.",
# "street": "Daniela Dlabača 21", "city": "Žilina", "zip": "010 01" }409 company_limit. Team members (e.g. support colleagues) are added via POST /api/v1/account/team/invite. Paginate lists: GET /companies?page=0&limit=100 (headers X-Total-Count, X-Total-Pages).Step 2
Activation at the Financial Administration: receiving and sending
pending_verification ≠ live on Peppol. According to the Financial Administration’s interpretation (e-invoice FAQ, examples 36 and 73), the company chooses its provider for receiving itself on the FS portal (one receiving provider per tax ID); for sending, the selection serves to obtain the verification token and the company may send through several providers. These are two independent steps:
Receiving invoices → status active
The customer selects Verteco on the FS portal (one-off, logging in with eID or PFS portal credentials)
- Send the customer to vpds.financnasprava.sk (provider selection: Verteco), where they log in with eID (slovensko.sk) and confirm the selection.
- The Financial Administration sends us a webhook with a cryptographically signed consent; we verify it automatically.
- The company switches to
status: "active", receives apeppolParticipantId(format0245:<tax ID digits>) and we automatically register it in the central SK SMP; from that moment it receives e-invoices. - You do nothing: catch the completion with the
company.activatedwebhook, or by pollingGET /companies/{id}untilpeppolParticipantIdis populated (it is set exclusively through this path).
Sending → sending-verified
Supply the verification token via the API
The verification token (Verifikačný údaj, VÚ) is a signed string the company receives from the Financial Administration upon selecting a provider (by e-mail / in the PDS portal). The customer enters it in your UI and you supply it in a single call; we verify the FS RSA signature:
curl -X POST https://peppol.verteco.digital/api/v1/companies/{id}/verification \
-H 'Authorization: Bearer vpt_8f2a…' \
-H 'Content-Type: application/json' \
-d '{"token": "<verification token, 1024 hex characters>"}'
# → 200 { "companyId": "…", "status": "active",
# "sendingVerified": true,
# "verificationMethod": "self", "verifiedAt": "…" }Invalid signature → 400 token_invalid. Missing tax ID → 400 company_dic_missing.
active as well as sending-verified and registered for receiving. Supplying the verification token via the API on its own unlocks sending only; registration for receiving (SMP) is triggered only by the selection on the FS portal. Recommended flow for SaaS: create the company via the API → send the customer to the selection at the FS → done.Step 3
Receiving invoices: HMAC-signed webhooks or pull via the API
A · Push: webhook per tenant (recommended)
Set a webhook URL for every company (and optionally a notification e-mail) and collect the signing secret (it is returned only once):
curl -X PUT https://peppol.verteco.digital/api/v1/companies/{id}/notifications \
-H 'Authorization: Bearer vpt_8f2a…' -H 'Content-Type: application/json' \
-d '{"webhookUrl": "https://vasa-saas.sk/peppol/webhook", "notificationEmail": "zakaznik@firma.sk"}'
curl -X POST https://peppol.verteco.digital/api/v1/companies/{id}/webhook/secret -H 'Authorization: Bearer vpt_8f2a…'
# → 200 { "secret": "vpt_…" } ← store it, shown only onceOn a received invoice we send a POST to your URL (event invoice.received; on a sent one invoice.sent):
POST https://vasa-saas.sk/peppol/webhook
Content-Type: application/json
X-Verteco-Event: invoice.received
X-Verteco-Signature: sha256=3f1a9c… ← HMAC-SHA256 over the raw body
{
"event": "invoice.received",
"companyId": "9fa8fd81-…",
"companyDic": "SK2121358349",
"peppolParticipantId": "0245:2121358349",
"documentId": "574d4e52-…",
"invoiceNumber": "2027-0142",
"senderId": "0245:2120433843",
"supplierName": "Supplier s.r.o.",
"receiverId": "0245:2121358349",
"issueDate": "2027-01-15",
"dueDate": "2027-01-29",
"deliveryDate": "2027-01-15",
"currency": "EUR",
"totalAmount": "1234.56",
"peppolMessageId": "a0b1c2d3-…"
}You set the partner signing key yourselves (intermediaries): PUT /api/v1/resellers/me/notification-webhook with the body { "url": "https://…" } stores the URL to which we will send the events of all your customer companies and, on first set-up, returns the signing key (secret); it is shown only once. Viewing it later requires the account password (in the portal in the company detail, or POST …/notification-webhook/reveal with the body { "password": "…" }); every viewing is written to the security log with IP and time, and you see the latest one in the GET response. Authentication is by your portal account (session or Bearer token); we never send the key by e-mail. That a company is an intermediary is visible in the API on its record as reseller: true (the whiteLabel flag marks only the companies of your customers).
Signature verification (Node.js):
import crypto from 'node:crypto';
function verifyVertecoWebhook(rawBody, signatureHeader, secret) {
// signature = "sha256=" + hex( HMAC-SHA256(secret, raw request body) )
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return signatureHeader?.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}
// Important: compute the HMAC over the RAW body (raw bytes), not over re-serialised JSON.| Delivery property | Value |
|---|---|
| Success | any 2xx response from your side |
| Retry | up to 8 attempts, exponential backoff 30 s → max 1 h, then dead-letter |
| Timeout | 15 s per attempt; redirects are not followed |
| URL | a public http(s) address; internal/private IPs are blocked (SSRF guard) |
| Deduplication | retries send an identical body; deduplicate by documentId |
| Test | POST /companies/{id}/webhook/test (sends a signed webhook.test event with "test": true) |
B · Pull: SAPI-SK receive (alternative or complement)
# list of received documents (incrementally via ?since=)
curl "https://peppol.verteco.digital/sapi/document/receive?since=2027-01-01T00:00:00Z&limit=200" \
-H 'Authorization: Bearer <sapi_access_token>' \
-H 'X-Peppol-Participant-Id: 0245:2121358349'
# → { "documents": [ { "documentId": "…", "documentTypeId": "…",
# "senderParticipantId": "…", "receiverParticipantId": "…", … } ],
# "nextPageToken": "50" }
# detail including the original XML exactly as it arrived from the network
curl https://peppol.verteco.digital/sapi/document/receive/{documentId} \
-H 'Authorization: Bearer <sapi_access_token>' -H 'X-Peppol-Participant-Id: 0245:2121358349'
# → { "metadata": { … }, "payload": "<Invoice …>", "payloadFormat": "XML" }
# acknowledge processing (idempotent)
curl -X POST https://peppol.verteco.digital/sapi/document/receive/{documentId}/acknowledge \
-H 'Authorization: Bearer <sapi_access_token>' -H 'X-Peppol-Participant-Id: 0245:2121358349'You can also reach the documents through the portal API: GET /companies/{id}/documents?direction=received, downloads …/documents/{docId}/download?format=xml|html|mls, bulk exports …/documents/export.csv and …/documents/export.zip (original XML). The e-mail notification to the company (if you enable it) carries the invoice as PDF + original XML attachments, the sender bears the company’s name and Reply-To points to the account owner (behaviour prepared for white-label).
Step 4
Sending invoices via SAPI-SK: OAuth2, idempotency, receipts
1 · Token exchange (OAuth2 client_credentials)
curl -X POST https://peppol.verteco.digital/sapi/auth/token -H 'Content-Type: application/json' \
-d '{
"grant_type": "client_credentials",
"client_id": "<UUID of your API token>",
"client_secret": "vpt_8f2a…"
}'
# → 200 { "access_token": "eyJ…", "token_type": "Bearer",
# "expires_in": 900, "refresh_token": "eyJ…" }
# the access token is valid for 15 min (renewal via POST /sapi/auth/renew, refresh 30 days);
# GET /sapi/auth/token/status returns should_refresh: true < 3 min before expiry2 · Sending a document (UBL 2.1, Peppol BIS Billing 3.0)
curl -X POST https://peppol.verteco.digital/sapi/document/send \
-H 'Authorization: Bearer <sapi_access_token>' \
-H 'X-Peppol-Participant-Id: 0245:2121358349' \
-H 'Idempotency-Key: fa-2027-0142' \
-H 'Content-Type: application/json' \
-d '{
"metadata": {
"documentId": "FA-2027-0142",
"documentTypeId": "busdox-docid-qns::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",
"processId": "cenbii-procid-ubl::urn:fdc:peppol.eu:2017:poacc:billing:01:1.0",
"senderParticipantId": "0245:2121358349",
"receiverParticipantId": "0245:2120433843"
},
"payload": "<?xml version=\"1.0\"?><Invoice …>…</Invoice>",
"payloadFormat": "XML",
"checksum": "<optional: SHA-256 hex of the payload>"
}'
# → 202 { "providerDocumentId": "<UUID with us>", "status": "ACCEPTED", "receivedAt": "…", "timestamp": "…" }
# on "REJECTED" (failed validation / rejected by the network) the response also carries
# a "detail" field with the specific reason (e.g. violated rules BR-CO-15, …)Idempotency-Key header (we recommend the invoice number or an internal ID) protects against duplicate sending: a repeated call with the same key returns the original result, while a new key for the same invoice may send it twice. A send in progress with the same key → 502 SAPI-PROC-001 (retryable: true, try again shortly); after a transport failure (failed) a retry with the same key really sends the document again. The same key also sends the document again when our pre-send check rejected it (REJECTED, nothing left for the network) or when it ended as undeliverable (after correcting the recipient identifier or after their registration); the procedure, including the issue date of the corrected invoice, is in the developer documentation. Payload limit: 10 MB. Before sending we validate documents with the same EN 16931 + Peppol BIS ruleset as on receipt; an invalid document returns REJECTED with the specific rules (e.g. BR-CO-15).3 · Batch sending (up to 100 documents)
curl -X POST https://peppol.verteco.digital/sapi/document/batch \
-H 'Authorization: Bearer <sapi_access_token>' -H 'X-Peppol-Participant-Id: 0245:2121358349' \
-H 'Content-Type: application/json' \
-d '{ "documents": [
{ "itemId": "fa-2027-0142", "idempotencyKey": "fa-2027-0142", "metadata": { … }, "payload": "…", "payloadFormat": "XML" },
…
] }'
# (schematic example: metadata has the same fields as a single send)
# → 202 { "total": 100, "accepted": 98, "rejected": 1, "failed": 1,
# "results": [ { "itemId": "…", "ok": true, "providerDocumentId": "…", "status": "ACCEPTED" }, … ] }
# items are processed sequentially; the failure of one does not affect the others4 · Delivery status and receipts (MLS)
Lifecycle of a sent document: pending → submitted → delivered / rejected (confirmed by the MLS receipt from the recipient’s Access Point) or failed (transport failure; retry with the same Idempotency-Key), or undeliverable (the recipient has no address in the Peppol network: we have taken over the document and will report it to the Financial Administration, it is delivered to no one). Track the statuses incrementally:
curl "https://peppol.verteco.digital/sapi/document/sent?since=2027-01-15T00:00:00Z" \
-H 'Authorization: Bearer <sapi_access_token>' -H 'X-Peppol-Participant-Id: 0245:2121358349'
# → { "documents": [ { "documentId": "574d4e52-…", "peppolMessageId": "a0b1c2d3-…",
# "status": "delivered", "statusDateTime": "…", … } ], … }
# documentId = providerDocumentId from the send response (UUID with us); match by it
# or by peppolMessageId; on "rejected" the row also carries statusDetail with the reason.
# ?since= also captures STATUS CHANGES (not only new documents); ideal for polling every few minutes
# receipt (MLS ApplicationResponse XML), the legal proof of delivery:
curl "https://peppol.verteco.digital/api/v1/companies/{id}/documents/{documentId}/download?format=mls" \
-H 'Authorization: Bearer vpt_8f2a…'POST /companies/{id}/documents/send with the body { "xml": "…", "receiverParticipantId": "0245:…" } (beta: the response shape may still change).Full parity
Everything a direct customer has, you can manage via the API
Your tenant never has to open our portal: every function a direct customer has in the dashboard exists as a REST endpoint under your token. Overview of the whole management surface (all Authorization: Bearer vpt_…, base /api/v1):
| Area | Endpoints |
|---|---|
| Companies (tenants) | GET/POST /companies · GET/PUT /companies/{id} · pagination ?page&limit + X-Total-Count |
| Sending verification | POST /companies/{id}/verification (verification token) |
| Pre-fill from the register | GET /lookup/company?ico= → name + address from the RPO |
| Documents | GET /companies/{id}/documents?direction=received|sent · GET …/documents/{docId} · POST …/documents/send · POST …/documents/send-test |
| Downloads and exports | GET …/documents/{docId}/download?format=xml|html|mls · GET …/documents/export.csv · GET …/documents/export.zip (?direction&from&to) |
| Invoice settings | GET/PUT /companies/{id}/invoice-settings: numbering ({YYYY}{NNNN}), due date, default IBAN and note |
| Customer address book | GET/POST /companies/{id}/address-book · DELETE …/address-book/{entryId} (auto-saved after a successful send) |
| Notifications and webhooks | GET/PUT /companies/{id}/notifications · POST …/webhook/secret · POST …/webhook/test · events invoice.received / invoice.sent / invoice.delivered / invoice.rejected / company.activated / company.deactivated / company.smp_registered_elsewhere |
| White-label mode | PUT /companies/{id} with { "whiteLabel": true }; the platform sends the tenant no e-mails of its own |
| Team | GET /account/team · POST /account/team/invite · POST /account/team/accept |
| API keys | GET/POST /tokens · DELETE /tokens/{id} |
| Documents (SAPI-SK) | POST /sapi/document/send · POST …/batch · GET …/receive (+detail, acknowledge) · GET …/sent |
Invisible infrastructure
White-label operation: the tenant communicates only with you
Target state: your customer uses your product, your brand and your support; we are the invisible infrastructure. Three steps that ensure it:
1 · Switch on white-label mode for the company
With
whiteLabel: truethe platform sends the tenant no e-mails of its own (e.g. confirmation of the provider selection) and does not take over their contact address from the selection at the FS. You handle the communication, under your own brand.bashcurl -X PUT https://peppol.verteco.digital/api/v1/companies/{id} -H 'Authorization: Bearer vpt_8f2a…' \ -H 'Content-Type: application/json' \ -d '{"ico":"12345678","dic":"SK1234567890","legalName":"Customer Company s.r.o.", "street":"Example Street 12","postalCode":"010 01","city":"Žilina", "iban":"SK31 1200 0000 1987 4263 7541","whiteLabel":true}' # the simplest is to send whiteLabel already with POST /companies (company creation); # NOTE: PUT is a full update; send all optional fields (address, IBAN), # omitted ones are cleared2 · Handle notifications with webhooks, not our e-mails
In white-label mode the platform sends no invoice e-mails at all (the
notificationEmailfield is ignored). Set a webhook and you learn about a received invoice viainvoice.received. You download the content via the API (XML / printable HTML) and send the customer your own e-mail from your own domain.3 · Catch the completion of the selection at the FS with the company.activated webhook
When the customer completes the provider selection on the Financial Administration portal, we send an event to the company’s webhook (no polling needed):
httpPOST https://vasa-saas.sk/peppol/webhook X-Verteco-Event: company.activated X-Verteco-Signature: sha256=… { "event": "company.activated", "companyId": "9fa8fd81-…", "companyDic": "SK2121358349", "peppolParticipantId": "0245:2121358349", "status": "active", "verifiedAt": "2027-01-05T09:12:33Z" }The event means: selection cryptographically verified, company switched to
status: "active", SK SMP registration submitted; the tenant is ready to receive as well as send (the verification token arrived together with the selection). The event is at-least-once; process it idempotently and always follow thestatusfield in the body (we send it only for genuinely activated companies; a company suspended by an administrator does not receive it).
What the tenant sees and what they do not (honestly)
Invisible to the tenant
- · the whole API, portal, dashboard (the tenant never needs them)
- · e-mails: in white-label mode none are sent by us
- · the technical identity in the network (our Access Point certificate); only other Access Points see it
The step that remains with the company
- · the one-off provider selection on the Financial Administration portal: there the customer selects “Verteco digital services” from the state list of certified providers. This applies equally to every provider on the market; for receiving it is, according to the Financial Administration’s interpretation, an obligation, for sending the company thereby obtains the verification token (FS FAQ, example 73).
- · the verification token from the FS, which the company supplies to us (through you) before sending
Even this last window can be rebranded: the Financial Administration allows certified providers to register partners as intermediaries of the delivery service. After the entry, the customer on vpds.financnasprava.sk selects your brand directly from the list. You generate the application for entry in a few minutes at /sprostredkovatel.
Security and statutory requirements
| Requirement | How it is met |
|---|---|
| Authorisation to send (§ 76a para. 2 point (b) of Act No. 222/2004) | fail-closed gate: without a cryptographically verified verification token from the FS every send returns 403; it cannot be bypassed via the API or via batch |
| The company's consent to the provider | selection on the FS portal (login with eID or PFS portal credentials); the FS delivers it to us signed (RSA-PSS); we verify the signature, not the claim |
| Personal data protection | invoice data in the EU (Frankfurt); processing under the terms and conditions and the privacy policy (links below); webhooks signed with HMAC, secrets cannot be read back |
| Proof of delivery | MLS receipt (ApplicationResponse) received over the verified AS4 channel from the recipient's Access Point, downloadable via the API as the legal proof of delivery |
| Tax data reporting (TDD) | handled automatically following the schedule on which the FS C5 interface is made available; the partner implements nothing |
| Tenant isolation | access only through company membership; the API token has strictly the same rights as your account; SAPI checks that the sender matches the authorised participant |
| ISO/IEC 27001 | no provider has to hold the certificate yet (OpenPeppol requires it from everyone from 1 October 2027); our ISO/IEC 27001:2022 certification is under way with the accredited body SKQS, s. r. o., Žilina (SNAS), chosen in September 2026; we plan the certificate by 1 July 2027 |
Legal documents: terms and conditions · personal data protection. You as the partner have the contractual relationship with us; towards your customers you act under your own terms.
Development and testing
Sandbox: public, no registration
You can try the whole SAPI-SK contract immediately with the public credentials client_id = sandbox / client_secret = sandbox. The sandbox validates requests exactly like production, but never delivers anything and sees no real data; the isolation is enforced directly in the signed token.
# 1 · sandbox token
curl -X POST https://peppol.verteco.digital/sapi/auth/token -H 'Content-Type: application/json' \
-d '{"grant_type":"client_credentials","client_id":"sandbox","client_secret":"sandbox"}'
# 2 · mock send: full contract validation, nothing is delivered
curl -X POST https://peppol.verteco.digital/sapi/document/send \
-H 'Authorization: Bearer <sandbox_token>' -H 'X-Peppol-Participant-Id: 0245:0000000000' \
-H 'Content-Type: application/json' \
-d '{"metadata":{"documentId":"TEST-1","documentTypeId":"…","senderParticipantId":"0088:sandbox-sender","receiverParticipantId":"0088:sandbox-receiver"},"payload":"<Invoice/>","payloadFormat":"XML"}'
# → 202 { "providerDocumentId": "sandbox-…", "status": "ACCEPTED", … }
# 3 · sample received document
curl "https://peppol.verteco.digital/sapi/document/receive" -H 'Authorization: Bearer <sandbox_token>' -H 'X-Peppol-Participant-Id: 0245:0000000000'
curl "https://peppol.verteco.digital/sapi/document/receive/sandbox-doc-0001" -H 'Authorization: Bearer <sandbox_token>' -H 'X-Peppol-Participant-Id: 0245:0000000000'Complements to the sandbox: the public e-invoice validator (same rules as the production AP), the test webhook (POST /companies/{id}/webhook/test) and a test invoice over the live network to your own company (POST /companies/{id}/documents/send-test).
No authentication
Public API: recipient check and document validation
Recipient check in the Peppol network
Before sending, check whether the recipient is registered (live SML/SMP lookup). It accepts an SK VAT ID, a tax ID and a full Peppol ID:
curl "https://peppol.verteco.digital/api/v1/public/peppol-check?id=SK2121358349"
# → { "registered": true, "participantId": "0245:2121358349",
# "smp": "…", "capabilities": ["Faktúra (BIS Billing)", …],
# "lastCheckedAt": "…" }E-invoice validation (EN 16931 + Peppol BIS)
The same ruleset every document passes through in our Access Point, ideal for CI or for developing your mapping:
curl -X POST "https://peppol.verteco.digital/api/v1/public/peppol-validate" \
-H 'Content-Type: application/xml' --data-binary @invoice.xml
# → { "valid": false,
# "errors": ["BR-CO-15: Invoice total amount with VAT …"],
# "warnings": [] } (limit 3 MB)Robustness
Rate limits, error responses and size limits
Rate limits (fixed 60-second window)
| Scope | Limit | Key |
|---|---|---|
| /api/v1/** (portal) and /sapi/document/* | dynamic, with a large margin | per API token |
| /sapi/auth/* and /api/v1/auth/* | stricter (anti brute-force) | per IP address |
| /api/v1/public/** (checker, validator) | stricter | per IP address |
Every response carries the headers RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset; on exceeding, a 429 arrives with a Retry-After header and the body { "error": "rate_limited", "message": "Too many requests. Slow down." }. (Exception: /auth/me and /auth/logout go in the regular per-token bucket.) Always read the current limit values from the headers; they are set so that normal operation, including peaks, never reaches them. For bulk onboarding / batch sending, pace your calls with backoff on 429. Need a higher limit? Write to us.
Two error shapes
Portal API (/api/v1/**)
{ "error": "ico_taken",
"message": "A company with this IČO already exists" }
// additionally on field validation:
{ "error": "validation_failed", "message": "Some fields are invalid",
"fields": { "dic": "IČ DPH musí byť 'SK' a 10 číslic" } }SAPI-SK (/sapi/**)
{ "error": {
"category": "AUTH",
"code": "SAPI-AUTH-003",
"message": "Sending is not enabled for this participant: a verified Verifikačný údaj is required.",
"retryable": false,
"correlation_id": "b4191dfc-…"
} }| SAPI code | HTTP | Meaning |
|---|---|---|
| SAPI-AUTH-001 | 401 | invalid / revoked client credentials |
| SAPI-AUTH-002 | 401 | missing / invalid / expired Bearer token |
| SAPI-AUTH-003 | 403 / 401 | the company for the participant does not exist, you have no access to it, the sender does not match, a verified verification token is missing; or (for /auth/token and /auth/renew, 401) the calling IP is not on the API key's allowlist set in the portal |
| SAPI-VAL-001 / 002 | 400 | invalid fields / missing X-Peppol-Participant-Id header |
| SAPI-RES-001 / 002 | 404 | the document does not exist / the original payload is not archived |
| SAPI-PROC-001 | 502 | temporary failure (retryable: true); retry with the same Idempotency-Key |
| SAPI-PROC-002 | 503 | temporary infrastructure error (retryable: true); retry with the same Idempotency-Key |
| SAPI-PROC-500 | 500 | unexpected error (retryable: false); do not retry, report the correlation_id to us |
Size limits
| Where | Limit |
|---|---|
| SAPI payload (send / batch item) | 10 MB per document; batch max 100 items |
| Portal send (xml field) | 2 MB |
| Public validator | 3 MB |
| Lists (receive / sent / documents) | limit max 200 per page, default 50 |
Business model
Resale and white-label: how it adds up
Receiving without the archive free of charge
A company with the Data archive switched off receives e-invoices free of charge up to 1,000 invoices a month (sent and received together), including the tax data reporting; document content is deleted with us 14 days after delivery. Companies under your brand (white-label) are created without the archive.
Sending and Data archive from €2/month
You pay for each company ID (IČO) that sent an invoice in the month or had the Data archive switched on (the default for new companies in the portal): €2 a month excluding VAT, deducted from your prepaid credit always on the 1st day of the month for the month just ended (first deduction on 1 February 2027 for January 2027). The price includes the archive of originals in the EU (1 GB), REST API, webhooks, connectors, several company IDs, B2G and white-label.
€0.01 above the limit
Every invoice (received or sent) above 1,000 invoices a month (sent and received together); no hard cap, we never block delivery.
Your margin is your business
You invoice your customers yourself, according to your own price list. You pay us according to the public price list, with no hidden partner fees.
White-label included
The white-label solution is part of the package: invoice e-mails bear the customer company's name, your UI is yours.
Volume agreement
For dozens and hundreds of companies we will prepare a volume agreement and raise the account limits free of charge. Write to us.
Go-live
Pre-launch checklist
- ✓Account + API token created, token stored securely on the server (never in the frontend).
- ✓Onboarding flow: POST /companies + redirecting the customer to the provider selection on the FS portal (eID).
- ✓Status polling: you track peppolParticipantId (the signal that the company receives) and the verified flag + status active before the first send.
- ✓Webhook endpoint: verifies X-Verteco-Signature (HMAC-SHA256 over the raw body), responds 2xx within 15 s, deduplicates by documentId.
- ✓Sending: Idempotency-Key on every send, backoff on 429 and SAPI-PROC-001, polling /sapi/document/sent?since= for delivery statuses.
- ✓Document mapping tested via the public validator and the sandbox; first live test via send-test.
- ✓Error states handled: 403 sending_not_verified, 409 ico_taken, REJECTED verdict with reason.
- ✓Account limits raised according to the number of companies (write to us at >10 companies).
Partner questions
Frequently asked questions from SaaS and platforms
- Does every one of our customers need an account in your portal?
- No. The model is a proxy: your backend holds a single API token, and you create and manage your customers' companies via the API within your own account. The customer takes exactly one step outside your product: a one-off provider selection on the Financial Administration portal (eID or PFS portal credentials). According to the Financial Administration's interpretation, the selection is mandatory for receiving; for sending, the company obtains through it the verification token (Verifikačný údaj) that we require before sending. A portal account with us is an optional bonus for the customer, not a condition.
- How do we find out that a customer has completed the selection at the Financial Administration?
- The simplest way: set a webhook for the company and listen for the company.activated event. We send it immediately after the cryptographic verification of the selection, together with the assigned peppolParticipantId. Alternatively, poll GET /api/v1/companies/{id}, where the reliable signal of a completed selection (and therefore of the ability to receive) is a populated peppolParticipantId field (it is set exclusively after the selection on the FS portal). The active status alone is not enough: it is also set when the verification token is supplied via the API, which unlocks sending only.
- Can we sell the service under our own brand (white-label)?
- Yes. The white-label solution is part of the Sending and receiving package, with no separate partner fees. You set whiteLabel: true on the company and the platform sends it no e-mails of its own; you consume notifications via webhooks and communicate under your own brand. You set the price for your customers and invoice them yourself; you pay us according to the public price list.
- How do we test the integration without real documents?
- Three tools: the public SAPI sandbox (client_id and client_secret = "sandbox") with full contract validation, which never delivers anything; the public e-invoice validator (POST /api/v1/public/peppol-validate) with the same EN 16931 + Peppol BIS rules as the production Access Point; and the test webhook (POST /companies/{id}/webhook/test), which sends a signed sample event to your URL.
- What if a customer sends thousands of invoices a month?
- No hard cap: above 1,000 invoices a month (sent and received together), every further invoice costs €0.01. The API supports batch sending (up to 100 documents per call) and incremental downloads via the since parameter. For large volumes of companies or documents we will prepare a volume agreement.
- Which document types can you receive and send?
- Peppol BIS Billing 3.0 invoices and credit notes (UBL 2.1) including the SK Billing rules, self-billing invoices and credit notes, and MLS receipts. The Slovak tax data reporting (TDD) for the documents is handled automatically, following the schedule on which the Financial Administration makes its interface available.
- Is the API versioned and stable?
- Yes. The machine-readable OpenAPI 3.1 specification is at /api/v1/openapi.json and covers both the portal API and SAPI-SK. SAPI-SK is a national standardised interface, not a proprietary contract, so the integration is not vendor lock-in. Changes are made in a backward-compatible way.
Start with the sandbox today · a live account takes a minute
Related: complete API documentation · OpenAPI 3.1 · ready-made e-shop plugins · for accountants · price list