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.

Certified by PA SK · EFSK000031Own Peppol AP · Seat PSK001128OpenAPI 3.1 + public sandboxNational SAPI-SK interface

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 ◀── sender
Two independent “gates” for every company: receiving: switched on after selecting Verteco on the Financial Administration portal (at that moment we automatically register the company in the central SK SMP and it receives a peppolParticipantId); 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. 1. Create an account at /register (e-mail + password, free, no commitment).
  2. 2. In the dashboard open API tokens (/dashboard/tokens) and create a token. The format is vpt_ + 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 OAuth2 client_id for SAPI-SK.
  3. 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:

bash
curl https://peppol.verteco.digital/api/v1/auth/me -H 'Authorization: Bearer vpt_8f2a…'
# → 200 { "id": "…", "email": "dev@vasa-saas.sk", … }
Key rotation = create a new token and revoke the old one (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.

bash
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)
FieldRules
ico8 digits; required on creation; immutable once set (400 ico_immutable); duplicate → 409 ico_taken
dicVAT 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)
legalNamerequired, max 500 characters; locked after verification by the Financial Administration (400 company_verified_locked)
street / city / postalCodeoptional (max 255 / 128 / 16 characters)
countryoptional, 2 characters, default "SK"
ibanoptional, max 34 characters; pre-filled into invoices created in our web interface
registeredAddressoptional, 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):

bash
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" }
Account limits: by default 100 companies per account and 25 team members; for SaaS partners we raise the limit free of charge as needed, just write to us. Exceeding it returns 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)

  1. Send the customer to vpds.financnasprava.sk (provider selection: Verteco), where they log in with eID (slovensko.sk) and confirm the selection.
  2. The Financial Administration sends us a webhook with a cryptographically signed consent; we verify it automatically.
  3. The company switches to status: "active", receives a peppolParticipantId (format 0245:<tax ID digits>) and we automatically register it in the central SK SMP; from that moment it receives e-invoices.
  4. You do nothing: catch the completion with the company.activated webhook, or by polling GET /companies/{id} until peppolParticipantId is 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:

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

If the customer makes the selection on the FS portal, both steps are completed at once: the webhook from the FS also carries the verification token, so the company is immediately 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):

bash
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 once

On a received invoice we send a POST to your URL (event invoice.received; on a sent one invoice.sent):

http
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):

javascript
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 propertyValue
Successany 2xx response from your side
Retryup to 8 attempts, exponential backoff 30 s → max 1 h, then dead-letter
Timeout15 s per attempt; redirects are not followed
URLa public http(s) address; internal/private IPs are blocked (SSRF guard)
Deduplicationretries send an identical body; deduplicate by documentId
TestPOST /companies/{id}/webhook/test (sends a signed webhook.test event with "test": true)

B · Pull: SAPI-SK receive (alternative or complement)

bash
# 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)

bash
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 expiry

2 · Sending a document (UBL 2.1, Peppol BIS Billing 3.0)

bash
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: the 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)

bash
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 others

4 · 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:

bash
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…'
The Slovak tax data reporting (TDD) for received as well as sent documents is handled automatically, following the schedule on which the Financial Administration makes the production C5 interface available; as a partner you implement nothing extra. A simpler alternative to SAPI for sending: the portal endpoint 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):

AreaEndpoints
Companies (tenants)GET/POST /companies · GET/PUT /companies/{id} · pagination ?page&limit + X-Total-Count
Sending verificationPOST /companies/{id}/verification (verification token)
Pre-fill from the registerGET /lookup/company?ico= → name + address from the RPO
DocumentsGET /companies/{id}/documents?direction=received|sent · GET …/documents/{docId} · POST …/documents/send · POST …/documents/send-test
Downloads and exportsGET …/documents/{docId}/download?format=xml|html|mls · GET …/documents/export.csv · GET …/documents/export.zip (?direction&from&to)
Invoice settingsGET/PUT /companies/{id}/invoice-settings: numbering ({YYYY}{NNNN}), due date, default IBAN and note
Customer address bookGET/POST /companies/{id}/address-book · DELETE …/address-book/{entryId} (auto-saved after a successful send)
Notifications and webhooksGET/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 modePUT /companies/{id} with { "whiteLabel": true }; the platform sends the tenant no e-mails of its own
TeamGET /account/team · POST /account/team/invite · POST /account/team/accept
API keysGET/POST /tokens · DELETE /tokens/{id}
Documents (SAPI-SK)POST /sapi/document/send · POST …/batch · GET …/receive (+detail, acknowledge) · GET …/sent
The only thing you cannot do on the tenant’s behalf is the one-off provider selection on the Financial Administration portal (eID or PFS portal credentials). According to the FS interpretation it is a condition of receiving that applies equally to direct customers; for sending, the tenant obtains the verification token through it. Everything else runs programmatically. Optional bonus: after the selection at the FS the tenant receives a magic link to our portal; they may, but never have to, use it.

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. 1 · Switch on white-label mode for the company

    With whiteLabel: true the 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.

    bash
    curl -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 cleared
  2. 2 · Handle notifications with webhooks, not our e-mails

    In white-label mode the platform sends no invoice e-mails at all (the notificationEmail field is ignored). Set a webhook and you learn about a received invoice via invoice.received. You download the content via the API (XML / printable HTML) and send the customer your own e-mail from your own domain.

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

    http
    POST 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 the status field in the body (we send it only for genuinely activated companies; a company suspended by an administrator does not receive it).

Important order: create the company via the API before you send the customer to the selection on the FS portal. If the selection happened for a tax ID that does not yet exist with us, the system would create the company automatically as self-service, including a welcome e-mail to the customer (outside white-label mode). When the order “company first, then selection” is observed, no e-mail of ours goes to the tenant.

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

RequirementHow 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 providerselection 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 protectioninvoice 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 deliveryMLS 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 isolationaccess 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 27001no 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.

bash
# 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:

bash
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:

bash
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)
Public endpoints have a stricter rate limit (15 calls/min per IP); they are intended for individual checks, not bulk scanning. In an integration, call them before sending; the results of positive checks are cached briefly.

Robustness

Rate limits, error responses and size limits

Rate limits (fixed 60-second window)

ScopeLimitKey
/api/v1/** (portal) and /sapi/document/*dynamic, with a large marginper API token
/sapi/auth/* and /api/v1/auth/*stricter (anti brute-force)per IP address
/api/v1/public/** (checker, validator)stricterper 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/**)

jsonc
{ "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/**)

json
{ "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 codeHTTPMeaning
SAPI-AUTH-001401invalid / revoked client credentials
SAPI-AUTH-002401missing / invalid / expired Bearer token
SAPI-AUTH-003403 / 401the 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 / 002400invalid fields / missing X-Peppol-Participant-Id header
SAPI-RES-001 / 002404the document does not exist / the original payload is not archived
SAPI-PROC-001502temporary failure (retryable: true); retry with the same Idempotency-Key
SAPI-PROC-002503temporary infrastructure error (retryable: true); retry with the same Idempotency-Key
SAPI-PROC-500500unexpected error (retryable: false); do not retry, report the correlation_id to us

Size limits

WhereLimit
SAPI payload (send / batch item)10 MB per document; batch max 100 items
Portal send (xml field)2 MB
Public validator3 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.

During the voluntary period (until 1 January 2027) everything is free of charge; prices according to the price list apply from the mandate. Do not want to integrate yourselves? We will carry out a managed connection of your system, priced according to scope, excluding VAT, one-off. One thing cannot be transferred: every company must have a verified verification token with us before sending (fail-closed gate according to the FS recommendation, § 76a para. 2 of Act No. 222/2004) and the provider selection on the FS portal is made by the company itself (eID or PFS portal credentials).

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.