Dokumentation

Für Entwickler: API-Referenz

Die REST-API des Portals und die nationale Schnittstelle SAPI-SK 1.0: Authentifizierung, Firmen, Belege, Webhooks, Fehler und Limits.

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.

Maschinenlesbare Schnittstelle (OpenAPI 3.1): /api/v1/openapi.json. Importieren Sie sie in Postman, öffnen Sie sie im Swagger Editor, oder generieren Sie sich über 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. 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. 2

    Generieren Sie einen API-Token

    Im Portal API tokeny → Vytvoriť (API-Tokens → Erstellen). Der Token vpt_… wird nur einmal angezeigt. Bewahren Sie ihn sicher auf.
  3. 3

    Führen Sie den ersten Aufruf aus

    Verwenden Sie den Token im Header Authorization:
    javascript
    const res = await fetch('https://peppol.verteco.digital/api/v1/companies', {
    headers: { Authorization: 'Bearer vpt_8f2a…' },
    });
    const companies = await res.json();

Testumgebung (Sandbox)

Testbelege werden nach 60 Tagen automatisch gelöscht: Die Umgebung dient zum Ausprobieren, nicht zur Archivierung. Das Produktivportal ist von dieser Aufbewahrungsfrist nicht betroffen.

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 Anmeldung im FS-Portal; 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 als 9950:SK<DIČ> eingetragen, weil das Schema 0245 einen Verifizierungscode der Finanzverwaltung verlangt, der im Test nicht existiert. In der Produktion wird 0245:<DIČ-Ziffern> erst nach der Auswahl des Anbieters im FS-Portal (Anmeldung per eID oder mit den Zugangsdaten) in das produktive Netzwerk registriert
Validierung
real: Regeln nach EN 16931 + Peppol BIS 3.0, dieselben wie im Produktivbetrieb
Zustellung
real über das Peppol-Testnetzwerk (AS4, Testzertifikat, Test-SML/SMP): ein im Testnetzwerk registrierter Empfänger erhält die Rechnung als empfangene Rechnung (inklusive E-Mail, PDF und Webhook); in das produktive Netzwerk geht nichts hinaus
Zustellnachweis (MLS)
echter MLS-Zustellnachweis aus dem Testnetzwerk
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 durch Anmeldung im FS-Portal (eID oder FS-Zugangsdaten) 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.

Die Testumgebung ist nicht Teil des produktiven Peppol-Netzwerks: Firmen werden in ein getrenntes Peppol-Testnetzwerk (Test-SMP/SML) registriert, nichts geht an reale Endpoints, und die darin enthaltenen Daten können jederzeit gelöscht werden. Verwenden Sie sie nicht für reale Rechnungen; dafür gibt es den Produktivbetrieb unter peppol.verteco.digital, wo die Registrierung im produktiven Netzwerk durch die per Anmeldung (eID oder FS-Zugangsdaten) bestätigte Auswahl des Anbieters im Portal der Finanzverwaltung freigeschaltet wird.

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

json
// 401 Unauthorized
{ "error": "unauthorized", "message": "Authentication required" }
Das Portal (Browser) verwendet ein internes Session-Cookie (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

TypFormatBeispiel
idUUID (String)08dd6c1e-…
ZeitstempelISO-8601 Instant (UTC)2026-06-03T09:40:27Z
issueDateDatum (YYYY-MM-DD)2026-06-03
totalAmountDezimalzahl120.00
fehlende Wertenull (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).

json
// 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)
Unsere API antwortet immer mit JSON und einem Feld 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

CodeHTTPWann
validation_failed400Body hat die Validierung nicht bestanden (siehe fields)
unauthorized401API-Token fehlt oder ist ungültig
forbidden403Aktion erfordert die Rolle owner/admin
company_not_found404Firma existiert nicht oder Sie sind nicht Mitglied
ico_taken409eine Firma mit diesem IČO existiert bereits
ico_immutable400IČO kann nicht geändert werden
company_dic_missing400Verifizierung des Versands ohne DIČ (slowakische Steuernummer) der Firma
token_invalid400Verifizierungsmerkmal (Verifikačný údaj, Signatur) stimmt nicht
document_not_found404Beleg existiert nicht
token_not_found404API-Token existiert nicht / gehört nicht Ihnen
rate_limited429zu 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
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." }
Praktische Antworten für Integratoren:
  • Limits sind an das Credential gebunden, nicht an die IP: 300 Aufrufe pro Minute pro API-Token (SAPI: pro Access-Token), Auth-Endpunkte 20 pro Minute pro IP und eine Schutzobergrenze von 600 pro Minute pro IP auf /api/v1. Eine serverseitige Integration hinter einer IP ist nicht betroffen; höhere Limits setzen wir auf Anfrage, nennen Sie uns die erwartete Spitze.
  • Polling ist ein vollwertiger Weg: GET /sapi/document/sent und /receive mit ?since und ?until alle 1 bis 5 Minuten; Webhooks sind eine Ergänzung, keine Voraussetzung.
  • Zeitstempel: Sendezeit = statusDateTime des Status delivered in /sapi/document/sent (bestätigt durch den MLS-Nachweis), Empfangszeit = creationDateTime in /sapi/document/receive; Übergabezeit der FS-Meldung = fsReportedAt (Webhook invoice.reported oder Feld fsReportedAt der invoice.*-Events); jeder Webhook trägt occurredAt und eventId.
  • PDF: die API liefert druckbares HTML (…/html, Portal ?format=html), aus dem ein PDF im Browser oder mit Headless Chrome gedruckt wird; einen eigenen PDF-Endpunkt gibt es nicht.
  • C#/.NET und andere Sprachen: generieren Sie den Client aus OpenAPI (NSwag, Kiota, openapi-generator); ein eigenes NuGet-Paket liefern wir nicht.

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.

GET/tokensSession / Token

Liste Ihrer aktiven Tokens (ohne Secret), nach createdAt absteigend.

POST/tokensSession / Token

Erstellt einen Token; gibt den Klartext zurück (ein einziges Mal).

FeldTypPflichtBeschreibung
namestringjaName des Tokens, 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

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

GET/companiessession / token

Liste der Firmen, deren Mitglied Sie sind (mit Ihrer Rolle).

POST/companiessession / token

Legt eine Firma an; Sie werden owner, status = pending_verification.

FeldTypPflichtBeschreibung
icostringjagenau 8 Ziffern
dicstringjaUID als SK + 10 Ziffern (z. B. SK2121358349); ein Nicht-Umsatzsteuerzahler sendet nur die 10-stellige DIČ, SK ergänzen wir
legalNamestringjaFirmenname, max. 500
registeredAddressstringneinFirmensitz, 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" }

Fehler: ico_taken (409) · validation_failed (400)

GET/companies/{id}session / token

Firmendetails (Sie müssen Mitglied sein).

PUT/companies/{id}owner / admin

Firma 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
Aktionen „owner / admin" stehen sowohl der Rolle 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.

POST/companies/{id}/verificationowner / admin

Self-Verify des Verifizierungsmerkmals. Bei Erfolg wird der status auf active gesetzt.

FeldTypPflichtBeschreibung
tokenstringjaVÚ = Hex-Signatur (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" }

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.

GET/companies/{id}/documentssession / token

Liste der Belege der Firma. Optionaler 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

Details eines einzelnen Belegs.

Fehler: document_not_found (404)

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

Eigenstä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)
Der SAPI Access Token ist mit einem separaten Schlüssel signiert (es ist weder der 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-0001 zum Testen von Parsing und Acknowledge
curl
# 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>'
Sandbox-Tokens sind isoliert: Sie stellen niemals in Peppol zu und sehen keine realen Belege. Für den produktiven Versand verwenden Sie client_id/client_secret aus dem Portal (unten).

Authentifizierung

POST/sapi/auth/tokenclient_credentials

Tauscht 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
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)

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

Gültigkeit und Ablauf des Access Tokens; should_refresh = true bei < 3 Min. bis zum Ablauf.

POST/sapi/auth/renewrefresh token

Stellt einen neuen Access + Refresh Token aus. Schlägt fehl, wenn der zugrunde liegende API-Token widerrufen wurde.

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

Gibt immer success zurück (RFC 7009). Dauerhafter Kill-Switch = Widerruf des API-Tokens im Portal.

Versand eines Dokuments

POST/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:

HeaderBeschreibung
AuthorizationBearer <access_token>
X-Peppol-Participant-IdTeilnehmer, in dessen Namen Sie senden (z. B. 0245:2121358349, also die Ziffern der DIČ, der slowakischen Steuernummer, ohne „SK“)
Idempotency-Keyeindeutiger Schlüssel pro Sendung; ein wiederholter Aufruf liefert das ursprüngliche Ergebnis und stellt nie doppelt zu. Ausnahme: wurde der erste Versuch schon vor dem Versand abgelehnt (Empfänger nicht im Peppol-Netzwerk, Validierungsfehler, unvollständige Anfrage), führt derselbe Schlüssel den Versand erneut aus, „korrigieren und erneut senden“ funktioniert also unter derselben Rechnungsnummer
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": "…" }
# bei Status "REJECTED" enthält die Response zusätzlich das Feld "detail"
# mit dem Ablehnungsgrund (Validierungsregeln, z. B. BR-CO-15)
metadata vs. UBL: die Felder in 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)

HTTP-Status vs. Verdikt. Nach dem nationalen SAPI-SK-Vertrag antwortet ein Versand immer mit 202, das Verdikt steht im Body (status ACCEPTED oder REJECTED). Soll auch der HTTP-Status das Verdikt tragen, senden Sie Prefer: handling=strict (RFC 7240): eine synchrone Ablehnung kommt dann als 422 mit der SAPI-Fehlerhülle zurück (SAPI-VAL-002 bei einem Validierungsfehler, SAPI-RES-003, wenn an den Empfänger nicht zugestellt werden kann; details[] enthalten providerDocumentId und detail), und die Antwort trägt Preference-Applied: handling=strict. ACCEPTED bleibt unverändert. Vor der Übergabe an den Access Point führen wir denselben SML/SMP-Lookup aus wie er. Ein Empfänger, der gar nicht im Peppol-Netz ist, bedeutet ACCEPTED mit undeliverable: true: Wir haben das Dokument übernommen und melden es der Finanzverwaltung unabhängig von der Zustellung (§ 85o Abs. 11, FS FAQ 9/DPH/2025/IM Beispiel 9), zugestellt wird es aber niemandem; auf GET /sapi/document/sent hat es den Status undeliverable, der Webhook erhält invoice.undeliverable, eine E-Mail geht an niemanden, und derselbe Idempotency-Key sendet nach der Registrierung des Empfängers erneut. Ein Empfänger, der im Netz ist, aber den Dokumenttyp nicht veröffentlicht, erhält sofort REJECTED, ohne Zustellversuch. retrying: true bei ACCEPTED bedeutet, dass der erste Zustellversuch fehlschlug (z. B. vorübergehend nicht erreichbares SMP) und der Access Point ihn selbst wiederholt, in der Regel innerhalb von 20 Minuten; detail nennt den Grund, das endgültige Verdikt kommt über GET /sapi/document/sent oder den Webhook.
POST/sapi/document/validateBearer (access)

Validiert ein Dokument ohne Versand: dieselben Regeln EN 16931 + Peppol BIS 3.0, die der Access Point vor dem Versand anwendet. Nichts wird gespeichert oder versendet; funktioniert auch mit dem Sandbox-Token. Body = das JSON-Paar { payload, payloadFormat } wie bei /document/send oder direkt das XML (Content-Type: application/xml). Limit 10 MB.

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

Fehler: SAPI-AUTH-002 (401) · SAPI-VAL-001 (400: leerer Body, ungültiges JSON, payloadFormat ungleich XML, > 10 MB) · SAPI-SYS-002 (502: Validator vorübergehend nicht verfügbar, erneut versuchen)

Empfang von Dokumenten

GET/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.

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 inklusive Payload (Roh-XML, so wie es über Peppol eingegangen ist).

Fehler: SAPI-RES-001 (404) · SAPI-RES-002 (404: Payload nicht archiviert)

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

Bestätigt die Übernahme des Dokuments durch Ihr System. Idempotent.

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

Archivlink (metadata.links.xml): das Geschäftsdokument selbst als XML-Datei, gleicher Inhalt wie der Payload im Detail.

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

Archivlink (metadata.links.html): generische druckbare Darstellung der Rechnung (HTML).

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

Archiv-Link (metadata.links.pdf): das PDF des Belegs. Hat der Lieferant sein eigenes Rechnungs-PDF ins XML eingebettet (BT-125), erhalten Sie dieses Original; sonst ein auf Anfrage aus dem gespeicherten XML erzeugtes PDF (kein PDF wird gespeichert). Header X-Verteco-Pdf-Source: supplier | generated. 404 SAPI-RES-002 nach Löschung des Inhalts, 503 bei nicht verfügbarem Renderer (später erneut oder /html).

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

Archivlink (metadata.links.attachments[].url): die Bytes eines im Dokument eingebetteten Anhangs, z. B. das PDF-Original des Lieferanten. Erzwungener Download.

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

Link ohne Anmeldung (optional): erzeugt einen NEUEN Zufallsschlüssel für den Beleg ({"rotate":true} ersetzt den bestehenden). Liefert url (Seite), xmlUrl, htmlUrl, pdfUrl und expiresAt; der Schlüssel wird nur einmal angezeigt. Die Firma muss „Download ohne Anmeldung“ aktiviert haben (sonst 403 SAPI-AUTH-004). DELETE widerruft ihn.

Archiv statt Kopie: jedes empfangene Dokument trägt in den Metadaten links (xml, html, pdf, Anhänge) und retention. Ein Integrator kann neben dem gebuchten Beleg nur den Link speichern und das Dokument erst beim Anzeigen laden; die Links verlangen denselben Bearer-Token und den Header X-Peppol-Participant-Id. retention.mode = storage bedeutet, dass die Firma empfangene Belege bei uns archiviert und wir das Original während der gesamten Vertragsbeziehung aufbewahren (AGB 11a.1). retention.mode = secure bedeutet, dass die Firma empfangene Belege bei uns nicht archiviert: der Inhalt wird zum Zeitpunkt retention.contentAvailableUntil unwiderruflich gelöscht, dem je Dokument gespeicherten geplanten Löschdatum (standardmäßig 14 Tage nach Empfang; die Frist gilt getrennt für empfangene und gesendete Belege, und jede Änderung der Einstellung zählt für bestehende Belege ab dem Tag der Änderung, nie früher), danach liefern die Links SAPI-RES-002; ein null-Wert bei mode secure heißt, dass wir den Inhalt wegen einer noch offenen Steuermeldung halten und kein Datum versprechen. Der Firmeninhaber stellt das Archiv im Firmendetail getrennt für empfangene und ausgestellte Belege ein. Links ohne Anmeldung: aktiviert der Inhaber im Firmendetail „Rechnungen ohne Anmeldung herunterladen“, liefert POST /sapi/document/receive/{id}/public-link einen Link mit eigenem Zufallsschlüssel, der den Beleg ohne Token öffnet (url für Menschen, xmlUrl und htmlUrl für Programme). Der Schlüssel steht genau einmal in dieser Antwort; der Link endet spätestens mit contentAvailableUntil, ist widerrufbar (DELETE) und antwortet nach Ablauf mit 410 und Grund, dann fordern Sie einen neuen an. Ohne die Portaleinstellung antwortet der Aufruf mit 403 SAPI-AUTH-004.
Links ohne Anmeldung: die Felder links.* verlangen immer den Bearer-Token (Archiv-Links). Hat die Firma „Download ohne Anmeldung“ aktiviert (Firmendetail → Datenarchiv oder PUT /companies/{id}/public-links), trägt der Belegdetail metadata.publicLink mit fertigen Adressen url, xmlUrl, htmlUrl und pdfUrl – bei jedem Lesen dieselbe Adresse und dieselbe, die der Nutzer im Portal an der Rechnung sieht. Speichern Sie sie zum gebuchten Beleg; POST …/public-link brauchen Sie nur, um einen neuen Schlüssel zu erzwingen. Bei ausgeschalteter Option ist publicLink.issued=false mit reason=public_links_disabled. Der Link ist dauerhaft: Er gilt, solange der Beleginhalt gespeichert ist (bei einer Firma ohne Archivierung empfangener Belege bis retention.contentAvailableUntil); expiresAt ist deshalb null (in SAPI-Antworten fehlt das Feld), nichts muss erneuert werden. xmlUrl und links.xml liefern den Geschäftsbeleg selbst (Wurzel Invoice/CreditNote) ohne die SBDH-Transporthülle. pdfUrl und links.pdf liefern das vom Lieferanten ins XML eingebettete PDF (BT-125), falls vorhanden, sonst ein aus dem XML erzeugtes PDF; der Header X-Verteco-Pdf-Source sagt, welches (supplier | generated).

Status gesendeter Dokumente

GET/sapi/discovery?receiverId=0245:2121358349

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

GET/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).

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 }

Fehler: SAPI-AUTH-002 (401) · SAPI-AUTH-003 (403) · SAPI-VAL-001 (400: ungültiges since)

Massenversand

POST/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.

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": "…" } ] }

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.

json
{ "error": {
  "category": "AUTH",
  "code": "SAPI-AUTH-001",
  "message": "Invalid client credentials.",
  "retryable": false,
  "correlation_id": "b4191dfc-…" } }
Hinweis: beim Versand stellen wir das Geschäftsdokument zu; die C2-Seite der Steuermeldung (TDD) zu gesendeten Belegen ist in Vorbereitung. Die Steuermeldung beim Empfang (C3) erzeugen und übermitteln wir automatisch an die Finanzverwaltung der Slowakischen Republik (Finančná správa, FS).

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.

GET/companies/{id}/notificationssession / token

Benachrichtigungseinstellungen: { webhookUrl, notificationEmail, hasSecret }.

PUT/companies/{id}/notificationsowner / admin

Setzt beide Kanäle (leerer String = hebt den jeweiligen Kanal auf).

FeldTypPflichtBeschreibung
webhookUrlstringneinleer oder http(s)-URL, max. 512
notificationEmailstringneingültige E-Mail-Adresse, max. 256
GET/companies/{id}/webhooksession / token

Webhook-Konfiguration: { url, hasSecret } (ohne Secret).

PUT/companies/{id}/webhookowner / admin

Setzt die Webhook-URL (generiert automatisch ein Signatur-Secret, falls noch keines existiert).

FeldTypPflichtBeschreibung
urlstringjahttp(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

Entfernt Webhook-URL und Secret. Gibt 204 zurück.

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

Generiert ein neues Signatur-Secret und gibt es EINMALIG zurück; speichern Sie es zur Prüfung von X-Verteco-Signature.

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

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

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, wenn keine Webhook-URL gespeichert ist

Webhook-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. Ist der erste Zustellversuch fehlgeschlagen und wiederholt ihn der Access Point selbst (in der Regel innerhalb einer Stunde), bleibt das Dokument submitted und statusDetail beginnt mit network_retrying: ; eine Zustellung ergibt dann delivered, ein Aufgeben rejected mit dem Präfix network_gave_up: . 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). company.smp_registered_elsewhere kommt, wenn eine Firma die FS-Auswahl abgeschlossen hat, ihr Eintrag im nationalen SMP aber von einem anderen Anbieter gehalten wird (Body wie company.activated plus action: "migration_code_required" und migrateUrl). Das Vorgehen für Partner steht in der Vermittler-Dokumentation. Bis der Kunde einen Migrationscode eingibt, stellt das Netzwerk an den alten Anbieter zu. Details in der Anleitung /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"
}

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:

json
{
"event": "invoice.delivered",          // oder "invoice.rejected"
"occurredAt": "2026-09-03T15:04:05Z",
"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):

json
{
"event": "company.activated",          // company.deactivated hat denselben Body ohne verifiedAt
"occurredAt": "2026-09-03T15:04:05Z",
"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, invoice.undeliverable, invoice.reported, company.activated, company.deactivated, company.smp_registered_elsewhere 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. Jedes Event trägt zusätzlich occurredAt (Ereigniszeit, ISO-8601 UTC) zum Sortieren sowie eventId (derselbe Wert wie im Header X-Verteco-Delivery-Id): eindeutig pro Event und bei jeder Wiederholung unverändert, also der richtige Schlüssel zum Deduplizieren (peppolMessageId wiederholt sich über invoice.sent, invoice.delivered und invoice.reported). invoice.*-Events tragen zusätzlich die Zeiten sentAt, deliveredAt, receivedAt und fsReportedAt (ISO-8601 UTC, null, bis das Ereignis eingetreten ist). invoice.reported kommt, wenn die Steuermeldung an die Finanzverwaltung (TDD) zum Beleg dem Zustellnetz übergeben wurde (§ 85o Abs. 11), bei einem Ausfall des C5 auch Tage später. Formale Schemata aller Events stehen im Abschnitt webhooks der OpenAPI-Spezifikation.

Der Partner-Benachrichtigungs-Webhook, die Kundenverwaltung (release, pause-sending) und das Abrechnungsmodell stehen nur eingetragenen Vermittlern zur Verfügung und sind beschrieben unter Für Vermittler.

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:

javascript
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));
}

Header jeder Zustellung: X-Verteco-Event (Event-Name), X-Verteco-Delivery-Id (= eventId im Body, bei jeder Wiederholung identisch), X-Verteco-Signature (der HMAC oben) und parallel die Standard-Webhooks-Header: webhook-id (= eventId), webhook-timestamp (Unix-Sekunden dieses Versuchs) und webhook-signature = v1,base64(HMAC-SHA256(secret, id + "." + timestamp + "." + body)). Schlüssel sind die UTF-8-Bytes Ihres Secrets; eine standardwebhooks-Bibliothek erwartet es als whsec_ + base64(secret). Empfohlene Verarbeitung: Signatur prüfen, Zustellungen mit einem webhook-timestamp älter als 5 Minuten ablehnen (Replay-Schutz), idempotent nach eventId verarbeiten, innerhalb von 15 Sekunden mit 2xx antworten und schwere Arbeit in die eigene Queue verlagern. Eine fehlgeschlagene Zustellung wiederholen wir 8-mal im Abstand von 30 s bis 1 h; danach landet sie im Dead-Letter mit E-Mail-Hinweis und bleibt im Realtime-Log sichtbar. Eine produktive Webhook-URL muss https:// sein; die Testumgebung akzeptiert auch http://.

Das Secret erhalten Sie über 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:

bash
# 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/admin der jeweiligen Firma sein; bei einer Firma, in der er nur member/viewer ist, kommt 403 forbidden zurü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 (und receiverId).
  • 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 bei 429 (Header Retry-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

FeldTypPflichtBeschreibung
idUUIDKennung der Firma
icostringHandelsregisternummer (IČO, 8 Ziffern)
dicstring|nullUSt-IdNr. (IČ DPH)
legalNamestringFirmenname
registeredAddressstring|nullSitz
peppolParticipantIdstring|nullTeilnehmer im Peppol-Netzwerk (nach der Registrierung)
statusstringpending_verification | active
rolestringowner | admin | member | viewer (Ihre Rolle)
createdAtInstantZeitpunkt der Erstellung

Document

FeldTypPflichtBeschreibung
idUUIDKennung des Belegs
directionstringsent | received
peppolMessageIdstring|nullID der Peppol-Nachricht
docTypeIdstring|nullDokumenttyp (Peppol)
senderIdstring|nullAbsender (scheme:id)
receiverIdstring|nullEmpfänger (scheme:id)
invoiceNumberstring|nullRechnungsnummer
issueDatedate|nullAusstellungsdatum
currencystring|nullWährung (z. B. EUR)
totalAmountnumber|nullBetrag
statusstringVerarbeitungsstatus
createdAtInstantZeitpunkt der Erfassung

User · Token · Webhook

FeldTypPflichtBeschreibung
UserObjekt{ id: UUID, email: string }
TokenObjekt{ id, name, prefix, lastUsedAt|null, createdAt }
WebhookObjekt{ 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, wobei cbc:ID die Nummer des Steuerbelegs zur erhaltenen Zahlung trägt und cbc:DocumentTypeCode den Wert 130 hat (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 über cbc:PrepaidAmount ausgedrückt.
  • Stornierung einer Rechnung: Gutschrift 381 (CreditNote) mit cac:BillingReference auf 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:AdditionalItemProperty mit etablierten Bezeichnungen wie BatchNumber, SerialNumber, ExpirationDate.
Es handelt sich um Konventionen der Community, nicht um verbindliche nationale Regeln: Das empfangende System muss auch einen Beleg verarbeiten können, der sie nicht verwendet. Sobald die Diskussion bei der Finanzverwaltung abgeschlossen wird, aktualisieren wir diesen Abschnitt laufend (verfolgen Sie /changelog).

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.

POST/public/peppol-validateöffentlich

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

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 = leerer Body (empty_document) oder Dokument über 3 MB (document_too_large)
GET/public/peppol-check?id=0245:2121358349öffentlich

Prü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.

json
// 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"
}

MCP-Server (KI-Assistenten)

Das Portal betreibt einen eigenen Server für das Model Context Protocol, den offenen Standard, über den KI-Assistenten (Claude Code, Claude Desktop, Cursor, VS Code Copilot und andere) externe Systeme anbinden. Der Assistent meldet sich mit einem gewöhnlichen API-Schlüssel des Kontos an und erhält sechs Werkzeuge; er sieht nie mehr als dieses Konto, und jeder Aufruf erscheint im API-Log des Schlüssels.

Adresse: https://peppol.verteco.digital/api/mcp. Transport: Streamable HTTP, JSON-RPC 2.0, zustandslos (POST /api/mcp, kein SSE-Stream, GET antwortet 405). Anmeldung mit dem Header Authorization: Bearer <API-Schlüssel>. Unterstützte Methoden: initialize, ping, tools/list, tools/call, resources/list, prompts/list.

Werkzeuge

  • list_companies: Firmen des Kontos mit Peppol-ID, Status und Rolle (Beginn jedes Gesprächs)
  • list_documents: gesendete/empfangene Rechnungen einer Firma mit Zustell- und Meldestatus, seitenweise
  • get_document: eine Rechnung im Detail samt MLS-Quittung und Meldung, optional das UBL-XML (bis 1 MB)
  • check_participant: Live-Prüfung des Empfängers im Peppol-Netz (SML/SMP)
  • validate_document: UBL-Validierung gegen EN 16931 + BIS 3.0 einschließlich der slowakischen Regeln
  • send_document: Versand einer Rechnung im Namen der Firma, erfordert confirm = true, die Versandfreigabe (Verifikačný údaj) gilt

Anbindung in Claude Code

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

Claude Desktop (über die Brücke mcp-remote, benötigt Node.js)

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

Der Versand einer Rechnung ist rechtlich bindend: Das Werkzeug lehnt einen Aufruf ohne confirm = true ab, und die Serveranweisungen verpflichten den Assistenten, die ausdrückliche Zustimmung des Nutzers zur konkreten Rechnung und zum Empfänger einzuholen. Fertige Konfigurationen für Cursor und VS Code sowie eine Schaltfläche zum Erstellen des Schlüssels finden Sie im Portal: API-Schlüssel → Reiter MCP-Server. API-Schlüssel → MCP-Server

Status (ping)

Öffentlicher Health-Check-Endpoint, geeignet für das Monitoring.

GET/pingöffentlich

Status des Backends.

json
// 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

Der Kern für Versand und Empfang (AS4) ist fertig und getestet. Die folgenden Endpoints pro Firma ergänzen wir noch; ihre Form kann sich noch ändern. Partner können frühzeitigen Zugang erhalten.
  • 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:

Miriama Mrkávková

Ihr Peppol-Kontakt

Miriama Mrkávková

+421 944 488 269·peppol​@​verteco.digital