ehrlicheElster API
API-Dokumentation
Eine REST-API, um ehrlicheElster in andere Plattformen zu integrieren: Rechnungen, Kunden, Ausgaben, Projekte, Zeiterfassung und Angebote erstellen und abrufen, per Webhooks über Ereignisse informiert werden — mit API-Keys, Scopes, Idempotenz und Rate-Limits.
https://api.ehrlicheelster.de/v1API-Key erstellenEinführung
Die ehrlicheElster API ist eine REST-Schnittstelle. Alle Anfragen laufen über HTTPS, Anfrage- und Antwortkörper sind JSON. Basis-URL:
https://api.ehrlicheelster.de/v1Jede Anfrage gehört zu genau einer Organisation – bestimmt durch den verwendeten API-Key. Ressourcen, die du erstellst, erscheinen sofort in deinem ehrlicheElster-Konto und folgen denselben Steuer- und Nummernregeln wie manuell angelegte Belege.
Authentifizierung
Authentifiziere jede Anfrage mit einem API-Key aus Einstellungen → Sonstiges → API-Keys. Ein Key beginnt mit ee_live_ und wird nur einmal angezeigt. Sende ihn im Authorization-Header als Bearer-Token (alternativ x-api-key):
Authorization: Bearer ee_live_dein_api_keyKeys tragen Scopes, die den Zugriff je Endpunkt begrenzen:
| Scope | Erlaubt |
|---|---|
| rechnungen:read | Rechnungen auflisten, abrufen, PDF laden |
| rechnungen:write | Rechnungen erstellen, Status ändern |
| kunden:read | Kunden auflisten und abrufen |
| kunden:write | Kunden anlegen und ändern |
| ausgaben:read | Ausgaben auflisten und abrufen |
| ausgaben:write | Ausgaben anlegen und ändern |
| projekte:read | Projekte auflisten und abrufen |
| projekte:write | Projekte anlegen und ändern |
| zeiten:read | Zeiteinträge und laufenden Timer abrufen |
| zeiten:write | Zeiten erfassen, ändern, löschen, Timer steuern |
| angebote:read | Angebote auflisten, abrufen, PDF laden |
| angebote:write | Angebote anlegen, ändern, in Rechnung umwandeln |
| webhooks:read | Webhooks auflisten und abrufen |
| webhooks:write | Webhooks anlegen und löschen |
Verbindung testen
/v1/mePrüft den API-Key und liefert Organisation, Scopes und Key-Info zurück — ideal, um beim Einrichten einer Integration die Verbindung zu verifizieren. Jeder gültige Key funktioniert (kein spezieller Scope nötig).
curl https://api.ehrlicheelster.de/v1/me -H "Authorization: Bearer ee_live_dein_api_key"{
"object": "account",
"organizationId": "665f…",
"organizationName": "Muster GmbH",
"scopes": ["rechnungen:read", "rechnungen:write", "kunden:read", "kunden:write", "ausgaben:read", "ausgaben:write", "webhooks:read", "webhooks:write"],
"apiKey": { "id": "665f…", "name": "Zapier", "prefix": "ee_live_a1b2", "last4": "9f3d" }
}Rate-Limits
Die API gibt es ab dem Pro-Plan. Pro API-Key sind im Pro-Plan 60 Anfragen pro Minute, im Business-Plan 120 Anfragen pro Minute erlaubt (Pro: 2 Keys und 5 Webhooks, Business: 20 Keys und 25 Webhooks). Jede Antwort enthält den aktuellen Stand; bei Überschreitung kommt 429 rate_limited mit einem Retry-After-Header.
| Header | Bedeutung |
|---|---|
| X-RateLimit-Limit | Maximale Anfragen pro Fenster. |
| X-RateLimit-Remaining | Verbleibende Anfragen im aktuellen Fenster. |
| X-RateLimit-Reset | Unix-Zeit (Sekunden), zu der das Fenster zurückgesetzt wird. |
Idempotenz
Damit ein Netzwerk-Retry keine doppelte Rechnung erzeugt, sende bei POST-Anfragen einen Idempotency-Key-Header (z. B. eine UUID). Die erste Anfrage wird verarbeitet, jede Wiederholung mit demselben Key liefert exakt dieselbe Antwort zurück (Header Idempotent-Replayed: true). Keys gelten 24 Stunden.
Idempotency-Key: 3f1c9a7e-8b2d-4e5f-9a1b-2c3d4e5f6a7bWird derselbe Key mit einem anderen Body verwendet, antwortet die API mit 422 idempotency_key_reused.
Pagination & Filter
List-Endpunkte sind cursor-basiert. Eine Antwort enthält data, has_more und next_cursor. Für die nächste Seite übergibst du next_cursor als starting_after.
Statuscodes & Fehler
Fehler liefern einen passenden HTTP-Status und einen JSON-Körper mit den Feldern error (Kürzel) und message (Klartext).
| Status | Code | Bedeutung |
|---|---|---|
| 200 / 201 | OK / Created | Erfolg — Ressource geladen bzw. erstellt. |
| 400 | validation_error · invalid_json | Eingabe fehlerhaft — Details im Feld message. |
| 401 | unauthorized | API-Key fehlt, ist ungültig oder wurde widerrufen. |
| 403 | forbidden | Dem API-Key fehlt der nötige Scope für diesen Endpunkt. |
| 404 | not_found | Ressource existiert nicht (oder gehört nicht zu deiner Organisation). |
| 409 | duplicate_rechnungsnummer · request_in_progress · invalid_status_transition · period_locked · entry_locked · timer_already_running · already_converted · duplicate_angebotsnummer | Konflikt: Nummer bereits vergeben, idempotente Anfrage läuft noch, Statuswechsel nicht erlaubt, Periode festgeschrieben (GoBD), Zeiteintrag gesperrt, Timer läuft bereits, Angebot schon in Rechnung umgewandelt. |
| 422 | idempotency_key_reused | Idempotency-Key mit abweichendem Body wiederverwendet. |
| 429 | rate_limited | Rate-Limit überschritten — siehe Retry-After-Header. |
| 500 | internal_error | Unerwarteter Serverfehler. |
Rechnung erstellen
/v1/rechnungenErstellt eine Rechnung. Netto-, Steuer- und Bruttobeträge werden serverseitig aus den Positionen berechnet, die Rechnungsnummer bei Bedarf automatisch vergeben. Scope: rechnungen:write.
Anfrageparameter
| Feld | Typ | Beschreibung |
|---|---|---|
| kunde | object | Empfänger. Pflicht: name oder firmenname. Weitere Felder: strasse, hausnummer, plz, ort, land, email, telefon, ustIdNr, isGeschaeftskunde. |
| positionen | array | Mindestens eine Position: beschreibung, menge, einzelpreis (netto) — plus optional einheit, steuersatz (0 | 7 | 19), rabatt (%). |
| rechnungsnummer | string | Optional. Ohne Angabe wird die nächste Nummer nach deinem Format vergeben (z. B. RE-2026-001). |
| datum | string | Optional, ISO-Datum. Standard: heute. |
| lieferdatum | string | Optional, ISO-Datum (Leistungsdatum). |
| zahlungszielTage | number | Optional. Fälligkeitsdatum = datum + N Tage. |
| kleinunternehmer | boolean | Optional. true setzt alle Positionen auf 0 % USt (§ 19 UStG). |
| waehrung | string | Optional, ISO-4217 (Standard EUR). Bei Fremdwährung wird der EUR-Gegenwert eingefroren. |
| status | string | Optional: "entwurf" oder "offen" (Standard "offen"). |
Beispiel-Anfrage
{
"kunde": {
"firmenname": "Muster GmbH",
"strasse": "Hauptstraße 12",
"plz": "10115",
"ort": "Berlin",
"land": "DE",
"email": "[email protected]",
"ustIdNr": "DE123456789",
"isGeschaeftskunde": true
},
"positionen": [
{ "beschreibung": "Webdesign – Konzept & Umsetzung", "menge": 12, "einheit": "Stunden", "einzelpreis": 95, "steuersatz": 19 },
{ "beschreibung": "Hosting (Jahrespauschale)", "menge": 1, "einheit": "Pauschale", "einzelpreis": 120, "steuersatz": 19 }
],
"zahlungszielTage": 14
}curl -X POST https://api.ehrlicheelster.de/v1/rechnungen \
-H "Authorization: Bearer ee_live_dein_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"kunde": { "firmenname": "Muster GmbH", "plz": "10115", "ort": "Berlin" },
"positionen": [
{ "beschreibung": "Beratung", "menge": 2, "einzelpreis": 150, "steuersatz": 19 }
],
"zahlungszielTage": 14
}'Antwort (201 Created)
{
"success": true,
"id": "665f1a2b3c4d5e6f7a8b9c0d",
"rechnungsnummer": "RE-2026-001",
"status": "offen",
"datum": "2026-06-29T00:00:00.000Z",
"faelligkeitsdatum": "2026-07-13T00:00:00.000Z",
"waehrung": "EUR",
"nettobetrag": 1260,
"umsatzsteuer": 239.4,
"bruttobetrag": 1499.4,
"steuerDetails": [ { "steuersatz": 19, "bemessungsgrundlage": 1260, "nettobetrag": 1260, "steuerbetrag": 239.4 } ]
}Rechnungen auflisten
/v1/rechnungenLiefert Rechnungen, neueste zuerst. Scope: rechnungen:read.
Query-Parameter
| Parameter | Typ | Beschreibung |
|---|---|---|
| limit | number | 1–100, Standard 25. |
| starting_after | string | Cursor: die id des letzten Elements der vorigen Seite (aus next_cursor). |
| status | string | Filtert nach Status (z. B. offen, bezahlt, storniert). |
| from / to | string | ISO-Datumsbereich über das Rechnungsdatum. |
| kunde | string | Teiltextsuche über den Kundennamen (case-insensitive). |
curl "https://api.ehrlicheelster.de/v1/rechnungen?limit=25&status=offen" \
-H "Authorization: Bearer ee_live_dein_api_key"
# Nächste Seite: next_cursor der vorigen Antwort als starting_after übergeben
curl "https://api.ehrlicheelster.de/v1/rechnungen?limit=25&starting_after=665f1a2b3c4d5e6f7a8b9c0d" \
-H "Authorization: Bearer ee_live_dein_api_key"Antwort
{
"object": "list",
"data": [
{ "id": "665f…", "rechnungsnummer": "RE-2026-042", "status": "offen", "bruttobetrag": 1499.4, "bezahlt": false, "datum": "2026-06-29T00:00:00.000Z", "kunde": "Muster GmbH" }
],
"has_more": true,
"next_cursor": "665f1a2b3c4d5e6f7a8b9c0d"
}Rechnung abrufen
/v1/rechnungen/:idRuft eine einzelne Rechnung ab. Scope: rechnungen:read. Unbekannte IDs liefern 404 not_found.
curl https://api.ehrlicheelster.de/v1/rechnungen/665f1a2b3c4d5e6f7a8b9c0d \
-H "Authorization: Bearer ee_live_dein_api_key"Rechnung als PDF
/v1/rechnungen/:id/pdfLiefert die Rechnung als PDF (application/pdf). Mit ?format=zugferd erhältst du ein hybrides ZUGFeRD/Factur-X-E-Rechnungs-PDF. Scope rechnungen:read.
curl https://api.ehrlicheelster.de/v1/rechnungen/665f…/pdf \
-H "Authorization: Bearer ee_live_dein_api_key" \
-o rechnung.pdfStatus ändern / bezahlt markieren
/v1/rechnungen/:idÄndert den Status einer Rechnung (z. B. als bezahlt oder storniert markieren). GoBD-Regeln werden geprüft; das Buchungsjournal wird automatisch nachgezogen und passende Webhooks (rechnung.paid, rechnung.storniert) ausgelöst. Erlaubte Felder: status, bezahlt, bezahltAm. Scope rechnungen:write.
curl -X PATCH https://api.ehrlicheelster.de/v1/rechnungen/665f… \
-H "Authorization: Bearer ee_live_dein_api_key" \
-H "Content-Type: application/json" \
-d '{ "status": "bezahlt" }'Ein unzulässiger Übergang (z. B. einer bereits stornierten Rechnung) antwortet mit 409 invalid_status_transition.
Kunden
Kunden anlegen, ändern und lesen — z. B. um deinen Kundenstamm mit einer anderen Plattform zu synchronisieren.
/v1/kunden/v1/kunden/v1/kunden/:id/v1/kunden/:idPflicht beim Anlegen: name oder firmenname. Die Kundennummer wird automatisch vergeben (z. B. K0001). Listen unterstützen ?search= sowie Pagination. Scopes kunden:read / kunden:write.
curl -X POST https://api.ehrlicheelster.de/v1/kunden \
-H "Authorization: Bearer ee_live_dein_api_key" \
-H "Content-Type: application/json" \
-d '{
"firmenname": "Muster GmbH",
"strasse": "Hauptstraße 12",
"plz": "10115",
"ort": "Berlin",
"email": "[email protected]",
"ustIdNr": "DE123456789",
"umsatzsteuerKategorie": "inland"
}'Ausgaben
Ausgaben/Belege erfassen und lesen — z. B. um Kosten aus einer anderen Plattform zu importieren. Netto und Vorsteuer werden aus bruttobetrag und steuersatz berechnet; die Buchung ins Journal erfolgt automatisch.
/v1/ausgaben/v1/ausgabenPflicht: lieferant, beschreibung, bruttobetrag, kategorie, datum. Identische Ausgaben (gleicher Betrag, Lieferant, Datum) werden automatisch als Dublette erkannt und zusammengeführt statt doppelt angelegt. Scopes ausgaben:read / ausgaben:write.
curl -X POST https://api.ehrlicheelster.de/v1/ausgaben \
-H "Authorization: Bearer ee_live_dein_api_key" \
-H "Content-Type: application/json" \
-d '{
"lieferant": "Amazon EU",
"beschreibung": "Büromaterial",
"bruttobetrag": 47.60,
"steuersatz": 19,
"kategorie": "buerobedarf",
"datum": "2026-06-20"
}'Einzelne Ausgabe abrufen / ändern
/v1/ausgaben/:id/v1/ausgaben/:idPATCH aktualisiert nur die übergebenen Felder. Bei Änderung von bruttobetrag oder steuersatz werden Netto und Vorsteuer neu berechnet (Mischsätze via nettobetrag + vorsteuer überschreiben die Automatik). Ist der Beleg bereits ins Journal gebucht und die Periode festgeschrieben (GoBD), antwortet die API mit 409 period_locked — eine Änderung ist dann nur per Storno und Neuerfassung möglich. Scope ausgaben:write.
| Feld | Typ | Beschreibung |
|---|---|---|
| beschreibung | string | Freitext-Beschreibung des Belegs. |
| datum | string | ISO-Datum des Belegs. |
| kategorie | string | Ausgabenkategorie (z. B. buerobedarf, bewirtung). |
| lieferant | string | Name des Lieferanten/Händlers. |
| bruttobetrag | number | Bruttobetrag. Bei Änderung werden nettobetrag und vorsteuer neu berechnet. |
| steuersatz | number | 0, 7 oder 19. Löst zusammen mit bruttobetrag eine Neuberechnung aus. |
| nettobetrag / vorsteuer | number | Optionaler Mischsatz-Override: beide zusammen übergeben, dann werden sie exakt übernommen statt gerechnet. |
| waehrung | string | ISO-4217 (3 Buchstaben). Bei Fremdwährung wird der EUR-Gegenwert neu eingefroren. |
| bezahlt | boolean | true setzt zusätzlich bezahltAm auf jetzt. |
| belegnummer | string | Interne Belegnummer. |
curl -X PATCH https://api.ehrlicheelster.de/v1/ausgaben/665f1a2b3c4d5e6f7a8b9c0d \
-H "Authorization: Bearer ee_live_dein_api_key" \
-H "Content-Type: application/json" \
-d '{ "bruttobetrag": 59.90, "steuersatz": 19, "bezahlt": true }'Projekte
Projekte sind der Kostenträger für Zeiterfassung und Faktura. Anlegen, ändern und lesen — z. B. um Projekte aus einem PM-Tool zu spiegeln.
/v1/projekte/v1/projekte/v1/projekte/:id/v1/projekte/:idDie Projektnummer wird automatisch als P-1, P-2 … vergeben. Es gibt kein DELETE — archiviere stattdessen per PATCH mit status: "archiviert". Scopes projekte:read / projekte:write.
Query-Parameter (Liste)
| Parameter | Typ | Beschreibung |
|---|---|---|
| limit | number | 1–100, Standard 25. |
| starting_after | string | Cursor: die id des letzten Elements der vorigen Seite (aus next_cursor). |
| status | string | Filtert nach Status (aktiv, pausiert, abgeschlossen, archiviert). |
| kundeId | string | Nur Projekte des angegebenen Kunden. |
| search | string | Teiltextsuche über Name und Projektnummer (case-insensitive). |
Anfrageparameter (POST)
| Feld | Typ | Beschreibung |
|---|---|---|
| name | string | Pflicht. Name des Projekts (Kostenträger). |
| kundeId | string | Optional. Verknüpfung zu einem Kunden. |
| kundeName | string | Optional. Anzeigename des Kunden (Cache). |
| status | string | Optional: aktiv (Standard), pausiert, abgeschlossen, archiviert. |
| abrechnungsart | string | Optional: "aufwand" (Standard) oder "nicht_abrechenbar". |
| stundensatz | number | Optional. Standard-Stundensatz in EUR (Standard 0). |
| standardSteuersatz | number | Optional: 0, 7 oder 19 (Standard 19). |
| abrechenbarDefault | boolean | Optional. Ob neue Zeiten standardmäßig abrechenbar sind (Standard true). |
| budgetStunden | number | Optional. Stundenbudget des Projekts. |
| farbe | string | Optional. Farbmarkierung (z. B. #0071e3). |
| startDatum / endeDatum | string | Optional. Laufzeit des Projekts. |
| notiz | string | Optional. Interne Notiz. |
| nummer | string | Optional. Ohne Angabe wird automatisch P-1, P-2 … vergeben. |
Beispiel
curl -X POST https://api.ehrlicheelster.de/v1/projekte \
-H "Authorization: Bearer ee_live_dein_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "Website-Relaunch Muster GmbH",
"kundeName": "Muster GmbH",
"stundensatz": 95,
"standardSteuersatz": 19,
"budgetStunden": 40
}'Antwort (201 Created)
{
"success": true,
"id": "665f1a2b3c4d5e6f7a8b9c0d",
"nummer": "P-1",
"name": "Website-Relaunch Muster GmbH",
"status": "aktiv",
"kundeId": null,
"kundeName": "Muster GmbH",
"abrechnungsart": "aufwand",
"stundensatz": 95,
"standardSteuersatz": 19,
"abrechenbarDefault": true,
"budgetStunden": 40,
"taetigkeiten": [],
"createdAt": "2026-07-06T10:00:00.000Z",
"updatedAt": "2026-07-06T10:00:00.000Z"
}Zeiterfassung
Zeiten auf Projekte buchen, ändern, löschen und per Timer live erfassen. Das datum ist immer ein Leistungsdatum im Format YYYY-MM-DD.
/v1/zeiten/v1/zeiten/v1/zeiten/:id/v1/zeiten/:id/v1/zeiten/:id/v1/zeiten/timer/v1/zeiten/timerEinträge sind nur editier- und löschbar, solange sie status: "offen" haben und nicht festgeschrieben sind — fakturierte oder festgeschriebene Zeit ist schreibgeschützt und antwortet mit 409 entry_locked. Scopes zeiten:read / zeiten:write.
Query-Parameter (Liste)
| Parameter | Typ | Beschreibung |
|---|---|---|
| limit | number | 1–100, Standard 25. |
| starting_after | string | Cursor: die id des letzten Elements der vorigen Seite. |
| projektId | string | Nur Zeiten des angegebenen Projekts. |
| status | string | Filtert nach Status (z. B. offen, fakturiert). |
| abrechenbar | string | "true" oder "false" — filtert nach Abrechenbarkeit. |
| from / to | string | Datumsbereich (YYYY-MM-DD) über das Leistungsdatum. |
Anfrageparameter (POST)
| Feld | Typ | Beschreibung |
|---|---|---|
| projektId | string | Pflicht. ID des Projekts, auf das die Zeit gebucht wird. |
| datum | string | Pflicht. Leistungsdatum im Format YYYY-MM-DD. |
| dauerMinuten | number | string | Pflicht. Dauer in ganzen Minuten — oder Freitext wie "1:30", "1,5h", "90m". |
| beschreibung | string | Optional. Tätigkeitsbeschreibung (Standard leer). |
| abrechenbar | boolean | Optional. Standard = abrechenbarDefault des Projekts. |
| stundensatz | number | Optional. Überschreibt den Satz; wird bei Erfassung eingefroren. |
| taetigkeitId / taetigkeit | string | Optional. Tätigkeit per ID (aus dem Projekt) oder als Freitext. |
Beispiel
curl -X POST https://api.ehrlicheelster.de/v1/zeiten \
-H "Authorization: Bearer ee_live_dein_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"projektId": "665f1a2b3c4d5e6f7a8b9c0d",
"datum": "2026-07-06",
"dauerMinuten": "1:30",
"beschreibung": "Frontend-Arbeiten"
}'Antwort (201 Created)
{
"success": true,
"id": "665f…",
"projektId": "665f1a2b3c4d5e6f7a8b9c0d",
"projektName": "Website-Relaunch Muster GmbH",
"datum": "2026-07-06",
"dauerMinuten": 90,
"beschreibung": "Frontend-Arbeiten",
"abrechenbar": true,
"stundensatz": 95,
"status": "offen",
"laufend": false,
"festgeschrieben": false,
"quelle": "manuell",
"createdAt": "2026-07-06T12:30:00.000Z",
"updatedAt": "2026-07-06T12:30:00.000Z"
}Timer (start / stop / cancel)
Ein laufender Timer ist ein Zeiteintrag mit laufend: true — es gibt genau einen je API-Key. stop finalisiert die Dauer, lässt den Eintrag aber auf status: "offen" (Mensch-Verify vor der Faktura); cancel verwirft ihn. Ein zweiter start bei laufendem Timer antwortet mit 409 timer_already_running.
# Timer starten
curl -X POST https://api.ehrlicheelster.de/v1/zeiten/timer \
-H "Authorization: Bearer ee_live_dein_api_key" \
-H "Content-Type: application/json" \
-d '{ "action": "start", "projektId": "665f1a2b3c4d5e6f7a8b9c0d", "beschreibung": "Bugfixing" }'
# Laufenden Timer abfragen → { "timer": { … } | null }
curl https://api.ehrlicheelster.de/v1/zeiten/timer -H "Authorization: Bearer ee_live_dein_api_key"
# Timer stoppen (Eintrag bleibt status:"offen") oder verwerfen
curl -X POST https://api.ehrlicheelster.de/v1/zeiten/timer \
-H "Authorization: Bearer ee_live_dein_api_key" \
-H "Content-Type: application/json" \
-d '{ "action": "stop" }' # alternativ: { "action": "cancel" }Angebote
Angebote erstellen, ändern, als PDF laden und in eine Rechnung umwandeln. Aufbau und Steuerlogik entsprechen den Rechnungen — Netto-, Steuer- und Bruttobeträge werden serverseitig aus den Positionen berechnet.
/v1/angebote/v1/angebote/v1/angebote/:id/v1/angebote/:id/v1/angebote/:id/convert/v1/angebote/:id/pdfStatus-Ablauf: entwurf → versendet → angenommen / abgelehnt → in_rechnung. Der Wechsel auf angenommen löst das Webhook-Event angebot.angenommen aus. Scopes angebote:read / angebote:write.
Query-Parameter (Liste)
| Parameter | Typ | Beschreibung |
|---|---|---|
| limit | number | 1–100, Standard 25. |
| starting_after | string | Cursor: die id des letzten Elements der vorigen Seite. |
| status | string | Filtert nach Status (entwurf, versendet, angenommen, abgelehnt, in_rechnung). |
| from / to | string | ISO-Datumsbereich über das Angebotsdatum. |
| kunde | string | Teiltextsuche über den Empfängernamen (case-insensitive). |
Anfrageparameter (POST)
| Feld | Typ | Beschreibung |
|---|---|---|
| kunde | object | Empfänger. Pflicht: name oder firmenname. Weitere Felder: ansprechpartner, strasse, hausnummer, plz, ort, land, email, telefon, ustIdNr, kundenNr, isGeschaeftskunde. |
| positionen | array | Mindestens eine Position: beschreibung, menge, einzelpreis (netto) — plus optional einheit, steuersatz (0–100), rabatt (%). |
| angebotsnummer | string | Optional. Ohne Angabe wird die nächste Nummer vergeben (z. B. AG-2026-001). |
| datum | string | Optional, ISO-Datum. Standard: heute. |
| gueltigkeitsdauer | number | Optional. Gültigkeit in Tagen (Standard 30). Setzt gueltigBis = datum + N. |
| zahlungsziel | number | Optional. Zahlungsziel in Tagen (Standard 14). |
| kleinunternehmer | boolean | Optional. true setzt alle Positionen auf 0 % USt (§ 19 UStG). |
| waehrung | string | Optional, ISO-4217 (Standard EUR). |
| notizen | string | Optional. Freitext auf dem Angebot. |
| status | string | Optional: "entwurf" (Standard) oder "versendet". |
Angebot erstellen
curl -X POST https://api.ehrlicheelster.de/v1/angebote \
-H "Authorization: Bearer ee_live_dein_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"kunde": { "firmenname": "Muster GmbH", "plz": "10115", "ort": "Berlin" },
"positionen": [
{ "beschreibung": "Konzept & Design", "menge": 8, "einzelpreis": 110, "steuersatz": 19 }
],
"gueltigkeitsdauer": 30,
"zahlungsziel": 14
}'In Rechnung umwandeln
Erzeugt aus dem Angebot eine Rechnung (Status entwurf), bucht sie ins Journal und markiert das Angebot als in_rechnung. Ein bereits umgewandeltes Angebot antwortet bei erneutem Aufruf mit 409 already_converted. Das PDF liefert GET /v1/angebote/:id/pdf als application/pdf.
curl -X POST https://api.ehrlicheelster.de/v1/angebote/665f…/convert \
-H "Authorization: Bearer ee_live_dein_api_key"
# Antwort: die erzeugte Rechnung (status "entwurf") — 201 Created.
# Erneuter Aufruf → 409 already_converted (mit rechnungId & rechnungsnummer).Webhooks
Registriere eine HTTPS-URL, um bei Ereignissen benachrichtigt zu werden — ideal, um nachgelagerte Prozesse in anderen Plattformen auszulösen (z. B. Fulfillment bei Zahlung).
Verfügbare Events
| Event | Ausgelöst wenn |
|---|---|
| rechnung.created | Eine Rechnung erstellt wurde (App oder API). |
| rechnung.paid | Eine Rechnung als bezahlt markiert wurde. |
| rechnung.storniert | Eine Rechnung storniert wurde. |
| kunde.created | Ein Kunde angelegt wurde. |
| ausgabe.created | Eine Ausgabe erfasst wurde. |
| projekt.created | Ein Projekt angelegt wurde. |
| zeiteintrag.created | Ein Zeiteintrag erfasst wurde. |
| angebot.created | Ein Angebot erstellt wurde. |
| angebot.angenommen | Ein Angebot als angenommen markiert wurde. |
Webhook registrieren
/v1/webhooksScope webhooks:write. Die Antwort enthält einmalig das secret zum Prüfen der Signaturen. Endpunkte verwalten: GET /v1/webhooks, DELETE /v1/webhooks/:id.
curl -X POST https://api.ehrlicheelster.de/v1/webhooks \
-H "Authorization: Bearer ee_live_dein_api_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://deine-app.de/hooks/ehrlicheelster",
"events": ["rechnung.created", "rechnung.paid"]
}'
# Antwort enthält EINMALIG das Signatur-Secret:
# { "id": "…", "url": "…", "events": [...], "secret": "whsec_…" }Zustellung & Payload
Wir senden ein POST mit dem Event im Header X-EhrlicheElster-Event und einer Signatur in X-EhrlicheElster-Signature. Fehlgeschlagene Zustellungen werden bis zu dreimal wiederholt.
POST https://deine-app.de/hooks/ehrlicheelster
X-EhrlicheElster-Event: rechnung.paid
X-EhrlicheElster-Signature: t=1751200000,v1=9f2b…
{
"id": "evt_2a1b3c…",
"type": "rechnung.paid",
"created": 1751200000,
"data": {
"id": "665f…",
"rechnungsnummer": "RE-2026-001",
"status": "bezahlt",
"bezahlt": true,
"bruttobetrag": 1499.4
}
}Signatur prüfen
Der Header hat das Format t=<timestamp>,v1=<hmac>. Berechne HMAC-SHA256 über `${t}.${rawBody}` mit deinem Webhook-Secret und vergleiche konstant-zeitig.
import crypto from "crypto";
// Wichtig: den ROHEN Request-Body verwenden (nicht den geparsten!).
function istGueltig(rawBody, header, secret) {
const teile = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const erwartet = crypto
.createHmac("sha256", secret)
.update(`${teile.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(teile.v1), Buffer.from(erwartet));
}SDKs & Integrationen
Aktuell gibt es kein offizielles SDK — die API ist bewusst eine schlanke REST-Schnittstelle, die sich mit jeder Standard-HTTP-Bibliothek ansprechen lässt. Die Beispiele in dieser Doku zeigen genau das: cURL, fetch in JavaScript und requests in Python. Alles, was du brauchst, ist der Bearer-Token im Authorization-Header.
Für No-Code-Anbindungen lassen sich Webhooks + REST mit Automatisierungs-Tools wie Zapier oder Make kombinieren: ein Webhook (z. B. rechnung.paid) stößt einen Ablauf an, der per REST weitere Ressourcen liest oder anlegt.
MCP-Server für KI-Assistenten
ehrlicheElster spricht das Model Context Protocol (MCP). Damit arbeiten Claude, ChatGPT, Cursor und andere MCP-fähige Assistenten direkt mit deiner Buchhaltung — Rechnungsentwürfe anlegen, Belege ansehen und Bankzahlungen zuordnen, Kunden und Produkte nachschlagen — ohne die App zu öffnen. Du meldest dich mit deinem ehrlicheElster-Konto an (OAuth 2.1), siehst auf der Freigabeseite genau, welche Rechte der Assistent bekommt, und kannst die Verbindung jederzeit trennen. Derzeit im Betatest: die Freigabe verlangt einen Freischaltcode.
https://api.ehrlicheelster.de/v1/mcpVerbinden
- claude.ai / Claude Desktop: Customize → Connectors → „Add custom connector“ → Adresse einfügen → „Sign in“ → anmelden → Rechte freigeben.
- Claude Code:
claude mcp add --transport http ehrlicheelster https://api.ehrlicheelster.de/v1/mcp, danach/mcp→ anmelden. - Cursor / ChatGPT / andere: als Remote-MCP-Server mit OAuth hinzufügen. Alternativ akzeptiert der Server einen API-Key als
Authorization: Bearer ee_live_…(nur mit den Scopes des Keys).
Was der Assistent darf
Rechte werden je Verbindung als Scopes vergeben — dieselben Namen wie bei API-Keys: rechnungen:read/write, kunden:read/write, ausgaben:read/write, bank:read/write, dazu berichte, steuern, buchhaltung, lohn, projekte, zeiten, vertraege, kontakte, aufgaben. Werkzeuge, die dein Tarif nicht enthält (z. B. Bankumsätze ohne Bankanbindung), sieht der Assistent gar nicht erst.
- Verkaufen: Kunden und Produkte, Angebote, Auftragsbestätigungen, Lieferscheine, Rechnungen (Entwurf, Gutschrift, Storno, Abschlag/Schluss), Mahnungen, Versand per E-Mail, Zahlungseingänge.
- Belege & Ausgaben: wartende Belege finden, Beleg als Bild ansehen, Ausgaben und Einnahmen, Eingangsrechnungen (auch aus PDF/XRechnung), Kontierung und Regeln, Belegdateien hochladen — direkt aus dem Assistenten oder über einen gebundenen Upload-Link.
- Bank: Kontostände, Umsätze, Zuordnung zu Belegen und Rechnungen, Reste schließen.
- Berichte & Steuern: GuV, BWA, EÜR, Cashflow, offene Posten, Steuerübersicht und -rücklage, UStVA-Posten, Zusammenfassende Meldung, Steuerwissen mit Fundstelle.
- Team, Projekte, Verträge, Aufgaben: Mitarbeiter und Lohnabrechnungen (eigener Scope, Beschäftigtendaten), Projekte und Zeiterfassung, Verträge, Kontakte, Aufgaben.
Insgesamt 118 Werkzeuge, dazu Einstiegs-Prompts (Rechnung schreiben, Belege zuordnen, offene Posten, Monatsüberblick) und die Firmeneinstellungen als Resource.
Grundsätze: Rechnungen entstehen als Entwurf, Zuordnungen brauchen deine Bestätigung im Chat, nichts wird still gebucht oder ans Finanzamt gesendet. Jeder Zugriff steht mit deinem Namen im Audit-Log deiner Firma.
Datenschutz
Der verbundene Assistent ruft die freigegebenen Daten bei seinemAnbieter ab — je nach Anbieter und Tarif auch außerhalb der EU. Er ist dein Auftragsverarbeiter, nicht unserer; unser Server steht in Deutschland. Wir empfehlen einen Business-Tarif mit Auftragsverarbeitungsvertrag (z. B. Claude Team/Enterprise oder API-Zugang), das Trainings-Opt-out beim Anbieter zu prüfen und nur die Rechte freizugeben, die du brauchst. Details in der Datenschutzerklärung und im AVV.
Support
Fragen zur API oder ein Problem mit einer Integration? Schreib uns an [email protected] — oder verwalte deine API-Keys direkt in den Einstellungen. Ein kompromittierter Key kann dort jederzeit widerrufen werden.