Live

Attachments API

Embed files into an existing PDF — proof of performance, inspection report, timesheet. The recipient finds them in their PDF reader's attachments pane. If the carrier was a PDF/A-3 (ZUGFeRD/Factur-X), its conformance is preserved.

POST/api/v1/invoice/attachments
POST/api/v1/pdf/attachments

Both paths take the same request and return the same response. What differs is the entitlement they bill against: /invoice/attachments requires pdf:invoice:attach, /pdf/attachments requires pdf:document:attach. On top of that, /invoice/attachments defaults relationship to "Supplement"; on /pdf/attachments the field stays empty unless you set it.

This does not turn a PDF into a ZUGFeRD invoice

This endpoint attaches files — nothing more. It does not make your own PDF an e-invoice: ZUGFeRD is more than "XML inside a PDF".

  • · No XMP metadata (the fx namespace) is written. A receiving system looks there first and finds nothing.
  • · A plain PDF is not converted to PDF/A-3; the response says so with pdfA3Conformant: false.
  • · The attachment is not marked as the invoice XML — the invoice stays the visible PDF, not the attachment.

Request

FieldTypeRequiredDescription
pdfstringThe carrier PDF, base64-encoded.
attachments[]arrayThe files to embed, at least one.
attachments[].filenamestringThe name shown in the attachments pane (max. 255 characters).
attachments[].contentBase64stringThe file content, base64-encoded.
attachments[].mimeTypestring-IANA media type of the file (max. 127 characters).
attachments[].descriptionstring-Description for the reader (max. 255 characters).
attachments[].relationshipenum-PDF/A-3 AFRelationship: Data, Source, Alternative, Supplement or Unspecified. On /invoice/attachments the default is Supplement.

Example

bash
1curl -X POST https://service.invoice-api.xhub.io/api/v1/invoice/attachments \
2 -H "Authorization: Bearer sk_live_abc123..." \
3 -H "Content-Type: application/json" \
4 -d '{
5 "pdf": "JVBERi0xLjcKJeLjz9MK...",
6 "attachments": [
7 {
8 "filename": "leistungsnachweis.pdf",
9 "mimeType": "application/pdf",
10 "contentBase64": "JVBERi0xLjQKJc...",
11 "description": "Stundennachweis März 2026",
12 "relationship": "Supplement"
13 }
14 ]
15 }'

Response

200 OK
json
1{
2 "success": true,
3 "data": "JVBERi0xLjcKJeLjz9MK...",
4 "pdfA3Conformant": true,
5 "attachmentCount": 1
6}
FieldTypeDescription
successbooleantrue when the attachments were embedded.
datastringThe resulting PDF, base64-encoded.
pdfA3Conformantbooleantrue when the carrier was PDF/A-3 and its conformance was preserved.
attachmentCountnumberNumber of embedded files.

Limits

20

files per request

15 MB

per file

30 MB

across all files of one request

The limits are configurable server-side; the values above are the service defaults. Operations can additionally restrict the permitted media types — a type that is not allowed is then refused with 400 MIME_NOT_ALLOWED.

StatusCodeDescription
400TOO_MANY_ATTACHMENTSMore files than permitted (default 20).
400INVALID_PDFThe carrier PDF could not be parsed.
403FORBIDDENThe entitlement is missing: pdf:invoice:attach for /invoice/attachments, pdf:document:attach for /pdf/attachments.
413ATTACHMENT_TOO_LARGEA single file is larger than permitted (default 15 MB).
413ATTACHMENTS_TOO_LARGEThe sum of all files is larger than permitted (default 30 MB).
429QUOTA_EXCEEDEDThe key's quota is exhausted.

The other way: attachments at generation time

When the invoice is still being created, attachments belong in the invoice data itself: the field invoice.attachments[] (BG-24, BT-122 to BT-125) is written into the XML as an EmbeddedDocumentBinaryObject while generating. This endpoint is for the other case — the PDF already exists.

json
1{
2 "invoice": {
3 "invoiceNumber": "RE-2026-001",
4 ...
5 "attachments": [
6 {
7 "filename": "leistungsnachweis.pdf",
8 "mimeType": "application/pdf",
9 "content": "JVBERi0xLjQKJc...",
10 "description": "Stundennachweis März 2026"
11 }
12 ]
13 }
14}

The difference in one sentence: invoice.attachments[] ends up in the invoice XML, /attachments attaches files to the PDF file. The field is called content there and contentBase64 here, and mimeType is required there.

Creator API