VeraAPI v1

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.

vera_uk_…  User-API

Deine Rechnungen, Belege und dein Profil. E-Rechnung erzeugen, eigener DATEV-Export, Validator, USt-IdNr.-Check.

vera_sk_…  Kanzlei-API

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.

Basis-URL
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.

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

SeiteScopes
Userinvoices:read invoices:write expenses:read profile:read einvoice:generate datev:read validate vat:read
Kanzleiclients:read invoices:read expenses:read readiness:read kontierung:write
Ein User-Key sieht nur die Daten seines Besitzers. Ein Kanzlei-Key wird ungültig, sobald die Kanzlei den Partnerstatus verliert. Behandle Keys wie Passwörter: nicht ins Frontend, nicht in Git, nicht in Logs.

Konventionen

Fehler

Jeder Fehler kommt mit passendem Status und demselben Body:

json
{ "error": { "code": "forbidden", "message": "API key is missing the validate scope." } }
StatuscodeWann
400bad_requestParameter oder Body fehlen / sind ungültig.
401unauthorizedKey fehlt, ist ungültig oder widerrufen.
402upgrade_requiredNutzer: API-Tarif nötig. Kanzlei: Mandantenlimit erreicht.
403forbiddenScope fehlt, Partnerstatus inaktiv oder kein aktives Mandat.
404not_foundRessource oder Endpoint gibt es nicht.
409conflictRechnungsnummer ist schon vergeben.
422PROFILE_INCOMPLETEProfil reicht für E-Rechnung / DATEV noch nicht.
429rate_limitedZu viele Aufrufe. Retry-After beachten.
500server_errorEtwas ist auf unserer Seite schiefgegangen.
502/503engine_error / unavailableDie Validierungs-Engine antwortet nicht.

Profil User-API

GET/v1/profileprofile:read

Dein Geschäftsprofil, also die Felder, die E-Rechnung und DATEV brauchen.

200 · json
{ "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

GET/v1/invoicesinvoices:read
GET/v1/invoices/{id}

Deine Ausgangsrechnungen, neueste zuerst, per Cursor paginiert. Einzeln über die id.

curl
curl "https://api.vera-business.com/v1/invoices?limit=50" \
  -H "Authorization: Bearer $VERA_UK"
200 · json
{ "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

FeldTypBeschreibung
idstringEindeutige ID der Rechnung.
invoice_numberstringRechnungsnummer.
issue_date · due_datedateRechnungs- und Fälligkeitsdatum im Format YYYY-MM-DD.
client_name · client_country · client_vat_idstringEmpfänger: Name, Ländercode und USt-IdNr., falls vorhanden.
net · vat · grossstringNetto, Steuer, Brutto als Dezimal-String.
vat_ratenumberSteuersatz in Prozent.
statusenumdraft, sent oder paid.
POST/v1/invoicesinvoices:write

Legt eine eigene Rechnung an und antwortet mit 201 und dem angelegten Rechnungs-Objekt.

Body

FeldTypPflichtBeschreibung
invoice_numberstringjaEindeutige Rechnungsnummer.
client_namestringjaName des Empfängers.
issue_datedatejaRechnungsdatum im Format YYYY-MM-DD.
totalnumberjaBruttobetrag, größer null.
itemsarrayneinPositionen: description, quantity, unit_price, total.
subtotal · vat_rate · vat_amountnumberneinNetto, Steuersatz, Steuerbetrag.
client_countrystringneinISO-Ländercode, Standard DE.
statusenumneinStandardwert draft, sonst sent oder paid.
client_email · client_vat_id · due_date · notesstringneinWeitere Felder des Empfängers und der Rechnung.
curl
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

GET/v1/expensesexpenses:read
GET/v1/expenses/{id}

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.

200 · json
{ "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

POST/v1/einvoiceeinvoice:generate

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

FeldTypPflichtBeschreibung
invoiceobjectjaRechnung in camelCase: invoiceNumber, clientName, items, subtotal, vatRate, total und weitere.
optionsobjectneinProfil- und Formatoptionen. Ohne Angabe wählt Vera anhand des Käuferlands.
curl
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" } }'
200 · json
{ "success": true, "xml": "<?xml …>", "filename": "2026-0043_XRechnung.xml" }
Reicht dein Profil noch nicht, kommt 422 mit { "success": false, "errors": ["PROFILE_INCOMPLETE: …"] }.

DATEV-Export User-API

GET/v1/datev-export?format=extf&year=&quarter=datev:read

Dein EXTF-Buchungsstapel im Format DATEV v13 als text/csv, kombiniert aus Ausgangsrechnungen und Eingangsbelegen.

Query

ParameterTypPflichtBeschreibung
formatstringneinNur extf, der Standardwert.
yearintegerneinFiltert auf das Belegdatum. Ohne Angabe: alle Belege.
quarterintegernein1–4, zusätzlicher Quartalsfilter auf das Belegdatum.
curl
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.

EventWann
invoice.createdDu hast eine Rechnung angelegt.
invoice.updatedEine Rechnung wurde geändert.*
invoice.paidEine Rechnung steht auf „paid".*
expense.createdDu hast einen Beleg angelegt.
POST an deine URL
{ "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

POST/v1/validate?profile=xrechnungvalidate

Prüft eine E-Rechnung gegen die offizielle KoSIT-Validator-Engine. Der Body ist rohes XML mit Content-Type: application/xml.

Query

ParameterTypPflichtBeschreibung
profileenumneinStandardwert xrechnung. Weiter: bis für Peppol BIS, ro, pt, hr.
curl
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
200 · json
{ "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

POST/v1/validate/batchvalidate

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

FeldTypPflichtBeschreibung
documentsarrayja1–100 Objekte. xml ist Pflicht, id und profile sind optional.
profileenumneinStandardprofil für Dokumente ohne eigenes profile.
curl
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 …>" }
      ] }'
200 · json
{ "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

POST/v1/vat/validatevat:read

Prüft eine USt-IdNr. samt Länderpräfix gegen VIES.

Body

FeldTypPflichtBeschreibung
vatNumberstringjaUSt-IdNr. mit Länderpräfix, z. B. DE123456789. Max. 20 Zeichen.
200 · json
{ "vat_number": "DE123456789", "valid": true,
  "name": "Muster GmbH", "address": "…", "country_code": "DE" }

Mandanten Kanzlei-API

GET/v1/clientsclients:read

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.

200 · json
{ "data": [ { "id": "a1b2c3d4-…", "email": "mandant@example.de",
  "scope": "read", "linked_at": "2026-05-20T09:14:00.000Z" } ], "next_cursor": null }

Rechnungen & Belege Kanzlei-API

GET/v1/clients/{id}/invoices
GET/v1/clients/{id}/expenses

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

GET/v1/clients/{id}/readiness?year=readiness:read

Die Abschluss-Ampel eines Mandanten für ein Jahr. Query: year, Standard ist das laufende Jahr.

200 · json
{ "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

GET/v1/clients/{id}/susa?year=

Summen- und Saldenliste je Mandant, SKR aus dessen Profil. Query: year; ohne Angabe über alle Belege.

200 · json
{ "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 }
GET/v1/clients/{id}/opos?asOf=YYYY-MM-DD

Offene Posten mit Aging je Mandant. Query: asOf steuert den Stichtag, Standard ist heute.

DATEV-Export Kanzlei-API

GET/v1/clients/{id}/datev-export?format=extf&year=&quarter=

EXTF-Buchungsstapel eines Mandanten als text/csv.

Query

ParameterTypPflichtBeschreibung
formatstringneinNur extf, der Standardwert.
year · quarterintegerneinFiltern auf das Belegdatum.
asyncbooleannein1 reiht den Export als Job ein: Antwort 202 mit job_id, Ergebnis über GET /v1/jobs/{job_id}. Für große Mandanten.
curl
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

POST/v1/clients/{id}/expenses/{eid}/kontierungkontierung:write

Setzt das Sachkonto eines Belegs und füttert nebenbei den Lernloop.

Body

FeldTypPflichtBeschreibung
cost_accountstringjaSachkonto nach SKR, etwa 6815.

Header: Idempotency-Key ist optional. Derselbe Key liefert dieselbe Antwort, ohne ein zweites Mal zu schreiben.

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

EventWann
invoice.createdNeue Ausgangsrechnung eines Mandanten.
document.addedNeuer Eingangsbeleg eines Mandanten.
period.readyMonat oder Quartal ist abschlussreif.
client.linked · client.revokedMandat erteilt oder widerrufen.
POST an deine URL
{ "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.

Node.js
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));
}
Python
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)
Antworte mit 2xx. Alles andere zählt als Fehlversuch und erhöht den Fehlerzähler. Verwirf Zustellungen, deren 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.

Endpoint
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 Code
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.

Gib KI-Clients einen Key ohne 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

WerkzeugWofür
search_invoicesÜberfällig, unbezahlt, von Kunde X, in einem Zeitraum.
get_invoiceEine Rechnung samt Positionen.
list_expensesEingangsseite; cost_account: null heißt noch nicht buchbar.
get_profileDeine Verkäuferdaten, inklusive fehlender Pflichtfelder.
get_open_itemsOPOS mit Aging und Netto-Position.
get_readinessAbschlussreife-Ampel, dieselbe Definition, die die Kanzlei sieht.
get_ustvaDeine gespeicherten Voranmeldungen. Rechnet nie neu.
validate_einvoiceKoSIT-Validierung, nennt die gerissenen BR-Regeln.
check_vat_numberUSt-IdNr. über VIES.
generate_einvoiceXML aus einer gespeicherten Rechnung, nennt das Validator-Profil.
create_invoice writeRechnung anlegen, idempotent über die Rechnungsnummer.
datev_exportStartet einen Job statt eine CSV zurückzugeben.
get_jobJob-Status und Download-Link, 15 Minuten gültig.

Der übliche Weg durch die Werkzeuge: create_invoicegenerate_einvoicevalidate_einvoicedatev_exportget_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.

PaketPreisDrin
Start0 €Read-API, bis 20 Mandanten, kein Webhook.
Pro149 €/MoBis 50 Mandanten, Webhooks, kontierung:write, SLA.
Scale349 €/MoUnbegrenzt 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.