Vera API
Zwei Zugänge, eine klare Grenze. Du arbeitest mit deinen eigenen Daten; Steuerkanzleien mit denen ihrer Mandanten. E-Rechnungen erzeugst und prüfst du über beide Seiten, einzeln oder hundert auf einmal.
Deine Rechnungen, Belege und dein Profil. E-Rechnung erzeugen, eigener DATEV-Export, Validator, USt-IdNr.-Check.
Die Daten einwilligender Mandanten: Mandantenliste, SuSa, OPOS, Kontierung korrigieren. Nur für Partner-Kanzleien.
Überblick
Normale REST-Endpoints, JSON rein und raus, die Statuscodes, die du erwartest. Versioniert über das Präfix /v1.
https://api.vera-business.com/v1
Welche Endpoints du erreichst, bestimmt der Typ deines Schlüssels. Diese Grenze ist strikt: ein User-Key erreicht nie Mandantendaten, ein Kanzlei-Key nie eigene Daten. Validator und USt-IdNr.-Prüfung stehen beiden Schlüsseltypen offen.
Die Grenze
Einige Endpoints sind Kanzleien vorbehalten: Sie arbeiten mit fremden Daten und setzen die Einwilligung des Mandanten voraus. Jeder solche Zugriff wird protokolliert.
| Was | vera_uk_ | vera_sk_ |
|---|---|---|
| Eigene Rechnungen / Belege / Profil lesen | ||
| Eigene Rechnung anlegen write | ||
| E-Rechnung erzeugen, EN16931-XML | ||
| DATEV-Export, EXTF | eigene | je Mandant |
| Validieren & in Massen validieren | ||
| USt-IdNr. über VIES prüfen | ||
Mandantenliste /clients | ||
| SuSa, OPOS, Abschlussreife je Mandant | ||
| Kontierung korrigieren write |
Schnellstart
Als Nutzer
Schalte den Vera-API-Tarif frei. Er liegt über Vera Pro, siehe Zugang. Erzeuge dann in Vera unter Einstellungen → „API & Integrationen" einen Key. Der Klartext wird genau einmal angezeigt.
curl https://api.vera-business.com/v1/invoices \ -H "Authorization: Bearer $VERA_UK"
Als Kanzlei
Im Cockpit Partner werden, 2FA einschalten, der Mandant bestätigt das Mandat. Den Key erzeugst du dann im Cockpit unter „API & Integrationen".
curl https://api.vera-business.com/v1/clients \ -H "Authorization: Bearer $VERA_SK"
Schlüssel & Scopes
Jeder Aufruf trägt einen Key im Authorization-Header. Am Präfix siehst du sofort, welche Seite er bedient:
Authorization: Bearer vera_uk_… # deine eigenen Daten Authorization: Bearer vera_sk_… # Mandantendaten der Kanzlei
Gespeichert wird nur der SHA-256-Hash, den Klartext siehst du einmalig. Verlierst du einen Key, widerrufst du ihn und erzeugst einen neuen. Keys tragen Scopes; fehlt einer, kommt 403 zurück.
| Seite | Scopes |
|---|---|
| User | invoices:read invoices:write expenses:read profile:read einvoice:generate datev:read validate vat:read |
| Kanzlei | clients:read invoices:read expenses:read readiness:read kontierung:write |
Konventionen
- Beträge kommen als String mit zwei Nachkommastellen, etwa
"1190.00", damit nichts gerundet wird. - Datum ist ISO-8601:
YYYY-MM-DDoder ein voller UTC-Zeitstempel. - Listen paginieren per Cursor: die Antwort liefert
next_cursor, du gibst ihn als?cursor=weiter.nullheißt: keine weitere Seite. limitist standardmäßig 100, höchstens 500. Neueste zuerst.- Rate-Limit pro Key und Minute. Bei
429wartest du, wasRetry-Aftersagt. User-Standard sind 120/min. - Schreibende Aufrufe akzeptieren einen
Idempotency-Key-Header.
Fehler
Jeder Fehler kommt mit passendem Status und demselben Body:
{ "error": { "code": "forbidden", "message": "API key is missing the validate scope." } }| Status | code | Wann |
|---|---|---|
| 400 | bad_request | Parameter oder Body fehlen / sind ungültig. |
| 401 | unauthorized | Key fehlt, ist ungültig oder widerrufen. |
| 402 | upgrade_required | Nutzer: API-Tarif nötig. Kanzlei: Mandantenlimit erreicht. |
| 403 | forbidden | Scope fehlt, Partnerstatus inaktiv oder kein aktives Mandat. |
| 404 | not_found | Ressource oder Endpoint gibt es nicht. |
| 409 | conflict | Rechnungsnummer ist schon vergeben. |
| 422 | PROFILE_INCOMPLETE | Profil reicht für E-Rechnung / DATEV noch nicht. |
| 429 | rate_limited | Zu viele Aufrufe. Retry-After beachten. |
| 500 | server_error | Etwas ist auf unserer Seite schiefgegangen. |
| 502/503 | engine_error / unavailable | Die Validierungs-Engine antwortet nicht. |
Profil User-API
Dein Geschäftsprofil, also die Felder, die E-Rechnung und DATEV brauchen.
{ "data": {
"legal_name": "Muster GmbH", "country": "DE",
"street": "Hauptstr. 1", "city": "Berlin", "postal_code": "10115",
"contact_email": "rechnung@muster.de", "vat_id": "DE123456789",
"tax_number": "151/815/08151", "iban": "DE89…", "vat_registered": true,
"chart_of_accounts": "SKR04", "fiscal_year_start": "01-01"
} }Rechnungen User-API
Deine Ausgangsrechnungen, neueste zuerst, per Cursor paginiert. Einzeln über die id.
curl "https://api.vera-business.com/v1/invoices?limit=50" \ -H "Authorization: Bearer $VERA_UK"
{ "data": [ {
"id": "…", "invoice_number": "2026-0042", "issue_date": "2026-05-31",
"due_date": "2026-06-14", "client_name": "Kunde GmbH", "client_country": "DE",
"client_vat_id": "DE99…", "net": "1000.00", "vat": "190.00", "gross": "1190.00",
"vat_rate": 19, "currency": "EUR", "status": "sent", "created_at": "…"
} ], "next_cursor": null }Felder im Rechnungs-Objekt
| Feld | Typ | Beschreibung |
|---|---|---|
id | string | Eindeutige ID der Rechnung. |
invoice_number | string | Rechnungsnummer. |
issue_date · due_date | date | Rechnungs- und Fälligkeitsdatum im Format YYYY-MM-DD. |
client_name · client_country · client_vat_id | string | Empfänger: Name, Ländercode und USt-IdNr., falls vorhanden. |
net · vat · gross | string | Netto, Steuer, Brutto als Dezimal-String. |
vat_rate | number | Steuersatz in Prozent. |
status | enum | draft, sent oder paid. |
Legt eine eigene Rechnung an und antwortet mit 201 und dem angelegten Rechnungs-Objekt.
Body
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
invoice_number | string | ja | Eindeutige Rechnungsnummer. |
client_name | string | ja | Name des Empfängers. |
issue_date | date | ja | Rechnungsdatum im Format YYYY-MM-DD. |
total | number | ja | Bruttobetrag, größer null. |
items | array | nein | Positionen: description, quantity, unit_price, total. |
subtotal · vat_rate · vat_amount | number | nein | Netto, Steuersatz, Steuerbetrag. |
client_country | string | nein | ISO-Ländercode, Standard DE. |
status | enum | nein | Standardwert draft, sonst sent oder paid. |
client_email · client_vat_id · due_date · notes | string | nein | Weitere Felder des Empfängers und der Rechnung. |
curl -X POST https://api.vera-business.com/v1/invoices \
-H "Authorization: Bearer $VERA_UK" -H "Content-Type: application/json" \
-d '{
"invoice_number": "2026-0043", "client_name": "Kunde GmbH",
"client_country": "DE", "issue_date": "2026-06-17",
"items": [{ "description": "Beratung", "quantity": 10, "unit_price": 100, "total": 1000 }],
"subtotal": 1000, "vat_rate": 19, "vat_amount": 190, "total": 1190, "status": "draft"
}'Fehler: 400 bei fehlenden Pflichtfeldern, 409 wenn die Rechnungsnummer schon existiert.
Belege User-API
Deine Eingangsbelege, neueste zuerst, per Cursor paginiert. has_receipt sagt, ob ein Belegscan hinterlegt ist; cost_account ist das Sachkonto und bleibt leer, solange nicht kontiert wurde.
{ "data": [ {
"id": "…", "expense_number": "B-2026-018", "issue_date": "2026-05-28",
"due_date": "2026-06-11", "vendor_name": "Lieferant AG", "vendor_invoice_number": "RE-99812",
"net": "210.08", "vat": "39.92", "gross": "250.00", "cost_account": "6815",
"category": "buero", "status": "booked", "has_receipt": true, "created_at": "…"
} ], "next_cursor": null }E-Rechnung erzeugen User-API
Erzeugt EN16931-XML, je nach Land als XRechnung, Factur-X oder Peppol BIS. Verkäufer ist immer dein eigenes Profil, du kannst also keine Rechnung in fremdem Namen ausstellen.
Body
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
invoice | object | ja | Rechnung in camelCase: invoiceNumber, clientName, items, subtotal, vatRate, total und weitere. |
options | object | nein | Profil- und Formatoptionen. Ohne Angabe wählt Vera anhand des Käuferlands. |
curl -X POST https://api.vera-business.com/v1/einvoice \
-H "Authorization: Bearer $VERA_UK" -H "Content-Type: application/json" \
-d '{ "invoice": { "invoiceNumber": "2026-0043", "clientName": "Kunde GmbH",
"clientCountry": "DE", "issueDate": "2026-06-17", "dueDate": "2026-07-01",
"items": [{ "description": "Beratung", "quantity": 1, "unitPrice": 1000, "total": 1000 }],
"subtotal": 1000, "vatRate": 19, "vatAmount": 190, "total": 1190,
"currency": "EUR", "status": "sent" } }'{ "success": true, "xml": "<?xml …>", "filename": "2026-0043_XRechnung.xml" }422 mit { "success": false, "errors": ["PROFILE_INCOMPLETE: …"] }.DATEV-Export User-API
Dein EXTF-Buchungsstapel im Format DATEV v13 als text/csv, kombiniert aus Ausgangsrechnungen und Eingangsbelegen.
Query
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---|---|
format | string | nein | Nur extf, der Standardwert. |
year | integer | nein | Filtert auf das Belegdatum. Ohne Angabe: alle Belege. |
quarter | integer | nein | 1–4, zusätzlicher Quartalsfilter auf das Belegdatum. |
curl "https://api.vera-business.com/v1/datev-export?year=2026&quarter=2" \ -H "Authorization: Bearer $VERA_UK" -o EXTF.csv
Fehler: 422 PROFILE_INCOMPLETE, solange DATEV-Berater-/Mandantennummer und Kontenrahmen im Profil fehlen.
Webhooks User-API
Registriere in Vera unter Einstellungen → „API & Integrationen" eine HTTPS-URL. Du bekommst ein Signing-Secret whsec_…, das genau einmal angezeigt wird. Der Body trägt nur Event und IDs; die Details holst du über die Endpoints oben.
| Event | Wann |
|---|---|
invoice.created | Du hast eine Rechnung angelegt. |
invoice.updated | Eine Rechnung wurde geändert.* |
invoice.paid | Eine Rechnung steht auf „paid".* |
expense.created | Du hast einen Beleg angelegt. |
{ "event": "invoice.created", "resource_id": "…", "occurred_at": "2026-06-17T08:32:10.000Z" }* invoice.updated und invoice.paid feuern nur, wenn der UPDATE-Kanal serverseitig aktiv ist. Signatur prüfen weiter unten.
Einzeln prüfen geteilt
Prüft eine E-Rechnung gegen die offizielle KoSIT-Validator-Engine. Der Body ist rohes XML mit Content-Type: application/xml.
Query
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---|---|
profile | enum | nein | Standardwert xrechnung. Weiter: bis für Peppol BIS, ro, pt, hr. |
curl -X POST "https://api.vera-business.com/v1/validate?profile=xrechnung" \ -H "Authorization: Bearer $VERA_UK" -H "Content-Type: application/xml" \ --data-binary @invoice.xml
{ "profile": "xrechnung", "valid": true, "verdict": "accept",
"errors": [], "warnings": [] }Felder: valid = konform · verdict = accept / reject · errors / warnings tragen je id, text und optional xpath. Fehler: 400 bei unbekanntem Profil oder leerem Body.
In Massen prüfen geteilt
Prüft viele E-Rechnungen in einem Aufruf, gedacht für Integrationen und große Läufe. Bis zu 100 Dokumente pro Aufruf; die Ergebnisse kommen in derselben Reihenfolge zurück wie die Eingabe.
Body
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
documents | array | ja | 1–100 Objekte. xml ist Pflicht, id und profile sind optional. |
profile | enum | nein | Standardprofil für Dokumente ohne eigenes profile. |
curl -X POST https://api.vera-business.com/v1/validate/batch \
-H "Authorization: Bearer $VERA_UK" -H "Content-Type: application/json" \
-d '{ "profile": "xrechnung", "documents": [
{ "id": "re-1", "xml": "<?xml …>" },
{ "id": "re-2", "profile": "bis", "xml": "<?xml …>" }
] }'{ "summary": { "total": 2, "valid": 1, "invalid": 1, "errored": 0 },
"results": [
{ "id": "re-1", "ok": true, "profile": "xrechnung", "valid": true,
"verdict": "accept", "errors": [], "warnings": [] },
{ "id": "re-2", "ok": true, "profile": "bis", "valid": false,
"verdict": "reject", "errors": [{ "id": "BR-DE-15", "text": "…" }], "warnings": [] }
] }ok sagt, ob die Prüfung durchlief, valid, ob das Dokument konform ist. Ein kaputtes Einzeldokument wie leeres XML oder ein falsches Profil stoppt den Batch nicht; es taucht mit ok:false und einem error auf.USt-IdNr. geteilt
Prüft eine USt-IdNr. samt Länderpräfix gegen VIES.
Body
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
vatNumber | string | ja | USt-IdNr. mit Länderpräfix, z. B. DE123456789. Max. 20 Zeichen. |
{ "vat_number": "DE123456789", "valid": true,
"name": "Muster GmbH", "address": "…", "country_code": "DE" }Mandanten Kanzlei-API
Alle aktiven Mandate deiner Kanzlei, nicht paginiert. Die id ist die Mandanten-ID für alle weiteren Kanzlei-Endpoints. Du siehst nur Mandanten mit aktiver Einwilligung, und jeder Zugriff landet im Protokoll.
{ "data": [ { "id": "a1b2c3d4-…", "email": "mandant@example.de",
"scope": "read", "linked_at": "2026-05-20T09:14:00.000Z" } ], "next_cursor": null }Rechnungen & Belege Kanzlei-API
Ausgangsrechnungen und Eingangsbelege eines Mandanten, paginiert wie bei der User-API. Die Felder sind dieselben wie unter /v1/invoices und /v1/expenses.
Pfad: {id} ist die Mandanten-ID aus /v1/clients. Query: limit bis 500, cursor. Alle /clients/{id}/…-Endpoints brauchen ein aktives Mandat, sonst 403.
Abschlussreife Kanzlei-API
Die Abschluss-Ampel eines Mandanten für ein Jahr. Query: year, Standard ist das laufende Jahr.
{ "status": "amber", "errors": 1, "warnings": 3, "infos": 0,
"open_receipts": 2, "unaccounted_expenses": 1,
"top_signals": [ { "code": "unaccounted_expense", "count": 1 },
{ "code": "missing_receipt", "count": 2 } ] }status ist green für bereit, amber für Hinweise und red für blockiert, etwa Belege ohne Sachkonto.
SuSa & OPOS Kanzlei-API
Summen- und Saldenliste je Mandant, SKR aus dessen Profil. Query: year; ohne Angabe über alle Belege.
{ "skr": "SKR03", "period": { "from": "2026-01-01", "to": "2026-12-31" },
"rows": [ { "account": "1400", "name": "…", "debit": 0, "credit": 0, "balance": 0 } ],
"totalDebit": 0, "totalCredit": 0, "arCount": 0, "apCount": 0 }Offene Posten mit Aging je Mandant. Query: asOf steuert den Stichtag, Standard ist heute.
DATEV-Export Kanzlei-API
EXTF-Buchungsstapel eines Mandanten als text/csv.
Query
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---|---|
format | string | nein | Nur extf, der Standardwert. |
year · quarter | integer | nein | Filtern auf das Belegdatum. |
async | boolean | nein | 1 reiht den Export als Job ein: Antwort 202 mit job_id, Ergebnis über GET /v1/jobs/{job_id}. Für große Mandanten. |
curl "https://api.vera-business.com/v1/clients/$CLIENT/datev-export?year=2026&async=1" \
-H "Authorization: Bearer $VERA_SK"
# → { "job_id": "…", "status": "queued", "poll": "/jobs/…" }Kontierung Kanzlei-API
Setzt das Sachkonto eines Belegs und füttert nebenbei den Lernloop.
Body
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
cost_account | string | ja | Sachkonto nach SKR, etwa 6815. |
Header: Idempotency-Key ist optional. Derselbe Key liefert dieselbe Antwort, ohne ein zweites Mal zu schreiben.
curl -X POST .../v1/clients/$CLIENT/expenses/$EID/kontierung \
-H "Authorization: Bearer $VERA_SK" -H "Content-Type: application/json" \
-d '{ "cost_account": "6815" }'
# → { "id": "…", "expense_number": "…", "cost_account": "6815", "updated": true }Fehler: 403 ohne Scope kontierung:write oder ohne aktives Mandat · 400 ohne cost_account · 404 wenn der Beleg nicht zum Mandanten gehört.
Webhooks Kanzlei-API
Im Cockpit registriert, ab Vera API Pro. Gleiche Signatur wie die User-Webhooks, der Body trägt zusätzlich die client_id.
| Event | Wann |
|---|---|
invoice.created | Neue Ausgangsrechnung eines Mandanten. |
document.added | Neuer Eingangsbeleg eines Mandanten. |
period.ready | Monat oder Quartal ist abschlussreif. |
client.linked · client.revoked | Mandat erteilt oder widerrufen. |
{ "event": "invoice.created", "client_id": "a1b2c3d4-…",
"resource_id": "…", "occurred_at": "2026-06-05T08:32:10.000Z" }Signatur prüfen
Beide Webhook-Typen schicken dieselben Header: X-Vera-Event, X-Vera-Timestamp und X-Vera-Signature: sha256=<hmac>. Rechne HMAC-SHA256 über den rohen Body mit deinem whsec_…-Secret und vergleiche zeitkonstant.
import crypto from 'crypto';
function verify(rawBody, header, whsec) {
const expected = 'sha256=' +
crypto.createHmac('sha256', whsec).update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}import hmac, hashlib
def verify(raw_body: bytes, header: str, whsec: str) -> bool:
expected = "sha256=" + hmac.new(whsec.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header)X-Vera-Timestamp älter als fünf Minuten ist.MCP User-API
Vera spricht das Model Context Protocol. Claude Code, Cursor und andere MCP-Clients bekommen damit 13 Werkzeuge auf deine eigenen Daten, und du fragst in normaler Sprache statt zu curlen: welche Rechnungen offen sind, welche EN16931-Regel eine XML reißt.
POST https://api.vera-business.com/mcp # JSON-RPC 2.0
Dahinter liegt keine zweite API, sondern dieselbe User-API: derselbe Key, dieselben Scopes, dasselbe Rate-Limit, derselbe Verbrauchszähler. Ein Kanzlei-Key vera_sk_ wird hier nicht akzeptiert; ein Modell bekommt keine Mandantendaten.
Einrichten
claude mcp add --transport http vera https://api.vera-business.com/mcp \ --header "Authorization: Bearer vera_uk_…" --scope user
Clients ohne HTTP-Transport erreichen denselben Endpoint über mcp-remote.
invoices:write. Der Key liegt in einer Konfigdatei auf dem Rechner, und ein Modell soll lesen und prüfen, nicht ausstellen. --scope project schreibt den Key in .mcp.json und damit ins Repo, nimm --scope user.Werkzeuge
| Werkzeug | Wofür |
|---|---|
search_invoices | Überfällig, unbezahlt, von Kunde X, in einem Zeitraum. |
get_invoice | Eine Rechnung samt Positionen. |
list_expenses | Eingangsseite; cost_account: null heißt noch nicht buchbar. |
get_profile | Deine Verkäuferdaten, inklusive fehlender Pflichtfelder. |
get_open_items | OPOS mit Aging und Netto-Position. |
get_readiness | Abschlussreife-Ampel, dieselbe Definition, die die Kanzlei sieht. |
get_ustva | Deine gespeicherten Voranmeldungen. Rechnet nie neu. |
validate_einvoice | KoSIT-Validierung, nennt die gerissenen BR-Regeln. |
check_vat_number | USt-IdNr. über VIES. |
generate_einvoice | XML aus einer gespeicherten Rechnung, nennt das Validator-Profil. |
create_invoice write | Rechnung anlegen, idempotent über die Rechnungsnummer. |
datev_export | Startet einen Job statt eine CSV zurückzugeben. |
get_job | Job-Status und Download-Link, 15 Minuten gültig. |
Der übliche Weg durch die Werkzeuge: create_invoice → generate_einvoice → validate_einvoice → datev_export → get_job. Der Export kommt als Job mit Download-Link zurück, damit keine CSV im Kontext des Modells landet.
get_ustva liest nur, was du in Vera erzeugt hast, und rechnet keine Steuer nach. Vera erzeugt E-Rechnungs-XML, versendet es aber nicht über das Peppol-Netz. Kein Werkzeug übermittelt etwas an eine Behörde.Einen Ein-Klick-Connector auf claude.ai gibt es noch nicht, der verlangt OAuth mit Dynamic Client Registration. Vorerst läuft der Zugang über denselben statischen Key wie die REST-API.
Zugang
User-API vera_uk_
Die programmatische API läuft im Vera-API-Tarif: 20 €/Mo zusätzlich zu Vera Pro. Vera Pro allein reicht nicht; ohne den API-Tarif antwortet die API mit 402 upgrade_required. Freischalten in Vera unter Einstellungen → API & Integrationen.
Kanzlei-API vera_sk_
Hängt am Partnerstatus. Das Cockpit selbst kostet nichts, also Weboberfläche und manueller DATEV-Export.
| Paket | Preis | Drin |
|---|---|---|
| Start | 0 € | Read-API, bis 20 Mandanten, kein Webhook. |
| Pro | 149 €/Mo | Bis 50 Mandanten, Webhooks, kontierung:write, SLA. |
| Scale | 349 €/Mo | Unbegrenzt Mandanten, höhere Limits, dedizierter Support. |
Bringt eine Kanzlei 20 Mandanten mit Vera Pro mit, kostet Pro nichts. Ab 50 gilt dasselbe für Scale. Die Freischaltung läuft automatisch, sobald die Zahl erreicht ist. Über dem Mandantenlimit kommt 402, Webhooks gibt es ab Pro. Partner werden über partners@vera-business.com.