REST-API-Referenz

E-Rechnungen im Format ZUGFeRD/Factur-X (EN 16931) per HTTP prüfen und erzeugen, dazu die Zahlungsverkehr-Endpoints für SEPA- und Auslandszahlungsdateien, BIC-Lookup und Adress-Strukturierung. Server-zu-Server gedacht, nicht für Aufrufe aus dem Browser.

Erster Aufruf in drei Schritten

  1. Key anlegen. Im Konto unter „API und Anwendungen“ einen API-Key erstellen. Er beginnt mit fnsm_ und wird genau einmal angezeigt.
  2. Basis-URL verwenden. Alle Endpunkte liegen unter https://finisma.de/api/v1, der Key geht als Bearer-Token im Header Authorization mit.
  3. Verbindung prüfen. GET /me antwortet mit Konto und Abo-Status und erzeugt nichts:
Anfrage
curl https://finisma.de/api/v1/me \
  -H "Authorization: Bearer $FINISMA_KEY"
Zum Ausprobieren ohne Kommandozeile: Die interaktive API-Dokumentation zeigt jeden Endpunkt mit Parametern und Schemas und sendet Anfragen mit Ihrem Key direkt aus der Seite.

Endpunkte im Überblick

Alle Pfade gelten relativ zu https://finisma.de/api/v1. Außer /formats, /codelists und /openapi.json braucht jeder Aufruf ein Credential, auch die kostenlosen.

MethodePfadZweckAbo nötig
GET/meKonto, Abo-Status und gebuchte Features zum Credentialnein
GET/formatsFormate mit Fähigkeiten und Limitsnein, auch ohne Key
GET/codelists/…Codelisten: document-types, units, vat-categories, payment-means, attachment-types, note-subjectsnein, auch ohne Key
GET/openapi.jsonMaschinenlesbare Spezifikation (OpenAPI 3.1)nein, auch ohne Key
POST/verifyE-Rechnung prüfennein, kostenlos
GET/vat/checkUSt-IdNr. live gegen VIES prüfennein, kostenlos
GET/peppol/checkPeppol-Erreichbarkeit eines Partnersnein, kostenlos
POST/extractionsRechnungsdaten aus einer PDF auslesenja, oder 1 Credit je Aufruf
POST/invoicesZUGFeRD-PDF erzeugen, mit eigener PDF oder aus den Daten gesetztnein: Abo, Credits oder Tageskontingent mit Wasserzeichen
POST/invoices?format=xmlNur das CII-XML, auch XRechnungja, oder 1 Credit
POST/payments/sepaSEPA-Sammelüberweisung (pain.001)ja, Feature payments
POST/payments/axzAuslandszahlungsdatei (AXZ)ja, Feature paymentsAbroad
GET/payments/bank-lookupBIC zu einer deutschen Bankleitzahlja, Feature payments oder paymentsAbroad
POST/payments/structure-addressesFreitext-Adressen per KI strukturierenja, Feature paymentsAbroad
POST/payments/validateZahlungsdatei pain.001 prüfen (SEPA, AXZ)nein: 5 pro Tag frei, danach Feature payments oder 1 Credit für 10 Prüfungen
POST/statements/validateKontoauszug prüfen (camt.052/053/054, camt.086)nein: 5 pro Tag frei, danach Feature payments oder 1 Credit für 10 Prüfungen
POST/statements/parseKontoauszug als JSON lesen (camt.052/053/054, camt.086)nein: 5 pro Monat frei, danach Feature payments oder 1 Credit je Aufruf

Authentifizierung

API-Key und OAuth-Token

Jeder Aufruf unter /api/v1 braucht ein Credential. Einen API-Key legen Sie in Ihrem Konto im Bereich „API und Anwendungen“ an. Der Key wird als Bearer-Token gesendet:

Header
Authorization: Bearer fnsm_…

Statt eines statischen Keys akzeptiert derselbe Header auch ein OAuth-2.1-Zugriffstoken (Bearer fnso_…), sinnvoll, wenn eine Integration ein fremdes finisma-Konto verbindet statt einen eigenen Key zu verwalten.

Was ein Aufruf kostet

  • Das Prüfen (/verify) ist kostenlos, verlangt aber ebenfalls ein Credential.
  • Das Erzeugen (/invoices) läuft mit aktivem Abo, mit Guthaben (Credits: 1 je Rechnung, ohne Wasserzeichen und ohne Tageslimit) oder aus dem kostenlosen Tageskontingent mit Wasserzeichen.
  • Das Auslesen (/extractions) läuft mit Abo oder kostet 1 Credit je Aufruf; steckt in der PDF schon eine E-Rechnung, kommen die Felder direkt aus der eingebetteten XML.
  • Das Prüfen von Zahlungsdateien und Kontoauszügen (/payments/validate, /statements/validate) und das Auslesen von Kontoauszügen (/statements/parse) haben ein kostenloses Kontingent je Konto, siehe Freikontingent und Abrechnung.

Credential und Abo prüfen: GET /me

Ob ein Credential trägt und was es darf, beantwortet GET https://finisma.de/api/v1/me: Konto, Abo-Status, bei OAuth die zugestimmten Scopes und die gebuchten Bausteine (features: converter, payments, paymentsAbroad). Damit lässt sich vorab prüfen, ob ein Konto etwa /payments/bank-lookup nutzen darf, statt es erst am 403 einer echten Anfrage zu merken. Für Verbindungstests in Integrationen gedacht.

Mit aktivem Abo steht dort auch fairUse: die Fair-Use-Grenze des Monats (limit, 100 Rechnungen plus 1.000 je Zusatzpaket), die bisher erzeugten Rechnungen ohne Wasserzeichen (used, Web, API und MCP zusammen) und die Zahl der Pakete. Eine Warenwirtschaft, die regelmäßig darüber liegt, bucht im Konto ein Zusatzpaket dazu.

Selbstauskunft: Formate und Codelisten

Sieben anonyme GET-Endpunkte beschreiben, was die API kann: erzeugt aus denselben Quelldateien wie Validierung und Generator, deshalb immer aktuell. Gedacht für Integratoren, die Belegarten, Einheiten oder Steuerkategorien VOR dem ersten Aufruf mappen wollen, und für Werkzeuge, die ihre Auswahlfelder live füllen.

Anfragen, ohne Key
curl https://finisma.de/api/v1/formats
curl https://finisma.de/api/v1/codelists/document-types
curl https://finisma.de/api/v1/codelists/units
curl https://finisma.de/api/v1/codelists/vat-categories
curl https://finisma.de/api/v1/codelists/payment-means
curl https://finisma.de/api/v1/codelists/attachment-types
curl https://finisma.de/api/v1/codelists/note-subjects

/formats nennt je Format die Fähigkeiten (erzeugen, prüfen), Anhangsregeln, Limits und Prüf-Engines. /codelists/units liefert zusätzlich die Normalisierungs-Aliasse ("Stck" wird C62): was dort steht, dürfen Sie schicken, der Rest fällt auf Stück zurück.

Rechnung prüfen

POST https://finisma.de/api/v1/verify mit der PDF als multipart-Feld pdf. Antwort ist ein JSON-Prüfbericht (Struktur, EN-16931-Regeln, PDF/A-3-Anbindung).

Anfrage
curl -X POST https://finisma.de/api/v1/verify \
  -H "Authorization: Bearer $FINISMA_KEY" \
  -F "pdf=@rechnung.pdf;type=application/pdf"

Geschäftspartner prüfen: USt-IdNr. und Peppol

Zwei kostenlose GET-Endpunkte für den Preflight vor dem Rechnungsversand (auch als MCP-Tools check_vat_id und check_peppol_participant verfügbar):

  • /vat/check prüft eine USt-IdNr. live gegen VIES, das EU-Bestätigungsverfahren.
  • /peppol/check schlägt per SML/SMP nach, ob ein Partner im Peppol-Netz erreichbar ist, und listet seine Dokumenttypen. Als Eingabe genügt eine GLN, eine deutsche USt-IdNr. oder eine Leitweg-ID.
Anfragen
curl "https://finisma.de/api/v1/vat/check?vatId=DE325821677" \
  -H "Authorization: Bearer $FINISMA_KEY"

curl "https://finisma.de/api/v1/peppol/check?participant=0088:4399901550926" \
  -H "Authorization: Bearer $FINISMA_KEY"

Zwei Zustände sind bewusst KEINE Fehler: status: "unavailable" beim VIES-Check heißt, der Mitgliedsstaat war nicht erreichbar, und sagt nichts darüber, ob die Nummer gültig ist. Und registered: false beim Peppol-Check ist ein normales Ergebnis; die Rechnung lässt sich dann klassisch als ZUGFeRD-PDF per E-Mail zustellen.

Rechnungsdaten aus einer PDF auslesen

POST https://finisma.de/api/v1/extractions mit der PDF als multipart-Feld pdf. Antwort ist JSON mit invoice (die EN-16931-Felder), issues (offene Probleme) und repairedPaths. Gedacht als Vorstufe zu /invoices: auslesen, prüfen lassen, korrigieren, erzeugen.

Anfrage
curl -X POST https://finisma.de/api/v1/extractions \
  -H "Authorization: Bearer $FINISMA_KEY" \
  -F "pdf=@rechnung.pdf;type=application/pdf"

Gelesen wird mit derselben Pipeline wie im Web-Formular: Textpfad oder Vision-Modell je nach PDF, eine Reparaturschleife für unvollständige Modellantworten und deterministische Nachträge für IBAN und USt-IdNr. aus dem Rechnungstext.

Dieser Endpunkt erfordert ein aktives Abo oder Guthaben (1 Credit je Aufruf); das kostenlose Tageskontingent der Erzeugung gilt hier nicht. Je Aufruf läuft ein kostenpflichtiges Sprachmodell; das Kontingent von /invoices deckt das nicht. Ohne Abo kommt 403 subscription_required. Das Rate-Limit liegt bei 10 Aufrufen pro Minute und Credential, deutlich unter dem der übrigen Endpoints, weil jeder Aufruf ein Modell beschäftigt.

Platzhalter: was repairedPaths bedeutet

Ein Sprachmodell liest nicht jedes Feld sicher. Wo die Extraktion nichts Verwertbares fand, steht ein Platzhalter statt eines geratenen Werts, und der Pfad des Feldes steht in repairedPaths (Punkt-Notation, etwa seller.name oder lines.0.lineNetAmount).

PlatzhalterWoVerhalten bei /invoices
KORRIGIERENTextfelderblockiert als EXTRACTION_UNCERTAIN
1900-01-01Datumsfelderblockiert als EXTRACTION_UNCERTAIN
0Betrags- und Mengenfelderläuft durch, wenn die Summen zufällig aufgehen. Nur repairedPaths verrät diesen Fall.

Wer repairedPaths ignoriert, schickt die Platzhalter unbemerkt weiter in /invoices und erzeugt eine formal gültige Rechnung mit falschem Inhalt. Die Liste gehört deshalb einem Menschen vorgelegt oder mindestens geloggt.

ZUGFeRD-Rechnung erzeugen

POST https://finisma.de/api/v1/invoices nimmt die visuelle PDF (pdf) und die strukturierten EN-16931-Daten (invoice, JSON) und bettet das CII-XML ein. Die Antwort ist standardmäßig die fertige PDF/A-3 binär. Die PDF ist optional: Ohne sie setzt finisma die Rechnung aus den Daten, siehe Rechnung aus Daten.

Anfrage
curl -X POST https://finisma.de/api/v1/invoices \
  -H "Authorization: Bearer $FINISMA_KEY" \
  -F "pdf=@rechnung.pdf;type=application/pdf" \
  -F "invoice=@invoice.json;type=application/json" \
  -F "filename=rechnung-zugferd.pdf" \
  -o rechnung-zugferd.pdf

Mit Accept: application/json kommt stattdessen { pdf_base64, bytes }. Das muss ausdrücklich angefordert werden: die Vorgabe vieler HTTP-Bibliotheken lautet application/json, text/plain, */*, und darauf antwortet die API binär, damit niemand ungefragt eine Base64-Hülle in eine .pdf-Datei schreibt.

Wie die Felder in invoice.json aussehen, zeigen fünf Beispiele weiter unten: kleinstmögliche Rechnung, Gutschrift, Bau-Abschlagsrechnung, Reverse Charge und XRechnung.

Rechnung aus Daten, ohne PDF

Wer keine fertige Rechnungs-PDF hat, lässt den pdf-Part weg. finisma setzt die Rechnung dann aus den Daten: Kopf mit Rechnungsnummer und Datum, beide Parteien, Positionen, Summen, Steueraufschlüsselung und Zahlungsangaben, auf A4 mit Seitenzahlen. Es ist dieselbe Vorlage wie im ZUGFeRD-Viewer. Die Ansicht entsteht aus genau dem CII-XML, das danach eingebettet wird; Sichtdokument und Daten können also nicht voneinander abweichen.

Anfrage mit Logo (multipart)
curl -X POST https://finisma.de/api/v1/invoices \
  -H "Authorization: Bearer $FINISMA_KEY" \
  -F "invoice=@invoice.json;type=application/json" \
  -F "logo=@logo.png;type=image/png" \
  -F "lang=de" \
  -F "filename=rechnung-2026-001.pdf" \
  -o rechnung-2026-001.pdf

Dieselbe Anfrage geht auch als Content-Type: application/json. Die Felder sind dieselben wie beim MCP-Tool create_zugferd: invoice als Objekt, dazu optional logo_base64 (Base64 oder data:-URI), logo_mime, lang, filename und attachments (je filename und data_base64, optional mime_type und description). Das Beispiel baut den Body mit jq aus invoice.json und dem Logo:

Anfrage als JSON
jq -n --slurpfile inv invoice.json \
      --arg logo "$(base64 < logo.png | tr -d '\n')" \
      '{invoice: $inv[0], logo_base64: $logo, lang: "de"}' \
| curl -X POST https://finisma.de/api/v1/invoices \
    -H "Authorization: Bearer $FINISMA_KEY" \
    -H "Content-Type: application/json" \
    -d @- \
    -o rechnung-2026-001.pdf

Logo: PNG, JPEG oder SVG, höchstens 500 KB. Es steht rechts oben über der Absenderzeile, höchstens 22 mm hoch und 70 mm breit; das Seitenverhältnis bleibt erhalten. Den Bildtyp erkennt die API am Inhalt. Ein SVG mit Skript, Ereignis-Attributen, eingebettetem HTML oder Verweisen auf andere Dateien lehnt die API mit 400 invalid_input ab; in dem Fall das Logo als PNG schicken. Ein Logo zusammen mit einer eigenen PDF ist ebenfalls ein 400: In eine fremde PDF zeichnet finisma nichts hinein.

Sprache: lang ist de (Standard) oder en und steuert Beschriftungen, Zahlen- und Datumsformat. Die Rechnungsdaten selbst bleiben, wie sie sind.

Abrechnung und Prüfung: wie bei der eigenen PDF. Mit Abo oder Guthaben (1 Credit) kommt die Rechnung ohne Wasserzeichen; im kostenlosen Tageskontingent trägt sie das finisma-Prüfsiegel, und die Vorlage hält oben Platz dafür frei. Vor der Auslieferung prüft finisma die fertige Datei autoritativ (PDF/A-3 und EN 16931). Fällt sie durch, antwortet die API mit 502 upstream_error und den Befunden in details, und ein verbrauchter Credit oder Kontingentplatz wird zurückgebucht.

Anhänge

Optionale Anhänge werden als weitere attachments-Felder angehängt (PDF, PNG, JPEG, CSV, XLSX, ODS, XML; je max. 10 MB). Sie landen als rechnungsbegründende Unterlage im XML (BG-24, Dokumentenart 916) und zugleich als eingebettete Datei in der PDF/A-3.

Wozu eine Datei gehört, sagt attachmentDescriptions(BT-123): je Anhang ein Text-Part, in derselben Reihenfolge wie die Datei-Parts. Ohne Angabe steht dort der Dateiname, und der Empfänger muss raten.

Anfrage mit Anhang
curl -X POST https://finisma.de/api/v1/invoices \
  -H "Authorization: Bearer $FINISMA_KEY" \
  -F "pdf=@rechnung.pdf;type=application/pdf" \
  -F "invoice=@invoice.json;type=application/json" \
  -F "attachments=@stunden.csv;type=text/csv" \
  -F "attachmentDescriptions=Stundennachweis März 2026" \
  -o rechnung-zugferd.pdf

Nur das XML (format=xml)

Wer das Sichtdokument selbst erzeugt oder nur an eine Behörde einreicht, braucht die PDF nicht. ?format=xml liefert das reine CII-XML. Dieser Weg ruft den Einbettungsdienst nicht auf und ist deshalb deutlich schneller; der pdf-Part entfällt, es genügt invoice.

Anfrage
curl -X POST "https://finisma.de/api/v1/invoices?format=xml" \
  -H "Authorization: Bearer $FINISMA_KEY" \
  -F "invoice=@invoice.json;type=application/json" \
  -F "filename=rechnung.xml" \
  -o rechnung.xml

Statt des Query-Parameters wirkt auch Accept: application/xml, nach derselben engen Regel wie bei JSON: Der Header muss application/xml nennen und darf weder application/pdf noch */* enthalten. Mit Accept: application/json kommt statt des XML-Dokuments { xml, bytes }.

Erzeugt wird byteidentisch dasselbe XML, das der PDF-Weg einbettet: gleiche Eingabe, gleiche Prüfung (Schema und EN-16931-Geschäftsregeln), gleiche Fehlercodes. Anhänge bleiben möglich und landen als rechnungsbegründende Unterlage im XML (BT-125, Base64 im Dokument selbst); die PDF/A-3-Ebene entfällt naturgemäß. Für öffentliche Auftraggeber erzeugt dieser Weg mit "profile": "xrechnung" die XRechnung 3.0; Beispiel 5 zeigt die Pflichtfelder.

Der XML-Weg setzt ein aktives Abo oder Guthaben voraus. Das kostenlose Tageskontingent arbeitet mit dem finisma-Prüfsiegel als Wasserzeichen, und ein XML kann kein Wasserzeichen tragen. Mit Guthaben kostet der Aufruf einen Credit; ohne Abo und ohne Guthaben antwortet der Endpunkt mit 403 subscription_required; der PDF-Weg bleibt kostenlos nutzbar.

Beispiel 1: die kleinstmögliche Rechnung

So wenig braucht eine gültige E-Rechnung: zwei Parteien, eine Position, eine Steuerzeile, drei Summen. Alles Übrige sind Defaults des Schemas (Währung EUR, Einheit Stück, Steuerkategorie S). Als invoice.json gespeichert läuft das Beispiel unverändert durch den curl-Aufruf oben.

invoice.json
{
  "invoiceNumber": "2026-001",
  "issueDate": "2026-08-19",
  "dueDate": "2026-09-02",
  "seller": {
    "name": "Muster GmbH",
    "vatId": "DE123456789",
    "address": { "line1": "Hauptstr. 1", "postalCode": "20095", "city": "Hamburg" }
  },
  "buyer": {
    "name": "Kunde AG",
    "address": { "line1": "Marktplatz 5", "postalCode": "10115", "city": "Berlin" }
  },
  "lines": [
    { "name": "Beratung August", "quantity": 1, "unitPriceNet": 100, "vatRatePercent": 19, "lineNetAmount": 100 }
  ],
  "vatBreakdown": [
    { "ratePercent": 19, "basisAmount": 100, "taxAmount": 19 }
  ],
  "totalNet": 100,
  "totalVat": 19,
  "totalGross": 119
}

Beispiel 2: Gutschrift mit Bezug auf die Ursprungsrechnung

Gutschriften, Korrektur-, Abschlags- und Schlussrechnungen laufen über dasselbe Feld: documentTypeCode nach UNCL 1001. Ohne Angabe gilt 380, Handelsrechnung.

CodeDokumentart
380Handelsrechnung (Standard ohne Angabe)
381Gutschrift
384Korrekturrechnung
386Vorausrechnung
326Teilrechnung
389Selbstfakturierung
383Belastungsanzeige
875 bis 877Bauleistung

Bei Gutschrift und Korrektur gehört der Bezug auf die Ursprungsrechnung dazu (precedingInvoiceNumber und precedingInvoiceDate, BT-25/BT-26). Die Beträge bleiben in jedem Fall positiv: die Richtung trägt der Dokumenttyp, nicht das Vorzeichen. Abschlags- und Schlussrechnungen übergeben zusätzlich paidAmount (BT-113, bereits gezahlter Bruttobetrag).

invoice.json
{
  "invoiceNumber": "GS-2026-017",
  "documentTypeCode": "381",
  "precedingInvoiceNumber": "2026-001",
  "precedingInvoiceDate": "2026-08-19",
  "issueDate": "2026-08-26",
  "buyerReference": "04011000-1234512345-06",
  "purchaseOrderReference": "878561",
  "seller": {
    "name": "Muster GmbH",
    "vatId": "DE123456789",
    "address": { "line1": "Hauptstr. 1", "postalCode": "20095", "city": "Hamburg" }
  },
  "buyer": {
    "name": "Kunde AG",
    "address": { "line1": "Marktplatz 5", "postalCode": "10115", "city": "Berlin" }
  },
  "lines": [
    {
      "name": "Rückvergütung Beratung August",
      "sellerArticleId": "BER-01",
      "globalArticleId": "4012345678901",
      "quantity": 1,
      "unitPriceNet": 40,
      "vatRatePercent": 19,
      "lineNetAmount": 40
    }
  ],
  "vatBreakdown": [
    { "ratePercent": 19, "basisAmount": 40, "taxAmount": 7.6 }
  ],
  "totalNet": 40,
  "totalVat": 7.6,
  "totalGross": 47.6,
  "payment": {
    "reference": "Gutschrift zu 2026-001",
    "terms": "Erstattung binnen 14 Tagen auf das bekannte Konto."
  },
  "notes": [
    "Gutschrift wegen reduziertem Stundenumfang."
  ]
}

Beispiel 3: Bau-Abschlagsrechnung mit Projekt, Vertrag und Nachlass

Kopf-Felder für Bau- und Projektrechnungen

Bau- und Projektrechnungen tragen Kopf-Felder, die im Handel selten sind, für die Zuordnung beim Empfänger aber entscheidend:

FeldBTBedeutung
projectReferenceBT-11Projekt; die Buchhaltung des Auftraggebers bucht auf das Projekt.
contractReferenceBT-12Vertrag; niemals in die Bestellnummer schreiben.
despatchAdviceRefBT-16Lieferschein.
invoicingPeriodBG-14Der Leistungszeitraum mit start und end als ISO-Datum, eines von beiden genügt.
deliveryBG-13Die Baustelle.

GLN und Lieferantennummer

Für den Handel, etwa EDEKA oder Metro, tragen Rechnungen die GLN (Global Location Number, 13 Ziffern) der Parteien: buyer.gln für den Käufer (BT-46) und delivery.gln für die Lieferstelle (BT-71). finisma schreibt beide als GlobalID mit dem Schema 0088 und prüft die Prüfziffer der Lieferstellen-GLN.

Vergibt der Kunde eine eigene Lieferantennummer, gehört sie nach seller.customerAssignedId. finisma schreibt sie als Verkäuferkennung BT-29 (ram:ID ohne Schema) in die XML.

Ausschnitt: Lieferstelle mit GLN
"delivery": {
  "gln": "4012345000009",
  "name": "Markt Köln-Ehrenfeld",
  "address": { "line1": "Venloer Str. 1", "city": "Köln", "postalCode": "50823", "countryCode": "DE" }
}

Abzüge und Zuschläge

allowanceCharges (BG-20/BG-21) bildet Abzüge und Zuschläge auf die ganze Rechnung ab, etwa den Nachlass: charge: false ist ein Abzug, true ein Zuschlag, der Betrag steht immer ohne Vorzeichen. Die Rechenkette folgt EN 16931: totalNet ist der Betrag nach den Abzügen (Positionssumme − Abzüge + Zuschläge, BR-CO-13), und die Bemessungsgrundlage des betroffenen Steuersatzes sinkt entsprechend.

Ein Sicherheitseinbehalt ist bewusst kein Abzug: Er mindert weder Betrag noch Steuer, sondern gehört im Klartext in die Zahlungsbedingungen. Geschuldet wird der volle Betrag, zurückgehalten nur ein Teil davon.

Rabatte auf eine einzelne Position führen Sie als lines[].allowanceCharges (BG-27/BG-28, siehe den Mengenrabatt der ersten Position): lineNetAmount ist der Betrag nach diesen Einträgen, ein Grund (Text oder Code) ist Pflicht (BR-42), und die Validierung rechnet die Zeilenkette Menge × Preis − Abzüge + Zuschläge vor der Erzeugung nach.

Prozentangaben je Position

Rabatte und Zuschläge je Position dürfen als Prozentsatz kommen: Mit percent und ohne amount rechnet die API den Betrag selbst, als round2(basisAmount × percent / 100). Ohne basisAmount gilt Menge × unitPriceNet der Position, und zwar für jeden Eintrag einzeln, nicht gestaffelt. Einen Staffelrabatt (10 %, dann 5 % auf den Rest) geben Sie deshalb mit eigenem basisAmount an. Beide Werte stehen danach im XML (BT-137/BT-138).

Den Nettobetrag der Position rechnen Sie mit diesem Betrag: Im Beispiel sind 4 % von 250 × 50 = 12.500 genau 500, lineNetAmount also 12.000. Liegen Sie daneben, nennt die Fehlermeldung (LINE_NET_MISMATCH) den erwarteten Wert. Ein mitgegebener amount gewinnt immer, passt er nicht zum Prozentsatz, gibt es eine Warnung.

Prozentangaben auf Rechnungsebene

Dasselbe gilt für Abzüge und Zuschläge auf die ganze Rechnung: Ohne amount rechnet die API round2(basisAmount × percent / 100). Ohne basisAmount ist der Grundbetrag die Summe der lineNetAmount aller Positionen mit dem Steuersatz ratePercent, also nach den Positionsrabatten. Führt derselbe Satz mehrere Kategorien (etwa E und AE bei 0 %), zählen nur die Positionen mit categoryCode. Fehlt ratePercent, genügt das, solange alle Positionen denselben Satz haben, der Satz wird dann ergänzt. Im Beispiel sind das 2 % von 12.000 + 3.500 = 15.500, also 310. Netto, Steueraufschlüsselung und Brutto rechnen Sie mit diesem Betrag.

invoice.json
{
  "invoiceNumber": "2026-017",
  "documentTypeCode": "875",
  "issueDate": "2026-08-28",
  "dueDate": "2026-09-11",
  "currency": "EUR",
  "contractReference": "V-2026-0815",
  "projectReference": "P-2026-042",
  "despatchAdviceRef": "LS-2026-1183",
  "invoicingPeriod": { "start": "2026-08-03", "end": "2026-08-28" },
  "seller": {
    "name": "Trockenbau Muster GmbH",
    "vatId": "DE123456789",
    "address": { "line1": "Werkstr. 1", "city": "Hamburg", "postalCode": "20095", "countryCode": "DE" }
  },
  "buyer": {
    "name": "Generalunternehmer AG",
    "address": { "line1": "Bauhof 3", "city": "Berlin", "postalCode": "10115", "countryCode": "DE" }
  },
  "delivery": {
    "name": "BV Musterpark, Haus B",
    "address": { "line1": "Parkallee 12", "city": "Potsdam", "postalCode": "14469", "countryCode": "DE" }
  },
  "lines": [
    {
      "name": "Metallständerwände gem. Aufmaß",
      "quantity": 250,
      "unitCode": "MTK",
      "unitPriceNet": 50,
      "vatRatePercent": 19,
      "allowanceCharges": [
        { "charge": false, "percent": 4, "reason": "Mengenrabatt" }
      ],
      "lineNetAmount": 12000
    },
    { "name": "Brandschutzverkleidung F90", "quantity": 70, "unitCode": "MTK", "unitPriceNet": 50, "vatRatePercent": 19, "lineNetAmount": 3500 }
  ],
  "allowanceCharges": [
    { "charge": false, "percent": 2, "reason": "Nachlass", "categoryCode": "S", "ratePercent": 19 }
  ],
  "vatBreakdown": [
    { "ratePercent": 19, "basisAmount": 15190, "taxAmount": 2886.10, "categoryCode": "S" }
  ],
  "totalNet": 15190,
  "totalVat": 2886.10,
  "totalGross": 18076.10,
  "payment": {
    "iban": "DE02120300000000202051",
    "bic": "BYLADEM1001",
    "reference": "2026-017",
    "terms": "Zahlbar innerhalb 14 Tagen. Sicherheitseinbehalt 5 % = 903,81 EUR, fällig nach Abnahme."
  }
}

Beispiel 4: Reverse Charge (Steuerschuldnerschaft des Leistungsempfängers)

Für Leistungen an Unternehmen im EU-Ausland (§ 13b UStG) schuldet der Empfänger die Umsatzsteuer. In der Rechnung heißt das:

  • Alle Sätze 0 %, Kategorie AE in der vatBreakdown-Aufschlüsselung. Die Positionen erben die Kategorie über ihren Steuersatz, dort genügt vatRatePercent: 0.
  • Führt eine Rechnung zwei Aufschlüsselungen mit demselben Satz (etwa E und AE, beide 0 %), setzen Sie zusätzlich categoryCode an den betroffenen Positionen (BT-151), sonst ist die Zuordnung mehrdeutig und die Validierung lehnt ab.
  • Pflicht ist die USt-IdNr. des Käufers (buyer.vatId) oder, wenn er keine hat, seine Handelsregisternummer (buyer.registrationId, BT-47). Ohne beides lehnt die Validierung vor der Erzeugung mit BR-AE-02 ab, es wird weder eine Datei erzeugt noch ein Credit verbraucht. Bei innergemeinschaftlicher Lieferung (K) genügt die Registernummer nicht.
  • Der Befreiungsgrund (exemptionReason, BT-120) ist optional; ohne Angabe schreibt der Generator den Standardtext „Reverse charge“, ein eigener Text wie hier gewinnt. Zusätzlich möglich: exemptionReasonCode (BT-121, für Reverse Charge VATEX-EU-AE).
invoice.json
{
  "invoiceNumber": "2026-042",
  "issueDate": "2026-09-05",
  "dueDate": "2026-09-19",
  "seller": {
    "name": "Muster GmbH",
    "vatId": "DE123456789",
    "address": { "line1": "Hauptstr. 1", "postalCode": "20095", "city": "Hamburg" }
  },
  "buyer": {
    "name": "Beispiel GmbH",
    "vatId": "ATU12345675",
    "address": { "line1": "Ringstraße 12", "postalCode": "1010", "city": "Wien", "countryCode": "AT" }
  },
  "lines": [
    { "name": "Softwareentwicklung September", "quantity": 20, "unitCode": "HUR", "unitPriceNet": 90, "vatRatePercent": 0, "lineNetAmount": 1800 }
  ],
  "vatBreakdown": [
    {
      "ratePercent": 0,
      "basisAmount": 1800,
      "taxAmount": 0,
      "categoryCode": "AE",
      "exemptionReason": "Steuerschuldnerschaft des Leistungsempfängers"
    }
  ],
  "totalNet": 1800,
  "totalVat": 0,
  "totalGross": 1800,
  "payment": {
    "iban": "DE02120300000000202051",
    "reference": "2026-042",
    "terms": "Zahlbar innerhalb 14 Tagen ohne Abzug"
  }
}

Für Marktplatz- und Factoring-Rechnungen gibt es außerdem payee (BG-10, der abweichende Zahlungsempfänger, Name ist Pflicht) und payment.accountName (BT-85, der Kontoinhaber); receivingAdviceRef (BT-15) ergänzt die Wareneingangsmeldung. Alle Felder stehen vollständig in der OpenAPI-Spezifikation.

Beispiel 5: XRechnung für öffentliche Auftraggeber

Behörden und andere öffentliche Auftraggeber in Deutschland verlangen die XRechnung. "profile": "xrechnung" erzeugt sie: Das XML trägt dann die KoSIT-CIUS-Kennung und den Peppol-Geschäftsprozess (BT-23/BT-24), und die Validierung prüft die zusätzlichen deutschen Pflichten vor der Erzeugung. Pflicht sind:

  • die Leitweg-ID in buyerReference (BT-10, teilt Ihnen der Auftraggeber mit),
  • ein Verkäufer-Ansprechpartner mit Name, Telefon und E-Mail (seller.contact, BR-DE-2),
  • Zahlungsangaben (payment mit IBAN oder Zahlungsart, BR-DE-1),
  • die elektronische Adresse beider Parteien (electronicAddress, BT-34/BT-49), als E-Mail-Adresse (Schema EM, Standard) oder Leitweg-ID (Schema 0204).

Die XRechnung ist ein reines XML-Format: Rufen Sie den Endpoint mit ?format=xml auf; der PDF-Weg (ZUGFeRD) lehnt das Profil ab.

invoice.json
{
  "profile": "xrechnung",
  "invoiceNumber": "2026-071",
  "issueDate": "2026-09-05",
  "dueDate": "2026-10-05",
  "buyerReference": "04011000-1234512345-06",
  "seller": {
    "name": "Muster GmbH",
    "vatId": "DE123456789",
    "address": { "line1": "Hauptstr. 1", "postalCode": "20095", "city": "Hamburg" },
    "contact": {
      "name": "Maria Muster",
      "phone": "+49 40 1234560",
      "email": "rechnung@muster-gmbh.de"
    },
    "electronicAddress": { "id": "rechnung@muster-gmbh.de" }
  },
  "buyer": {
    "name": "Stadt Beispielhausen, Amt für Gebäudemanagement",
    "address": { "line1": "Rathausplatz 1", "postalCode": "21079", "city": "Beispielhausen" },
    "electronicAddress": { "id": "04011000-1234512345-06", "scheme": "0204" }
  },
  "lines": [
    { "name": "Wartung Lüftungsanlage, 3. Quartal", "quantity": 1, "unitPriceNet": 1450, "vatRatePercent": 19, "lineNetAmount": 1450 }
  ],
  "vatBreakdown": [
    { "ratePercent": 19, "basisAmount": 1450, "taxAmount": 275.5 }
  ],
  "totalNet": 1450,
  "totalVat": 275.5,
  "totalGross": 1725.5,
  "payment": {
    "iban": "DE02120300000000202051",
    "reference": "2026-071",
    "terms": "Zahlbar innerhalb 30 Tagen ohne Abzug"
  }
}

Rechnungsdaten (invoice)

Die Datenstruktur folgt EN 16931. Vollständig und maßgeblich steht sie in der OpenAPI-Spezifikation (Schema Invoice), die direkt aus dem Validierungsschema erzeugt wird und damit immer aktuell ist. Lesbar und zum Ausprobieren gibt es sie als interaktive API-Dokumentation. Ein Beispiel mit Zahlungsdaten, Skonto und Hinweisen:

invoice.json
{
  "invoiceNumber": "2026-001",
  "issueDate": "2026-07-22",
  "currency": "EUR",
  "seller": {
    "name": "Muster GmbH",
    "vatId": "DE123456789",
    "address": { "line1": "Hauptstr. 1", "city": "Hamburg", "postalCode": "20095", "countryCode": "DE" }
  },
  "buyer": {
    "name": "Kunde AG",
    "address": { "line1": "Marktplatz 5", "city": "Berlin", "postalCode": "10115", "countryCode": "DE" }
  },
  "lines": [
    { "name": "Beratung", "quantity": 10, "unitCode": "HUR", "unitPriceNet": 100, "vatRatePercent": 19, "lineNetAmount": 1000 }
  ],
  "vatBreakdown": [
    { "ratePercent": 19, "basisAmount": 1000, "taxAmount": 190, "categoryCode": "S" }
  ],
  "totalNet": 1000,
  "totalVat": 190,
  "totalGross": 1190,
  "payment": {
    "iban": "DE02120300000000202051",
    "bic": "BYLADEM1001",
    "reference": "2026-001",
    "terms": "Zahlbar innerhalb 30 Tagen ohne Abzug",
    "discountPercent": 2.25,
    "discountDays": 14
  },
  "notes": [
    "Eigentumsvorbehalt bis zur vollständigen Bezahlung."
  ]
}

Zahlungsbedingungen und Skonto

Skonto wird strukturiert über discountPercent und discountDays übergeben (Tage ab Rechnungsdatum). EN 16931 hat dafür kein eigenes Feld, deshalb schreibt finisma die KoSIT-Konvention in die Zahlungsbedingungen (BT-20): zuerst Ihr Text aus terms, darunter je Stufe eine Marker-Zeile.

Zahlungsbedingungen im XML (BT-20)
Zahlbar innerhalb 30 Tagen ohne Abzug
#SKONTO#TAGE=14#PROZENT=2.25#

Liegt die Skonto-Frist nach dem Fälligkeitsdatum oder ist der Satz unplausibel hoch, wird die Rechnung weiterhin erzeugt, der Fall aber als Warnung gemeldet: formal gültig, inhaltlich prüfenswert.

Zahlungsart und SEPA-Lastschrift

Ohne Angabe schreibt der Generator bei gesetzter payment.iban die Zahlungsart 58 (SEPA-Überweisung, BT-81). payment.meansCode wählt explizit; die kuratierte Codeliste liefert GET /codelists/payment-means (30 Überweisung, 58 SEPA-Überweisung, 59 SEPA-Lastschrift). Für die Lastschrift (59) sind Mandatsreferenz (BT-89), Gläubiger-ID (BT-90) und das belastete Käuferkonto (BT-91) Pflicht. Fehlt eines, lehnt die Validierung vor der Erzeugung ab; ein Empfängerkonto gehört dort nicht in die Datei.

Ausschnitt: Lastschrift
"payment": {
  "meansCode": "59",
  "mandateId": "MANDAT-2026-042",
  "creditorId": "DE98ZZZ09999999999",
  "debitedIban": "DE02120300000000202051",
  "reference": "2026-042"
}

Artikelnummern und GTIN

Jede Position kann drei Kennungen tragen. Sie sind optional, aber der einzige Weg, Artikelnummern maschinell auswertbar zu übergeben statt sie in den Positionstext zu schreiben.

FeldBTBedeutung
sellerArticleIdBT-155Ihre eigene Artikel- oder Katalognummer.
buyerArticleIdBT-156Die Nummer, unter der der Kunde denselben Artikel führt (Bestell- oder EDV-Nummer).
globalArticleIdBT-157Normierte Kennung, in der Praxis eine GTIN. Eine EAN-13 ist eine GTIN-13 und gehört hierher.
globalArticleSchemeBT-157-1Nummernkreis der normierten Kennung, Code aus dem ISO-6523-Verzeichnis. Ohne Angabe wird 0160 (GS1 GTIN) angenommen.
Ausschnitt: Position mit Kennungen
"lines": [
  {
    "name": "Beratung",
    "sellerArticleId": "JH-1",
    "buyerArticleId": "4440",
    "globalArticleId": "4012345678901",
    "quantity": 10, "unitCode": "HUR", "unitPriceNet": 100,
    "vatRatePercent": 19, "lineNetAmount": 1000
  }
]

Abschlags- und Schlussrechnungen

Bereits geleistete Abschlagszahlungen kommen als paidAmount (Bruttobetrag, BT-113). Der offene Zahlbetrag wird daraus berechnet: totalGross − paidAmount. Wichtig: totalGross bleibt der volle Bruttobetrag der Gesamtleistung, nicht der offene Rest. Ohne paidAmount nennt die Rechnung den vollen Betrag als offen, und der Empfänger zahlt die Anzahlung ein zweites Mal.

Diese Konstellation ist am EN-16931-Validator unsichtbar (die Rechnung bleibt in sich schlüssig), deshalb prüft finisma sie zusätzlich anhand des Rechnungstextes und weist im Prüfbericht darauf hin. Ein Sicherheitseinbehalt ist keine Abschlagszahlung: Er gehört weder in paidAmount noch in allowanceCharges, sondern im Klartext in die Zahlungsbedingungen (siehe Beispiel 3).

Freitext-Hinweise

notes nimmt Hinweise der Rechnung auf (Eigentumsvorbehalt, Leistungszeitraum, Reverse-Charge-Vermerk). Jeder Eintrag wird eine eigene Notiz im XML (BT-22), höchstens 20 Einträge.

Zahlungsverkehr-Endpoints

Die Zahlungsverkehr-Endpoints liegen wie alles andere unter /api/v1: https://finisma.de/api/v1/payments/sepa und https://finisma.de/api/v1/payments/axz erzeugen die Zahlungsdateien, https://finisma.de/api/v1/payments/bank-lookup und https://finisma.de/api/v1/payments/structure-addresses liefern zu. Diese vier akzeptieren dasselbe Bearer-Credential wie oben (API-Key oder OAuth-Token, unabhängig vom Scope) oder alternativ die normale Login-Session des Browsers; anders als bei /verify und /invoices entscheidet keine OAuth-Scope-Prüfung, sondern ausschließlich das gebuchte Feature des Kontos.

https://finisma.de/api/v1/payments/validate prüft eine vorhandene Zahlungsdatei. Es verlangt einen API-Key oder ein OAuth-Token (Scope invoice:verify) und rechnet wie die Kontoauszug-Endpunkte über ein Freikontingent ab.

FeatureSchaltet freiKommt mit
converter/verify, /invoicesjedem aktiven Abo (8 € zzgl. MwSt./Monat oder höher)
payments/payments/sepa, /payments/bank-lookup; ohne Tageslimit auch /payments/validate, /statements/validate, /statements/parsedem Zahlungsverkehr-Abo (29 € zzgl. MwSt./Monat, schließt converter ein)
paymentsAbroad/payments/axz, /payments/structure-addresses, /payments/bank-lookupdem Auslandsmodul-Add-on (19 € zzgl. MwSt./Monat, nur zusammen mit dem Zahlungsverkehr-Abo buchbar, zusammen 48 € zzgl. MwSt./Monat)

GET https://finisma.de/api/v1/me meldet die gebuchten Features eines Kontos im Feld features, siehe Abschnitt „Authentifizierung“ oben.

POST /payments/sepa

Erzeugt aus einem Zahllauf (JSON) die SEPA-Sammelüberweisung (pain.001.001.09, SCT). Es ist dieselbe Erzeugung wie auf /zahlungen, das XML ist byteidentisch. Gedacht für ERP-Anbindungen und Workflows wie „Rechnungen rein, Zahldatei raus“. Erfordert payments. Die Antwort ist standardmäßig das XML als Datei (Content-Disposition, Dateiname sepa-inland-<Datum>.xml).

Anfrage
curl -X POST https://finisma.de/api/v1/payments/sepa \
  -H "Authorization: Bearer $FINISMA_KEY" \
  -H "Content-Type: application/json" \
  -d @zahllauf.json \
  -o sepa-inland.xml

Vor der Erzeugung laufen die Geschäftsregeln der /zahlungen-Seite (IBAN-Prüfziffern, Beträge, Verwendungszweck-Längen, Ausführungsdatum nicht in der Vergangenheit). Ein Verstoß ist ein 422 mit der vollständigen Issue-Liste in details (je Eintrag code, message, path, severity). Eine Datei wird dabei nicht erzeugt: lieber eine klare Absage als eine Datei, die die Bank ablehnt. Nach der Erzeugung prüft ein QA-Gate das XML autoritativ gegen das DK-TVS; scheitert es, kommt 502 mit den Befunden statt der Datei.

Mit Accept: application/json kommt statt des XML die Hülle { xml, filename, summary, warnings, verification }. Es gilt dieselbe enge Regel wie bei /invoices: der Header muss application/json nennen und darf weder application/xml noch */* enthalten. Der Standard-Header vieler HTTP-Bibliotheken bekommt also das XML, nicht ungefragt JSON. warnings sind nicht blockierende Meldungen (etwa die Dopplungs-Warnung); verification sagt, ob die Datei das autoritative QA-Gate durchlaufen hat.

Ein vollständiger Zahllauf mit zwei Zahlungen, die zweite mit eigener End-to-End-Referenz (ohne Angabe steht NOTPROVIDED in der Datei). Als zahllauf.json gespeichert läuft das Beispiel unverändert durch den curl-Aufruf oben:

zahllauf.json
{
  "debtor": {
    "name": "Muster GmbH",
    "iban": "DE89370400440532013000",
    "bic": "COBADEFFXXX"
  },
  "requestedExecutionDate": "2030-01-15",
  "payments": [
    {
      "creditorName": "Beispiel AG",
      "creditorIban": "DE02120300000000202051",
      "amount": 1190,
      "currency": "EUR",
      "remittanceInfo": "Rechnung 2026-001"
    },
    {
      "creditorName": "Zulieferer GmbH",
      "creditorIban": "DE44500105175407324931",
      "amount": 250.5,
      "currency": "EUR",
      "remittanceInfo": "Rechnung 4711",
      "endToEndId": "RG-4711"
    }
  ]
}

Gehaltszahlungen kennzeichnet "categoryPurpose": "SALA" an der einzelnen Zahlung. In der Datei steht dann CtgyPurp/Cd SALA im Zahlungsblock, und Gehaltszahlungen bekommen einen eigenen PmtInf-Block. Ein Lauf mit Gehältern und Rechnungen ergibt also eine Datei mit zwei Blöcken. Ob Ihre Bank Gehaltsdateien getrennt eingereicht haben will, klären Sie mit ihr. Andere Werte als SALA weist das Schema mit 400 ab, an /payments/axz ist das Feld ein 422 (PAY_CATEGORY_PURPOSE_AXZ).

POST /payments/axz

Erzeugt aus einem Zahllauf die Auslandszahlungsdatei nach dem DK-AXZ-Schema (GBIC_5): beliebige ISO-Währungen, Entgeltregelung je Zahlung (chargeBearer, Standard SHAR), Ausführungspriorität über serviceLevel (NURG, URGP, SDVA). Erfordert paymentsAbroad; payments allein reicht nicht. Ablauf, Fehlerformen und Accept-Regel wie bei /payments/sepa, das QA-Gate prüft gegen das AXZ-TVS; Dateiname axz-ausland-<Datum>.xml.

Zwei Angaben verlangt das AXZ-Profil zusätzlich: die Empfängeradresse (creditorAddress mit mindestens Ort und Länderkennung) und die Adresse des Auftraggebers (debtor.address). Statt einer IBAN ist auch eine nationale Kontonummer möglich (creditorAccountOther, dann BIC oder Empfängerbank mit Name und Adresse in creditorAgent).

Anfrage
curl -X POST https://finisma.de/api/v1/payments/axz \
  -H "Authorization: Bearer $FINISMA_KEY" \
  -H "Content-Type: application/json" \
  -d @zahllauf-ausland.json \
  -o axz-ausland.xml
zahllauf-ausland.json
{
  "debtor": {
    "name": "Muster GmbH",
    "iban": "DE89370400440532013000",
    "bic": "COBADEFFXXX",
    "currency": "EUR",
    "address": { "townName": "Hamburg", "countryCode": "DE" }
  },
  "requestedExecutionDate": "2030-01-15",
  "payments": [
    {
      "creditorName": "US Supplies Inc.",
      "creditorIban": "GB29NWBK60161331926819",
      "creditorBic": "NWBKGB2LXXX",
      "amount": 4800,
      "currency": "USD",
      "remittanceInfo": "Invoice INV-2030-017",
      "creditorAddress": {
        "streetName": "5th Avenue",
        "buildingNumber": "100",
        "postCode": "10001",
        "townName": "New York",
        "countryCode": "US"
      }
    }
  ]
}

GET /payments/bank-lookup

Liefert den BIC zu einer deutschen Bankleitzahl aus dem Bundesbank-Verzeichnis. Erfordert payments oder paymentsAbroad.

ParameterBedeutung
countryISO-3166-1-alpha-2-Ländercode. Nur DE liefert einen Treffer; jeder andere Wert liefert { "found": false, "reason": "unsupported_country" } (auch das mit Status 200, kein Fehler).
bankCodeDie achtstellige deutsche Bankleitzahl.
Anfrage
curl "https://finisma.de/api/v1/payments/bank-lookup?country=DE&bankCode=10070000" \
  -H "Authorization: Bearer $FINISMA_KEY"
Antwort bei einem Treffer
{ "found": true, "bic": "DEUTDEBBXXX", "bankName": "Deutsche Bank", "dataDate": "2026-06-08" }
Antwort: kein Treffer für eine unbekannte, aber gültig aufgebaute BLZ
{ "found": false }

POST /payments/structure-addresses

Zerlegt bis zu 50 Freitext-Adressen (je 1 bis 4 Zeilen) per KI in Ort, Postleitzahl, Straße und Hausnummer, gedacht für Auslandsüberweisungen ohne strukturierte Adressfelder. Erfordert paymentsAbroad (payments allein reicht hier nicht, anders als bei bank-lookup). Ohne Persistenz: Die Adressdaten werden nur für den Aufruf verarbeitet.

Anfrage
curl -X POST https://finisma.de/api/v1/payments/structure-addresses \
  -H "Authorization: Bearer $FINISMA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "id": "1", "countryCode": "FR", "addressLines": ["12 Rue de la Paix", "75002 Paris"] }
    ]
  }'
Antwort, ein Ergebnis pro eingehender ID, in derselben Reihenfolge
{
  "items": [
    {
      "id": "1",
      "townName": "Paris",
      "postCode": "75002",
      "streetName": "Rue de la Paix",
      "buildingNumber": "12",
      "confident": true
    }
  ]
}

War sich das Modell insgesamt nicht sicher, kommt nur { "id": "1", "confident": false } zurück. Es wird nichts erfunden, auch keine addressLines. Diesen Fall sollte die aufrufende Anwendung dem Nutzer zur manuellen Prüfung vorlegen.

POST /payments/validate

Prüft eine fertige Zahlungsdatei pain.001, gleich aus welchem Programm sie stammt: SEPA-Überweisung oder Auslandszahlung (AXZ), Versionen .09 und .03. Das Profil erkennt die API selbst, das Feld profile (sct oder axz) legt es fest. Geprüft wird in zwei Stufen: mit den fachlichen Regeln des pain.001-Validators (IBAN-Prüfziffern, Beträge, Kontrollsumme, Ausführungsdatum, Zeichensatz) und gegen das Schema (DK-TVS für SEPA bzw. AXZ, ISO-Schema für .03). Die Datei wird nicht gespeichert. Als MCP-Tool heißt die Funktion validate_payment_file.

Anfrage mit Datei (multipart, Feld file)
curl -X POST https://finisma.de/api/v1/payments/validate \
  -H "Authorization: Bearer $FINISMA_KEY" \
  -F "file=@zahllauf.xml"
Anfrage als JSON (Feld xml, optional profile)
jq -Rs '{xml: ., profile: "axz"}' zahllauf.xml | \
  curl -X POST https://finisma.de/api/v1/payments/validate \
    -H "Authorization: Bearer $FINISMA_KEY" \
    -H "Content-Type: application/json" \
    -d @-
Antwort, gekürzt
{
  "summary": "pain.001.001.09, Profil SEPA-Überweisung: 2 Zahlungen, 150.50 EUR. ...",
  "result": {
    "message": "pain.001",
    "version": "09",
    "profile": "sct",
    "profileSource": "detected",
    "summary": {
      "payments": 2,
      "declaredPayments": 2,
      "controlSum": "150.50",
      "declaredControlSum": "150.50",
      "currencies": [{ "currency": "EUR", "payments": 2, "total": "150.50" }],
      "executionDates": ["2026-11-13"],
      "paymentBlocks": 1
    },
    "findings": [
      {
        "code": "PAY_CHARSET_REPLACED",
        "severity": "info",
        "message": "Für den SEPA-Zeichensatz wurden Zeichen ersetzt: \"ä\" → \"ae\".",
        "path": "payments.0.creditorName"
      }
    ],
    "xsd": {
      "available": true,
      "ruleset": "de.dk:pain001:GBIC_5",
      "valid": true,
      "verdict": "valid",
      "errorCount": 0,
      "warningCount": 0,
      "findings": []
    },
    "verdict": "valid",
    "schemaChecked": true,
    "billing": { "via": "free_quota", "freeRemaining": 4, "freePeriod": "day" }
  }
}
  • findings sind die Befunde der Prüfregeln, je Eintrag code, severity (error, warning, info), message und path.
  • xsd.findings nennen bei Schemafehlern line und column in der Datei.
  • verdict fasst beides zusammen: invalid bei einem Fehler in einer der beiden Stufen, valid_with_warnings bei Warnungen, sonst valid.
  • Ist der Validierungsdienst nicht erreichbar, steht xsd.available auf false und schemaChecked auf false; das Urteil beruht dann nur auf den Prüfregeln.
  • Eine Datei, die gar keine pain.001 ist, endet mit 400; eine beschädigte pain.001 (kein wohlgeformtes XML) bekommt das Urteil invalid mit Fundstelle.

Kontoauszüge prüfen und lesen

Zwei Endpunkte für die Dateien, die die Bank liefert: camt.052 (Kontobericht), camt.053 (Kontoauszug) und camt.054 (Avis) in den Versionen .02 und .08, dazu camt.086 (Gebührenabrechnung) in .01 bis .05. Die Eingabe ist dieselbe wie bei /payments/validate: multipart mit dem Feld file oder JSON mit xml bzw. file_base64. Ein ZIP, etwa eine EBICS-Abholung, wird entpackt; Dateien darin, die keine unterstützte camt-Datei sind, stehen mit Grund in skipped. Als MCP-Tools heißen die Funktionen validate_bank_statement und parse_bank_statement.

GrenzeWert
Datei oder ZIPhöchstens 10 MB, darüber 413
ZIP entpackthöchstens 50 Dateien, je Datei 10 MB, zusammen 30 MB

POST /statements/validate

Prüft jede Datei mit den Regeln des camt-Viewers (Anfangssaldo plus Umsätze gleich Endsaldo, Summen und Zähler, IBAN-Prüfziffern, Reihenfolge der Auszüge, bei camt.086 Menge mal Preis und Steuern) und gegen das ISO-Schema der jeweiligen Version. Jede Datei bekommt ein eigenes Urteil, der Aufruf ein Gesamturteil. Bei camt.086 über mehrere Dateien meldet deliveryFindings fehlende Seiten einer Lieferung.

Anfrage
curl -X POST https://finisma.de/api/v1/statements/validate \
  -H "Authorization: Bearer $FINISMA_KEY" \
  -F "file=@auszug.xml"
Antwort, gekürzt
{
  "summary": "auszug.xml: camt.053.001.08, 0 Regelfehler, Schema ok, valid_with_warnings. ...",
  "result": {
    "files": [
      {
        "name": "auszug.xml",
        "message": "camt.053",
        "version": "08",
        "statements": 2,
        "items": 10,
        "findings": [
          {
            "code": "STMT_BALANCE_MISMATCH",
            "severity": "warning",
            "message": "Anfangssaldo 12500,00 plus Haben 1690,00 minus Soll 1292,40 ergibt 12897,60, der Endsaldo nennt 12897,61 (Differenz 0,01).",
            "path": "statements.0"
          }
        ],
        "xsd": { "available": true, "ruleset": "iso.20022:camt053:08", "valid": true, "verdict": "valid", "errorCount": 0, "warningCount": 0, "findings": [] },
        "verdict": "valid_with_warnings"
      }
    ],
    "deliveryFindings": [],
    "skipped": [],
    "verdict": "valid_with_warnings",
    "schemaChecked": true,
    "billing": { "via": "free_quota", "freeRemaining": 4, "freePeriod": "day" }
  }
}

POST /statements/parse

Liefert den Inhalt als JSON, im selben Modell, das der camt-Viewer anzeigt. Eine Schemaprüfung findet hier nicht statt; parseFindings enthält nur Hinweise des Lesens. Gedacht für die Buchhaltung: Zahlungseingänge zuordnen, Umsätze in ein ERP übernehmen, Bankgebühren auswerten.

Anfrage
curl -X POST https://finisma.de/api/v1/statements/parse \
  -H "Authorization: Bearer $FINISMA_KEY" \
  -F "file=@auszug.xml"
Antwort, gekürzt auf eine Buchung
{
  "summary": "auszug.xml: camt.053.001.08, 1 Auszug/Auszüge, 2 Buchungen.",
  "result": {
    "files": [
      {
        "name": "auszug.xml",
        "message": "camt.053",
        "version": "08",
        "header": { "msgId": "CAMT053-2026-10-01-0001", "creDtTm": "2026-10-02T06:15:00+02:00" },
        "statements": [
          {
            "id": "STMT-2026-10-01-A-DK",
            "legalSeqNb": 8,
            "account": { "iban": "DE02120300000000202051", "currency": "EUR", "ownerName": "Musterfirma GmbH" },
            "balances": [
              { "type": "OPBD", "amount": 12897.6, "currency": "EUR", "creditDebit": "CRDT", "date": "2026-09-30" },
              { "type": "CLBD", "amount": 13997.7, "currency": "EUR", "creditDebit": "CRDT", "date": "2026-10-01" }
            ],
            "entries": [
              {
                "amount": 1190,
                "currency": "EUR",
                "creditDebit": "CRDT",
                "reversal": false,
                "status": "BOOK",
                "bookingDate": "2026-10-01",
                "valueDate": "2026-10-01",
                "bankTxCode": { "domain": "PMNT", "family": "RCDT", "subFamily": "ESCT", "proprietary": "NTRF+166", "gvc": "166" },
                "transactions": [
                  {
                    "refs": { "endToEndId": "RE-2026-0815" },
                    "amount": 1190,
                    "counterparty": { "name": "Adler Logistik GmbH", "iban": "DE75512108001245126199" },
                    "remittance": {
                      "unstructured": ["EREF+RE-2026-0815", "SVWZ+Rechnung RE-2026-0815 vom 15.09.2026"],
                      "dkKeys": { "eref": "RE-2026-0815", "svwz": "Rechnung RE-2026-0815 vom 15.09.2026", "rest": [] }
                    }
                  }
                ]
              }
            ]
          }
        ],
        "parseFindings": []
      }
    ],
    "skipped": [],
    "billing": { "via": "free_quota", "freeRemaining": 4, "freePeriod": "month" }
  }
}

Wo die üblichen Felder einer Buchung stehen (camt.052, camt.053, camt.054):

FeldPfad in statements[]
Buchungsdatum, Valutaentries[].bookingDate, entries[].valueDate
Betrag, Währung, Soll/Habenentries[].amount (ohne Vorzeichen), currency, creditDebit (CRDT Haben, DBIT Soll); Stornos tragen reversal: true
Gegenpartei, IBANentries[].transactions[].counterparty.name, .iban
Verwendungszwecktransactions[].remittance.unstructured; DK-Schlüssel (SVWZ+, EREF+ und weitere) zerlegt in remittance.dkKeys
End-to-End-Referenztransactions[].refs.endToEndId
GVC, BkTxCdentries[].bankTxCode (domain, family, subFamily, proprietary, gvc)

Bei camt.086 enthält statements[] die Abrechnungen mit services[] (Menge, Stückpreis, Entgelt, Steuer). Dazu kommen totals je Preiswährung (net, waived, tax, gross) und settlementTotals für Belastungen in einer anderen Abrechnungswährung:

Ausschnitt einer camt.086-Antwort
"totals": [
  { "currency": "EUR", "services": 10, "net": 348.9, "waived": 9.9, "tax": 60.52, "gross": 409.42 }
],
"settlementTotals": [
  { "currency": "USD", "value": 31.59, "services": 2 }
]

Freikontingent und Abrechnung

Für /payments/validate, /statements/validate und /statements/parse ist ein finisma-Konto nötig (API-Key oder OAuth-Token). Abgerechnet wird je Aufruf in dieser Reihenfolge:

  1. Konto mit Zahlungsverkehr-Abo (29 € zzgl. MwSt./Monat, Feature payments): frei, ohne Limit. Das Konverter-Abo deckt diese Endpunkte nicht ab.
  2. Sonst das kostenlose Kontingent je Konto. Beim Prüfen gilt es je Tag und beginnt um Mitternacht neu, beim Auslesen gilt es je Kalendermonat (beides Europe/Berlin).
  3. Danach das Guthaben, das Sie unter /konto aufladen. Prüfungen laufen über einen Prüfblock: 1 Credit kauft 10 Prüfungen, gemeinsam für Zahlungsdateien und Kontoauszüge. Offene Prüfungen verfallen nicht. Das Auslesen kostet je Aufruf einen Credit.
  4. Ohne Guthaben antwortet die API mit 402 payment_required; der Header Retry-After nennt die Sekunden bis zum neuen Kontingent.
EndpunktKostenlosDanach ohne Abo
POST /payments/validate5 pro Tag1 Credit für 10 Prüfungen
POST /statements/validate5 pro Tag1 Credit für 10 Prüfungen
POST /statements/parse5 pro Monat1 Credit je Aufruf

Jede Antwort nennt in billing, womit bezahlt wurde: via ist free_quota, subscription, check_block oder credit. freeRemaining nennt die verbleibenden kostenlosen Aufrufe im Zeitraum freePeriod (day oder month, beim Abo null). Beim Prüfblock steht in checkBlockRemaining, wie viele Prüfungen noch offen sind; creditsCharged erscheint, wenn ein Credit abgebucht wurde. Dateien, die schon an Format oder Größe scheitern, kosten nichts. Bricht die Verarbeitung nach der Buchung ab, gehen Kontingent, Prüfung bzw. Credit zurück. Ist die Schemaprüfung nicht erreichbar (schemaChecked: false), kostet das Prüfen ebenfalls nichts; billing trägt dann refunded: true.

Fehler

Alle Endpoints, auch die Zahlungsverkehr-Endpoints, antworten im selben Format: JSON { "error", "code", "details" } mit passendem HTTP-Status. Für structure-addresses kommen invalid_input (400, Body entspricht nicht dem Schema) und upstream_error (502, der KI-Dienst ist nicht erreichbar) dazu. Die Zahlungsdatei-Endpoints trennen zwei Ablehnungsstufen: 400, wenn der Body nicht dem Schema entspricht, und 422, wenn er dem Schema entspricht, aber Geschäftsregeln verletzt; die Issue-Liste steht dann in details.

StatuscodeBedeutung
400invalid_inputDatei oder Rechnungsdaten fehlerhaft, bei den Prüf-Endpunkten auch: kein unterstütztes Format oder keine unterstützte Version.
422invalid_inputNur /payments/sepa und /payments/axz: der Zahllauf entspricht dem Schema, verletzt aber Geschäftsregeln (z. B. IBAN-Prüfziffer). details enthält die Issue-Liste, eine Datei wird nicht erzeugt.
401auth_required / invalid_keyKein Credential mitgeschickt bzw. ungültig, abgelaufen oder widerrufen.
403subscription_requiredWeder Abo noch Guthaben: beim Auslesen, bei format=xml und beim Erzeugen erst, wenn auch Guthaben (und beim Erzeugen das Tageskontingent) nicht greifen.
402payment_requiredNur /payments/validate und /statements/…: Freikontingent aufgebraucht, kein Zahlungsverkehr-Abo und kein Guthaben. Die Meldung steht auf Englisch und Deutsch; Retry-After nennt die Sekunden bis Mitternacht bzw. beim Auslesen bis zum Monatsanfang.
413payload_too_largeNur /payments/validate und /statements/…: Datei größer als 10 MB.
429free_quota_exhaustedTageslimit der kostenlosen Erzeugung erreicht; Wartezeit bis Mitternacht im Header Retry-After.
403insufficient_scopeOAuth-Token ohne den nötigen Scope für diese Funktion.
429rate_limitedZu viele Anfragen; Wartezeit im Header Retry-After.
502upstream_errorFehler in einem nachgelagerten Dienst. Bei den Zahlungsdatei-Endpoints auch: das erzeugte XML hat das autoritative QA-Gate nicht bestanden (details enthält die Befunde); es wird keine ungeprüfte Datei ausgeliefert.

Hinweise

  • Die API ist für Server-zu-Server-Aufrufe gedacht. CORS-Header gibt es nur für finisma.de selbst, Aufrufe aus dem Browser fremder Seiten blockt der Browser deshalb.
  • Hochgeladene Dateien werden nur für die Verarbeitung gehalten und nicht dauerhaft gespeichert.
  • Automatisierungen wie n8n nutzen den invoice-Teil als JSON und den pdf-Teil als Binär-Datei im selben multipart-Request.

Maschinenlesbare Spezifikation: https://finisma.de/api/v1/openapi.json, kurz auch unter finisma.de/openapi.json. Interaktiv lesen und ausprobieren: interaktive API-Dokumentation.