Documentation

For developers: API reference

The portal REST API and the national SAPI-SK 1.0 interface: authentication, companies, documents, webhooks, errors and limits.

Introduction

The Verteco API is REST over HTTPS. Both request and response are JSON (UTF-8). Start by creating an account, then an API token, and make your first call within a minute. All paths are relative to the base URL below.

The complete test environment (sandbox) is available at test.peppol.verteco.digital. It behaves the same as production and additionally offers test tools (webhook of the Financial Administration of the Slovak Republic (Finančná správa, FS), verification token (Verifikačný údaj), company removal) and a simulated provider selection instead of the FS portal (VPDS).

Base URL
https://peppol.verteco.digital/api/v1
Version
v1 (in the path). Backward-incompatible changes will ship under a new version.
Protocol
HTTPS only · TLS 1.2+ · TLS A+
Format
application/json (UTF-8)
Auth
cookie session (portal) or Bearer token (server-to-server)
Timestamps
ISO-8601 (UTC), e.g. 2026-06-03T09:40:27Z

Certified AP

A real Peppol Access Point (Seat ID PSK001128), not a reseller. We passed OpenPeppol conformance (19/19) and are in production.

Valid documents

Server-side EN 16931 + Peppol BIS 3.0 Schematron validation of every received AND sent document. You can also validate a document in advance: the public validator runs at /validator (and as an API at POST /api/v1/public/peppol-validate); errors are returned with the specific rule (e.g. BR-CO-15). Validated samples for Slovak scenarios (non-VAT payer, foreign currency, reverse charge, credit note, advances, embedded PDF) are at /en/vzory.

Multi-tenant

One account and one token for N companies, ideal for SaaS, ERP and accountants.

SK TDD automatically

We generate and send the tax report (corner-5 / TDD) to the Financial Administration (FS) on your behalf.

Machine-readable interface (OpenAPI 3.1): /api/v1/openapi.json. Import it into Postman, open it in the Swagger Editor, or generate a typed client in any language (TS, Java, PHP, Python…) with openapi-generator. It covers both the portal API and SAPI-SK.

Quick start

Which interface is for you

SAPI-SK 1.0 (/sapi)

Accounting or invoicing software, ERP, multi-company platforms: the national interface for sending, receiving, statuses, receipts and FS reports. Recommended for every integration that sends or reads documents.

OAuth2 client credentials: client_id = API key UUID, client_secret = the vpt_ key, 15-minute access token.

Portal API (/api/v1)

Companies, webhooks, notifications, exports and file downloads; partner console (/resellers/me). Complements SAPI, does not replace it.

Authorization: Bearer vpt_… (the account’s API key).

MCP server (/api/mcp)

An AI assistant (Claude Code, Cursor, Claude Desktop) should read documents, check recipients and send after explicit confirmation.

Bearer vpt_… through the MCP client.

E-mail to the company inbox

No code: the XML invoice as an e-mail attachment to the company’s secret address; validated and sent as from the portal.

Sender address on the allow-list + SPF/DKIM.

You create both the account and the API token in the portal (via the browser). The integration then runs exclusively through the API token. Your app does not need to handle registration, login or passwords.

  1. 1

    Create an account in the portal

    Sign up with your e-mail and confirm it via the link in the e-mail.
  2. 2

    Generate an API token

    In the portal, go to API tokeny → Vytvoriť (API tokens → Create). The vpt_… token is shown only once. Store it securely.
  3. 3

    Make your first call

    Use the token in the Authorization header:
    javascript
    const res = await fetch('https://peppol.verteco.digital/api/v1/companies', {
    headers: { Authorization: 'Bearer vpt_8f2a…' },
    });
    const companies = await res.json();
  4. 4

    Select the provider on the Financial Administration portal

    In production, selecting Verteco on the FS portal (vpds.financnasprava.sk) is needed even for send-only use: it unlocks sending (the verification token) and activates the company. Receiving may stay with your accounting program's provider: a company has exactly one receiver in the network but may have several senders.

Quick start with curl (test environment first)

Six calls from an account to the first delivered invoice, all against test.peppol.verteco.digital where a company is active the moment it is created and documents go to the Peppol test network. Expected answers are abbreviated.

shell
export BASE=https://test.peppol.verteco.digital   # production: https://peppol.verteco.digital
export KEY=vpt_…        # step 1
  1. 1

    Account and API key

    Register at test.peppol.verteco.digital/register (no confirmation e-mail in the test environment) and create a key on the API keys page. It is shown once and is used in full: the key list shows only its beginning (vpt_xxxxxxxx…) and a key copied from there with the dots is refused by the API with a 400. $KEY below.

  2. 2

    Verify the key

    shell
    curl -s $BASE/api/v1/auth/me -H "Authorization: Bearer $KEY"
    json
    { "id": "7f1d…", "email": "dev@firma.sk", "emailVerified": true, "persona": "developer" }
  3. 3

    Create a company

    shell
    curl -s -X POST $BASE/api/v1/companies -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" -d '{"ico":"12345678","dic":"SK2120000001","legalName":"Test s. r. o.","street":"Hlavná 1","city":"Žilina","postalCode":"01001"}'
    json
    { "id": "5c0e…", "legalName": "Test s. r. o.", "dic": "SK2120000001", "status": "active",
      "peppolParticipantId": "0245:2120000001", "verified": true }
  4. 4

    SAPI access token

    shell
    curl -s -X POST $BASE/sapi/auth/token -H "Content-Type: application/json" -d '{"grant_type":"client_credentials","client_id":"<key UUID from the API keys page>","client_secret":"'$KEY'"}'
    json
    { "access_token": "eyJhbGciOiJIUzI1NiJ9…", "token_type": "Bearer", "expires_in": 900,
      "refresh_token": "eyJhbGciOiJIUzI1NiJ9…" }
  5. 5

    Validate without sending

    shell
    curl -s -X POST $BASE/sapi/document/validate -H "Authorization: Bearer $ACCESS" -H "Content-Type: application/json" -d '{"payload":"<Invoice …>","payloadFormat":"XML"}'
    json
    { "valid": true, "errors": [], "warnings": [] }
  6. 6

    Send and follow the status

    shell
    curl -s -X POST $BASE/sapi/document/send -H "Authorization: Bearer $ACCESS" -H "X-Peppol-Participant-Id: 0245:2120000001" -H "Idempotency-Key: FA-2026-0001" -H "Content-Type: application/json" -d @send.json, then curl -s "$BASE/sapi/document/sent?since=2026-10-01T00:00:00Z" -H "Authorization: Bearer $ACCESS" -H "X-Peppol-Participant-Id: 0245:2120000001"
    json
    HTTP/2 202
    { "providerDocumentId": "6a3f…", "status": "ACCEPTED", "peppolMessageId": "f1c7…", "receivedAt": "2026-10-03T12:00:01Z" }
    
    { "documents": [ { "documentId": "6a3f…", "status": "delivered", "sentAt": "2026-10-03T12:00:01Z",
      "deliveredAt": "2026-10-03T12:00:14Z", "hasMls": true, "fsReport": { "state": "reported" } } ] }
Going live: swap $BASE for peppol.verteco.digital, create a production account and key, and let the client choose the provider on the Financial Administration portal (the company activates itself, sending is unlocked by the verification token).

Test environment (sandbox)

Test documents are deleted automatically after 60 days: the environment is for trying things out, not for archiving. This retention does not apply to the production portal.

In addition to the SAPI mock sandbox (below), we also operate a full-featured test environment, a complete copy of this portal with separate data, where you can try the entire flow (registration → company → sending → receiving → notifications) end-to-end without any impact on production.

Registration
no confirmation e-mail; the account is usable immediately
Company approval
automatic, directly in the UI or via the API; there is no provider selection on the portal of the Financial Administration of the Slovak Republic (Finančná správa, FS) and no FS portal login; right after creating a company with a VAT ID (IČ DPH) you can both send and receive
Network registration
automatic, into the Peppol test network. Note the two layers of the identifier: the API and the portal use the same format as production (peppolParticipantId = 0245:<DIČ digits>, where DIČ is the Slovak tax identification number; your integration code does not change); in the test SMP/SML the company is technically registered as 9950:SK<DIČ>, because the 0245 scheme requires a Financial Administration verification code that does not exist in the test environment. In production, 0245:<DIČ digits> is registered in the live Peppol network only after the provider selection on the FS portal (login with eID or the portal credentials)
Validation
real: EN 16931 + Peppol BIS 3.0 rules, the same as in production
Delivery
real, over the Peppol test network (AS4, test certificate, test SML/SMP): a recipient registered in the test network receives the invoice as a received invoice (including the e-mail, PDF and webhook); nothing leaves for the live network
Delivery receipt (MLS)
a real MLS receipt from the test network
E-mails
are really sent (to the addresses you enter), with the [TEST] prefix
Fault simulation
the header X-Sandbox-Simulate: 503 (or 502, 500, 429) on any /sapi/document/* call with a valid token returns the matching error; nothing is processed or stored. For 500 (SAPI-PROC-500) and 429 (SAPI-SYS-003 with Retry-After) you get the SAPI error envelope; 503 and 502 pass through the network edge (DigitalOcean, Cloudflare) as HTTP 504 with an HTML body and no envelope, exactly how a real outage looks from outside, so you also test handling of a non-JSON response. Validation errors come from invalid XML, 429 also from exceeding 300 calls per minute. Production ignores the header
Price
free, with no limits for testing

Everything that works here works there: the portal, the REST API, SAPI-SK 1.0, e-shop plugins and webhooks. Just switch the domain in your integration to test.peppol.verteco.digital and use the tokens created in the test environment. Ideal for integration development, CI tests and training accountants before the production rollout. Test documents belong there only: in production every domestic document between VAT payers is reported to the Financial Administration and the report cannot be withdrawn. In the production portal test only with the „Poslať skúšobnú faktúru“ button (send a test invoice, 0,00 €); from 1 January 2027 production refuses TEST-/VITAJTE- numbers (test_document_in_production).

Note: the edge protection of the test domain blocks the generic headers User-Agent: Python-urllib and User-Agent: Java/1.8.x (the default User-Agent of HttpsURLConnection in Java 8) with HTTP 403 "error code: 1010" before the request even reaches our API; the production domain peppol.verteco.digitallets them through. Set your own User-Agent: in Java either with the JVM flag -Dhttp.agent=my-app/1.0 (no code change; Java appends "Java/1.8", and the resulting "my-app/1.0 Java/1.8.0_xxx" passes, only a header starting with "Java/1.8" is blocked) or on the connection with conn.setRequestProperty("User-Agent", "my-app/1.0"). Common clients (requests, httpx, Java 11+, Apache HttpClient, okhttp, axios, Go, PHP, curl) work unchanged.

Why companies are approved automatically

The Financial Administration has no test environment for the VPDS portal: provider selection on vpds.financnasprava.sk runs only in production and is verified by logging in to the FS portal (eID or FS portal credentials), so in production you cannot “select” an arbitrary company that is not your own. So that you can still test your integration, we simulate this step in the test environment: every company you create is approved automatically (without selection at FS), so you can set up both a sender and a recipient and go through the entire flow.

For testing FS webhooks (provider selection) we have our own counterpart to the Financial Administration tool: test.peppol.verteco.digital/sandbox-nastroje. There you can generate a valid FS-style verification token (Verifikačný údaj) and send a complete webhook to your endpoint, exactly as the FS portal does during a real selection. Companies created for testing can in turn be deregistered (from the portal and from the test SMP), precisely because of the missing test mode on the Financial Administration side.

The test environment is not part of the live Peppol network: companies are registered in a separate test network (test SMP/SML), nothing is sent to real endpoints, and its data may be wiped at any time. Do not use it for real invoices; for those, production is at peppol.verteco.digital, where registration in the live network is unlocked by provider selection on the Financial Administration portal confirmed by login (eID or FS portal credentials).

Authentication

Your integration authenticates with an API token in the header Authorization: Bearer vpt_…. You create the token in the portal (API tokens); it has the format vpt_ + 40 hex characters, we store only its SHA-256 hash, and it has the same access as your account. Test it via /auth/me:

curl
curl https://peppol.verteco.digital/api/v1/auth/me -H 'Authorization: Bearer vpt_8f2a…'
# 200 → { "id": "…", "email": "vy(at)firma.sk" }   (token works)

With a missing or invalid token the API returns:

json
// 401 Unauthorized
{ "error": "unauthorized", "message": "Authentication required" }
The portal (browser) uses an internal session cookie (portal_session, JWT, 7 days); for a server-to-server integration you do not need it. Public without auth are /ping, /openapi.json and the endpoints under /public/* (the peppol-check lookup, the peppol-validate validator, see the section Public tools for details, and the service status via status); everything else requires a session or a Bearer token.

Conventions & formats

TypeFormatExample
idUUID (string)08dd6c1e-…
timestampsISO-8601 Instant (UTC)2026-06-03T09:40:27Z
issueDatedate (YYYY-MM-DD)2026-06-03
totalAmountdecimal number120.00
missing valuesnull (not an omitted field)"dic": null

Pagination & idempotency

/companies and /companies/{id}/documents support optional ?page&limit (the response remains a JSON array; the total count is in the X-Total-Count / X-Total-Pages headers). Without parameters they return the whole array, documents sorted by createdAt descending; /tokens always returns the whole array. The Idempotency-Key header is supported by the national interface SAPI-SK on POST /sapi/document/send; for programmatic sending, use exactly that.

Versioning and backward compatibility

  • The contract only grows: new fields (omitted when null), new optional parameters, new endpoints and events. Existing field names, enum values and status codes do not change.
  • A new enum value or webhook event may appear at any time; ignore what you do not know and answer an unknown event with 2xx.
  • Timestamps are ISO 8601 UTC with a variable number of fractional digits; parse them, never compare them as text. Amounts arrive as strings with two decimals.
  • Should a change ever be non-additive, we announce it in the changelog (e-mail subscription available) at least 90 days ahead and keep the old shape during a transition period.
  • API-key security model (since 3 October 2026): a key reads and sends documents and manages companies, notifications and webhooks. Account and key management (creating, scoping or revoking keys, MFA, password, sessions, invitations, company transfer, contract signatures) is done by a signed-in person only; a key gets 403 token_not_allowed there. GET /api/v1/tokens stays open to keys.
  • The contract version is info.version in openapi.json and every change has a changelog line tagged API or Webhooks.

/changelog · /changelog/odber · openapi.json

Errors

Downloadable error dictionary: the very dictionary the portal uses to translate a rejection detail (EN 16931 and Peppol BIS rule ids, patterns of the network errors, the statusReason codes of GET /sapi/document/sent) is published as JSON at /docs/peppol-errors.json (sk, en, de, hu). Use it for a table of common errors in your accounting software: match the rule id anywhere in the detail and show unknown rules together with their id. Since 7 Oct 2026 every JSON response carries Content-Type: application/json;charset=UTF-8.

Errors have a uniform shape with a machine-readable error code. Validation errors add a fields map (first error per field).

json
// business error
{ "error": "invalid_credentials", "message": "Invalid email or password" }

// validation error (400); field messages are returned in Slovak ("IČO musí byť 8 číslic" = "IČO must be 8 digits")
{ "error": "validation_failed", "message": "Some fields are invalid",
"fields": { "ico": "IČO musí byť 8 číslic" } }

HTTP statuses

200 / 201 / 204
success (OK / Created / No Content)
400
invalid input (see error / fields)
401
missing or invalid authentication
403
insufficient permission (role)
404
the resource does not exist or you have no access to it
405
HTTP method not supported for this path
409
conflict (IČO / e-mail already exists)
415
unsupported Content-Type (XML endpoints expect application/xml)
429
rate limit exceeded (RateLimit-* and Retry-After headers, JSON body rate_limited)
Our API always answers with JSON carrying an error field. A response without a JSON body (HTML or text such as error code: 1010 with status 403, or 502/504 during a deployment) did not come from the API but from the infrastructure in front of it (edge protection, gateway). The typical cause of 403/1010 is a generic library User-Agent (Python-urllib, Java/1.8.x on the test domain); the fix is described in the Test environment section.

Most common error codes

CodeHTTPWhen
validation_failed400the body failed validation (see fields)
unauthorized401missing or invalid API token
forbidden403the action requires the owner/admin role
company_not_found404the company does not exist or you are not a member of it
ico_taken409a company with this IČO (company registration number) already exists
ico_immutable400the IČO cannot be changed
company_dic_missing400sending verification without the company DIČ (Slovak tax identification number)
token_invalid400the verification token (Verifikačný údaj, signature) does not match
document_not_found404the document does not exist
token_not_found404the API token does not exist / is not yours
rate_limited429too many requests

Access Point outage and retries

  • If the API answers 502, 503 or 504, or does not answer at all, we did not take the document over: repeat the call later with the same Idempotency-Key, nothing is sent twice.
  • A document the API accepted (202 ACCEPTED, also with retrying: true) leaves on its own after recovery: the Access Point retries the delivery and you see the state via GET /sapi/document/sent or a webhook; if it finally gives up, the document ends as failed and the same key sends it again.
  • Receiving at network level does not depend on the portal: a delivery that did not get through during a portal outage is retried by the sending Access Point under the Peppol rules and the document appears in the portal after recovery. FS reports that did not leave because of the outage are back-filled by an automatic recovery run (a transport failure is retried, a content rejection is not).
  • Status and availability history are on /status; in the test environment you simulate an outage with the X-Sandbox-Simulate header (section Test environment).

SAPI-SK codes (field error.code)

CodeMeaning
SAPI-AUTH-001invalid client_id/client_secret or a blocked account (401)
SAPI-AUTH-002missing or malformed Authorization: Bearer header (401)
SAPI-AUTH-003key not authorised for the participant, sending not unlocked or paused (403)
SAPI-AUTH-004IP address not on the key’s allow-list (403)
SAPI-VAL-001invalid request body (400)
SAPI-VAL-002X-Peppol-Participant-Id missing (400)
SAPI-PROC-001temporary: a send with this key is in flight, retry shortly (502, retryable)
SAPI-PROC-002document rejected before sending (422 with Prefer: handling=strict)
SAPI-PROC-003without Idempotency-Key: a different document was already transmitted under this number (409, details: documentId, status)
SAPI-RES-001document does not exist or belongs to another participant (404)
SAPI-RES-002document content no longer stored (company retention) or never archived (404; the code stays for compatibility)
SAPI-RES-003 / 004attachment or MLS receipt does not exist yet (404)
SAPI-SYS-001 / 002temporary infrastructure or validator error (503/502, retryable)
SAPI-SYS-003rate limit exceeded (429, Retry-After)
SAPI-PROC-500unexpected error, send the correlation_id to support (500)

Complete error catalogue of the portal API (326)

Every code the backend returns in the error field with the text the portal shows. Generated from the same dictionary the UI uses, so it cannot go stale.

Complete error catalogue of the portal API
account_existsAn account already exists for this e-mail. Sign in and accept the invitation in the portal.
account_inactiveYour account is not active. Please contact us.
account_under_reviewYour account is being reviewed. Call us or write to [email protected].
acknowledge_requiredConfirm that you understand the risks of downloads without login.
acknowledgement_requiredConfirm that you understand the consequences of terminating the contract.
address_book_fullThe address book is full. Remove unused clients and try again.
admin_email_unverifiedAdministration access requires a verified e-mail address. Click the link in the message we sent you at sign-up.
admin_mfa_requiredAdministration access requires two-factor authentication. Turn it on in Settings.
ai_budgetThe AI draft budget is used up or you reached the daily limit. Write the reply manually.
ai_failedThe reply draft could not be generated. Please try again.
ai_unavailableAI reply drafts are not available right now.
already_completedThe company transfer has already been completed.
already_memberThis user already has access (member of the company or your team).
attachment_externalThis attachment is stored at the sender as a link, not inside the invoice itself.
attachment_invalidAn attachment could not be sent (type not allowed, corrupted content or size exceeded). Check the attachments and try again.
attachment_not_foundThe attachment was not found.
attachment_not_readableThe attachment could not be read.
billing_unconfiguredSubscription payments are not available yet.
blockedThe test webhook cannot be sent to this address (local or internal addresses are not allowed).
body_requiredPlease write the text of your message.
body_too_longThe message can have at most 4000 characters.
cannot_change_selfYou cannot change your own role. Ask the other company administrator.
cannot_invite_selfYou cannot invite yourself or accept your own invitation.
cannot_release_to_selfThe company is already under this account.
cannot_remove_ownerThe company owner cannot be removed. Ownership changes through a company transfer.
cannot_restrict_adminA portal administrator account cannot be restricted this way.
cannot_transfer_selfThe company already belongs to this e-mail.
captcha_failedThe bot check failed. Please try again.
captcha_requiredThe bot check failed; reload the page and try again.
channel_unknownAn old record without a known channel cannot be replayed. Re-POST it manually to the right endpoint.
chat_not_foundThe conversation was not found or is empty.
client_not_foundThe company does not belong to this partner account.
client_not_removableThe company cannot be removed: it is verified by the Financial Administration, active, registered in the SMP or has documents. Use release instead.
company_activeAn active company cannot be deleted.
company_address_requiredBefore sending an invoice, add the company address (street, city, postal code) in the company details.
company_already_activeThe company is already active; activation instructions are not needed.
company_dic_missingAdd the company DIČ (tax ID) first, then try the verification again.
company_incompleteBefore signing, complete the company details: legal name, IČO, Peppol identifier and full address.
company_limitYou reached the maximum number of companies per account. Contact support and we will raise the limit.
company_name_mismatchThe legal name does not match the official register (ŽR/OR SR) for this IČO.
company_not_activeA migration code can only be issued for an active company registered in the Peppol network.
company_not_foundThe company was not found.
company_not_registeredThe company is not activated yet: complete the Verteco selection on the Financial Administration portal, then you can send.
company_not_verifiedMigration is possible only after selecting Verteco on the Financial Administration portal (the company must be active and verified).
company_verified_lockedThe legal name of a verified company can only be changed to the official register entry. Add the IČO first or write to us.
confirm_name_mismatchThe entered name does not match the registered reseller name.
confirmation_requiredSigning requires confirming your authority and your agreement with the wording.
conflictThis record already exists.
content_unavailableThe content of this document is no longer stored or cannot be read as an invoice.
contract_pendingThe contractual relationship arises on the first entry into the portal (Art. 4 of the Terms); none has been recorded for this company yet, so the contract cannot be issued.
country_invalidCountry must be a two-letter ISO code (SK, CZ, AT, DE…), not a name.
country_outside_euThe online application is open to entities established in an EU member state. Entities outside the EU: write to [email protected].
current_ip_missingThe list must include your current IP address, otherwise you would lock yourself out after signing out.
daily_limitYou reached the daily message limit. Try again tomorrow or write to [email protected].
delivery_failedThe test webhook could not be delivered; your endpoint did not respond.
delivery_not_foundThe webhook delivery record was not found.
deregister_cooldownOnly one company per IP address can be removed every 60 seconds. Try again in a moment.
dic_immutableThe Peppol identifier cannot be changed: it is the identity of the company in the Peppol network.
dic_requiredEnter the company DIČ (tax ID: SK + 10 digits).
dic_takenA company with this DIČ already exists in the portal.
directory_unavailableThe central SMP is not responding or refused the change. Please try again in a moment.
document_no_xmlThe stored original XML is gone; the report cannot be rebuilt.
document_not_foundThe document was not found.
document_too_largeThe document is too large (limit 3 MB).
domestic_subjectA Slovak entity submits the application after signing in at /sprostredkovatel/ziadost.
duplicate_invoice_numberA document with this number was already sent. A new document needs a new number; the same document is never sent twice.
email_change_cooldownWe already sent the confirmation e-mail. You can request another one in 10 minutes.
email_invalidEnter a valid e-mail address.
email_not_verifiedConfirm your e-mail first. We sent you the link.
email_send_company_not_activeSending by e-mail can be switched on only for an active company with a Peppol identifier.
email_send_unavailableSending by e-mail is not available in this environment.
email_takenThis e-mail address is already used by another account.
email_unchangedThis is your current e-mail address.
empty_documentPaste the invoice XML.
empty_ideaPlease tell us what we should improve.
empty_messageThe message is empty.
empty_replyThe reply is empty.
entry_not_foundThe address book client was not found.
export_format_unknownUnknown export format.
forbiddenYou are not allowed to do this.
forwarder_unreachableThe document processing service is not responding. Try again in a moment.
fs_change_already_sentThe request has already gone to the Financial Administration and cannot be cancelled. File a new one if needed.
fs_change_confirm_requiredConfirm that you request all the changes at once.
fs_change_cooldownThe next change request can be filed 48 hours after the previous one.
fs_change_field_readonlyThe company ID and tax ID cannot change; a new identity means a new registration.
fs_change_no_changesYou have not entered any change against the data at the Financial Administration.
fs_change_not_foundThe change request was not found.
fs_change_not_openThe request is already closed.
fs_change_not_queuedOnly a request waiting to be sent can be sent now.
fs_change_pendingOne change request is already waiting or with the Financial Administration. File the next one once it is closed.
fs_secret_invalidThe key has 32 characters (digits and the letters a to f). Copy the whole content of the .txt file from the zip sent by the Financial Administration.
fs_secret_mismatchThe key does not match the signature of calls the Financial Administration has already sent to your endpoint. Check the zip and the copied key; if your proxy alters the call body, write to us.
has_documentsThe company has documents in the archive; deletion is blocked (statutory archiving).
holding_unavailableReleasing the client is not possible right now. Contact support.
ico_immutableThe IČO cannot be changed.
ico_requiredThe IČO is required (8 digits).
ico_takenA company with this IČO already exists under another account. Sign in with it or write to us.
instructions_cooldownWe already sent the instructions for this company. They can be sent again after 24 hours.
integration_not_foundUnknown integration.
internalAn unexpected error occurred. Please try again.
internal_errorAn unexpected server error occurred. Please try again; the error has been logged.
invalid_actionUnknown action.
invalid_afterThe after parameter must be an ISO 8601 date and time (e.g. 2026-09-05T10:00:00Z).
invalid_amountThe amount must be a number.
invalid_billing_modelUnknown billing model. Choose one of the offered models.
invalid_countrySelect the country of the registered office.
invalid_credentialsIncorrect e-mail or password.
invalid_current_passwordThe current password is not correct.
invalid_dateThe date must be in the format YYYY-MM-DD.
invalid_default_companyThe default company must be one of your companies.
invalid_dicEnter a valid DIČ (10 digits, optionally with the SK prefix) or VAT ID.
invalid_directionThe document direction must be sent or received.
invalid_due_daysThe due period must be 0 to 365 days.
invalid_emailEnter a valid e-mail address.
invalid_feed_urlThe feed URL must start with https://.
invalid_icoEnter a valid IČO (8 digits) or company registration number.
invalid_idEnter a Slovak VAT ID or DIČ (e.g. SK2121358349) or a Peppol ID (e.g. 0245:2121358349).
invalid_idsThe list of documents is not valid.
invalid_inviteThe invitation is invalid or has already been used.
invalid_ip_entryThe list contains an invalid entry. Allowed are IP addresses (e.g. 185.76.8.211) and CIDR ranges (e.g. 185.76.8.0/24).
invalid_keyThe reseller key must have 3 to 40 characters: lowercase letters, digits and hyphens.
invalid_limitThe limit must be at least 1, or empty (default value).
invalid_linkThe sign-in link is invalid or has expired. Request a new one.
invalid_localeUnsupported language.
invalid_monthThe month must be in the format YYYY-MM.
invalid_notification_emailOne of the addresses is not a valid e-mail:
invalid_notify_eventUnknown notification event.
invalid_notify_localeThe e-mail language must be Slovak, English, German or Hungarian.
invalid_notify_modeUnknown notification mode. Choose: every event, daily digest, weekly digest or none.
invalid_passwordIncorrect password.
invalid_patternThe numbering format must have at most 64 characters and contain the sequence {N}, {NNN} or {NNNN} exactly once.
invalid_payloadThe invoice (XML) is missing or too large.
invalid_personaUnknown account role.
invalid_phoneEnter the phone number as +421 9xx xxx xxx (digits, spaces, optionally hyphens).
invalid_prefsThe display settings contain an invalid value and could not be saved.
invalid_receiverThe recipient must have a Peppol identifier in the form scheme:value (e.g. 0245:2121358349).
invalid_requestRequired data is missing (e.g. company name, 10-digit DIČ, valid e-mail or endpoint URL).
invalid_roleThe role must be administrator, member or read-only.
invalid_seqThe next sequence number must be at least 1.
invalid_sessionInvalid conversation. Reload the page and try again.
invalid_signatureThe payment notification could not be verified.
invalid_signupThe sign-up is no longer valid or has expired. Start the registration again.
invalid_sortUnknown sort order.
invalid_stateUnknown state.
invalid_statusUnknown status.
invalid_tagThe tag cannot be empty.
invalid_tokenThe link is invalid or has already been used.
invalid_vpds_urlThe selection link must start with https://vpds.financnasprava.sk/.
invalid_webThe website must be a valid address (https://...).
invalid_webhook_urlThe webhook URL must be a valid https:// address (at most 512 characters).
invalid_windowThe time window is not valid: from must be before to and the window at most 31 days.
invalid_xmlThe document XML cannot be read (corrupted file or a DOCTYPE declaration, which is not allowed).
invite_email_mismatchThis invitation is for a different e-mail address. Sign in with the account it was sent to.
invite_expiredThe invitation has expired. Ask for a new one.
ip_not_allowedSigning in from this IP address is not allowed for this account.
lead_no_emailThe lead has no e-mail address and cannot be contacted.
lead_not_contactableOnly a lead in the state new can be contacted.
lead_not_foundThe lead was not found.
link_goneThis link is no longer valid (expired, revoked, or the document content is no longer stored).
lookup_unavailableThe register or verification service is currently unavailable. Try again in a moment.
malformed_requestThe request is not in a valid format. Reload the page and try again.
manager_requiredOnly the company owner or an administrator can make this change.
member_is_ownerThis user is the company owner. Ownership changes through a company transfer.
member_not_foundThe member was not found.
members_fullThe company reached the maximum number of members.
method_not_allowedThis operation is not supported for this address.
mfa_already_enabledTwo-factor authentication is already turned on.
mfa_code_invalidThe code does not match. Try the current code from your app or a backup code.
mfa_not_enabledBackup codes belong to two-factor authentication. Turn it on first.
mfa_not_startedStart the two-factor authentication setup first.
mfa_requiredEnter the code from your authenticator app.
migration_code_liveThe previous migration code is still valid. Cancel it first.
migration_code_noneThere is no active migration code request.
migration_code_pendingA migration code request is already in progress.
migration_code_rejectedThe SMP rejected the migration code. Check it with the previous provider; codes are single-use and time-limited.
migration_code_requiredEnter the migration code from the previous provider.
migration_rejectedThe central SMP rejected the takeover request. Try again or write to us.
missing_fieldsThe name and the owner e-mail are required.
missing_parameterA required parameter is missing from the request.
mls_not_availableNo delivery receipt (MLS) is available for this document yet.
name_requiredEnter the name (client name or legal name).
no_billing_accountYou do not have a billing account yet. Set up a subscription first.
no_bodyThe record has no stored request body and cannot be replayed.
no_contentThe document content is no longer available; the link cannot be issued.
no_dicThe company has no DIČ (tax ID).
no_documentsThere are no documents (with stored XML) for the selected period.
no_evidenceNeither the original XML nor a delivery receipt is stored for this document.
no_idsSelect at least one document.
no_ownerThe company has no owner. Use direct reassignment instead.
no_secretThe signing key does not exist yet. Set the notification webhook URL first.
no_verification_tokenThe company has no stored verification token from the Financial Administration; SMP registration requires it.
not_a_resellerThis account is not the owner of a reseller registration.
not_acceptableThe server answers in JSON format only.
not_available_for_white_labelNot available for companies under a reseller brand.
not_failedOnly failed webhooks can be retried.
not_foundRecord not found.
not_resellerThis account is not the owner of a reseller registration.
not_sandbox_rowThis reseller registration is managed by an administrator and cannot be switched off from the account.
note_too_longThe note can have at most 200 characters.
notification_emails_too_longThe addresses are too long together (256 characters at most).
owner_not_foundNo owner account exists for this e-mail. Create it first.
owner_of_companiesTo delete the account, first request a migration code or deregister the company from the Peppol network. After that you can delete the account permanently.
owner_requiredOnly the owner of the company account can do this.
participant_not_registeredThe company has no record in the central SMP under Verteco yet; set the listing after registration.
partner_managedThis company is managed by its intermediary; the contract confirmation is issued by them.
party_missing_in_textThe text being signed does not contain the company identification. Reload the page and try again.
password_already_setYou already have a password. Use the change password option.
password_not_setThe account has no password. Set one in Settings and try again.
password_requiredSet a password first (Security); then you can confirm this change.
payment_mismatchThe payment could not be matched to the application. Please contact us.
payment_unconfiguredPayment is not available right now. Try later or contact us.
pdf_unavailablePDF generation is temporarily unavailable. Try again in a moment or use Print / PDF.
profile_too_longOne of the profile fields is too long. Shorten the text.
public_link_existsThe document already has an active link. Choose to replace it if you want a new one.
public_links_disabledThe company has not enabled invoice downloads without login. The owner can turn it on in the company details (Data archive).
public_links_unavailablePublic document links are currently switched off.
rate_limitedToo many requests. Try again in a moment.
read_only_memberThe read-only role cannot change document states or tags.
received_onlyA public link is issued only for received documents.
receiving_toggle_cooldownReceiving can be switched at most three times per hour. The next change is possible once an hour has passed since the oldest of the last three changes.
registered_elsewhereThe participant is still registered with another provider; the code did not trigger the takeover.
renderer_unavailableThe preview cannot be rendered right now (the rendering service is restarting). Try again in a moment; sending the invoice is not blocked.
reply_already_cancelledThis reply was cancelled by a colleague in the meantime.
reply_already_sentThis reply has already been sent; it cannot be cancelled or sent again.
reply_not_foundThe scheduled reply was not found.
reply_to_invalidThe results address is not a valid e-mail address.
representative_incompleteEnter both the name and the position of the authorised person, or leave both fields empty.
request_rejectedThe request contains a character that is not allowed in a header or the URL. If you sent an API key, use the full client_secret shown when the key was created (the key list shows only its beginning with …) and plain quotes.
reseller_not_foundThe reseller was not found.
reseller_ownerYour account owns a reseller registration. Arrange its hand-over with support; then you can delete the account.
reseller_requiredThis part is only for the account of a registered intermediary.
sbd_not_availableThis document has no stored transport envelope (SBDH). Download the document XML instead.
schedule_not_foundThis scheduled export no longer exists.
sender_email_invalidThe sender address is not a valid e-mail address.
sender_email_systemAutomated mailboxes (noreply, postmaster, mailer-daemon…) cannot be added; use a person's or the invoicing system's mailbox.
sender_existsThis address is already on the allow-list.
sender_limitAt most 20 allowed senders are possible.
sending_not_verifiedSending is not allowed: the company does not have a verified provider selection on the Financial Administration portal yet.
sending_pausedSending is paused for this company by its managing account. Receiving invoices continues.
service_unavailableThe service is temporarily unavailable. Please try again in a moment.
session_fullThis conversation is too long. Reload the page and start a new one.
session_not_foundThe device was not found.
shoptet_feed_blockedThe address points to a private network. Paste the public address of your shop.
shoptet_feed_httpShoptet refused the address. Check the hash in the address and that the export is not limited to one IP address.
shoptet_feed_invalid_xmlThe export response is not valid XML. Check the address in Shoptet.
shoptet_feed_not_configuredThe Shoptet connection is not set up yet.
shoptet_feed_not_orders_xmlThis is not a Shoptet order export: the address must end with /export/orders.xml.
shoptet_feed_not_system_xmlPaste the address of the “System Shoptet XML” export (its address contains patternId=-11). Custom templates, CSV and XLS are not supported.
shoptet_feed_too_largeThe export response is too large (over 10 MB). We ask for orders week by week, so such a response is usually a product export, not an order export. Check the export type.
shoptet_feed_unreachableThe Shoptet export did not respond. Try again in a moment; Shoptet allows only one export at a time.
sidlo_incompleteThe address must contain street, postal code and city, e.g. "Ulica 1, 010 01 Mesto".
smp_cancel_failedCancelling the code at the central SMP failed. Try again in a moment or write to [email protected].
smp_deregister_failedDeregistration from the Peppol network failed. Try again in a moment.
smp_held_elsewhereThis company's registration is held by another provider. No migration code from us is needed.
smp_not_testThe test environment is not connected to the test SMP correctly. Please write to us.
smp_unavailableThe central SMP is not responding right now; try again in a moment.
smtp_fromAddress_requiredEnter the sender address.
smtp_fromAddress_too_longThe sender address is too long.
smtp_from_forbiddenThe sender address cannot be on a Verteco domain.
smtp_from_invalidThe sender address is not a valid e-mail.
smtp_from_not_yoursThe sender address must be on your company's domain (like the owner, member or company notification e-mail).
smtp_from_takenThis sender address is already used by another company.
smtp_host_blockedThe server cannot be found in DNS or is not publicly reachable (internal addresses are not allowed).
smtp_host_invalidEnter the server's domain name (e.g. smtp.company.com), not an IP address.
smtp_host_requiredEnter the SMTP server address.
smtp_host_too_longThe SMTP server address is too long.
smtp_incompleteFill in the server, port, username, password and sender address.
smtp_password_invalidThe password must not contain spaces or line breaks.
smtp_password_requiredEnter the SMTP account password (it must be re-entered when the server changes).
smtp_port_not_allowedAllowed ports are 465, 587 and 2525.
smtp_security_invalidSecurity must be STARTTLS or SSL.
smtp_test_cooldownA test e-mail can be sent once per minute. Try again in a moment.
smtp_test_failedThe test e-mail could not be sent. Check the SMTP server settings.
smtp_username_invalidThe username must not contain spaces or line breaks.
smtp_username_requiredEnter the SMTP account username.
smtp_username_too_longThe username is too long.
state_not_for_directionThis state is not possible for the document direction (received or sent).
stripe_errorThe payment gateway could not be opened or the payment could not be verified. Please try again.
support_not_on_sandboxSupport runs on the production portal peppol.verteco.digital, not in the test environment.
tag_too_longThe tag is too long.
target_blockedThe target account is blocked; the transfer is not possible.
team_fullYou reached the maximum number of team members.
termination_confirmedA confirmed termination notice cannot be cancelled from the app. Write to [email protected].
termination_pendingA termination request is already in progress. Confirm it via the e-mail link or cancel it.
terms_not_acceptedYou need to accept the terms and conditions to create an account.
test_document_in_productionTest documents (number TEST-… or VITAJTE-…) are not sent through the live network from 1 January 2027. Use the test environment test.peppol.verteco.digital for trials.
ticket_link_expiredThe request has been closed for more than 90 days; the link is no longer valid. Write to us again at [email protected].
ticket_not_foundThe request was not found or the link is not valid.
token_companies_requiredSelect at least one company the key may access.
token_company_not_allowedThis API key has no access to this company (it was created for selected companies only).
token_company_not_memberA key can only cover companies your account is a member of.
token_expiredThe link has expired. Request a new one.
token_invalidThe link or verification token is invalid or no longer valid.
token_limitYou have too many active API keys (max 50). Revoke unused ones and try again.
token_not_allowedThis setting can only be changed after signing in to the portal, not with an API key.
token_not_foundThe API key was not found.
token_not_revokedThis API key is active; there is nothing to restore.
token_restore_expiredThe key was revoked more than 7 days ago and can no longer be restored. Create a new key.
token_scope_invalidThe key scope must be "all" or "selected".
too_many_idsAt most 500 documents can be processed at once.
too_many_notification_emailsEnter at most 5 e-mail addresses.
too_many_schedulesA company can have at most five scheduled exports. Delete one before adding another.
too_many_tagsThe document has too many tags.
too_many_ticketsYou have too many open requests. Please wait for a reply.
transfer_canceledThe company transfer was cancelled.
transfer_expiredThe transfer link has expired. Request a new transfer.
transfer_staleThe company ownership changed in the meantime; the transfer was cancelled.
unauthorizedPlease sign in again.
unknown_documentOnly the DPA, SLA and NDA documents can be signed.
unsupported_documentThe preview can render only an invoice or a credit note (UBL Invoice or CreditNote).
unsupported_media_typeThis content type is not accepted. Send the data as JSON.
user_not_foundNo account with this e-mail was found. The user needs to register first.
validation_failedSome fields are not filled in correctly.
validator_unavailableValidation is currently unavailable. Try again in a moment.
verification_code_rejectedThe SMP rejected the verification token from the FS selection; repeat the selection.
verification_token_missingThe verification token from the FS selection is missing; repeat the selection and try again.
verified_company_requiredOnly an account with at least one verified company can submit the application.
version_staleThe document wording changed in the meantime. Reload the page and sign the current version.
webhook_not_configuredSet and save the webhook URL first.
webhook_url_https_requiredIn production the webhook URL must start with https:// (a delivery carries customer identifiers and a signature).
webhook_url_unreachableThe webhook URL must be publicly reachable. Localhost and internal networks cannot be reached from our servers.
white_label_owner_fixedA company run through a partner is managed by the partner account. Another owner is added by the partner releasing the company, or by selecting Verteco on the Financial Administration portal.
wrong_endpointThis is not the token endpoint. Use POST /sapi/auth/token with client_id and client_secret.
xml_not_availableThe original XML is not stored for this document; its content is no longer available.
xml_requiredThe document XML is missing, the preview cannot be rendered.
xml_too_largeThe document is too large for a preview (at most 8 MB including attachments).

Rate limits

The API is protected by rate limiting in a 60-second window (per instance). The limit and the remaining quota are returned in every response via headers; when exceeded, the API returns 429 Too Many Requests with Retry-After.

Regular calls
dynamic limit with a large headroom, per API token (or session, otherwise IP); the current value is returned in the RateLimit-Limit header
Auth (/auth/*)
stricter limit per IP (anti brute-force; except /auth/me and /auth/logout)
RateLimit-Limit
limit in the window
RateLimit-Remaining
how many requests remain
RateLimit-Reset
seconds until the window resets
Retry-After
seconds until the next attempt (on 429)
http
HTTP/2 429 Too Many Requests
RateLimit-Limit: <limit in the window>
RateLimit-Remaining: 0
RateLimit-Reset: 37
Retry-After: 37

{ "error": "rate_limited", "message": "Too many requests. Slow down." }
Practical answers for integrators:
  • Discounts and rounding: rules R040 (allowance amount = base × % / 100) and R120 (line amount = quantity × price − line allowances) both tolerate 0.02 €; only R046 for a price-level discount (PriceAmount = BaseAmount − Amount) is exact. The unit price (BT-146) and the gross price and price discount (BT-147, BT-148) have no decimal limit; every other amount has 2. If your system applies the discount to the unit price and rounds it, write it at price level: PriceAmount = the rounded net unit price, BaseAmount = the list price, Amount = the exact difference, line amount = round(quantity × PriceAmount, 2); a percentage cannot be stated there (it belongs in the line note). The second option is an amount-only line allowance with reason code 95; state percentage and base in a line allowance only when base = round(quantity × exported PriceAmount, 2) and Amount = round(base × % / 100, 2). A negative quantity with a positive price (returned goods, a taxed advance deducted) is allowed in credit notes and invoices alike. Samples 11, 15 and 16 on /en/vzory.
  • Limits are bound to the credential, not the IP: 300 calls per minute per API token (SAPI: per access token), auth endpoints 20 per minute per IP and a protective ceiling of 600 per minute per IP on /api/v1. A server-side integration behind one IP is not affected; higher limits are set on request, tell us your expected peak.
  • Capacity: the volume is not limited by the price list, sending is asynchronous (the call hands the document over and delivery runs in the background) and a batch call accepts up to 100 documents at once; for a planned peak write us the expected volume in advance and we raise the key's limit.
  • Polling is a fully supported path: GET /sapi/document/sent and /receive with ?since and ?until every 1 to 5 minutes; webhooks are a complement, not a requirement.
  • Timestamps (§ 76a(2)(d)): sentAt = hand-over to the delivery service (which fulfils the issuer's duty under § 85o(11)), deliveredAt = the receiver's Access Point confirmed by MLS, fsReportedAt = reported to FS; for received documents creationDateTime = receipt. All three are in every row of /sapi/document/sent, in /sapi/document/sent/{id} and in the webhooks (plus occurredAt).
  • PDF: GET /sapi/document/receive/{id}/pdf and GET /sapi/document/sent/{id}/pdf (also linked in links.pdf) return the supplier's PDF when the document carries one, otherwise a PDF generated from the XML; printable HTML is at …/html (portal ?format=html).
  • C#/.NET and other languages: generate the client from OpenAPI (NSwag, Kiota, openapi-generator); we do not ship our own NuGet package.
  • Key scope: when creating an API key (API keys page) you choose whether it covers all companies of the account (including ones added later) or only selected ones. A call with the key for another company returns 403 token_company_not_allowed (SAPI-SK: SAPI-AUTH-003); rotating the key keeps its scope. A read-only key does not exist yet.
  • Base path: every SAPI-SK call, including our extensions (GET /sapi/document/sent, GET /sapi/document/receive/{id}/pdf, GET /sapi/discovery), goes under /sapi with the same access token and X-Peppol-Participant-Id header. A 401 “Authentication required” without a SAPI-… code means the path is outside /sapi (for example /api/v1/document/sent); /api/v1 is the portal API and expects the vpt_ key as Bearer, not a SAPI access token.
  • We do not check the issue date (BT-2) against the day of hand-over: the document enters the network the moment we receive it, so send it on its issue date and do not queue it past midnight. A document in a foreign currency is reported to the Financial Administration with the tax amount in EUR from BT-110 and BT-111 (TaxCurrencyCode EUR), as sample 03 shows.
  • Delivery date: the XML carries it as BT-72 (cac:Delivery/cbc:ActualDeliveryDate); a tax document for a received payment carries the payment date in BT-72 (sample 12). Our own service invoices and documents from the portal always state it, even when it equals the issue date (§ 74(1)(d) of the VAT Act requires it only when they differ). When reading, take BT-72, else BT-7 (cbc:TaxPointDate), else the issue date.

Rate-limit budgets (current values)

The window is 60 seconds. Every response carries RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy (limit;w=window seconds), a 429 also Retry-After. Values may be raised without notice; read the headers, not this table.

CallsBudget keyLimit
/sapi/* and /api/mcp with a tokenthe client_id behind the token (access-token rotation does not reset it)300 / min
/api/v1/* with an API key or sessionkey (hashed) or session300 / min
POST /sapi/auth/token, renew, revokeIP60 / min
/api/v1/auth/* (login, reset)IP20 / min
Public tools (/api/v1/public/peppol-check, validator)IP15 / min
Bulk check (/public/peppol-check/batch)IP6 / min
Live network lookups (peppol-check, /sapi/discovery, MCP check_participant)IP or key; plus the whole portal40 / h per caller and 150 / h in total; past the budget the nightly sweep answers (source: index), the bulk check is not limited
Registration, forgotten password, contact formIP15 / hour
Protective ceiling for anything with an unverified bearerIP600 / min
http
HTTP/2 200
RateLimit-Limit: 300
RateLimit-Remaining: 287
RateLimit-Reset: 41
RateLimit-Policy: 300;w=60
A 429 on /sapi carries, besides error and message, code SAPI-SYS-003, retryable true and correlation_id. Platforms with many tenants behind one IP: tell us the expected peak and we size the budget individually.

API tokens

Opaque vpt_… tokens for server-to-server access. The plaintext is shown only once at creation. Save it. We store only the SHA-256 hash and a 12-character prefix for display.

GET/tokenssession / token

List of your active tokens (without the secret), createdAt descending.

POST/tokenssession / token

Creates a token; returns the plaintext (once only).

FieldTypeRequiredDescription
namestringyestoken name, max 128
curl
curl -X POST https://peppol.verteco.digital/api/v1/tokens -b cookies.txt \
-H 'Content-Type: application/json' -d '{"name":"moja-appka"}'
# 201 Created
{ "id":"…","name":"moja-appka","token":"vpt_8f2a…","prefix":"vpt_8f2a3b…","createdAt":"…" }
DELETE/tokens/{id}session / token

Revokes the token. Returns 204.

Errors: token_not_found (404)

Companies

Companies, identified by company registration number (IČO) / VAT ID (IČ DPH), that you manage. Access is tied to membership: you only see companies you are a member of. The creator of a company becomes its owner.

GET/companiessession / token

List of companies you are a member of (with your role).

POST/companiessession / token

Adds a company; you become its owner, status = pending_verification.

FieldTypeRequiredDescription
icostringyesexactly 8 digits
dicstringyesVAT ID as SK + 10 digits (e.g. SK2121358349); a non-VAT payer sends the bare 10-digit DIČ and we prepend SK
legalNamestringyeslegal (business) name, max 500
registeredAddressstringnoregistered office address, max 1000
curl
curl -X POST https://peppol.verteco.digital/api/v1/companies \
-H 'Authorization: Bearer vpt_8f2a…' -H 'Content-Type: application/json' \
-d '{"ico":"53412834","dic":"SK2121358349","legalName":"Verteco digital services, s. r. o."}'

# 201 Created
{ "id":"08dd…","ico":"53412834","dic":"SK2121358349","legalName":"…",
"registeredAddress":null,"peppolParticipantId":null,
"status":"pending_verification","role":"owner","createdAt":"2026-06-03T09:40:27Z" }

Errors: ico_taken (409) · validation_failed (400)

GET/companies/{id}session / token

Company detail (you must be a member).

PUT/companies/{id}owner / admin

Updates a company. The IČO is immutable (it must equal the existing value).

Errors: forbidden (403) · ico_immutable (400) · company_not_found (404)

The status field

pending_verification
after creation; sending is not yet unlocked
active
verified, sending unlocked

Member role

owner
company creator (full access)
admin
administrator (edits, webhooks, verification)
member
member (read)
viewer
read-only
Actions marked „owner / admin" are available to both the owner and admin roles (company manager).

Sending verification (verification token, Verifikačný údaj)

Before sending, the company must be verified using the verification token (Verifikačný údaj, VÚ), a signed token issued by the Financial Administration of the Slovak Republic (Finančná správa, FS). After successful verification, the status of the company changes to active and sending is unlocked.

POST/companies/{id}/verificationowner / admin

Self-verification of the verification token (Verifikačný údaj). On success, sets status to active.

FieldTypeRequiredDescription
tokenstringyesVÚ = hex signature (prod/pPFS 1024 hex, test/tPFS 768 hex)
curl
curl -X POST https://peppol.verteco.digital/api/v1/companies/{id}/verification \
-H 'Authorization: Bearer vpt_8f2a…' -H 'Content-Type: application/json' \
-d '{"token":"<1024-hex VÚ>"}'

# 200 OK
{ "companyId":"08dd…","status":"active","sendingVerified":true,
"verificationMethod":"self","verifiedAt":"2026-06-15T…Z" }

Errors: company_dic_missing (400) · token_invalid (400) · forbidden (403)

Documents

Log of Peppol documents (received and sent) for a company. It fills up as invoices flow through the Access Point. Tied to company membership, sorted by createdAt descending.

GET/companies/{id}/documentssession / token

List of the company's documents. Optional filter ?direction=sent|received.

curl
curl 'https://peppol.verteco.digital/api/v1/companies/{id}/documents?direction=received' \
-H 'Authorization: Bearer vpt_8f2a…'
# 200 OK
[ { "id":"…","direction":"received","peppolMessageId":"…","docTypeId":"…",
  "senderId":"0088:7300010000001","receiverId":"0245:2121358349",
  "invoiceNumber":"2026001","issueDate":"2026-06-03",
  "currency":"EUR","totalAmount":120.00,"status":"received","createdAt":"…" } ]
GET/companies/{id}/documents/{docId}session / token

Detail of a single document.

Errors: document_not_found (404)

GET/companies/{id}/documents/{docId}/downloadsession / token

Standalone printable HTML of the invoice. ?inline=1 → displays in the browser, otherwise downloads.

Returns text/html, with Content-Disposition naming the file faktura-<číslo>.html (číslo = the invoice number).

SAPI-SK 1.0 (national interface)

SAPI-SK is the standardized national REST interface between a client/ERP system and an Access Point (sapi-sk.sk). We implement it in full. This means you are not tied to our proprietary API shape, and you write the integration once for any SAPI-SK Access Point.

Base URL
https://peppol.verteco.digital/sapi
Authentication
OAuth2 client_credentials → short-lived access token (JWT)
client_id
UUID of your API token (listed in the API tokens section / dashboard)
client_secret
the vpt_… token itself from the portal
Version
1.3 (10 operations: 4× auth, 6× documents)
The SAPI access token is signed with a separate key (it is neither the portal vpt_ token nor a session). Revoking the API token in the portal immediately invalidates both /auth/token and /auth/renew.

Sandbox (trial environment)

Want to try SAPI-SK without registering and without any risk? Use the public sandbox credentials. The sandbox validates requests exactly like production, but never sends anything to the Peppol network and does not work with real data; it returns realistic mock responses. Ideal for development, CI and integration onboarding.

client_id
sandbox
client_secret
sandbox
send
full contract validation + mock 202 (nothing is delivered)
receive
1 sample document sandbox-doc-0001 for testing parsing and acknowledge
curl
# 1) sandbox token (no registration required)
curl -X POST https://peppol.verteco.digital/sapi/auth/token \
-H 'Content-Type: application/json' \
-d '{ "client_id": "sandbox", "client_secret": "sandbox",
      "grant_type": "client_credentials" }'

# 2) mock send: it is validated, but NOTHING is actually delivered
curl -X POST https://peppol.verteco.digital/sapi/document/send \
-H 'Authorization: Bearer <sandbox access_token>' \
-H 'X-Peppol-Participant-Id: 0245:0000000000' \
-H 'Content-Type: application/json' \
-d '{ "metadata": { "documentId": "TEST-1",
        "documentTypeId": "urn:…::Invoice##…::2.1",
        "senderParticipantId": "0088:sandbox-sender",
        "receiverParticipantId": "0088:sandbox-receiver" },
      "payload": "<Invoice>…</Invoice>", "payloadFormat": "XML" }'
# 202 { "providerDocumentId": "sandbox-…", "status": "ACCEPTED", … }

# 3) sample inbox + detail of the sample document
curl https://peppol.verteco.digital/sapi/document/receive \
-H 'Authorization: Bearer <sandbox access_token>'
curl https://peppol.verteco.digital/sapi/document/receive/sandbox-doc-0001 \
-H 'Authorization: Bearer <sandbox access_token>'
Sandbox tokens are isolated: they never deliver to Peppol and never see real documents. For live sending, use the client_id/client_secret from the portal (below).

Authentication

POST/sapi/auth/tokenclient_credentials

Exchanges client_id + client_secret for an access token (15 min) and a refresh token (30 days). Store the token and use it for the full 15 minutes: requesting a new token on every call is unnecessary overhead (the request limit also counts calls to /auth/token).

curl
curl -X POST https://peppol.verteco.digital/sapi/auth/token \
-H 'Content-Type: application/json' \
-d '{ "client_id": "<token UUID>", "client_secret": "vpt_8f2a…",
      "grant_type": "client_credentials" }'
# 200 OK
{ "access_token": "eyJhbGciOi…", "token_type": "Bearer",
"expires_in": 900, "refresh_token": "eyJhbGciOi…" }

Errors: SAPI-AUTH-001 (401) · SAPI-AUTH-003 (401: IP outside the key allowlist) · SAPI-VAL-001 (400)

GET/sapi/auth/token/statusBearer (access)

Validity and expiry of the access token; should_refresh = true when less than 3 min remain.

POST/sapi/auth/renewrefresh token

Issues a new access + refresh token. Fails if the underlying API token has been revoked.

json
// body
{ "refresh_token": "eyJhbGciOi…" }
POST/sapi/auth/revoke–

Always returns success (RFC 7009). The permanent kill switch is revoking the API token in the portal.

Sending a document

POST/sapi/document/sendBearer (access)

Sends a Peppol business document (UBL / BIS 3.0) to the recipient through our Access Point. Sending is fail-closed: the company must have a verified verification token (Verifikačný údaj). The response may carry an additive warnings[] array with the same non-blocking hints as /document/validate (a TEST- number, a recipient from the same account, an issue date that is not the sending day); a warning never blocks the document.

Required headers:

HeaderDescription
AuthorizationBearer <access_token>
X-Peppol-Participant-Idthe participant you are sending on behalf of (e.g. 0245:2121358349, the digits of the DIČ (Slovak tax identification number) without the "SK" prefix)
Idempotency-Keyone key per invoice, derived from something stable (e.g. your system + the invoice id or number); a repeated call with it returns the original result and never delivers twice, while a new key for the same invoice bypasses this protection. The key is scoped to the company (X-Peppol-Participant-Id). Exception: if the first attempt never entered the network (validation error, incomplete request, recipient does not publish the document type, the access point gave up retrying, transport error with status failed) or ended as undeliverable, the same key performs the send again, so "fix and resend" works under the same invoice number; see the note Correcting and resending below. The key never expires: it is stored on the document record, which we keep permanently (retention wipes only the content), so a repeat with the same key returns the original result even months later.
curl
curl -X POST https://peppol.verteco.digital/sapi/document/send \
-H 'Authorization: Bearer eyJ…' \
-H 'X-Peppol-Participant-Id: 0245:2121358349' \
-H 'Idempotency-Key: 7b1f0e2a-…' \
-H 'Content-Type: application/json' \
-d '{ "metadata": {
        "documentId": "INV-2026-001",
        "documentTypeId": "urn:…::Invoice##…::2.1",
        "processId": "urn:…:bis:billing:3.0",
        "senderParticipantId": "0245:2121358349",
        "receiverParticipantId": "0088:7300010000001",
        "creationDateTime": "2026-06-17T10:00:00Z" },
      "payload": "<Invoice …>…</Invoice>",
      "payloadFormat": "XML" }'
# 202 Accepted
{ "providerDocumentId": "…", "status": "ACCEPTED",
"receivedAt": "2026-06-17T10:00:01Z", "timestamp": "…" }
# when status is "REJECTED", the response also carries a "detail" field
# with the rejection reason (validation rules, e.g. BR-CO-15)
metadata vs. UBL: the fields in metadata are routing and technical fields: documentId is your internal identifier, not the invoice number. Business data (invoice number cbc:ID, issue date, due date, delivery date, currency, amount) is taken directly from the UBL payload; you do not send any of it in metadata, and UBL is always the source of truth for display in the portal and for webhooks. documentTypeId: the full busdox-docid-qns identifier; a bare namespace or a CustomizationID alone is normalised to the type derived from the payload, a type that contradicts the payload is refused with SAPI-VAL-001; the participant scheme must come from the EAS list.

Errors: SAPI-AUTH-002 (401) · SAPI-AUTH-003 (403) · SAPI-VAL-001 (400) · SAPI-PROC-001 (502) · SAPI-PROC-002 (503) · SAPI-PROC-500 (500: do not retry, report the correlation_id)

HTTP status vs. verdict. Under the national SAPI-SK contract a send always answers 202and the verdict is in the body (status ACCEPTED or REJECTED). If you want the HTTP status to carry the verdict too, send Prefer: handling=strict (RFC 7240): a synchronous rejection then comes back as 422 with the SAPI error envelope (SAPI-VAL-002 for a validation failure, SAPI-RES-003 when the recipient cannot be delivered to; details[] carry providerDocumentId and detail) and the response says Preference-Applied: handling=strict. ACCEPTED is unchanged. Before handing the document to the Access Point we run the same SML lookup it would. The document is validated first: only a valid document is handed over and reported, an invalid one gets REJECTED with validation_failed even when the recipient is not in the network. A recipient that is not in the Peppol network at all means ACCEPTED with undeliverable: true: we took the document and report it to the Financial Administration regardless of delivery (§ 85o ods. 11, FS FAQ 9/DPH/2025/IM, part II, ex. 9), but nobody receives it; it shows status undeliverable on GET /sapi/document/sent, the webhook gets invoice.undeliverable, no e-mail is sent and the same Idempotency-Key sends again once the recipient identifier is corrected or the recipient registers (see the note Correcting and resending below). A recipient that is in the network but does not publish the document type gets REJECTED immediately, with no delivery attempt. retrying: true on an ACCEPTED response means the first delivery attempt failed (for example a temporarily unavailable SMP) and the Access Point retries on its own, typically within 20 minutes; detail carries the reason and the final verdict arrives via GET /sapi/document/sent or the webhook.
Correcting and resending. Every attempt the API takes on (a 202 answer, or 422 under handling=strict) gets its own providerDocumentId, a rejected one too.
  • REJECTED by our pre-send check (validation error, incomplete request, recipient does not publish the document type, its SMP record has no endpoint for the type, the access point gave up retrying): nothing entered the network, nothing was delivered and nothing is reported to the Financial Administration. Correct the data and send the document again with the same Idempotency-Key. The new attempt gets a new providerDocumentId; the first record stays in the sent list as rejected with its reason, only as the audit trail of the failed attempt, and it cannot be deleted. Always bind the invoice in your system to the latest providerDocumentId. The invoice number may stay the same, the invoice has not been issued in the sense of the law yet. Its issue date (BT-2) must however be the day it actually goes out: an e-invoice counts as issued on the day it is handed over to the delivery service, and it must be sent no later than 15 days after the supply or the payment (FS FAQ 9/DPH/2025/IM, part I, ex. 74). If the corrected invoice goes out on a later day, change the issue date to that day.
  • Handed over to the network (ACCEPTED without undeliverable: submitted, delivered or retrying in the background; this includes a document the recipient's MLS rejected after transmission, which a repeated call returns as REJECTED with the reason): the same key returns the result of that send and nothing is sent twice. Never send a delivered invoice again; it is corrected by a credit note (381) or a debit note (383).
  • undeliverable (the recipient has no address in the Peppol network): the invoice was handed over, it is reported to the Financial Administration regardless of delivery and the issuer's obligation is fulfilled (FS FAQ 9/DPH/2025/IM, part I, ex. 42 and 75). The key is released. If the recipient identifier was wrong (for example an IČO or a DIČ with a typo instead of 0245:<10 digits of the DIČ>, or the recipient is in the network under another identifier), correct the identifier and send again with the same key; keep the invoice unchanged (same number and date), it has already been handed over. An 8-digit number is an IČO and is never found in the network. If the identifier is correct and the recipient simply is not registered, send the same invoice another way, best its XML by e-mail (PDF only as a preview). Under FS FAQ part I, ex. 75 this is re-sending the same invoice, not issuing a new one, so do not issue a new invoice with another number. Once the recipient registers, you can send the same invoice through Peppol again by repeating the call with the same Idempotency-Key.
  • Wrong buyer identifier or an empty own one: an X-Peppol-Participant-Id header with the scheme only (for example 0245:, when the company's DIČ is missing in the software settings) is refused on send with 403 SAPI-AUTH-003 and an explanation, and /document/validate with such a header adds a warning to warnings before you send. A buyer given as 0088 (GLN) or as 0245 with 8 digits (the IČO instead of the DIČ) is not found in the Slovak network: the document ends as undeliverable and its statusReason (the detail in the send response) carries the fix: 0245 plus the 10 digits of the DIČ.
  • Prevent errors: before sending call POST /sapi/document/validate (the same rules as sending, stores and sends nothing) and GET /sapi/discovery?receiverId=… (is the recipient in the network and which document types it accepts). The final state is on GET /sapi/document/sent?invoiceNumber=…, GET /sapi/document/sent/{id}, POST /sapi/document/sent/status or the webhooks invoice.delivered, invoice.rejected and invoice.undeliverable.
POST/sapi/document/validateBearer (access)

Validates a document without sending it: the same EN 16931 + Peppol BIS 3.0 rules the Access Point applies before dispatch. Nothing is stored or sent; works with the sandbox token too. Body = the JSON pair { payload, payloadFormat } used by /document/send, or the raw XML (Content-Type: application/xml). Limit 10 MB. The response also carries a warnings[] array with non-blocking hints (for example a TEST- number, a recipient that is another company of the same account, or an issue date BT-2 that is not the sending day); a warning never blocks the document.

curl
curl -X POST https://peppol.verteco.digital/sapi/document/validate \
-H 'Authorization: Bearer eyJ…' \
-H 'Content-Type: application/xml' \
--data-binary @invoice.xml
# 200 OK
{ "valid": false,
  "errors": [ "BR-CO-15: Invoice total amount with VAT (BT-112) = … " ],
  "warnings": [],
  "checkedAt": "2026-09-10T12:00:00Z" }

Errors: SAPI-AUTH-002 (401) · SAPI-VAL-001 (400: empty body, invalid JSON, payloadFormat other than XML, > 10 MB) · SAPI-SYS-002 (502: validator temporarily unavailable, retry)

Receiving documents

GET/sapi/document/receiveBearer (access)

List of received documents (oldest first); the metadata also carries invoiceNumber, so you can identify a document without downloading the payload. Query: ?pageToken, ?limit (max 200), ?status (filter: received = only documents you have not acknowledged yet, acknowledged = only acknowledged ones, no parameter = all received documents; case-insensitive, any other value returns error SAPI-VAL-001), ?invoiceNumber (exact match on cbc:ID), ?since and ?until (ISO-8601 instant; window by time of receipt, e.g. documents from the last 5 days, bulk download for external archiving or reconstruction of accounting records), ?deliveryDateFrom and ?deliveryDateTo (ISO-8601 date; filter by the delivery date stated in the document). The X-Peppol-Participant-Id header is required.

json
// 200 OK
{ "documents": [ { "documentId":"…","documentTypeId":"…",
  "senderParticipantId":"0088:…","receiverParticipantId":"0245:…",
  "creationDateTime":"2026-06-17T…Z" } ],
"nextPageToken": "50" }
GET/sapi/document/receive/{documentId}Bearer (access)

Detail including the payload (raw XML, exactly as it arrived via Peppol).

Errors: SAPI-RES-001 (404) · SAPI-RES-002 (404: payload not archived)

POST/sapi/document/receive/{documentId}/acknowledgeBearer (access)

Confirms that your system has taken over the document. Idempotent.

GET/sapi/document/receive/{documentId}/xmlBearer (access)

Archive link (metadata.links.xml): the business document itself as an XML file, same content as the payload in the detail.

GET/sapi/document/receive/{documentId}/htmlBearer (access)

Archive link (metadata.links.html): generic printable rendering of the invoice (HTML).

GET/sapi/document/receive/{documentId}/pdfBearer (access)

Archive link (metadata.links.pdf): the PDF of the document. When the supplier embedded their own invoice PDF in the XML (BT-125) you get that original; otherwise a PDF rendered on demand from the stored XML (no PDF is stored). Header X-Verteco-Pdf-Source: supplier | generated. 404 SAPI-RES-002 once the content was wiped, 503 while the renderer is unavailable (retry or fall back to /html).

GET/sapi/document/receive/{documentId}/attachments/{index}Bearer (access)

Archive link (metadata.links.attachments[].url): the bytes of one attachment embedded in the document, e.g. the supplier's PDF original. Forced download.

POST/sapi/document/receive/{documentId}/public-linkBearer (access)

No-sign-in link (optional): issues a NEW random key for the document ({"rotate":true} replaces the existing one). Returns url (landing page), xmlUrl, htmlUrl, pdfUrl and expiresAt; the key is shown once. The company must have "download without sign-in" enabled (403 SAPI-AUTH-004 otherwise). DELETE revokes it.

Archive instead of a copy: every received document carries links (xml, html, pdf, attachments) and retention in its metadata. An integrator may store only the link next to the booked entry and fetch the document when it is displayed; the links require the same Bearer token and X-Peppol-Participant-Id header. retention.mode = storage means the company archives received documents with us and we keep the original for the whole contractual relationship (Terms 11a.1). retention.mode = secure means the company does not archive received documents with us: the content is irreversibly removed at retention.contentAvailableUntil, the planned deletion date stored on every document (14 days from receipt by default; the window is set separately for received and sent documents, and every change of the setting counts from the day of the change for existing documents, never earlier), after which the links return SAPI-RES-002; a null contentAvailableUntil with mode secure means we are still holding the content for a pending statutory tax report and promise no date. The company owner sets the archive per direction (received / issued) in the company detail. Links without authentication: if the owner enables "Download invoices without authentication" in the company detail, POST /sapi/document/receive/{id}/public-link returns a link with its own random key that opens the document without a token (url for a person, xmlUrl and htmlUrl for programs). The key is in that response exactly once; the link never outlives contentAvailableUntil, can be revoked (DELETE) and answers 410 with a reason once expired, at which point request a new one. Without the portal setting the call answers 403 SAPI-AUTH-004.
Links without sign-in: the links.* fields always require the Bearer token (they are archive links). When the company has "download without sign-in" enabled (company detail → Data archive, or PUT /companies/{id}/public-links), the document detail carries metadata.publicLink with ready-made url, xmlUrl, htmlUrl and pdfUrl – the same address on every read and the one the user sees in the portal on the invoice. Store it next to the booked document; POST …/public-link is only needed to force a fresh key. With the option off, publicLink.issued=false with reason=public_links_disabled. The link is permanent: it lives as long as the document content is stored (for a company that does not archive received documents, until retention.contentAvailableUntil), so expiresAt is null (omitted in SAPI responses) and nothing needs refreshing. xmlUrl and links.xml return the business document itself (Invoice/CreditNote root) without the SBDH transport envelope. pdfUrl and links.pdf return the PDF the supplier embedded in the XML (BT-125) when there is one, otherwise a PDF rendered from the XML; the X-Verteco-Pdf-Source header says which (supplier | generated).

Status of sent documents

GET/sapi/discovery?receiverId=0245:2121358349

Preflight before sending: is the recipient registered in the Peppol network, and which document types can they receive? The same SML lookup the Access Point performs; receiverId accepts a Peppol ID, a VAT ID (IČ DPH) or the bare DIČ. Response: {registered, participantId, smp, documentTypes, checkedAt}. The X-Peppol-Participant-Id header is not needed here.

GET/sapi/document/sentBearer (access)

Delivery status of sent documents: pending (being handed over to the network) → submitted → delivered / rejected (confirmed by the delivery receipt (MLS) from the recipient's AP); failed = transport error (a retry with the same Idempotency-Key sends again). Query: ?pageToken, ?limit, ?status (pending / submitted / sent / delivered / rejected / failed; case-insensitive, any other value returns error SAPI-VAL-001), ?invoiceNumber (exact match on cbc:ID: the status of a specific invoice in a single call), ?since and ?until (ISO-8601 instant; time window), ?deliveryDateFrom and ?deliveryDateTo (ISO-8601 date; filter by delivery date).

json
// 200 OK
{ "documents": [ {
  "documentId": "…",
  "receiverParticipantId": "0245:1084695645",
  "peppolMessageId": "befc9112-…",
  "status": "delivered",
  "statusDateTime": "2026-07-09T14:52:31Z",
  "creationDateTime": "2026-07-09T14:52:12Z" } ],
"nextPageToken": null }

Errors: SAPI-AUTH-002 (401) · SAPI-AUTH-003 (403) · SAPI-VAL-001 (400: invalid since)

MLS receipt and delivery evidence

The MLS receipt (Peppol Message Level Status) is the receiving Access Point's answer to our transmission: AP = accepted and delivered, AB = delivered without business confirmation, RE = rejected with a reason. The portal stores it with the sent document and flips the status accordingly: AP and AB both mean delivered (with AB the statusDetail starts with "MLS AB: " and mls.responseCode is AB; under SPOG on MLS that is delivery without a business confirmation, not a reservation), RE means rejected. Under § 76a(2)(d) of Act 222/2004 the provider must be able to prove the date and time of sending, delivery and receipt; every row of GET /sapi/document/sent and every webhook carries those three times: sentAt (hand-over to the delivery service, which under § 85o(11) fulfils the issuer's duty), deliveredAt (the receiver's Access Point confirmed by MLS) and fsReportedAt (tax data reported to FS). A foreign Access Point may never send an MLS: the document then stays delivered with the detail as4_receipt_only and no receipt.

GET/sapi/document/sent/{documentId}Bearer (access)

Detail of one sent document: the same row as in the list (incl. sentAt, deliveredAt, fsReportedAt, hasMls, links) plus mls.responseCode (AP/AB/RE), mls.receivedAt and mls.url. mls is null until a receipt arrives.

json
// 200 OK
{ "document": {
    "documentId": "…", "peppolMessageId": "befc9112-…", "status": "delivered",
    "sentAt": "2026-09-17T08:00:12Z", "deliveredAt": "2026-09-17T08:00:31Z", "fsReportedAt": null,
    "hasMls": true,
    "links": { "detail": "…/sapi/document/sent/…", "mls": "…/sapi/document/sent/…/mls" } },
  "mls": { "responseCode": "AP", "receivedAt": "2026-09-17T08:00:31Z", "url": "…/sapi/document/sent/…/mls" } }

Errors: SAPI-AUTH-002 (401) · SAPI-AUTH-003 (403) · SAPI-RES-001 (404: foreign or received document) · SAPI-RES-004 (404: no receipt yet)

GET/sapi/document/sent/{documentId}/mlsBearer (access)

The receipt as a file: the MLS ApplicationResponse from the receiver's Access Point referencing the transmission's peppolMessageId. Proof of delivery or rejection; forced download. SAPI-RES-004 while no receipt has arrived.

GET/sapi/document/sent/{documentId}/xmlBearer (access)

Content of a sent document (SAPI 1.8.15): the bare UBL XML we handed to the network (transport envelope stripped), forced download. The counterpart of the receive-side links: an external accountant's program can load the client's ISSUED invoices this way (MK-soft: „Načítavať aj vystavené faktúry“). Every row carries links.xml, links.html and links.pdf. SAPI-RES-002 once the company's retention wiped the content.

GET/sapi/document/sent/{documentId}/htmlBearer (access)

Printable rendering of a sent document, built from the stored XML (nothing is stored).

GET/sapi/document/sent/{documentId}/pdfBearer (access)

PDF of a sent document: the issuer's own PDF embedded in the XML (BT-125) when present, otherwise rendered from the XML; header X-Verteco-Pdf-Source: supplier | generated. 503 SAPI-PROC-001 while the renderer is unavailable (use /html).

Resellers: the same calls with the client's X-Peppol-Participant-Id header return the receipts of every managed company with one token; the portal shows the same receipt on the sent invoice detail (Download receipt) and the MCP tool get_document includes it.

FS report evidence, integrity and timeline (SAPI 1.8.14)

Since 1.8.14 every row of GET /sapi/document/sent, the sent detail and the received list/detail carry an fsReport object: state = reported (the report was handed to the C5 collection point, reportedAt = fsReportedAt), pending (the report is owed, the hand-over failed and is retried automatically, reason = c5_unavailable) or not_required (no report arises; reason says why: cross_border, self_send_test, issuer_without_vat_id, recipient_without_vat_id, not_billing_document, tdd_disabled, before_2026_09_01, not_recorded). After the hand-over tddMessageId (the report's SBDH InstanceIdentifier) appears and after C5 answers c5ResponseCode (AP/AB/RE), c5ResponseAt, c5Detail and the summary outcome: accepted (AP), accepted_with_reservations (AB), rejected (RE) or null until C5 answers. outcome = rejected means the report was handed over but the Financial Administration refused it (c5Detail carries the violated rule, e.g. BR-AE-08); state stays reported because the hand-over happened, and the portal re-sends the corrected report automatically (outcome becomes accepted once C5 accepts it). Sent rows also carry statusReason (a machine code behind statusDetail: network_retrying, network_retrying_sent, as4_receipt_only, recipient_not_registered, transport_failed, mls_rejected, validation_failed), xmlSha256 (SHA-256 of the handed-over XML, kept after the content wipe), retention and receiptRetention = permanent (the MLS receipt is never deleted). The receipt in the detail has mls.sha256 and mls.messageId. Received documents gained peppolMessageId, status (received/acknowledged), receivedAt, acknowledgedAt and xmlSha256 (SHA-256 of the business document bytes exactly as links.xml serves them, without the SBDH transport envelope; computed on receipt and kept after the content wipe). Both lists filter with ?fsReport=reported|pending|not_required|rejected (rejected = outcome rejected), sent also with ?status=undeliverable. The POST /sapi/document/send response carries peppolMessageId at acceptance. Everything is additive; existing fields are unchanged.

POST/sapi/document/sent/statusBearer (access)

Status of up to 200 sent documents in one call: the same rows as the list plus mls when the receipt has arrived. Ids that are not this company's sent documents come back in missing; the call never fails because of them. Replaces N calls of GET /sapi/document/sent/{id} in an ERP sync.

json
// POST { "documentIds": ["7c1e…", "9af0…", "unknown"] }  (max 200)
{ "documents": [ {
    "documentId": "7c1e…", "status": "delivered", "statusReason": null, "peppolMessageId": "befc9112-…",
    "sentAt": "2026-09-21T08:00:12Z", "deliveredAt": "2026-09-21T08:00:31Z", "fsReportedAt": "2026-09-21T08:05:40Z",
    "fsReport": { "state": "reported", "reportedAt": "2026-09-21T08:05:40Z", "reason": null, "outcome": "accepted",
                  "tddMessageId": "3f2a…", "c5ResponseCode": "AP", "c5ResponseAt": "2026-09-21T08:06:02Z", "c5Detail": null },
    "xmlSha256": "9b74…", "retention": { "mode": "storage", "contentAvailableUntil": null }, "receiptRetention": "permanent",
    "hasMls": true, "links": { "detail": "…", "mls": "…" },
    "mls": { "responseCode": "AP", "receivedAt": "2026-09-21T08:00:31Z", "url": "…", "sha256": "e1c0…", "messageId": "mls-…" } } ],
  "missing": ["9af0…", "unknown"] }

Errors: SAPI-AUTH-002 (401) · SAPI-AUTH-003 (403) · SAPI-VAL-001 (400: 0 / > 200 id)

GET/sapi/document/sent/{documentId}/eventsBearer (access)

The document's timeline ordered by time: submitted, network_retrying, delivered / rejected / undeliverable / failed, mls_received, fs_reported, fs_c5_verdict, content_wiped, webhook_delivered and more (ignore unknown types). Phase 1 is derived from the stored facts, not a journal of every intermediate status change.

Errors: SAPI-AUTH-002 (401) · SAPI-AUTH-003 (403) · SAPI-RES-001 (404: foreign or received document)

GET/sapi/document/receive/{documentId}/eventsBearer (access)

Timeline of a received document: received, acknowledged, fs_reported, fs_c5_verdict, content_wiped, webhook deliveries.

Webhooks: every invoice.* event carries the same fsReport object; new events invoice.report_pending (the report hand-over failed, it stays owed and is retried; once per document) and invoice.report_rejected (C5 rejected the report, c5Detail carries the reason). New events reach only integrations subscribed to all events; answer 2xx to unknown types. The sandbox returns fsReport not_required with reason tdd_disabled for the sample document and an empty documents array for the batch call. On a refusal fsReport.outcome is rejected and the document is listed by ?fsReport=rejected. Self-billing (document code 389, the invoice is issued by the buyer): we deliver the document, but we do not yet guarantee the Financial Administration report for it, because the report is built from the supplier named in the document, not from the sender; fsReport may stay pending or rejected. Write to us before you roll out self-billing.

Batch sending

POST/sapi/document/batchBearer (access)

Up to 100 documents in a single call. Each item goes through the full single-send logic (idempotency via itemId + idempotencyKey, reservation, submit, verdict) and returns its own result; a failure of one item does not stop the others. Items are processed sequentially; retry failed items with the same idempotencyKey.

json
// request
{ "documents": [ {
  "itemId": "fa-2026-001",
  "idempotencyKey": "fa-2026-001",
  "metadata": { "documentId": "2026001", "documentTypeId": "…", "processId": "…",
                "senderParticipantId": "0245:2121358349", "receiverParticipantId": "0245:1084695645" },
  "payload": "<Invoice …>", "payloadFormat": "XML" } ] }

// 202 Accepted
{ "total": 2, "accepted": 1, "rejected": 1, "failed": 0,
"results": [
  { "itemId": "fa-2026-001", "ok": true,  "providerDocumentId": "…", "status": "ACCEPTED" },
  { "itemId": "fa-2026-002", "ok": false, "errorCode": "SAPI-AUTH-003", "errorMessage": "…" } ] }

Errors: SAPI-AUTH-002 (401) · SAPI-AUTH-003 (403) · SAPI-VAL-001 (400: empty or oversized list)

Error model

All SAPI errors share a uniform envelope with a category, a stable code, a retryable flag and a correlation_id for support. On a credentials error (SAPI-AUTH-001) the envelope also carries hint (a human-readable explanation of what belongs into Client Id and Client Secret) and docs (a link to the guide; MK-soft programs are sent straight to /navody/mksoft#prepojenie). Both fields are optional and absent elsewhere (1.8.20).

json
{ "error": {
  "category": "AUTH",
  "code": "SAPI-AUTH-001",
  "message": "Invalid client credentials.",
  "retryable": false,
  "correlation_id": "b4191dfc-…" } }
Note: when sending, we deliver the business document; the C2 side of the tax reporting (TDD) for sent documents is in preparation. Tax reporting on receipt (C3) is generated and submitted to the Financial Administration of the Slovak Republic (Finančná správa, FS) automatically.

Notifications & webhooks

For a received invoice we can notify the company by e-mail or by webhook (POST to your URL). Delivery is durable: it is stored in an outbound queue and delivered asynchronously (15 s timeout); on failure we retry up to 12× with exponential backoff (30 s → max 8 h, about 16 hours in total), then dead-letter. HTTP 401/403 means your endpoint requires an authentication we do not send; verify the webhook-signature header instead and answer 2xx. An outage of your server therefore does not lose the notification; we deliver it on the next attempt. SSRF protection blocks loopback/local addresses; the webhook URL must be public.

GET/companies/{id}/notificationssession / token

Notification settings: { webhookUrl, notificationEmail, notificationEmails, hasSecret }.

PUT/companies/{id}/notificationsowner / admin

Sets both channels (an empty string disables the given channel).

FieldTypeRequiredDescription
webhookUrlstringnoempty or http(s) URL, max 512
notificationEmailstringnoone address or up to 5 separated by commas, max 256; each gets the received-invoice e-mails (400 invalid_notification_email / too_many_notification_emails)
GET/companies/{id}/webhooksession / token

Webhook configuration: { url, hasSecret } (without the secret).

PUT/companies/{id}/webhookowner / admin

Sets the webhook URL (auto-generates a signing secret if none exists yet).

FieldTypeRequiredDescription
urlstringyeshttp(s) URL, max 512
curl
curl -X PUT https://peppol.verteco.digital/api/v1/companies/{id}/webhook \
-H 'Authorization: Bearer vpt_8f2a…' -H 'Content-Type: application/json' \
-d '{"url":"https://vas-system.sk/peppol/webhook"}'
# 200 OK { "url":"https://vas-system.sk/peppol/webhook", "hasSecret": true }
DELETE/companies/{id}/webhookowner / admin

Removes both the webhook URL and the secret. Returns 204.

POST/companies/{id}/webhook/secretowner / admin

Generates a new signing secret and returns it ONCE; store it for verifying X-Verteco-Signature.

json
// 200 OK
{ "secret": "vpt_8f2a…" }
POST/companies/{id}/webhook/testowner / admin

Synchronously sends a test notification (event webhook.test, signed, SSRF-protected) to the stored URL and returns WHAT it sent and WHAT came back. The same is triggered by the „Otestovať webhook“ (Test webhook) button in the company detail.

json
// 200 OK
{
"sent":     { "url": "https://vas-system.sk/peppol/webhook", "event": "webhook.test",
              "signed": true, "payload": "{…}" },
"received": { "status": 200, "body": "OK", "durationMs": 142, "error": null }
}
// 400 webhook_not_configured if no webhook URL is stored

Webhook payload

For both a received (invoice.received) and a sent (invoice.sent) invoice we send a POST with this body; the event field distinguishes the type.

Two further events announce the network's verdict on an invoice you have sent: invoice.delivered (the recipient's Access Point confirmed delivery with a delivery receipt (MLS)) and invoice.rejected (rejected, either by the network or during validation before sending); invoice.undeliverable reports a recipient without a record in the Peppol network (accepted, reported to the FS, not delivered). They carry the identification of the document and the company (documentId, invoiceNumber, receiverId, peppolMessageId, companyDic, peppolParticipantId) plus status, statusDetail with the rejection reason, and statusDateTime. If the first delivery attempt failed and the Access Point retries it on its own (usually within an hour), the document stays submitted and statusDetail starts with network_retrying: ; a delivery then yields delivered, giving up yields rejected with a network_gave_up: prefix. Thanks to them you do not need to poll for the status of sent invoices.

The company.activated event arrives when a customer completes provider selection on the portal (VPDS) of the Financial Administration of the Slovak Republic (Finančná správa, FS); body: event, companyId, companyDic, peppolParticipantId, status, verifiedAt. company.deactivated in turn arrives when a company is deregistered from the network (same body without verifiedAt). company.smp_registered_elsewhere arrives when a company completed the FS selection but its national SMP record is held by another provider (body as company.activated plus action: "migration_code_required" and migrateUrl). The partner procedure is in the intermediary documentation. Until the customer enters a migration code the network delivers to the old provider. Details in the guide /saas:

json
{
"event": "invoice.received",
"eventId": "7f1c2d9e-4b1a-4e3d-9c1f-0a2b3c4d5e6f",
"occurredAt": "2026-09-03T15:04:05Z",
"companyId": "08dd…",
"documentId": "…",
"invoiceNumber": "2026001",
"senderId": "0088:7300010000001",
"supplierName": "Dodávateľ s.r.o.",
"receiverId": "0245:2121358349",
"issueDate": "2026-06-03",
"dueDate": "2026-06-17",
"deliveryDate": "2026-06-03",
"currency": "EUR",
"totalAmount": "120.00",
"peppolMessageId": "…",
"companyDic": "SK2121358349",
"peppolParticipantId": "0245:2121358349"
}

The companyDic and peppolParticipantId fields identify the company the event relates to (important for partners that use a single webhook URL for all of their clients).

Network verdict on a sent invoice:

json
{
"event": "invoice.delivered",          // or "invoice.rejected"
"occurredAt": "2026-09-03T15:04:05Z",
"companyId": "08dd…",
"companyDic": "SK2121358349",
"peppolParticipantId": "0245:2121358349",
"documentId": "…",
"invoiceNumber": "2026001",
"receiverId": "0245:2120049096",
"peppolMessageId": "…",
"status": "delivered",                 // or "rejected"
"statusDetail": null,                  // for rejected: the rejection reason
"statusDateTime": "2026-06-03T10:15:42Z"
}

Company activation after provider selection on the FS portal (VPDS):

json
{
"event": "company.activated",          // company.deactivated has the same body without verifiedAt
"occurredAt": "2026-09-03T15:04:05Z",
"companyId": "08dd…",
"companyDic": "SK2121358349",
"peppolParticipantId": "0245:2121358349",
"status": "active",
"verifiedAt": "2026-06-03T10:02:11Z"
}

The event name is also carried in the X-Verteco-Event header. Full list of events: invoice.received, invoice.sent, invoice.delivered, invoice.rejected, invoice.undeliverable, invoice.reported, invoice.report_pending, invoice.report_rejected, company.activated, company.deactivated, company.smp_registered_elsewhere and the test event webhook.test. We recommend setting an unknown event type aside and logging it; we always announce a new type in advance. Every event also carries occurredAt (event time, ISO-8601 UTC) for ordering, and eventId (the same value as the X-Verteco-Delivery-Id header): unique per event and unchanged on every retry, so it is the right de-duplication key (peppolMessageId repeats across invoice.sent, invoice.delivered and invoice.reported). invoice.* events also carry the times sentAt, deliveredAt, receivedAt and fsReportedAt (ISO-8601 UTC, null until the fact exists). invoice.reported arrives when the tax report to the Financial Administration (TDD) for the document was handed to the delivery network (§ 85o par. 11), possibly days later during a C5 outage. Formal schemas of all events are in the webhooks section of the OpenAPI specification.

The partner notification webhook, client management (release, pause-sending) and the billing model are available to registered intermediaries only and are described in For intermediaries.

Signature verification

If the company has a secret, we send the X-Verteco-Signature header in the form sha256=HMAC-SHA256(secret, raw body) (lowercase hex). Always compute the HMAC over the exact bytes of the body:

javascript
import crypto from 'node:crypto';

// rawBody = the exact bytes of the request body (not re-serialized JSON)
function verify(rawBody, header, secret) {
const expected = 'sha256=' +
  crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

Headers of every delivery: X-Verteco-Event (event name), X-Verteco-Delivery-Id (= eventId in the body, identical on every retry), X-Verteco-Signature (the HMAC above) and, in parallel, the Standard Webhooks headers: webhook-id (= eventId), webhook-timestamp (unix seconds of this attempt) and webhook-signature = v1,base64(HMAC-SHA256(secret, id + "." + timestamp + "." + body)). The key is the UTF-8 bytes of your secret; a standardwebhooks library expects it as whsec_ + base64(secret). Recommended handling: verify the signature, refuse a delivery whose webhook-timestamp is older than 5 minutes (replay protection), process idempotently by eventId, answer 2xx within 15 seconds and defer heavy work to your own queue. A failed delivery is retried 12 times, 30 s to 8 h apart (about 16 hours in total); then it is dead-lettered with an e-mail alert and stays visible in the Realtime log. A production webhook URL must be https://; the test environment also accepts http://.

You obtain the secret via POST /companies/{id}/webhook/secret; it returns it once (rotation generates a new one). Without a secret we do not send the X-Verteco-Signature header.

Bulk setup: one token, multiple companies

A single API token (bound to your account/e-mail) manages all companies it owns: whoever creates a company via POST /companies becomes its owner and can configure its webhook. The webhook is per company (its own URL and secret), so with the same token you can connect any number of companies:

bash
# 1) list of your companies (paginated, see Companies)
curl 'https://peppol.verteco.digital/api/v1/companies?page=0&limit=100' -H 'Authorization: Bearer vpt_8f2a…'

# 2) for EACH company {id}: set the webhook (and/or e-mail)
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://vas-system.sk/peppol/webhook","notificationEmail":"faktury@firma.sk"}'

# 3) fetch the signing secret (returned ONLY ONCE) and store it for signature verification
curl -X POST https://peppol.verteco.digital/api/v1/companies/{id}/webhook/secret -H 'Authorization: Bearer vpt_8f2a…'
# → { "secret": "…" }
  • The token must be owner/admin of the given company; for a company where it is only member/viewer, it returns 403 forbidden (no cross-tenant access).
  • You can give each company a different URL or the same one for all; in the payload you distinguish them by companyId (and receiverId).
  • With hundreds of companies, respect the per-token rate limit (the current value is returned in the RateLimit-Limit header); batch with backoff on 429 (Retry-After header).
  • The webhook is actually fired only once the company is active in Peppol (after selecting Verteco as provider with the Financial Administration) and therefore actually receives documents.

Data models

Fields of the objects returned by the API.

Company

FieldTypeRequiredDescription
idUUID–company identifier
icostring–company registration number (IČO, 8 digits)
dicstring|null–VAT ID (IČ DPH)
legalNamestring–legal name
registeredAddressstring|null–registered office address
peppolParticipantIdstring|null–Peppol participant (after registration)
statusstring–pending_verification | active
rolestring–owner | admin | member | viewer (your role)
createdAtInstant–creation time

Document

FieldTypeRequiredDescription
idUUID–document identifier
directionstring–sent | received
peppolMessageIdstring|null–Peppol message ID
docTypeIdstring|null–document type (Peppol)
senderIdstring|null–sender (scheme:id)
receiverIdstring|null–receiver (scheme:id)
invoiceNumberstring|null–invoice number
issueDatedate|null–issue date
currencystring|null–currency (e.g. EUR)
totalAmountnumber|null–total amount
statusstring–processing status
createdAtInstant–record time

User · Token · Webhook

FieldTypeRequiredDescription
Userobject–{ id: UUID, email: string }
Tokenobject–{ id, name, prefix, lastUsedAt|null, createdAt }
Webhookobject–{ url: string|null, hasSecret: boolean }

Fields are loaded straight from the published openapi.json, so these tables are always identical to what the API returns.

Loading data models from openapi.json…

Glossary

DIČ
Slovak tax identification number (10 digits). The Slovak Peppol ID is 0245: + the DIČ digits, without SK.
IČ DPH
VAT identification number (SK + DIČ). Only VAT payers have one; a non-payer issues with tax category O.
IČO
Company registration number (8 digits). Field ico in the API, BT-30 in UBL.
Verification token (VÚ)
A signature of the Financial Administration created when the taxpayer selects a provider on the FS portal; it unlocks sending for the company.
VPDS
The FS application where a taxpayer selects its delivery-service provider (us).
TDD
Tax Data Document, the invoice report to the Financial Administration; we build and send it for you.
C5 / C6
The fifth and sixth corner of the Peppol model: the Financial Administration’s access point that receives TDDs (0242:001180).
MLS
Message Level Status, the receipt from the recipient’s access point (AP = accepted, RE = rejected, AB = acknowledged).
SBDH
Standard Business Document Header, the envelope a document travels in; its InstanceIdentifier is the peppolMessageId.
SML / SMP
The network directory: SML finds the recipient’s SMP, the SMP says which access point and document types the recipient accepts.

Slovak conventions from implementer practice

Beyond Peppol BIS Billing 3.0, Slovak ERP and invoicing software vendors are gradually converging on a shared interpretation of optional fields (the discussion takes place in a Slack channel run by the Financial Administration of the Slovak Republic, Finančná správa, FS). Below are the conventions our portal already respects today: all of them are valid BIS constructs, they pass through our API unchanged, and received documents display them in the human-readable preview and PDF as well.

  • Deduction of a taxed advance payment on a line: a negative line with cac:DocumentReference, where cbc:ID carries the number of the tax document for the received payment and cbc:DocumentTypeCode is 130 (BIS: invoice line object identifier, max. 1 per line). A received invoice with such a line is shown in our preview with the note „odpočet zálohy – daňový doklad č. …" (advance deduction, tax document no. …).
  • Tax document for a received payment: according to the Financial Administration FAQ, the code InvoiceTypeCode 388 (Tax invoice) is used. It passes both our API and validation; the deduction of an unpaid (untaxed) advance is expressed via cbc:PrepaidAmount.
  • Invoice cancellation: a credit note 381 (CreditNote) with cac:BillingReference to the original invoice, not a negative 380 invoice. Note: the network rejects code 384 for Slovak parties (rule PEPPOL-EN16931-P0112 allows it only between German entities); for an upward correction use debit note 383.
  • BT-83 PaymentID: Slovak practice is converging on the payer reference format /VS…/SS…/KS…; a bare variable symbol (variabilný symbol) is also common. Our processing passes the value through unchanged and displays it with the payment details.
  • Additional item data (batches, serial numbers, expiration dates) via cac:AdditionalItemProperty with established names such as BatchNumber, SerialNumber, ExpirationDate.
These are community conventions, not binding national rules: a receiving system must also handle a document that does not use them. As the discussion at the Financial Administration concludes, we will keep this section updated (watch /changelog).

Document attachments (BT-125)

Attachments (PDF, images) can be attached to an e-invoice as base64 in the cac:AdditionalDocumentReference element (BT-125). For received documents we process attachments up to 25 MB each; when sending, the whole XML including base64 attachments must stay under 10 MB via SAPI-SK and under 2 MB via the portal tab Odoslať súborom. Peppol does not define a single network-wide limit; individual providers set their own (FS FAQ 9/DPH/2025/IM, example no. 67), so for very large attachments also check the limit of the other party's provider.

Public tools (validation, recipient check)

Two helper endpoints without authentication: the same validation core and the same SML lookup our Access Point uses. They are suitable for CI and for pre-send checks; a stricter public rate limit applies to them.

POST/public/peppol-validatepublic

Validation of an e-invoice against the UBL 2.1 schema, EN 16931 and Peppol BIS Billing 3.0, the same as the UI validator at /validator. Request body = the UBL 2.1 XML directly (Invoice / CreditNote, or the whole Peppol SBD), Content-Type application/xml, limit 3 MB.

bash
curl -X POST https://peppol.verteco.digital/api/v1/public/peppol-validate \
-H "Content-Type: application/xml" \
--data-binary @faktura.xml
json
// 200 OK
{
"valid": false,
"errors": [
  "BR-CO-09: [BR-CO-09]-The Seller VAT identifier (BT-31) … shall have a prefix in accordance with ISO code…"
],
"warnings": []
}
// 400 = empty body (empty_document) or document over 3 MB (document_too_large)
GET/public/peppol-check?id=0245:2121358349public

Recipient check in the live Peppol network (SML lookup): is it registered and which document types can it receive? The id parameter accepts a Peppol ID (0245:…), a VAT ID (IČ DPH, SK…) or the bare DIČ (Slovak tax identification number). Authenticated equivalent for ERP pipelines: GET /sapi/discovery.

json
// 200 OK
{
"registered": true,
"participantId": "0245:2121358349",
"smp": "sml.peppol-smp.sk",
"capabilities": ["Faktúra (BIS Billing)", "Dobropis", "Self-billing", "MLS doručenky"], // Slovak labels: Invoice (BIS Billing), Credit note, Self-billing, MLS delivery receipts
"lastCheckedAt": "2026-08-31T…Z"
}

MCP server (AI assistants)

The portal runs its own server for the Model Context Protocol, the open standard through which AI assistants (Claude Code, Claude Desktop, Cursor, VS Code Copilot and others) connect to external systems. The assistant signs in with an ordinary API key of the account and gets six tools; it never sees more than that account and every call appears in the key's API log.

Address: https://peppol.verteco.digital/api/mcp. Transport: Streamable HTTP, JSON-RPC 2.0, stateless (POST /api/mcp, no SSE stream, GET answers 405). Sign-in with the header Authorization: Bearer <API key>. Supported methods: initialize, ping, tools/list, tools/call, resources/list, prompts/list.

Tools

  • list_companies: the account's companies with Peppol ID, status and role (start of every conversation)
  • list_documents: sent/received invoices of a company with delivery and tax-report state, paged
  • get_document: one invoice in detail including the MLS receipt and tax report, optionally the UBL XML (up to 1 MB)
  • check_participant: live recipient check in the Peppol network (SML)
  • validate_document: UBL validation against EN 16931 + Peppol BIS 3.0
  • send_document: send an invoice on behalf of the company, requires confirm = true and the Verifikačný údaj gate applies

Connecting in Claude Code

bash
claude mcp add --transport http verteco-peppol https://peppol.verteco.digital/api/mcp \
  --header "Authorization: Bearer vpt_..."

Claude Desktop (through the mcp-remote bridge, needs Node.js)

json
{
  "mcpServers": {
    "verteco-peppol": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://peppol.verteco.digital/api/mcp", "--header", "Authorization: Bearer vpt_..."]
    }
  }
}

Sending an invoice is legally binding: the tool refuses a call without confirm = true and the server instructions tell the assistant to obtain the user's explicit consent to the exact invoice and recipient. Ready-made configurations for Cursor and VS Code and a button that creates the key are in the portal: API keys → MCP server tab. API keys → MCP server

Sending by e-mail (company address)

In the portal a company switches on its own address <24 random characters>@peppol.send.verteco.app. An e-mail with a UBL XML attachment sent to that address from an allowed mailbox is sent through the Peppol network exactly as if the file had been uploaded on the Send by file tab; the sender gets one reply with the result per attachment and the document is in the portal and the API with its delivery status, MLS receipt and FS report. Set-up: Connections and integrations → Sending by e-mail (company owner or administrator, signed in). /dashboard/integrations/email

Processing rules

  • Only mail from an address on the company's allow-list (up to 20) is accepted; everything else is discarded without a reply.
  • By default the sender domain must pass SPF or DKIM at the delivering server (DMARC-style relaxed alignment). The delivering server additionally refuses mail that fails both SPF and DKIM and mail against the sender's DMARC policy. The check can be switched off in the settings; not recommended.
  • Attachment: UBL 2.1 Invoice or CreditNote (Peppol BIS Billing 3.0), .xml extension, optionally in an SBDH envelope; at most 10 attachments per message, 15 MB per attachment, 200 messages per address per day. PDF and the message text are not processed.
  • IFS Finvoice 2.0: an .xml attachment with the IFS SOAP envelope (IFSFinvoice) or a bare Finvoice is converted to UBL and then processed the same way. Conversion rules: INV02 or a negative total = credit note (381) with positive amounts and the preceding invoice reference (OriginalInvoiceNumber, with IFS OrderIdentifier); seller and buyer by their Slovak VAT id (SellerOrganisationTaxCode, BuyerOrganisationTaxCode → 0245:DIČ), a buyer outside Slovakia is not converted; VAT breakdown from VatSpecificationDetails, 0 % rows as category E with the reason from VatFreeText, otherwise “Oslobodené od DPH”; IFS tax-rounding rows (net 0,00, VAT 0,01) are dropped and a unit price that does not match the row net is recomputed; units per UN/ECE (kg → KGM, pcs → H87, h → HUR, unknown → C62 with the original text in the line note); the embedded invoice image (InvoiceAttachments, PDF) is attached to the document up to 5 MB; EUR only. The conversion result is stated in the reply and in the company log.
  • Results address: when a system mailbox sends the invoices, choose another address for the reply with the results in the settings (replyTo in PUT /companies/{id}/email-send, empty = reply to the sender).
  • The seller in the XML (supplier EndpointID, BT-34) must be this company, otherwise the message is rejected (a forwarded invoice from your own supplier is never sent as yours). The buyer must have an EndpointID with a scheme (BT-49).
  • The same message (Message-ID) is processed once. The document carries origin = email in the API and webhooks, actor = the sender address.
The address is a capability: whoever knows it and is on the allow-list sends on the company's behalf. That is why it can be replaced at any time (Generate a new address; the old one stops working immediately) and every receipt is in the company's log and in the security record.

Status (ping)

Public health-check endpoint, suitable for monitoring.

GET/pingpublic

Backend status.

json
// 200 OK
{ "service": "peppol-portal-backend", "status": "ok", "timestamp": "2026-06-17T…Z" }

A live overview of all components is available on the system status page.

What is done and what is coming

Already in production (often assumed missing)

  • POST /companies/{id}/documents/send is a stable portal interface; for ERP integrations SAPI-SK with Idempotency-Key remains the recommendation.
  • SMP registration happens automatically after the FS portal selection; moving from another provider via POST /companies/{id}/smp-migrate with a migration code.
  • MLS receipts, delivery evidence and FS reports (fsReport) on every /sapi/document/sent row; webhook replay for a time window.
  • Keys limited to selected companies; MCP server for AI assistants; UBL samples on /vzory; public validator.

In preparation (ordered by integrator demand)

  • Asynchronous batch: POST /sapi/document/batch answers 202 with a job link and per-item status.
  • Change feed GET /sapi/document/changes?since&cursor with a cursor instead of offset paging.
  • Key expiry and rotation in the portal, read-only keys.
  • Several webhook endpoints per company with an event filter.
Missing something specific? Write to [email protected] what and for how many documents; it shapes the order.