Entwickler & API
epago ist doppelte Buchführung mit DATEV-Kontenrahmen und GoBD-Regeln als
Datenbank-Trigger. Die Public API v1 ist kein zweiter, laxerer Weg daneben: jeder
Schreibzugriff läuft durch dieselbe Prüfschicht wie die Oberfläche — Soll gleich
Haben, Steuerautomatik, Periodensperre, Storno statt Löschen. Zugang bekommst du über
einen API-Schlüssel mit Scope read oder
write, in zwei Umgebungen: einer Sandbox zum
Ausprobieren und dem Live-Zugang zu deiner echten Buchhaltung.
Zuerst: die Sandbox
Bevor du gegen deine echte Buchhaltung testest, nimm die Sandbox. Jeder Account
bekommt einen eigenen Test-Mandanten mit einem kleinen Startbestand — Kunden, einen
Lieferanten, Rechnungen und Buchungen — unabhängig von deinen echten Daten. Ein
Sandbox-Schlüssel beginnt mit epago_test_ und
erreicht ausschließlich diesen Test-Mandanten, nie deine echte Buchhaltung.
- Anlegen und Zurücksetzen im Tab API-Schlüssel unter Mein Konto in der App.
- Zurücksetzen erzeugt einen frischen Test-Mandanten mit neuem Startbestand. Der alte bleibt als abgeschlossener Stand stehen — GoBD gilt auch in der Sandbox, gelöscht wird nichts.
- Aus der Sandbox geht nichts nach draußen: keine E-Mail, keine ELSTER-Übermittlung, keine Bankverbindung, keine Belastung eines Zahlungsmittels.
- Ein eigener Login in die Sandbox existiert nicht — erreichbar ist sie ausschließlich über die API, mit dem Sandbox-Schlüssel.
- Rate-Limit 30 Anfragen pro Minute (Live-Schlüssel: 60 — beides am Schlüssel einstellbar).
Live-Schlüssel für deine echte Buchhaltung legst du an derselben Stelle an, sobald du mit der Sandbox durch bist.
Quickstart in drei Schritten
1. Sandbox-Schlüssel anlegen
Unter Mein Konto → API-Schlüssel
einen Schlüssel mit Umgebung Sandbox
und Scope write erzeugen. Er beginnt mit epago_test_ und wird nur einmal angezeigt —
sofort sichern.
2. Verbindung testen
Die Antwort nennt Mandant, Schlüssel-Scopes und die Umgebung — an einem
Sandbox-Schlüssel steht dort "apiKey.umgebung": "sandbox".
curl -H "Authorization: Bearer $EPAGO_KEY" \
https://app.epago.de/api/v1/me {
"tenant": { "userId": "sandbox:…", "companyName": "Testmandant (Sandbox)" },
"apiKey": { "keyId": "…", "permissions": ["read", "write"], "umgebung": "sandbox" }
} 3. Erste Buchung als Entwurf
Die vereinfachte Form: Bruttobetrag, Geldkonto, Sachkonto — der Server baut die
Buchungszeilen samt Steuerzeile mit dem Kontenrahmen des Mandanten. Ein Entwurf
(status: "draft", auch der Default) bucht
nichts fest und lässt sich wieder löschen — der richtige Einstieg für die erste
Anfrage. Die Kontonummern sind SKR03 (Quelle: wissen/datev-kontenrahmen/skr03-2026.json
im Repository) — 1000 Kasse, 8400 Erlöse 19 % USt; im SKR04 sind es andere
Nummern, die Antwort nennt den Kontenrahmen des Mandanten mit.
curl -X POST -H "Authorization: Bearer $EPAGO_KEY" \
-H "Content-Type: application/json" \
https://app.epago.de/api/v1/journal-entries \
-d '{
"date": "2026-06-12",
"description": "Barverkauf",
"status": "draft",
"betrag": 119.00,
"bruttoKonto": "1000",
"sachKonto": "8400"
}' Weitere Beispiele — Buchung mit fertigen Zeilen, Storno, Zahlung an einem Beleg — stehen in der Referenz unten und in der Postman-Sammlung.
API-Referenz
Alle vierzehn Endpunkte mit Parametern, Beispielen und Antwortschemata — geladen zur Laufzeit aus derselben Spezifikation, aus der auch die Postman-Sammlung entsteht.
Authentifizierung und Grenzen
Schlüssel senden
Als Authorization: Bearer epago_… oder im x-api-key-Header. Ein Schlüssel gehört zu
genau einem Mandanten; der Mandant kommt immer aus dem Schlüssel, nie aus dem
Anfragekörper.
Scopes
read liest alles. write legt Buchungen an, storniert und lädt Belege hoch — schließt read ein. Nie Default: beim Erzeugen eines Schlüssels explizit wählen.
Rate-Limit
60 Anfragen/Minute je Live-Schlüssel, 30 je Sandbox-Schlüssel (Schiebefenster,
einstellbar am Schlüssel), zusätzlich 600/Minute je IP. Das Limit gilt über alle
Instanzen hinweg. Jede Antwort trägt den Zustand des Fensters in X-RateLimit-Limit, X-RateLimit-Remaining und X-RateLimit-Reset; eine 429 zusätzlich in Retry-After (Sekunden).
Widerruf
Ein Schlüssel lässt sich jederzeit unter Mein Konto widerrufen oder ablaufen lassen — die Sperre wirkt sofort, ohne Cache-Verzögerung.
Fehlerformat und -codes
Jeder Fehler hat denselben Aufbau, ohne Stacktrace, ohne interne Pfade:
{ "error": { "code": "insufficient_scope", "message": "…" } } | HTTP | Code | Bedeutung |
|---|---|---|
| 400 | bad_request / validation_error | Pflichtfeld fehlt, Soll ist nicht gleich Haben, ungueltige Steuerzeile oder Status. |
| 400 | file_too_large / unsupported_media_type | Beleg-Upload: Datei ueber 10 MB oder nicht erlaubter Dateityp. |
| 400 | content_mismatch | Beleg-Upload: Dateiinhalt passt nicht zum angegebenen mimeType (Magic-Bytes-Pruefung). |
| 400 | zahlungsart_erforderlich / begruendung_erforderlich / rueckzahlungsgrund_erforderlich | Eine Zahlung laesst einen Rest offen oder ist eine Rueckzahlung: die Antwort traegt Vorschlag, Restbetrag und Optionen mit. |
| 401 | missing_api_key / invalid_api_key / api_key_expired / api_key_revoked | Schluessel fehlt, ist ungueltig, abgelaufen oder widerrufen. |
| 402 | feature_locked / account_gesperrt | Die API gehoert nicht zum Tarif des Mandanten, oder der Zugang ist wegen einer offenen Zahlung gesperrt. |
| 403 | insufficient_scope | Der Schluessel hat den benoetigten Scope nicht (zum Beispiel read auf einer Schreib-Route). |
| 404 | not_found | Ressource nicht gefunden — auch bei einer ID aus einem fremden Mandanten. |
| 409 | period_closed / conflict / cannot_reverse | Festgeschriebene Periode, oder eine Buchung, die nicht stornierbar ist. |
| 409 | payment_not_allowed | Auf diesen Beleg geht keine Zahlung ein (Entwurf, storniert, unbekannter Status). |
| 423 | konto_gesperrt_loeschantrag | Der Mandant hat die Loeschung seiner Daten beantragt; bis zur Ruecknahme wird nicht mehr verarbeitet. |
| 429 | rate_limit_exceeded | Rate-Limit ueberschritten. Retry-After nennt die Wartezeit in Sekunden. |
| 500 | internal_error / reverse_incomplete | Serverfehler. Bei reverse_incomplete: die Gegenbuchung wurde angelegt, die Markierung des Originals ist fehlgeschlagen — nicht automatisch wiederholen, Support kontaktieren. |
Versionierung und Änderungsverlauf
Die Version steht im Pfad (/api/v1). Innerhalb
von v1 wird nur additiv geändert: neue Endpunkte, neue optionale Felder, neue
Fehlerfelder. Ein bestehendes Feld verschwindet nicht und wechselt nicht die
Bedeutung. info.version in der Spezifikation
folgt SemVer; eine nicht additive Änderung bekäme /api/v2.
- Jede Antwort traegt den Zustand des Rate-Limit-Fensters: X-RateLimit-Limit, X-RateLimit-Remaining und X-RateLimit-Reset (Unix-Zeit in Sekunden).
- Die Absage mit Status 429 nennt zusaetzlich Retry-After in Sekunden (RFC 9110, 10.2.3).
- Das Limit gilt ueber alle Instanzen hinweg. Vorher zaehlte jede Function-Instanz fuer sich, das zugesagte Limit war damit nicht belastbar.
- GET /me nennt die Umgebung des Schlüssels (umgebung: live oder sandbox). Sandbox-Schlüssel (epago_test_…) arbeiten auf dem Testmandanten des Accounts, den man unter Mein Konto → API-Schlüssel anlegt und zurücksetzt; sie erreichen die echte Buchhaltung nie und haben ein Limit von 30 Anfragen pro Minute.
- Zahlungen an Belegen: POST /invoices/{id}/payments erfasst eine Zahlung, GET /invoices/{id}/payments liest die erfassten Zahlungen. Bleibt nach der Zahlung ein Rest offen, ist zahlungsart Pflicht (vollzahlung, teilzahlung, skonto, forderungsausfall); die Absage traegt Vorschlag, Restbetrag und Optionen mit, statt eine Entgeltminderung zu raten.
- Vereinfachte Buchungsform: POST /journal-entries nimmt neben lines[] auch betrag, bruttoKonto und sachKonto. Die Buchungszeilen samt Steuerzeile baut der Server mit dem Kontenrahmen des Mandanten, und die Antwort sagt mit zeilen, steuerschluessel, split und kontenrahmen, was daraus geworden ist. Mit seite gibt der Aufrufer die Soll- oder Habenseite an, wo der Steuerfall sie nicht vorgibt.
- Jede Operation nennt ihren Scope in x-scope (read oder write).
- Die Absagen des Zugangs stehen jetzt an jeder Operation: 402 bei fehlendem Tarif oder gesperrtem Zugang (feature_locked, account_gesperrt), 423 bei einem laufenden Loeschantrag (konto_gesperrt_loeschantrag). Der 402 traegt seinen Fehlercode, statt als bad_request herauszukommen.
- Die Beschreibung der Spezifikation nennt Grenzen, Fehlerformat und Versionsregel.
- Start der Public API v1 mit 14 Endpunkten: Verbindungstest (/me), Kontenplan und Kontenblatt, Buchungen lesen, anlegen und stornieren, Rechnungen lesen, Beleg hochladen, BWA, Umsatzsteuer-Voranmeldung und Saldenliste.
- Authentifizierung per API-Schluessel (Authorization: Bearer oder x-api-key) mit den Scopes read und write.
- Schreibzugriffe laufen durch dieselbe Pruefschicht wie die Oberflaeche: Soll gleich Haben, Steuerautomatik, Periodensperre, Storno statt Loeschen.
- Kein DELETE: gebuchte Saetze werden nicht geloescht, sondern storniert.
Postman
Die Sammlung entsteht aus derselben Spezifikation wie die Referenz oben — kein von Hand gepflegtes Duplikat.
- Beide Dateien in Postman importieren (Sammlung und Umgebung).
- Die Umgebung epago API
oben rechts auswählen und in der Variable
apiKeydeinen Schlüssel eintragen —baseUrlsteht bereits aufhttps://app.epago.de/api/v1. - Anfrage auswählen und senden — für einen ersten Test eignet sich
GET /memit einem Sandbox-Schlüssel.
MCP-Server
epago-mcp ist ein zweiter Zugang zu derselben
API — für Claude, Codex und andere MCP-fähige Werkzeuge. Er läuft lokal (stdio),
braucht denselben API-Schlüssel wie ein direkter Aufruf und schreibt über dieselbe
Prüfschicht. Zum Ausprobieren eignet sich ein Sandbox-Schlüssel.
Was die API nicht kann
Damit hier nichts steht, was die API nicht hält: aktuell nicht möglich sind eine Rechnung anlegen, eine Mahnung auslösen, Kunden verwalten, ein PDF eines Belegs abrufen und jedes Löschen — gebuchte Sätze werden storniert, nicht gelöscht (GoBD). Lesend geht mehr als schreibend: Rechnungen, Zahlungen und Berichte lassen sich abrufen, angelegt werden sie noch nicht über die API. Was davon geplant ist, steht ohne Termin im Änderungsverlauf oben, sobald es so weit ist.