Quickstart & Rezepte
Fünf vollständige Beispiele. Jedes wurde vor der Veröffentlichung gegen die laufende API ausgeführt — die gezeigten Antworten sind die echten, Beträge und Kennungen unverändert, base64-Inhalte gekürzt.
Bevor Sie anfangen
- Basis-URL
https://api.invoapi.de- Anmeldung
Authorization: Bearer inv_live_…— alternativ der HeaderX-API-Key. Schlüssel erstellen Sie im Dashboard.- Ohne Schlüssel
/api/v1/health/api/v1/formats/api/v1/formats/{id}- Bei Rückfragen
- Jede Antwort trägt
X-Request-ID. Schicken Sie die Kennung mit — damit finden wir genau Ihren Aufruf im Protokoll.
1Gültiges ZUGFeRD-PDF aus JSON erzeugen
Ein hybrides PDF/A-3 (Factur-X) mit eingebettetem XML, in derselben Anfrage geprüft. Der häufigste Einstieg.
curl -X POST https://api.invoapi.de/api/v1/generate \
-H "Authorization: Bearer $INVOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"format": "zugferd",
"syntax": "cii",
"profile": "en16931",
"output": "pdf",
"validate": true,
"invoice": {
"invoice_number": "RE-2026-0100",
"issue_date": "2026-08-23",
"due_date": "2026-09-22",
"currency": "EUR",
"buyer_reference": "04011000-12345-34",
"delivery": { "date": "2026-08-23" },
"seller": {
"name": "Muster GmbH", "street": "Musterstraße 1",
"postal_code": "10115", "city": "Berlin", "country_code": "DE",
"vat_id": "DE123456789", "email": "rechnung@muster.de",
"contact": {
"name": "Erika Muster", "phone": "+49 30 1234567",
"email": "rechnung@muster.de"
}
},
"buyer": {
"name": "Beispiel AG", "street": "Teststraße 10",
"postal_code": "20095", "city": "Hamburg", "country_code": "DE",
"vat_id": "DE987654321", "email": "kreditor@beispiel.de"
},
"items": [
{ "name": "Softwareentwicklung", "quantity": 12,
"unit": "HUR", "unit_price": 110, "vat_rate": 19 }
],
"payment": {
"means_code": "58", "iban": "DE02120300000000202051",
"terms": "Zahlbar innerhalb von 30 Tagen ohne Abzug."
}
}
}'{
"success": true,
"format": "zugferd", "syntax": "cii", "version": "2.5.2", "profile": "en16931",
"valid": true,
"invoice_id": "RE-2026-0100",
"totals": {
"net": "1320.00", "tax": "250.80",
"gross": "1570.80", "payable": "1570.80", "currency": "EUR"
},
"validation": {
"status": "valid",
"rule_sets": [
{ "name": "XML Schema for UN/CEFACT XML (SCRDM - CII uncoupled)",
"kind": "xml-schema", "passed": true },
{ "name": "Schematron rules for EN16931 (CII)",
"kind": "schematron", "passed": true },
{ "name": "PDF/A-3 conformance (veraPDF)",
"kind": "pdf-a", "passed": true },
{ "name": "ZUGFeRD / Factur-X profile rules (Mustang)",
"kind": "schematron", "passed": true }
]
},
"document": {
"mime_type": "application/pdf",
"filename": "RE-2026-0100.pdf",
"content_base64": "JVBERi0xLjQK…"
}
}Das PDF kommt als base64 in content_base64 — nicht als Binärdatei. In der Shell: `… | jq -r .document.content_base64 | base64 -d > rechnung.pdf`. Der PDF/A-Schritt prüft den Container, nicht nur das XML.
2Eine vorhandene Datei prüfen
Vier Wege, dasselbe Ergebnis. Nimm den, der zu deinem HTTP-Client passt — alle vier sind gleichwertig.
# 1 — Datei-Upload
curl -X POST https://api.invoapi.de/api/v1/validate \
-H "Authorization: Bearer $INVOAPI_KEY" \
-F "file=@rechnung.xml;type=application/xml"
# 2 — rohes XML im Body
curl -X POST https://api.invoapi.de/api/v1/validate \
-H "Authorization: Bearer $INVOAPI_KEY" \
-H "Content-Type: application/xml" \
--data-binary @rechnung.xml
# 3 — JSON mit eingebettetem XML
curl -X POST https://api.invoapi.de/api/v1/validate \
-H "Authorization: Bearer $INVOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"xml": "<Invoice …>"}'
# 4 — JSON mit base64 (auch für PDFs)
curl -X POST https://api.invoapi.de/api/v1/validate \
-H "Authorization: Bearer $INVOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{"content_base64": "PD94bWwg…"}'{
"status": "invalid",
"valid": false,
"detected_format": "xrechnung",
"syntax": "ubl",
"result": {
"summary": { "errors": 1, "warnings": 0, "infos": 1, "rules_checked": null },
"issues": [
{
"severity": "error",
"rule": "BR-DE-6",
"message": "Das Element \"Seller contact telephone number\" (BT-42) muss übermittelt werden.",
"explanation": "Eine XRechnung-spezifische Regel (deutsche Verschärfung von EN 16931).",
"suggestion": "Setzen Sie seller.contact.phone.",
"field": "seller.contact.phone",
"business_term": "BT-42",
"rule_set": "Schematron rules for Invoice - CIUS XRechnung (UBL)"
}
]
},
"request_id": "req_…"
}Jeder Befund trägt neben der Regel-ID auch explanation, suggestion und field — du brauchst keine eigene Übersetzungstabelle für KoSIT-Regeln.
3XRechnung in ZUGFeRD umwandeln
convert nimmt eine Quelle (content_base64, xml oder kanonisches invoice) und ein Ziel (xml, json, html, pdf).
curl -X POST https://api.invoapi.de/api/v1/convert \
-H "Authorization: Bearer $INVOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"content_base64": "'"$(base64 -i rechnung-xrechnung.xml)"'",
"target": "pdf",
"format": "zugferd"
}'{
"success": true,
"source": { "format": "xrechnung", "syntax": "ubl" },
"target": "pdf",
"format": "zugferd",
"document": {
"mime_type": "application/pdf",
"filename": "RE-2026-0042.pdf",
"content_base64": "JVBERi0xLjQK…"
}
}Für target: "xml" und "pdf" ist format erforderlich — es sagt, in welchen Standard umgewandelt wird. Für "json" und "html" ist es optional.
4Format erkennen und Inhalt auslesen
Zwei leichte Endpunkte ohne Validierung: detect sagt, was ein Dokument ist; parse liest es in das kanonische Modell.
# Was ist das überhaupt?
curl -X POST https://api.invoapi.de/api/v1/detect \
-H "Authorization: Bearer $INVOAPI_KEY" \
-F "file=@unbekannt.pdf;type=application/pdf"
# Inhalt als JSON herauslesen
curl -X POST https://api.invoapi.de/api/v1/parse \
-H "Authorization: Bearer $INVOAPI_KEY" \
-H "Content-Type: application/xml" \
--data-binary @rechnung.xml{
"format": "zugferd",
"syntax": "cii",
"version": "2.5.2",
"profile": "en16931",
"container": "pdf",
"document_type": "invoice",
"confidence": 1.0,
"evidence": [
"CustomizationID (BT-24) is 'urn:cen.eu:en16931:2017'",
"factur-x.xml found as an embedded file"
]
}Ein unbekanntes Dokument liefert format: "unknown" mit confidence: 0.0 — niemals eine geratene Zuordnung.
5Summen prüfen, ohne ein Dokument zu erzeugen
calculate rechnet Netto, Steueraufschlüsselung und Bruttobetrag aus, ohne etwas zu erzeugen. Nützlich, um die eigene Berechnung gegenzuprüfen.
curl -X POST https://api.invoapi.de/api/v1/calculate \
-H "Authorization: Bearer $INVOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"invoice": {
"invoice_number": "RE-1", "issue_date": "2026-08-23", "currency": "EUR",
"seller": { "name": "Muster GmbH", "street": "Musterstraße 1",
"postal_code": "10115", "city": "Berlin",
"country_code": "DE", "vat_id": "DE123456789" },
"buyer": { "name": "Beispiel AG", "street": "Teststraße 10",
"postal_code": "20095", "city": "Hamburg", "country_code": "DE" },
"items": [
{ "name": "Beratung", "quantity": 10, "unit": "HUR",
"unit_price": 100, "vat_rate": 19 },
{ "name": "Handbuch", "quantity": 3, "unit": "C62",
"unit_price": 24.5, "vat_rate": 7 }
]
}
}'{
"totals": {
"net": "1073.50", "tax": "195.15",
"gross": "1268.65", "payable": "1268.65", "currency": "EUR"
},
"breakdown": [
{ "category": "S", "rate": "19.00",
"taxable_amount": "1000.00", "tax_amount": "190.00" },
{ "category": "S", "rate": "7.00",
"taxable_amount": "73.50", "tax_amount": "5.15" }
],
"warnings": [],
"request_id": "req_…"
}Gerundet wird kaufmännisch und je Steuergruppe: 73,50 € × 7 % = 5,145 € wird zu 5,15 €, nicht zu 5,14 €.
Gut zu wissen
Welcher Endpunkt nimmt was entgegen
| Endpunkt | akzeptiert |
|---|---|
| validate · detect · parse · render | multipart/form-data (Feld file) · rohes application/xml · application/pdf · text/plain · JSON mit xml, content_base64 oder invoice |
| generate · convert · calculate | JSON |
Aufruf aus dem Browser
Geht nicht, und das ist Absicht: Die API setzt für fremde Domains keine CORS-Header, ein fetch von einer Webseite scheitert daher. Der Grund ist Ihr API-Schlüssel — was der Browser senden kann, kann ein Besucher im Quelltext lesen. Rufen Sie die API aus Ihrem Backend auf.
Grenzen
- Dokumente bis 10 MiB; darüber
413. - Jede Antwort trägt
X-RateLimit-Limit,X-RateLimit-RemainingundX-RateLimit-Reset(Fensterlänge in Sekunden). Bei Überschreitung429mitRetry-After. - Ein Rumpf ohne
Content-Typewird mit415abgelehnt — er lässt sich nicht lesen.
Fehler sehen immer gleich aus
{
"error": {
"code": "BAD_REQUEST",
"message": "…",
"details": [ { "field": "…", "rule": "…", "message": "…" } ],
"request_id": "req_…"
}
}ubl und cii sind Syntaxen, keine eigenen Standards
Ein damit erzeugtes Dokument trägt als CustomizationID (BT-24) die EN-16931-Kennung — denn das ist der Standard, den es erfüllt. detect meldet es deshalb als en16931. Das ist kein Rundlauf-Fehler: Die Syntax bestimmt die XML-Grammatik, der Standard die Regeln.
Welche Formate und Fähigkeiten aktuell unterstützt werden, fragen Sie live unter GET /api/v1/formats ab — nichts davon muss fest verdrahtet werden. Die vollständige Referenz steht in der Dokumentation.

