Einführung
Die Verteco API ist REST über HTTPS. Request und Response sind JSON (UTF-8). Beginnen Sie mit dem Anlegen eines Kontos, dann eines API-Tokens, und führen Sie den ersten Aufruf innerhalb einer Minute aus. Alle Pfade sind relativ zur unten angegebenen Basis-URL.
Die komplette Testumgebung (Sandbox) finden Sie auf test.peppol.verteco.digital. Sie verhält sich genauso wie die Produktion und bietet zusätzlich Testwerkzeuge (FS-Webhook, Verifizierungsmerkmal (Verifikačný údaj), Löschen einer Firma) und eine simulierte Anbieterauswahl anstelle des Portals der Finanzverwaltung der Slowakischen Republik (Finančná správa, FS).
- Base URL
https://peppol.verteco.digital/api/v1- Version
- v1 (im Pfad). Rückwärtsinkompatible Änderungen erscheinen unter einer neuen Version.
- Protokoll
- nur HTTPS · TLS 1.2+ · TLS A+
- Format
- application/json (UTF-8)
- Auth
- Cookie-Session (Portal) oder Bearer-Token (Server-to-Server)
- Zeitangaben
- ISO-8601 (UTC), z. B. 2026-06-03T09:40:27Z
Zertifizierter AP
Ein echter Peppol Access Point (Seat ID PSK001128), kein Reseller. Wir haben die OpenPeppol-Conformance bestanden (19/19), im Produktivbetrieb.
Valide Belege
Serverseitige Validierung nach EN 16931 + Peppol BIS 3.0 Schematron bei jedem empfangenen UND gesendeten Beleg. Sie können einen Beleg auch vorab prüfen: der öffentliche Validator läuft unter /validator (und als API unter POST /api/v1/public/peppol-validate); Fehler geben wir mit der konkreten Regel zurück (z. B. BR-CO-15).
Multi-tenant
Ein Konto und ein Token für N Firmen, ideal für SaaS, ERP und Buchhalter.
SK TDD automatisch
Die Steuermeldung (corner-5 / TDD) an die Finanzverwaltung erzeugen und senden wir für Sie.
openapi-generator einen typisierten Client in einer beliebigen Sprache (TS, Java, PHP, Python…). Sie deckt sowohl die Portal-API als auch SAPI-SK ab.Schnellstart
Konto und API-Token legen Sie im Portal an (über den Browser). Die Integration läuft danach ausschließlich über den API-Token. Registrierung, Anmeldung und Passwörter muss Ihre App nicht behandeln.
- 1
Legen Sie ein Konto im Portal an
Registrieren Sie sich per E-Mail und bestätigen Sie es über den Link in der E-Mail. - 2
Generieren Sie einen API-Token
Im Portal API tokeny → Vytvoriť (API-Tokens → Erstellen). Der Tokenvpt_…wird nur einmal angezeigt. Bewahren Sie ihn sicher auf. - 3
Führen Sie den ersten Aufruf aus
Verwenden Sie den Token im HeaderAuthorization:javascriptconst res = await fetch('https://peppol.verteco.digital/api/v1/companies', { headers: { Authorization: 'Bearer vpt_8f2a…' }, }); const companies = await res.json();
Testumgebung (Sandbox)
Neben der SAPI-Mock-Sandbox (unten) betreiben wir auch eine vollwertige Testumgebung, eine vollständige Kopie dieses Portals mit getrennten Daten, in der Sie den gesamten Ablauf (Registrierung → Firma → Senden → Empfangen → Benachrichtigungen) end-to-end ausprobieren können, ohne jegliche Auswirkung auf den Produktivbetrieb.
- Registrierung
- ohne Bestätigungs-E-Mail; das Konto ist sofort nutzbar
- Genehmigung der Firma
- automatisch, direkt in der Oberfläche oder über die API; ohne Auswahl des Anbieters im Portal der Finanzverwaltung der Slowakischen Republik (Finančná správa, FS) und ohne eID; unmittelbar nach dem Anlegen einer Firma mit USt-IdNr. (IČ DPH) können Sie sowohl senden als auch empfangen
- Registrierung im Netzwerk
- automatisch in das Peppol-Testnetzwerk. Beachten Sie die zwei Ebenen des Identifikators: API und Portal verwenden dasselbe Format wie die Produktion (
peppolParticipantId = 0245:<DIČ-Ziffern>, DIČ = slowakische Steuernummer; Ihr Integrationscode ändert sich nicht); im Test-SMP/SML wird die Firma technisch als9950:SK<DIČ>eingetragen, weil das Schema 0245 einen Verifizierungscode der Finanzverwaltung verlangt, der im Test nicht existiert. In der Produktion wird0245:<DIČ-Ziffern>erst nach der Auswahl des Anbieters per eID in das produktive Netzwerk registriert - Validierung
- real: Regeln nach EN 16931 + Peppol BIS 3.0, dieselben wie im Produktivbetrieb
- Zustellung
- simuliert: Das Dokument verlässt den Testserver nie; existiert der Empfänger (USt-IdNr.) in der Testumgebung, wird ihm die Rechnung als empfangene Rechnung zugestellt (inklusive E-Mail, PDF und Webhook)
- Zustellnachweis (MLS)
- simuliert, ausdrücklich als SANDBOX gekennzeichnet
- E-Mails
- werden real versendet (an die Adressen, die Sie angeben), mit dem Präfix
[TEST] - Preis
- kostenlos, ohne Limits zum Ausprobieren
Dort funktioniert alles wie hier: Portal, REST API, SAPI-SK 1.0, E-Shop-Plugins und Webhooks. Es genügt, in Ihrer Integration die Domain durch test.peppol.verteco.digital zu ersetzen und die in der Testumgebung erstellten Token zu verwenden. Ideal für die Entwicklung der Integration, CI-Tests und die Schulung von Buchhaltern vor dem Produktivstart.
Hinweis: Der Edge-Schutz der Testdomain blockiert die generischen Header User-Agent: Python-urllib und User-Agent: Java/1.8.x (der Standard-User-Agent von HttpsURLConnection in Java 8) mit HTTP 403 "error code: 1010" noch vor unserer API; die Produktivdomain peppol.verteco.digital lässt sie durch. Setzen Sie einen eigenen User-Agent: in Java entweder per JVM-Parameter -Dhttp.agent=meine-app/1.0(ohne Codeänderung; Java hängt "Java/1.8" an, das resultierende "meine-app/1.0 Java/1.8.0_xxx" wird durchgelassen, blockiert wird nur ein Header, der direkt mit "Java/1.8" beginnt) oder auf der Verbindung mit conn.setRequestProperty("User-Agent", "meine-app/1.0"). Gängige Clients (requests, httpx, Java 11+, Apache HttpClient, okhttp, axios, Go, PHP, curl) funktionieren unverändert.
Warum Firmen automatisch genehmigt werden
Die Finanzverwaltung hat keine Testumgebung des VPDS-Portals: Die Auswahl des Anbieters auf vpds.financnasprava.sk läuft nur in der Produktion und wird über slovensko.sk (eID) verifiziert, sodass Sie im Produktivbetrieb keine beliebige fremde Firma „auswählen" können. Damit Sie Ihre Integration trotzdem testen können, simulieren wir diesen Schritt in der Testumgebung: Jede Firma, die Sie anlegen, wird automatisch genehmigt (ohne Auswahl bei der FS), sodass Sie Absender und Empfänger anlegen und den gesamten Ablauf durchlaufen können.
Zum Testen der FS-Webhooks (Auswahl des Anbieters) haben wir ein eigenes Gegenstück zum Werkzeug der Finanzverwaltung: test.peppol.verteco.digital/sandbox-nastroje. Dort erzeugen Sie ein gültiges Verifizierungs-Token (Verifizierungsmerkmal, Verifikačný údaj) im Stil der FS und senden einen vollständigen Webhook an Ihren Endpoint, genau so, wie es das FS-Portal bei einer realen Auswahl tut. Zu Testzwecken angelegte Firmen können Sie wiederum deregistrieren (aus dem Portal und aus dem Test-SMP), gerade weil es auf Seiten der Finanzverwaltung keinen Testmodus gibt.
Authentifizierung
Die Integration authentifiziert sich mit einem API-Token im Header Authorization: Bearer vpt_…. Den Token erstellen Sie im Portal (API-Tokens); er hat das Format vpt_ + 40 Hex-Zeichen, wir speichern nur seinen SHA-256-Hash und er hat dieselben Zugriffsrechte wie Ihr Konto. Testen Sie ihn über /auth/me:
curl https://peppol.verteco.digital/api/v1/auth/me -H 'Authorization: Bearer vpt_8f2a…'
# 200 → { "id": "…", "email": "vy(at)firma.sk" } (Token funktioniert)Bei fehlendem oder ungültigem Token gibt die API zurück:
// 401 Unauthorized
{ "error": "unauthorized", "message": "Authentication required" }portal_session, JWT, 7 Tage); bei einer Server-to-Server-Integration benötigen Sie es nicht. Öffentlich ohne Auth sind /ping, /openapi.json und die Endpoints unter /public/* (Prüfung peppol-check, Validator peppol-validate, Details dazu im Abschnitt Öffentliche Werkzeuge, sowie der Dienststatus status); alles andere erfordert eine Session oder einen Bearer-Token.Konventionen & Formate
| Typ | Format | Beispiel |
|---|---|---|
id | UUID (String) | 08dd6c1e-… |
Zeitstempel | ISO-8601 Instant (UTC) | 2026-06-03T09:40:27Z |
issueDate | Datum (YYYY-MM-DD) | 2026-06-03 |
totalAmount | Dezimalzahl | 120.00 |
fehlende Werte | null (Feld ist vorhanden, wird nicht weggelassen) | "dic": null |
Paginierung & Idempotenz
/companies und /companies/{id}/documents unterstützen optional ?page&limit (die Response bleibt ein JSON-Array; die Gesamtzahl finden Sie in den Headern X-Total-Count / X-Total-Pages). Ohne Parameter geben sie das gesamte Array zurück, Belege nach createdAt absteigend sortiert; /tokens gibt immer das gesamte Array zurück. Den Header Idempotency-Key unterstützt die nationale Schnittstelle SAPI-SK auf POST /sapi/document/send; für den programmatischen Versand verwenden Sie genau diese.Fehler
Fehler haben eine einheitliche Form mit einem maschinenlesbaren error-Code. Validierungsfehler ergänzen eine Map fields (erster Fehler pro Feld).
// Business-Fehler
{ "error": "invalid_credentials", "message": "Invalid email or password" }
// Validierungsfehler (400); die Texte in "fields" liefert die API auf Slowakisch
{ "error": "validation_failed", "message": "Some fields are invalid",
"fields": { "ico": "IČO musí byť 8 číslic" } }HTTP-Status
- 200 / 201 / 204
- Erfolg (OK / erstellt / kein Inhalt)
- 400
- ungültige Eingabe (siehe error / fields)
- 401
- fehlende oder ungültige Authentifizierung
- 403
- unzureichende Berechtigung (Rolle)
- 404
- Ressource existiert nicht oder Sie haben keinen Zugriff darauf
- 405
- HTTP-Methode für diesen Pfad nicht unterstützt
- 409
- Konflikt (Handelsregisternummer IČO / E-Mail existiert bereits)
- 415
- nicht unterstützter Content-Type (XML-Endpoints erwarten application/xml)
- 429
- Rate-Limit überschritten (Header RateLimit-* und Retry-After, JSON-Body rate_limited)
error. Eine Antwort ohne JSON-Body (HTML oder Text wie error code: 1010 mit Status 403, oder 502/504 während eines Deployments) kam nicht von der API, sondern von der davor liegenden Infrastruktur (Edge-Schutz, Gateway). Typische Ursache von 403/1010 ist ein generischer Bibliotheks-User-Agent (Python-urllib, Java/1.8.x auf der Testdomain); die Lösung steht im Abschnitt Testumgebung.Vollständiger Fehlerkatalog
| Code | HTTP | Wann |
|---|---|---|
validation_failed | 400 | Body hat die Validierung nicht bestanden (siehe fields) |
unauthorized | 401 | API-Token fehlt oder ist ungültig |
forbidden | 403 | Aktion erfordert die Rolle owner/admin |
company_not_found | 404 | Firma existiert nicht oder Sie sind nicht Mitglied |
ico_taken | 409 | eine Firma mit diesem IČO existiert bereits |
ico_immutable | 400 | IČO kann nicht geändert werden |
company_dic_missing | 400 | Verifizierung des Versands ohne DIČ (slowakische Steuernummer) der Firma |
token_invalid | 400 | Verifizierungsmerkmal (Verifikačný údaj, Signatur) stimmt nicht |
document_not_found | 404 | Beleg existiert nicht |
token_not_found | 404 | API-Token existiert nicht / gehört nicht Ihnen |
rate_limited | 429 | zu viele Requests |
Rate-Limits
Die API ist durch Rate-Limiting in einem Fenster von 60 Sekunden geschützt (pro Instanz). Limit und Restkontingent liefern wir in jeder Response über Header; bei Überschreitung kommt 429 Too Many Requests mit Retry-After zurück.
- Normale Aufrufe
- dynamisches Limit mit großer Reserve, pro API-Token (oder Session, sonst IP); den aktuellen Wert liefert der Header RateLimit-Limit
- Auth (/auth/*)
- strengeres Limit pro IP (Anti-Brute-Force; außer /auth/me und /auth/logout)
RateLimit-Limit- Limit im Fenster
RateLimit-Remaining- wie viel noch übrig ist
RateLimit-Reset- Sekunden bis zum Zurücksetzen des Fensters
Retry-After- Sekunden bis zum nächsten Versuch (bei 429)
HTTP/2 429 Too Many Requests
RateLimit-Limit: <Limit im Fenster>
RateLimit-Remaining: 0
RateLimit-Reset: 37
Retry-After: 37
{ "error": "rate_limited", "message": "Too many requests. Slow down." }API-Tokens
Opake Tokens vpt_… für den Server-to-Server-Zugriff. Der Klartext wird nur einmal bei der Erstellung angezeigt. Speichern Sie ihn. Wir speichern nur den SHA-256-Hash und ein 12-stelliges Präfix zur Anzeige.
/tokensSession / TokenListe Ihrer aktiven Tokens (ohne Secret), nach createdAt absteigend.
/tokensSession / TokenErstellt einen Token; gibt den Klartext zurück (ein einziges Mal).
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
name | string | ja | Name des Tokens, max. 128 |
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":"…" }/tokens/{id}Session / TokenWiderruft den Token. Gibt 204 zurück.
Fehler: token_not_found (404)
Firmen
Firmen (Handelsregisternummer IČO / USt-IdNr. IČ DPH), die Sie verwalten. Der Zugriff ist an die Mitgliedschaft gebunden: Sie sehen nur Firmen, deren Mitglied Sie sind. Wer eine Firma anlegt, wird ihr owner.
/companiessession / tokenListe der Firmen, deren Mitglied Sie sind (mit Ihrer Rolle).
/companiessession / tokenLegt eine Firma an; Sie werden owner, status = pending_verification.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
ico | string | ja | genau 8 Ziffern |
dic | string | ja | Format SK + 10 Ziffern (z. B. SK2121358349) |
legalName | string | ja | Firmenname, max. 500 |
registeredAddress | string | nein | Firmensitz, max. 1000 |
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" }Fehler: ico_taken (409) · validation_failed (400)
/companies/{id}session / tokenFirmendetails (Sie müssen Mitglied sein).
/companies/{id}owner / adminFirma bearbeiten. Die IČO ist unveränderlich (muss der bestehenden entsprechen).
Fehler: forbidden (403) · ico_immutable (400) · company_not_found (404)
Feld status
pending_verification- nach dem Anlegen; das Senden ist noch nicht freigeschaltet
active- verifiziert, Senden freigeschaltet
Rolle des Mitglieds
owner- Ersteller der Firma (Vollzugriff)
admin- Administrator (Änderungen, Webhooks, Verifizierung)
member- Mitglied (Lesen)
viewer- nur Lesen
owner als auch admin (Firmenmanager) zur Verfügung.Verifizierung für das Senden (Verifizierungsmerkmal)
Vor dem Senden muss die Firma über das Verifizierungsmerkmal (Verifikačný údaj, VÚ) verifiziert werden, ein signiertes Token der Finanzverwaltung der Slowakischen Republik (Finančná správa, FS). Nach erfolgreicher Verifizierung wechselt der status der Firma auf active und das Senden wird freigeschaltet.
/companies/{id}/verificationowner / adminSelf-Verify des Verifizierungsmerkmals. Bei Erfolg wird der status auf active gesetzt.
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
token | string | ja | VÚ = Hex-Signatur (prod/pPFS 1024 hex, test/tPFS 768 hex) |
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" }Fehler: company_dic_missing (400) · token_invalid (400) · forbidden (403)
Dokumente
Protokoll der Peppol-Belege (empfangene und gesendete) je Firma. Es füllt sich, während Rechnungen über den Access Point fließen. An die Mitgliedschaft in der Firma gebunden, sortiert nach createdAt absteigend.
/companies/{id}/documentssession / tokenListe der Belege der Firma. Optionaler Filter ?direction=sent|received.
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":"…" } ]/companies/{id}/documents/{docId}session / tokenDetails eines einzelnen Belegs.
Fehler: document_not_found (404)
/companies/{id}/documents/{docId}/downloadsession / tokenEigenständiges HTML der Rechnung zum Drucken. ?inline=1 → Anzeige im Browser, sonst Download.
Liefert text/html, Content-Disposition mit dem Dateinamen faktura-<číslo>.html (číslo = Rechnungsnummer).
SAPI-SK 1.0 (nationale Schnittstelle)
SAPI-SK ist die standardisierte nationale REST-Schnittstelle zwischen dem Kunden-/ERP-System und dem Access Point (sapi-sk.sk). Wir implementieren sie in vollem Umfang. Dadurch sind Sie nicht an die proprietäre Form unserer API gebunden und schreiben die Integration einmal für jeden beliebigen SAPI-SK Access Point.
- Base URL
https://peppol.verteco.digital/sapi- Authentifizierung
- OAuth2 client_credentials → kurzlebiger Access Token (JWT)
- client_id
- UUID Ihres API-Tokens (Liste im Dashboard-Bereich „API tokeny“, dt. API-Tokens)
- client_secret
- der
vpt_…-Token selbst aus dem Portal - Version
- 1.3 (10 Operationen: 4× Auth, 6× Dokumente)
vpt_-Token des Portals noch die Session). Der Widerruf des API-Tokens im Portal macht sofort sowohl /auth/token als auch /auth/renew ungültig.Sandbox (Probeumgebung)
Möchten Sie SAPI-SK ohne Registrierung und ohne Risiko ausprobieren? Verwenden Sie die öffentlichen Sandbox-Zugangsdaten. Die Sandbox validiert Requests genau wie der Produktivbetrieb, sendet aber niemals etwas in das Peppol-Netzwerk und arbeitet nicht mit realen Daten; sie liefert realistische Mock-Responses. Ideal für Entwicklung, CI und das Onboarding einer Integration.
- client_id
sandbox- client_secret
sandbox- send
- vollständige Validierung des API-Kontrakts + Mock 202 (es wird nichts zugestellt)
- receive
- 1 Beispielbeleg
sandbox-doc-0001zum Testen von Parsing und Acknowledge
# 1) Sandbox-Token (ohne Registrierung)
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-Versand: wird validiert, aber es wird NICHTS real zugestellt
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) Beispiel-Postfach + Detail des Beispielbelegs
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>'client_id/client_secret aus dem Portal (unten).Authentifizierung
/sapi/auth/tokenclient_credentialsTauscht client_id + client_secret gegen einen Access Token (15 Min.) und einen Refresh Token (30 Tage). Speichern Sie den Token und verwenden Sie ihn die vollen 15 Minuten: ein neuer Token bei jedem Aufruf ist unnötiger Overhead (das Request-Limit gilt auch für /auth/token).
curl -X POST https://peppol.verteco.digital/sapi/auth/token \
-H 'Content-Type: application/json' \
-d '{ "client_id": "<UUID des Tokens>", "client_secret": "vpt_8f2a…",
"grant_type": "client_credentials" }'
# 200 OK
{ "access_token": "eyJhbGciOi…", "token_type": "Bearer",
"expires_in": 900, "refresh_token": "eyJhbGciOi…" }Fehler: SAPI-AUTH-001 (401) · SAPI-AUTH-003 (401: IP außerhalb der Allowlist des Schlüssels) · SAPI-VAL-001 (400)
/sapi/auth/token/statusBearer (access)Gültigkeit und Ablauf des Access Tokens; should_refresh = true bei < 3 Min. bis zum Ablauf.
/sapi/auth/renewrefresh tokenStellt einen neuen Access + Refresh Token aus. Schlägt fehl, wenn der zugrunde liegende API-Token widerrufen wurde.
// Body
{ "refresh_token": "eyJhbGciOi…" }/sapi/auth/revoke–Gibt immer success zurück (RFC 7009). Dauerhafter Kill-Switch = Widerruf des API-Tokens im Portal.
Versand eines Dokuments
/sapi/document/sendBearer (access)Sendet ein Peppol-Geschäftsdokument (UBL / BIS 3.0) über unseren Access Point an den Empfänger. Der Versand ist fail-closed: die Firma muss ein verifiziertes Verifizierungsmerkmal (Verifikačný údaj) besitzen.
Pflicht-Header:
| Header | Beschreibung |
|---|---|
Authorization | Bearer <access_token> |
X-Peppol-Participant-Id | Teilnehmer, in dessen Namen Sie senden (z. B. 0245:2121358349, also die Ziffern der DIČ, der slowakischen Steuernummer, ohne „SK“) |
Idempotency-Key | eindeutiger Schlüssel; ein wiederholter Aufruf liefert das ursprüngliche Ergebnis |
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": "…" }
# bei Status "REJECTED" enthält die Response zusätzlich das Feld "detail"
# mit dem Ablehnungsgrund (Validierungsregeln, z. B. BR-CO-15)metadata dienen dem Routing und sind technisch: documentId ist Ihr interner Identifikator, nicht die Rechnungsnummer. Die Geschäftsdaten (Rechnungsnummer cbc:ID, Ausstellungsdatum, Fälligkeit, Lieferdatum, Währung, Betrag) übernehmen wir direkt aus dem UBL-Payload; nichts davon senden Sie in metadata, und UBL ist stets die Quelle der Wahrheit für die Anzeige im Portal wie auch für Webhooks.Fehler: SAPI-AUTH-002 (401) · SAPI-AUTH-003 (403) · SAPI-VAL-001 (400) · SAPI-PROC-001 (502) · SAPI-PROC-002 (503) · SAPI-PROC-500 (500: nicht wiederholen, melden Sie die correlation_id)
Empfang von Dokumenten
/sapi/document/receiveBearer (access)Liste der empfangenen Dokumente (älteste zuerst); die Metadaten enthalten auch invoiceNumber, sodass Sie den Beleg ohne Download des Payloads identifizieren. Query: ?pageToken, ?limit (max. 200), ?status (received / acknowledged; unabhängig von Groß-/Kleinschreibung, ein anderer Wert liefert den Fehler SAPI-VAL-001), ?invoiceNumber (exakte Übereinstimmung mit cbc:ID), ?since und ?until (ISO-8601-Instant; Zeitfenster nach Empfangszeitpunkt, z. B. Belege der letzten 5 Tage, Massendownload für eine externe Archivierung oder die Rekonstruktion der Buchhaltung), ?deliveryDateFrom und ?deliveryDateTo (ISO-8601-Datum; Filter nach dem Lieferdatum aus dem Beleg). Der Header X-Peppol-Participant-Id ist Pflicht.
// 200 OK
{ "documents": [ { "documentId":"…","documentTypeId":"…",
"senderParticipantId":"0088:…","receiverParticipantId":"0245:…",
"creationDateTime":"2026-06-17T…Z" } ],
"nextPageToken": "50" }/sapi/document/receive/{documentId}Bearer (access)Detail inklusive Payload (Roh-XML, so wie es über Peppol eingegangen ist).
Fehler: SAPI-RES-001 (404) · SAPI-RES-002 (404: Payload nicht archiviert)
/sapi/document/receive/{documentId}/acknowledgeBearer (access)Bestätigt die Übernahme des Dokuments durch Ihr System. Idempotent.
Status gesendeter Dokumente
/sapi/discovery?receiverId=0245:2121358349Preflight vor dem Versand: Ist der Empfänger im Peppol-Netzwerk registriert und welche Belegtypen kann er empfangen? Derselbe SML/SMP-Lookup, den auch der Access Point durchführt; receiverId akzeptiert eine Peppol-ID, eine USt-IdNr. (IČ DPH) sowie die DIČ selbst. Response: {registered, participantId, smp, documentTypes, checkedAt}. Der Header X-Peppol-Participant-Id ist hier nicht erforderlich.
/sapi/document/sentBearer (access)Zustellstatus gesendeter Belege: pending (Übergabe an das Netzwerk) → submitted → delivered / rejected (bestätigt durch den Zustellnachweis (MLS) vom AP des Empfängers); failed = Transportfehler (ein Retry mit demselben Idempotency-Key sendet erneut). Query: ?pageToken, ?limit, ?status (pending / submitted / sent / delivered / rejected / failed; unabhängig von Groß-/Kleinschreibung, ein anderer Wert liefert den Fehler SAPI-VAL-001), ?invoiceNumber (exakte Übereinstimmung mit cbc:ID: der Status einer konkreten Rechnung mit einem einzigen Aufruf), ?since und ?until (ISO-8601-Instant; Zeitfenster), ?deliveryDateFrom und ?deliveryDateTo (ISO-8601-Datum; Filter nach Lieferdatum).
// 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 }Fehler: SAPI-AUTH-002 (401) · SAPI-AUTH-003 (403) · SAPI-VAL-001 (400: ungültiges since)
Massenversand
/sapi/document/batchBearer (access)Bis zu 100 Dokumente in einem Aufruf. Jedes Element durchläuft die vollständige Logik des Einzelversands (Idempotenz über itemId + idempotencyKey, Reservierung, Submit, Verdikt) und liefert ein eigenes Ergebnis; der Fehler eines Elements stoppt die übrigen nicht. Die Elemente werden sequenziell verarbeitet; failed-Elemente wiederholen Sie mit demselben idempotencyKey.
// 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": "…" } ] }Fehler: SAPI-AUTH-002 (401) · SAPI-AUTH-003 (403) · SAPI-VAL-001 (400: leere/zu große Liste)
Fehlermodell
Alle SAPI-Fehler haben einen einheitlichen Envelope mit Kategorie, stabilem Code, dem Flag retryable und einer correlation_id für den Support.
{ "error": {
"category": "AUTH",
"code": "SAPI-AUTH-001",
"message": "Invalid client credentials.",
"retryable": false,
"correlation_id": "b4191dfc-…" } }Benachrichtigungen & Webhooks
Bei einer empfangenen Rechnung können wir die Firma per E-Mail oder per Webhook (POST an Ihre URL) benachrichtigen. Die Zustellung ist durable: Sie wird in eine Ausgangsqueue geschrieben und asynchron zugestellt (15 s Timeout); bei einem Fehlschlag wiederholen wir bis zu 8× mit exponentiellem Backoff (30 s → max. 1 h), danach Dead-Letter. Ein Ausfall Ihres Servers verliert die Benachrichtigung also nicht, wir stellen sie beim nächsten Versuch zu. Der SSRF-Schutz blockiert Loopback-/lokale Adressen; die Webhook-URL muss öffentlich erreichbar sein.
/companies/{id}/notificationssession / tokenBenachrichtigungseinstellungen: { webhookUrl, notificationEmail, hasSecret }.
/companies/{id}/notificationsowner / adminSetzt beide Kanäle (leerer String = hebt den jeweiligen Kanal auf).
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
webhookUrl | string | nein | leer oder http(s)-URL, max. 512 |
notificationEmail | string | nein | gültige E-Mail-Adresse, max. 256 |
/companies/{id}/webhooksession / tokenWebhook-Konfiguration: { url, hasSecret } (ohne Secret).
/companies/{id}/webhookowner / adminSetzt die Webhook-URL (generiert automatisch ein Signatur-Secret, falls noch keines existiert).
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
url | string | ja | http(s)-URL, max. 512 |
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 }/companies/{id}/webhookowner / adminEntfernt Webhook-URL und Secret. Gibt 204 zurück.
/companies/{id}/webhook/secretowner / adminGeneriert ein neues Signatur-Secret und gibt es EINMALIG zurück; speichern Sie es zur Prüfung von X-Verteco-Signature.
// 200 OK
{ "secret": "vpt_8f2a…" }/companies/{id}/webhook/testowner / adminSendet synchron eine Testbenachrichtigung (Event webhook.test, signiert, SSRF-geschützt) an die gespeicherte URL und gibt zurück, WAS gesendet wurde und WAS zurückkam. Dasselbe lösen Sie mit der Schaltfläche „Otestovať webhook“ (Webhook testen) in den Firmendetails aus.
// 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, wenn keine Webhook-URL gespeichert istWebhook-Payload
Sowohl bei einer empfangenen (invoice.received) als auch bei einer gesendeten (invoice.sent) Rechnung senden wir einen POST mit diesem Body; das Feld event unterscheidet den Typ.
Zwei weitere Events melden das Urteil des Netzwerks zu einer Rechnung, die Sie gesendet haben: invoice.delivered (der Access Point des Empfängers hat die Zustellung mit einem Zustellnachweis (MLS) bestätigt) und invoice.rejected (abgelehnt, entweder durch das Netzwerk oder bereits bei der Validierung vor dem Versand). Sie tragen die Identifikation des Belegs und der Firma (documentId, invoiceNumber, receiverId, peppolMessageId, companyDic, peppolParticipantId) sowie status, statusDetail mit dem Ablehnungsgrund und statusDateTime. Dank ihnen müssen Sie den Status gesendeter Rechnungen nicht per Polling abfragen.
Das Event company.activated kommt, wenn ein Kunde die Auswahl des Anbieters im FS-Portal (VPDS) der Finanzverwaltung der Slowakischen Republik (Finančná správa, FS) abschließt (Body: event, companyId, companyDic, peppolParticipantId, status, verifiedAt); company.deactivated dagegen bei der Abmeldung der Firma aus dem Netzwerk (gleicher Body ohne verifiedAt); Details in der Anleitung /saas:
{
"event": "invoice.received",
"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"
}Die Felder companyDic und peppolParticipantId identifizieren die Firma, auf die sich das Event bezieht (wichtig für Partner, die eine einzige Webhook-URL für alle ihre Kunden verwenden).
Urteil des Netzwerks zu einer gesendeten Rechnung:
{
"event": "invoice.delivered", // oder "invoice.rejected"
"companyId": "08dd…",
"companyDic": "SK2121358349",
"peppolParticipantId": "0245:2121358349",
"documentId": "…",
"invoiceNumber": "2026001",
"receiverId": "0245:2120049096",
"peppolMessageId": "…",
"status": "delivered", // oder "rejected"
"statusDetail": null, // bei rejected: Ablehnungsgrund
"statusDateTime": "2026-06-03T10:15:42Z"
}Aktivierung der Firma nach der Auswahl im FS-Portal (VPDS):
{
"event": "company.activated", // company.deactivated hat denselben Body ohne verifiedAt
"companyId": "08dd…",
"companyDic": "SK2121358349",
"peppolParticipantId": "0245:2121358349",
"status": "active",
"verifiedAt": "2026-06-03T10:02:11Z"
}Den Event-Namen trägt auch der Header X-Verteco-Event. Vollständige Liste der Events: invoice.received, invoice.sent, invoice.delivered, invoice.rejected, company.activated, company.deactivated sowie das Test-Event webhook.test. Einen unbekannten Event-Typ sollten Sie beiseitelegen und protokollieren; einen neuen Typ kündigen wir immer im Voraus an.
Signaturprüfung
Hat die Firma ein Secret, senden wir den Header X-Verteco-Signature in der Form sha256=HMAC-SHA256(secret, raw Body) (lowercase hex). Berechnen Sie den HMAC immer über die exakten Bytes des Bodys:
import crypto from 'node:crypto';
// rawBody = die exakten Bytes des Request-Bodys (kein erneut serialisiertes 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));
}POST /companies/{id}/webhook/secret; es wird einmalig zurückgegeben (eine Rotation erzeugt ein neues). Ohne Secret senden wir den Header X-Verteco-Signature nicht.Massenkonfiguration: ein Token, mehrere Firmen
Ein einziger API-Token (an Ihr Konto/Ihre E-Mail gebunden) verwaltet alle Firmen, die dieses Konto besitzt: Wer eine Firma über POST /companies anlegt, wird deren owner und kann ihren Webhook einrichten. Der Webhook gilt pro Firma (eigene URL und eigenes Secret), sodass Sie mit demselben Token beliebig viele Firmen anbinden:
# 1) Liste Ihrer Firmen (seitenweise, siehe Firmen)
curl 'https://peppol.verteco.digital/api/v1/companies?page=0&limit=100' -H 'Authorization: Bearer vpt_8f2a…'
# 2) für JEDE Firma {id}: Webhook (und/oder E-Mail) setzen
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) Signatur-Secret abholen (wird NUR EINMAL zurückgegeben) und für die Signaturprüfung speichern
curl -X POST https://peppol.verteco.digital/api/v1/companies/{id}/webhook/secret -H 'Authorization: Bearer vpt_8f2a…'
# → { "secret": "…" }- Der Token muss
owner/adminder jeweiligen Firma sein; bei einer Firma, in der er nur member/viewer ist, kommt403 forbiddenzurück (kein Cross-Tenant-Zugriff). - Sie können jeder Firma eine andere URL geben oder allen dieselbe; im Payload unterscheiden Sie sie anhand von
companyId(undreceiverId). - Bei Hunderten von Firmen beachten Sie das Rate-Limit pro Token (den aktuellen Wert liefert der Header
RateLimit-Limit); arbeiten Sie in Batches mit Backoff bei429(HeaderRetry-After). - Der Webhook wird erst dann tatsächlich ausgelöst, wenn die Firma in Peppol aktiv ist (nach der Auswahl von Verteco als Anbieter bei der Finanzverwaltung) und somit tatsächlich Belege empfängt.
Datenmodelle
Felder der von der API zurückgegebenen Objekte.
Company
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
id | UUID | – | Kennung der Firma |
ico | string | – | Handelsregisternummer (IČO, 8 Ziffern) |
dic | string|null | – | USt-IdNr. (IČ DPH) |
legalName | string | – | Firmenname |
registeredAddress | string|null | – | Sitz |
peppolParticipantId | string|null | – | Teilnehmer im Peppol-Netzwerk (nach der Registrierung) |
status | string | – | pending_verification | active |
role | string | – | owner | admin | member | viewer (Ihre Rolle) |
createdAt | Instant | – | Zeitpunkt der Erstellung |
Document
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
id | UUID | – | Kennung des Belegs |
direction | string | – | sent | received |
peppolMessageId | string|null | – | ID der Peppol-Nachricht |
docTypeId | string|null | – | Dokumenttyp (Peppol) |
senderId | string|null | – | Absender (scheme:id) |
receiverId | string|null | – | Empfänger (scheme:id) |
invoiceNumber | string|null | – | Rechnungsnummer |
issueDate | date|null | – | Ausstellungsdatum |
currency | string|null | – | Währung (z. B. EUR) |
totalAmount | number|null | – | Betrag |
status | string | – | Verarbeitungsstatus |
createdAt | Instant | – | Zeitpunkt der Erfassung |
User · Token · Webhook
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
User | Objekt | – | { id: UUID, email: string } |
Token | Objekt | – | { id, name, prefix, lastUsedAt|null, createdAt } |
Webhook | Objekt | – | { url: string|null, hasSecret: boolean } |
Slowakische Konventionen aus der Praxis der Implementierer
Über Peppol BIS Billing 3.0 hinaus einigen sich die slowakischen Hersteller von ERP- und Fakturierungssystemen laufend auf eine gemeinsame Interpretation optionaler Felder (die Diskussion läuft im Slack-Kanal der Finanzverwaltung der Slowakischen Republik, Finančná správa, FS). Nachfolgend die Konventionen, die unser Portal bereits heute berücksichtigt: Alles sind gültige BIS-Konstrukte, sie passieren unsere API unverändert, und empfangene Belege zeigen sie auch in der menschenlesbaren Vorschau und im PDF an.
- Abzug einer versteuerten Anzahlung auf Positionsebene: eine negative Position mit
cac:DocumentReference, wobeicbc:IDdie Nummer des Steuerbelegs zur erhaltenen Zahlung trägt undcbc:DocumentTypeCodeden Wert130hat (BIS: invoice line object identifier, max. 1 pro Position). Eine empfangene Rechnung mit einer solchen Position wird in unserer Vorschau mit dem Hinweis „odpočet zálohy – daňový doklad č. …" (Abzug der Anzahlung, Steuerbeleg Nr. …) angezeigt. - Steuerbeleg zur erhaltenen Zahlung: gemäß den FAQ der Finanzverwaltung wird
InvoiceTypeCode 388(Tax invoice) verwendet. Er passiert sowohl unsere API als auch die Validierung; der Abzug einer nicht bezahlten (nicht versteuerten) Anzahlung wird übercbc:PrepaidAmountausgedrückt. - Stornierung einer Rechnung: Gutschrift
381(CreditNote) mitcac:BillingReferenceauf die ursprüngliche Rechnung, keine negative Rechnung 380. Achtung: Den Code 384 lehnt das Netzwerk für slowakische Parteien ab (die Regel PEPPOL-EN16931-P0112 erlaubt ihn nur zwischen deutschen Subjekten); für eine Korrektur nach oben dient die Belastungsanzeige 383. - BT-83 PaymentID: in der slowakischen Praxis setzt sich für die Zahlerreferenz das Format
/VS…/SS…/KS…durch; das variable Symbol allein ist ebenfalls üblich. Unsere Verarbeitung überträgt den Wert unverändert und zeigt ihn bei den Zahlungsangaben an. - Zusätzliche Positionsangaben (Chargen, Seriennummern, Verfallsdaten) über
cac:AdditionalItemPropertymit etablierten Bezeichnungen wieBatchNumber,SerialNumber,ExpirationDate.
Anlagen zu Belegen (BT-125)
An eine E-Rechnung können Anlagen (PDF, Bilder) als base64 im Element cac:AdditionalDocumentReference (BT-125) angehängt werden. Unser Limit beträgt 25 MB pro Anlage. Peppol legt kein einheitliches netzwerkweites Limit fest, die einzelnen Anbieter bestimmen es selbst (FS FAQ 9/DPH/2025/IM, Beispiel Nr. 67); prüfen Sie daher bei sehr großen Anlagen auch das Limit des Anbieters der Gegenseite.
Öffentliche Werkzeuge (Validierung, Empfängerprüfung)
Zwei Hilfs-Endpoints ohne Authentifizierung: derselbe Validierungskern und derselbe SML/SMP-Lookup, die auch unser Access Point verwendet. Sie eignen sich für CI ebenso wie für Prüfungen vor dem Versand; für sie gilt ein strengeres öffentliches Rate Limit.
/public/peppol-validateöffentlichValidierung einer E-Rechnung gegen EN 16931 + Peppol BIS Billing 3.0 (einschließlich der slowakischen Regeln), dasselbe wie der UI-Validator unter /validator. Request-Body = direkt das UBL 2.1 XML (Invoice / CreditNote, ggf. das gesamte Peppol SBD), Content-Type application/xml, Limit 3 MB.
curl -X POST https://peppol.verteco.digital/api/v1/public/peppol-validate \
-H "Content-Type: application/xml" \
--data-binary @faktura.xml// 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 = leerer Body (empty_document) oder Dokument über 3 MB (document_too_large)/public/peppol-check?id=0245:2121358349öffentlichPrüfung des Empfängers im produktiven Peppol-Netzwerk (SML/SMP-Lookup): Ist er registriert, und welche Belegtypen kann er empfangen? Der Parameter id akzeptiert eine Peppol ID (0245:…), eine USt-IdNr. (IČ DPH, SK…) sowie die reine DIČ (slowakische Steuernummer). Die Bezeichnungen in capabilities werden auf Slowakisch zurückgegeben. Authentifiziertes Gegenstück für ERP-Pipelines: GET /sapi/discovery.
// 200 OK
{
"registered": true,
"participantId": "0245:2121358349",
"smp": "sml.peppol-smp.sk",
"capabilities": ["Faktúra (BIS Billing)", "Dobropis", "Self-billing", "MLS doručenky"],
"lastCheckedAt": "2026-08-31T…Z"
}Status (ping)
Öffentlicher Health-Check-Endpoint, geeignet für das Monitoring.
/pingöffentlichStatus des Backends.
// 200 OK
{ "service": "peppol-portal-backend", "status": "ok", "timestamp": "2026-06-17T…Z" }Eine Live-Übersicht aller Komponenten finden Sie auf der Systemstatus-Seite.
In Vorbereitung
- POST
/companies/{id}/peppol/register· Manuelle Registrierung im Peppol SMP über die API. Heute erfolgt sie automatisch bei der Auswahl des Anbieters im FS-Portal (VPDS) der Finanzverwaltung der Slowakischen Republik (Finančná správa, FS).
POST /companies/{id}/documents/send ist bereits verfügbar (Beta: die Form der Response kann sich noch ändern); für den stabilen programmatischen Versand empfehlen wir SAPI-SK POST /sapi/document/send mit Idempotency-Key.Möchten Sie frühzeitigen Zugang zur Integration, eine Sandbox oder haben Sie eine Frage? Melden Sie sich direkt:
