← Entwickler-Dokumentation

REST-API-Referenz

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

Authentifizierung

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:

Authorization: Bearer fnsm_…

Das Prüfen (/verify) ist kostenlos, verlangt aber ebenfalls ein Credential. Das Erzeugen (/invoices) setzt ein aktives Abo voraus. 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.

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.

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

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

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

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

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.

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

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. Ein Beispiel mit Zahlungsdaten, Skonto und Hinweisen:

{
  "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.

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.

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

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

Zwei zusätzliche Endpoints liegen nicht unter /api/v1, sondern unter /api: https://finisma.de/api/payments/bank-lookup und https://finisma.de/api/payments/structure-addresses. Beide 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.

FeatureSchaltet freiKommt mit
converter/verify, /invoicesjedem aktiven Abo (8 € zzgl. MwSt./Monat oder höher)
payments/payments/bank-lookupdem Zahlungsverkehr-Abo (29 € zzgl. MwSt./Monat, schließt converter ein)
paymentsAbroad/payments/bank-lookup, /payments/structure-addressesdem 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.

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.
curl "https://finisma.de/api/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" }

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.

curl -X POST https://finisma.de/api/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.

Abweichendes Fehlerformat der Zahlungsverkehr-Endpoints

Anders als die übrige REST-API (siehe „Fehler“ unten) antworten /payments/bank-lookup und /payments/structure-addresses nur mit { "error" }, Klartext, kein maschinenlesbares code-Feld. Außerdem gibt es kein eigenes 401 für ein fehlendes oder ungültiges Credential: Fehlt die Authentifizierung, liefern beide Endpoints denselben 403 wie beim fehlenden Feature.

StatusBedeutung
400nur structure-addresses: Request-Body entspricht nicht dem Schema (z. B. mehr als 50 Items oder eine Adresszeile über 70 Zeichen).
403Feature fehlt ODER keine gültige Authentifizierung mitgeschickt.
502nur structure-addresses: der KI-Dienst ist derzeit nicht erreichbar.

Fehler (verify, invoices, me)

Fehler kommen als JSON { "error", "code", "details" } mit passendem HTTP-Status:

StatuscodeBedeutung
400invalid_inputDatei oder Rechnungsdaten fehlerhaft.
401auth_required / invalid_keyKein Credential mitgeschickt bzw. ungültig, abgelaufen oder widerrufen.
403subscription_requiredErzeugen ohne aktives Abo.
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.

Hinweise

  • Die API ist für Server-zu-Server-Aufrufe gedacht. Es werden keine CORS-Header gesetzt, Aufrufe direkt aus dem Browser sind nicht vorgesehen.
  • 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