Ihr Layout, unser XML
Sie haben Ihr Rechnungs-PDF schon — aus dem ERP, aus der Druckerei, aus der eigenen Vorlage. Dieser Endpunkt nimmt es als Träger, erzeugt das CII-XML aus Ihren Rechnungsdaten und gibt genau dieses PDF zurück, mit dem XML als factur-x.xml darin.
/api/v1/invoice/{countryCode}/{format}/embedDer Unterschied zu /generate
Beide Endpunkte erzeugen dieselbe E-Rechnung aus denselben Daten und laufen über dasselbe Entitlement. Der einzige Unterschied ist die Seite, die der Empfänger sieht.
- • Bei /generate kommt das Sichtblatt aus unserer Vorlage — dann ist die PDF/A-3-Konformität unser Problem.
- • Bei /embed kommt das Sichtblatt von Ihnen und bleibt unverändert; wir legen nur das XML hinein und setzen die Kennungen.
- • Gleiche Rechnungsdaten, gleiche Prüfkette, gleiche Fehlerbilder (422 mit denselben Regelcodes).
template und templateId gibt es auf dieser Route nicht. Der Rumpf ist strikt und weist sie mit 400 ab, statt sie still zu ignorieren — das Layout ist ja Ihres.
Zur Creator API (/generate)Nur für die Zwitterformate
Ein Zwitterdokument braucht ein Sichtblatt, für das ein Träger-PDF einspringen kann. Reine XML-Formate wie xrechnung oder ubl haben keines, und das reine pdf trägt kein XML — beide antworten mit 400.
Pfad-Parameter
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
countryCode | string | Land des Zwitterformats — de, ch, fr oder be. | |
format | string | Das Zwitterformat: zugferd für DE und CH, facturx für FR und BE. |
Anfrage
| Feld | Typ | Pflicht | Beschreibung |
|---|---|---|---|
pdf | string (base64) | Ihr Träger-PDF, Base64-kodiert. Es wird das Sichtblatt des Zwitterdokuments. | |
invoice | object | Die Rechnungsdaten, aus denen das XML erzeugt wird — dieselbe Struktur wie bei /generate, mit denselben Pflichtfeldern. | |
validate | boolean | - | Führt zusätzlich die XSD- und Schematron-Prüfung aus und gibt das Urteil im Block validation zurück. Kostet eine zweite Einheit, genau wie bei /generate. |
formatOptions.profile | string | - | ZUGFeRD-/Factur-X-Profil, Standard EN16931. |
formatOptions.version | string | - | Fassung des Formats, Standard 2.4 für ZUGFeRD und 1.08 für Factur-X. |
PDF/A-3 wird deklariert, nicht konvertiert
Wir setzen die Kennung an dem Träger, den Sie geschickt haben. Wir schreiben Ihr PDF nicht um. Bei einem beliebigen PDF kann diese Deklaration deshalb falsch sein.
- • Am häufigsten, wenn Ihr PDF Schriften ohne eingebettete Schriftdatei verwendet — das verlangt PDF/A-3.
- • Was wir tatsächlich geprüft haben, steht in carrier.checks; was auffiel, in carrier.warnings.
- • Ein vollständiger PDF/A-3b-Nachweis (veraPDF) findet bei uns nicht statt. Abgelehnt wird deswegen nichts: Die Feststellung ist unsere, die Entscheidung ist Ihre.
Wenn Ihr Empfänger einen vollständigen Nachweis verlangt: Schicken Sie Ihren Träger einmal durch einen PDF/A-Konverter, bevor er zu uns kommt — oder nutzen Sie /generate, wo das PDF unseres ist.
Beispiel
1curl -X POST https://service.invoice-api.xhub.io/api/v1/invoice/de/zugferd/embed \2 -H "Authorization: Bearer sk_live_abc123..." \3 -H "Content-Type: application/json" \4 -d '{5 "pdf": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9UeXBlL1BhZ2UvTWVkaWFCb3...",6 "validate": true,7 "invoice": {8 "invoiceNumber": "RE-2025-001",9 "type": "invoice",10 "issueDate": "2025-01-15",11 "dueDate": "2025-02-15",12 "currency": "EUR",13 "seller": {14 "name": "Meine Firma GmbH",15 "street": "Musterstraße 1",16 "city": "Berlin",17 "postalCode": "10115",18 "countryCode": "DE",19 "vatId": "DE123456789",20 "bankAccount": {21 "iban": "DE89370400440532013000",22 "bic": "COBADEFFXXX"23 }24 },25 "buyer": {26 "name": "Kunde AG",27 "street": "Kundenweg 42",28 "city": "München",29 "postalCode": "80331",30 "countryCode": "DE",31 "vatId": "DE987654321"32 },33 "countrySpecific": {34 "countryCode": "DE",35 "buyerReference": "BUYER-REF-001"36 },37 "items": [38 {39 "position": 1,40 "description": "Beratungsleistung",41 "quantity": 10,42 "unit": "HUR",43 "unitPrice": 150.00,44 "taxRate": 19,45 "taxCategoryCode": "S",46 "netAmount": 1500.00,47 "taxAmount": 285.00,48 "grossAmount": 1785.0049 }50 ],51 "subtotal": 1500.00,52 "total": 1785.00,53 "taxSummary": [54 {55 "taxRate": 19,56 "netAmount": 1500.00,57 "taxAmount": 285.0058 }59 ]60 }61 }'Antwort
200 OK1{2 "success": true,3 "format": "zugferd",4 "filename": "RE-2025-001.pdf",5 "mimeType": "application/pdf",6 "hash": "9f2c4e1b7a0d5836f4b2c9e18a7d3045c6b1e8f29a4d70b3c5e81f6a2d9c4b70",7 "payloadHash": "3a7f1e9c2b8d4056a1c7e3f9b2d84e6015c9a7f3e1b8d2460c5a9f7e3b1d8460",8 "data": "JVBERi0xLjQKJeLjz9MKMyAwIG9iago8PC9UeXBlL1BhZ2Uv...",9 "embeddedXml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><rsm:CrossIndustryInvoice ...",10 "carrier": {11 "pdfA3Declared": true,12 "checks": [13 { "code": "PDF-001", "severity": "error", "passed": true },14 { "code": "XMP-003", "severity": "warning", "passed": true }15 ],16 "warnings": [17 {18 "code": "CARRIER_FONTS_NOT_EMBEDDED",19 "message": "The carrier uses 2 fonts without an embedded font file."20 }21 ]22 },23 "validation": {24 "valid": true,25 "status": "passed",26 "results": [27 { "format": "zugferd-2.4-en16931", "valid": true, "errors": [] }28 ]29 },30 "appliedFormatOptions": {31 "version": "2.4",32 "profile": "EN16931"33 }34}| Feld | Typ | Beschreibung |
|---|---|---|
success | boolean | Ob das Zwitterdokument entstanden ist. |
format | string | Das verwendete Ausgabeformat. |
filename | string | Vorgeschlagener Dateiname. |
mimeType | string | MIME-Typ des zurückgegebenen Dokuments. |
hash | string | SHA-256 der zurückgegebenen Bytes, hexadezimal. Nicht stabil über Aufrufe hinweg, weil das PDF einen Erzeugungszeitstempel trägt — für idempotente Namen payloadHash nehmen. |
payloadHash | string | SHA-256 über das kanonische JSON Ihrer Rechnungsdaten — der stabile Schlüssel für idempotente Aufrufe. hash wechselt bei jedem Aufruf, weil das PDF einen Zeitstempel trägt. |
data | string (base64) | Das Zwitterdokument, Base64-kodiert: Ihr Layout mit dem erzeugten XML darin. |
embeddedXml | string | Das eingebettete CII-XML im Klartext, zum Nachsehen. |
carrier | object | Was wir an dem erzeugten Dokument gemessen haben — ein Befund, keine Behauptung. |
validation | object | Das Prüfurteil — nur vorhanden, wenn die Anfrage validate: true gesetzt hat. |
appliedFormatOptions | object | Fassung und Profil, in denen das Dokument wirklich geschrieben wurde: Ihre Angaben oder die Vorgaben. |
Der carrier-Block
| Feld | Typ | Beschreibung |
|---|---|---|
pdfA3Declared | boolean | true heißt: Wir haben die PDF/A-3-Kennung auf Ihrem Träger gesetzt (XMP pdfaid:part=3, Stufe B, sRGB-OutputIntent). Es heißt nicht, dass Ihr Träger konform ist. |
checks | array | Die Einzelprüfungen, je mit code, severity (warning, error, fatal), passed und, wenn es einen gibt, message. |
warnings | array | Feststellungen zu Ihrem Träger, die wir melden, aber nicht ablehnen — etwa CARRIER_FONTS_NOT_EMBEDDED. |
Der validation-Block
Nur vorhanden, wenn Sie validate: true geschickt haben. status sagt, was passiert ist: passed — die Regelwerke liefen und fanden nichts; failed — sie fanden etwas, valid ist dann false und errors nennt es; not_run — sie liefen nicht vollständig, valid ist null und reason nennt den Grund (missing, load-failed, transform-failed oder not-bound). Ein not_run ist kein Freibrief: Über das Dokument ist damit nichts bewiesen.
Abrechnung
Der Aufruf läuft auf dem vorhandenen create-Entitlement des Formats und kostet eine Einheit — zwei, wenn Sie validate: true setzen. Kein neues Entitlement, kein Zuschlag.
Fehler
| Status | Code | Beschreibung |
|---|---|---|
| 400 | Bad Request | Das Land-Format-Paar ist kein Zwitterformat — ein reines XML-Format oder pdf hat kein Sichtblatt. |
| 400 | Bad Request | template oder templateId wurde geschickt. Auf dieser Route gibt es beides nicht. |
| 400 | INVALID_PDF | Der Träger ist kein lesbares PDF. |
| 401 | UNAUTHORIZED | API-Key fehlt oder ist ungültig. |
| 403 | FORBIDDEN | Der Key hält das create-Entitlement des Formats nicht. |
| 413 | PAYLOAD_TOO_LARGE | Das Träger-PDF ist größer als das konfigurierte Limit. |
| 422 | UNPROCESSABLE | Aus den Rechnungsdaten lässt sich keine konforme E-Rechnung bauen — derselbe Rumpf wie bei /generate, mit errors und complianceErrors und ohne data. |
| 429 | QUOTA_EXCEEDED | Das Kontingent des Formats ist erschöpft. |