Your layout, our XML
You already have your invoice PDF — from your ERP, your print shop, your own template. This endpoint takes it as the carrier, generates the CII XML from your invoice data and returns that same PDF with the XML inside it as factur-x.xml.
/api/v1/invoice/{countryCode}/{format}/embedThe difference from /generate
Both endpoints produce the same e-invoice from the same data and run on the same entitlement. The only difference is the page the recipient sees.
- • With /generate the visual page comes from our template — then PDF/A-3 conformance is our problem.
- • With /embed the visual page comes from you and stays as it is; we only place the XML inside and set the markers.
- • Same invoice data, same validation chain, same failure modes (422 with the same rule codes).
There is no template or templateId on this route. The body is strict and rejects them with a 400 rather than ignoring them silently — the layout is yours.
To the Creator API (/generate)Hybrid formats only
A hybrid document needs a visual page a carrier PDF can stand in for. XML-only formats such as xrechnung or ubl have none, and plain pdf carries no XML — both return 400.
Path parameters
| Field | Type | Required | Description |
|---|---|---|---|
countryCode | string | Country of the hybrid format — de, ch, fr or be. | |
format | string | The hybrid format: zugferd for DE and CH, facturx for FR and BE. |
Request
| Field | Type | Required | Description |
|---|---|---|---|
pdf | string (base64) | Your carrier PDF, base64-encoded. It becomes the visual page of the hybrid document. | |
invoice | object | The invoice data the XML is generated from — the same shape as /generate, with the same mandatory fields. | |
validate | boolean | - | Also runs the XSD and Schematron check and returns the verdict in the validation block. Costs a second unit, exactly as on /generate. |
formatOptions.profile | string | - | ZUGFeRD / Factur-X profile, EN16931 by default. |
formatOptions.version | string | - | Format version, 2.4 for ZUGFeRD and 1.08 for Factur-X by default. |
PDF/A-3 is declared, not converted
We set the marker on the carrier you sent. We do not rewrite your PDF. On an arbitrary PDF that declaration can therefore be wrong.
- • Most commonly when your PDF uses fonts without an embedded font file — which PDF/A-3 requires.
- • What we actually checked is listed in carrier.checks; what we found, in carrier.warnings.
- • A full PDF/A-3b proof (veraPDF) does not take place here. Nothing is rejected because of it: the finding is ours, the decision is yours.
If your recipient enforces a full proof: run your carrier through a PDF/A converter once before it reaches us — or use /generate, where the PDF is ours.
Example
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 }'Response
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}| Field | Type | Description |
|---|---|---|
success | boolean | Whether the hybrid document was produced. |
format | string | The output format used. |
filename | string | Suggested filename. |
mimeType | string | MIME type of the returned document. |
hash | string | SHA-256 of the returned bytes, hex-encoded. Not stable across calls, because the PDF carries a creation timestamp — use payloadHash for idempotent names. |
payloadHash | string | SHA-256 of the canonical JSON of your invoice data — the stable key for idempotent calls. hash changes on every call because the PDF carries a timestamp. |
data | string (base64) | The hybrid document, base64-encoded: your layout with the generated XML inside. |
embeddedXml | string | The embedded CII XML in plain text, for convenient inspection. |
carrier | object | What we measured on the document we produced — a finding, not a claim. |
validation | object | The conformance verdict — present only when the request set validate: true. |
appliedFormatOptions | object | The version and profile the document was actually written in: yours, or the defaults. |
The carrier block
| Field | Type | Description |
|---|---|---|
pdfA3Declared | boolean | true means we set the PDF/A-3 marker on your carrier (XMP pdfaid:part=3, conformance B, sRGB output intent). It does not mean your carrier is conformant. |
checks | array | The individual checks, each with code, severity (warning, error, fatal), passed and, where there is one, message. |
warnings | array | Findings about the carrier you sent that we report but do not reject on — for example CARRIER_FONTS_NOT_EMBEDDED. |
The validation block
Present only if you sent validate: true. status says what happened: passed — the rulebooks ran and found nothing; failed — they found something, valid is then false and errors names it; not_run — they did not run completely, valid is null and reason says why (missing, load-failed, transform-failed or not-bound). A not_run is not a pass: it proves nothing about the document.
Billing
The call runs on the format's existing create entitlement and costs one unit — two if you set validate: true. No new entitlement, no surcharge.
Errors
| Status | Code | Description |
|---|---|---|
| 400 | Bad Request | The country/format pair is not a hybrid format — an XML-only format or pdf has no visual page. |
| 400 | Bad Request | template or templateId was sent. Neither exists on this route. |
| 400 | INVALID_PDF | The carrier is not a readable PDF. |
| 401 | UNAUTHORIZED | API key missing or invalid. |
| 403 | FORBIDDEN | The key does not hold the format's create entitlement. |
| 413 | PAYLOAD_TOO_LARGE | The carrier PDF is larger than the configured limit. |
| 422 | UNPROCESSABLE | The invoice data cannot be turned into a conforming e-invoice — the same body as /generate, with errors and complianceErrors and without data. |
| 429 | QUOTA_EXCEEDED | The format's quota is exhausted. |