API reference
Every endpoint of the invoiceapi.io REST API, generated from its OpenAPI 3.1 description.
- Base URL
https://invoiceapi.io/v1- Authentication
Authorization: Bearer sk_test_… or sk_live_…- OpenAPI
- OpenAPI 3.1 JSON
Quickstart: your first test invoice
From a new account to your first test invoice from your own code. All you need is an email address and your language's HTTP client.
Sign in
Sign in at /login with your email address. We send you a one-time code by email. If the address is new, signing in creates your account.
Run the setup wizard
The wizard asks for your organisation, the invoice issuer, the layout and Terms, and the logo, in that order. At the end it creates a test key. Copy the key straight away: it is shown only once.
Find the template id
The id is in the curl command at the end of the wizard. You can also ask for it with the test key: GET /v1/templates returns each template with its id. Use a template that has a profileId. A template without a profile answers the call with 404 “Template has no associated profile”.
Generate the first invoice
Send the invoice data to this endpoint, with an Idempotency-Key of its own for each invoice. 201 means the invoice was created. 200 means the same key was used before, and you get the invoice that was already created.
POST /v1/templates/{id}/invoicesThe body carries no seller and no bank details: the template's invoice profile fills both. Anything you do send in the body takes precedence over the profile.
{ "buyer": { "name": "Kunde AG", "vatId": "DE987654321", "electronicAddress": "rechnung@kunde.example", "address": { "line1": "Kundenweg 1", "city": "München", "postCode": "80331", "countryCode": "DE" } }, "invoice": { "number": "R-2026-1001", "issueDate": "2026-07-01", "dueDate": "2026-07-31", "currency": "EUR", "buyerReference": "KND-2026-77" }, "items": [ { "name": "Beratung", "quantity": "8", "unit": "HUR", "unitPrice": "120.00", "vatRate": "19" } ], "payment": { "reference": "R-2026-1001" } }Download the files at outputs[].url soon, because the links expire. You can get fresh links at any time with GET /v1/invoices and the invoice's id.
The same call in your language
You need no SDK. Each example uses the HTTP client its language ships with, and reads the key and the template id from the environment variables INVOICEAPI_KEY and TEMPLATE_ID. The Python example sets its own User-Agent, because a call with urllib's default User-Agent is refused with 403.
: "${INVOICEAPI_KEY:?Set INVOICEAPI_KEY and TEMPLATE_ID first.}" "${TEMPLATE_ID:?Set INVOICEAPI_KEY and TEMPLATE_ID first.}" && \ curl -X POST "https://invoiceapi.io/v1/templates/$TEMPLATE_ID/invoices" \ -H "Authorization: Bearer $INVOICEAPI_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen 2>/dev/null || openssl rand -hex 16)" \ --data-binary @- <<'JSON' { "buyer": { "name": "Kunde AG", "vatId": "DE987654321", "electronicAddress": "rechnung@kunde.example", "address": { "line1": "Kundenweg 1", "city": "München", "postCode": "80331", "countryCode": "DE" } }, "invoice": { "number": "R-2026-1001", "issueDate": "2026-07-01", "dueDate": "2026-07-31", "currency": "EUR", "buyerReference": "KND-2026-77" }, "items": [ { "name": "Beratung", "quantity": "8", "unit": "HUR", "unitPrice": "120.00", "vatRate": "19" } ], "payment": { "reference": "R-2026-1001" } } JSONOutput formats
Without a choice, the API produces a ZUGFeRD PDF. Use ?output= to choose; the parameter can be repeated, for example ?output=zugferd_pdf&output=xrechnung_xml.
zugferd_pdf- The default: PDF/A-3 with embedded XML, checked with the KoSIT validator and veraPDF.
xrechnung_xml- The XML for public-sector buyers, checked with the KoSIT validator. It needs BT-10 (invoice.buyerReference), BT-34 and BT-49 (the seller's and the buyer's electronic addresses). The body above has BT-10 and BT-49; BT-34 comes from the template's invoice profile.
pdf- The plain PDF, not checked.
Answers a first call can get
Errors arrive as application/problem+json with the API's English title.
- 401
Invalid or missing API key - The key is missing, unknown or revoked.
- 404
Template not found - The template id is wrong or belongs to another organisation.
- 404
Template has no associated profile - The template has no invoice profile. Use a template with a profileId.
- 400
Invalid invoice payload - The body does not match the schema. errors names each failing path with its message.
- 400
Payload is not XRechnung-ready - You asked for xrechnung_xml, but the body lacks BT-10, BT-34 or BT-49.
- 422
Invoice failed compliance validation - The invoice failed validation. errors names each broken rule with its id, such as BR-DE-15.
Endpoints
/templates/{id}/invoicesGenerate an invoice from a template
Generates a ZUGFeRD PDF/A-3, an XRechnung XML and/or a plain PDF from the template and payload (see the `output` parameter for which checks each kind gets). Idempotent per Idempotency-Key: a repeat with the same key and an identical body replays the original invoice with a 200 instead of creating a new one. Supply the optional `deliverEmail` block to also email the finished invoice: delivery is attempted synchronously and its outcome is reported on the 201, but a delivery failure NEVER fails the request — the invoice is archived either way. The seller and the bank details (`iban`, `bic`, `accountName`) come from the template's invoice profile for every field the request leaves out; a field the request sends wins.
Parameters
| Field | In | Required | Description |
|---|---|---|---|
| id | path | Required | Resource id. |
| output | query | — | Repeatable. Which artifact kind(s) to produce. Defaults to zugferd_pdf when omitted. `zugferd_pdf`: a PDF/A-3 with the ZUGFeRD XML embedded; KoSIT checks the XML and veraPDF checks the PDF/A-3U. `xrechnung_xml`: the XRechnung XML, checked by KoSIT only; it needs BT-10 (`invoice.buyerReference`), BT-34 (`seller.electronicAddress`, usually from the template's profile) and BT-49 (`buyer.electronicAddress`), or the request gets a 400 "Payload is not XRechnung-ready". `pdf`: the rendered PDF only, with no embedded XML and no KoSIT or veraPDF check (the renderer's plain PDF, NOT validated), so an invoice that requests only `pdf` has an empty `validation.summary`. |
| Idempotency-Key | header | — | Client-supplied key. A repeat with the same key and body returns the original invoice (200); the same key with a different body returns 409. |
Request
| Field | Type | Required | Description |
|---|---|---|---|
| seller | object | — | Optional. Each field you leave out is taken from the template's invoice profile, where the profile has it; a field you send wins. |
| seller.name | string | — | Seller name (BT-27) |
| seller.tradingName | string | — | Seller trading name (BT-28) |
| seller.vatId | string | — | Seller VAT identifier (BT-31) |
| seller.taxNumber | string | — | Seller Steuernummer (BT-32) |
| seller.electronicAddress | string | — | Seller electronic address, scheme EM (BT-34; mandatory for XRechnung) |
| seller.address | object | — | Seller postal address (BG-5) |
| seller.address.line1 | string | — | Street line 1 (BT-35 / BT-50) |
| seller.address.line2 | string | — | Street line 2 (BT-36 / BT-51) |
| seller.address.city | string | — | City (BT-37 / BT-52; BR-DE-3/BR-DE-8) |
| seller.address.postCode | string | — | Post code (BT-38 / BT-53; BR-DE-4/BR-DE-9) |
| seller.address.countryCode | string | — | Country code (BT-40 / BT-55) |
| seller.contact | object | — | Seller contact (BG-6; mandatory per BR-DE-2) |
| seller.contact.name | string | — | Contact point (BT-41; BR-DE-5) |
| seller.contact.phone | string | — | Contact phone (BT-42; BR-DE-6) |
| seller.contact.email | string | — | Contact email (BT-43; BR-DE-7/BR-DE-28) |
| buyer | object | Required | — |
| buyer.name | string | Required | Buyer name (BT-44) |
| buyer.vatId | string | — | Buyer VAT identifier (BT-48) |
| buyer.legalRegistrationId | string | — | Buyer legal registration id (BT-47) |
| buyer.electronicAddress | string | — | Buyer electronic address, scheme EM (BT-49; mandatory for XRechnung) |
| buyer.address | object | Required | Buyer postal address (BG-8) |
| buyer.address.line1 | string | Required | Street line 1 (BT-35 / BT-50) |
| buyer.address.line2 | string | — | Street line 2 (BT-36 / BT-51) |
| buyer.address.city | string | Required | City (BT-37 / BT-52; BR-DE-3/BR-DE-8) |
| buyer.address.postCode | string | Required | Post code (BT-38 / BT-53; BR-DE-4/BR-DE-9) |
| buyer.address.countryCode | string | Required | Country code (BT-40 / BT-55) |
| invoice | object | Required | — |
| invoice.number | string | Required | Invoice number (BT-1) |
| invoice.issueDate | string | Required | Issue date (BT-2) |
| invoice.dueDate | string | — | Payment due date (BT-9) |
| invoice.currency | "EUR" | Required | Invoice currency (BT-5); v1 is EUR-only — non-EUR is a documented seam |
| invoice.buyerReference | string | — | Buyer reference (BT-10); Leitweg-ID for B2G; mandatory for XRechnung (BR-DE-15) |
| invoice.paymentTerms | string | — | Payment terms free text (BT-20) |
| invoice.notes | string[] | — | Invoice notes (BT-22) |
| items | object[] | Required | — |
| items[].name | string | Required | Item name (BT-153) |
| items[].description | string | — | Item description (BT-154) |
| items[].quantity | string | number | Required | Invoiced quantity (BT-129); negative lines are permitted |
| items[].unit | "H87" | "C62" | "HUR" | "DAY" | "KGM" | "MTR" | "MTK" | "MTQ" | "LTR" | "MON" | "KMT" | "XPP" | Required | Unit of measure, UN/ECE Rec 20/21 (BT-130), e.g. H87=piece, HUR=hour |
| items[].unitPrice | string | number | Required | Item net price (BT-146); must not be negative (BR-27) |
| items[].priceBaseQuantity | string | number | — | Price base quantity (BT-149), default 1 |
| items[].vatRate | string | number | — | VAT rate percent (BT-152), e.g. "19", "7", "0" |
| items[].vatCategory | "S" | "Z" | "E" | "AE" | "K" | "G" | — | VAT category (BT-151, UNTDID 5305); default S when vatRate > 0 |
| items[].exemptionReason | string | — | VAT exemption reason text (BT-120); required for category E, e.g. §19 UStG wording |
| items[].allowances | object[] | — | Line allowances (BG-27) |
| items[].allowances[].amount | string | number | — | Allowance amount (BT-136) |
| items[].allowances[].percent | string | number | — | Allowance percentage of the line gross (BT-138) |
| items[].allowances[].reason | string | — | Allowance reason (BT-139) |
| items[].charges | object[] | — | Line charges (BG-28) |
| items[].charges[].amount | string | number | — | Charge amount (BT-141) |
| items[].charges[].percent | string | number | — | Charge percentage of the line gross (BT-143) |
| items[].charges[].reason | string | — | Charge reason (BT-144) |
| allowances | object[] | — | Document level allowances (BG-20) |
| allowances[].amount | string | number | Required | Document level allowance amount (BT-92) |
| allowances[].vatCategory | "S" | "Z" | "E" | "AE" | "K" | "G" | Required | VAT category of the allowance (BT-95; required per BR-32) |
| allowances[].vatRate | string | number | — | VAT rate of the allowance (BT-96) |
| allowances[].reason | string | — | Reason (BT-97) |
| allowances[].reasonCode | string | — | Reason code, UNTDID 5189 (BT-98) |
| charges | object[] | — | Document level charges (BG-21) |
| charges[].amount | string | number | Required | Document level charge amount (BT-99) |
| charges[].vatCategory | "S" | "Z" | "E" | "AE" | "K" | "G" | Required | VAT category of the charge (BT-102; required per BR-37) |
| charges[].vatRate | string | number | — | VAT rate of the charge (BT-103) |
| charges[].reason | string | — | Reason (BT-104) |
| charges[].reasonCode | string | — | Reason code, UNTDID 7161 (BT-105) |
| payment | object | — | Optional. `iban`, `bic` and `accountName` you leave out are taken from the profile's payment defaults; a field you send wins. |
| payment.meansCode | "30" | "58" | — | Payment means code, UNTDID 4461 (BT-81); v1 supports credit transfer only |
| payment.iban | string | — | Payment account IBAN (BT-84) |
| payment.bic | string | — | BIC (BT-86) |
| payment.accountName | string | — | Payment account name (BT-85) |
| payment.reference | string | — | Remittance information (BT-83) |
| delivery | object | — | — |
| delivery.date | string | — | Actual delivery date (BT-72) |
| delivery.countryCode | string | — | Deliver-to country (BT-80) |
| totals | object | — | Optional client-side totals; generation is rejected if they mismatch the computed totals |
| totals.taxExclusive | string | number | — | Client-asserted invoice total without VAT (BT-109) |
| totals.totalVat | string | number | — | Client-asserted total VAT (BT-110) |
| totals.taxInclusive | string | number | — | Client-asserted invoice total with VAT (BT-112) |
| deliverEmail | DeliverEmail | — | Optional: email the generated invoice to these recipients. Delivery is attempted synchronously and NEVER fails the request — the invoice is archived either way. |
| deliverEmail.to | string[] | Required | Recipient addresses; lower-cased. IDN domains must be punycoded. |
| deliverEmail.cc | string[] | — | Carbon-copy addresses; lower-cased. IDN domains must be punycoded. |
| deliverEmail.subjectOverride | string | — | Replaces the generated subject line entirely. |
| deliverEmail.message | string | — | Free-text note rendered above the standard body. |
Response
- 200
application/jsonIdempotency-Key replay: the invoice already generated for this key and body. A replay NEVER sends email and never reports delivery — the `deliverEmail` field is absent, even if the replayed request carried a `deliverEmail` block. The original request owns the delivery; consult the invoice to see its state.
Field Type Required Description id string Required Invoice id. number string Required Invoice number (BT-1). mode "live" | "test" Required — status "generated" | "delivered" | "delivery_failed" Required — templateVersion integer Required — createdAt string Required — validation object Required — validation.summary string Required — outputs object[] Required — outputs[].kind "zugferd_pdf" | "xrechnung_xml" | "pdf" Required — outputs[].sha256 string Required — outputs[].bytes integer Required — outputs[].url string Required Signed GET URL (expires in <= 15 minutes). - 201
application/jsonInvoice generated. `deliverEmail` reports the delivery outcome, or is null.
Field Type Required Description id string Required Invoice id. number string Required Invoice number (BT-1). mode "live" | "test" Required — status "generated" | "delivered" | "delivery_failed" Required — templateVersion integer Required — createdAt string Required — validation object Required — validation.summary string Required — outputs object[] Required — outputs[].kind "zugferd_pdf" | "xrechnung_xml" | "pdf" Required — outputs[].sha256 string Required — outputs[].bytes integer Required — outputs[].url string Required Signed GET URL (expires in <= 15 minutes). deliverEmail DeliverEmailResult | null Required null unless the request carried a `deliverEmail` block. - 400
application/problem+jsonMalformed payload or deliverEmail block (schema validation failed, after the template's profile filled in the seller and bank details); an unknown `output` kind; or "Payload is not XRechnung-ready" when `xrechnung_xml` is requested without BT-10, BT-34 or BT-49.
- 401
application/problem+jsonMissing or invalid API key (Authorization: Bearer <key>).
- 402
application/problem+jsonMonthly quota exceeded (only enforced when billing is enabled).
- 403
application/problem+jsonRecipient not allowed (`https://invoiceapi.io/problems/recipient-not-allowed`): on the Free plan `deliverEmail` may address only members of your organization, in `to` and `cc`; customers from Starter. Checked before generation, so the request uses no document. Only enforced when billing is enabled.
- 404
application/problem+jsonTemplate, template version, or profile not found.
- 409
application/problem+jsonIdempotency-Key reused with a different request body.
- 422
application/problem+jsonCompliance validation failed (KoSIT / veraPDF findings: each `errors[]` entry names its engine in `source`; the example is a veraPDF rejection), or a live invoice carries the sample seller VAT ID or IBAN (type https://invoiceapi.io/problems/sample-seller-data). To fix the latter, remove `seller` and the bank details (`payment.iban`, `bic`, `accountName`) from the request so the template's profile fills them, or send your own values; if the profile itself carries the sample data, fix it under /profiles.
- 429
application/problem+jsonRate limit exceeded; see the Retry-After header.
/invoicesList invoices
Cursor-paginated. Test-mode invoices are excluded unless mode=test.
Parameters
| Field | In | Required | Description |
|---|---|---|---|
| profile | query | — | Filter by profile id. |
| from | query | — | Filter by record creation time (createdAt), lower bound, inclusive (any Date.parse-able value). NOT the invoice issue date (BT-2). |
| to | query | — | Filter by record creation time (createdAt), upper bound, inclusive (any Date.parse-able value). NOT the invoice issue date (BT-2). |
| mode | query | — | live (default) or test. |
| cursor | query | — | Opaque pagination cursor from a previous nextCursor. |
| limit | query | — | Page size, clamped to [1, 100] (default 25). |
Response
- 200
application/jsonA page of invoices.
Field Type Required Description data PublicInvoice[] Required — data[].id string Required Invoice id. data[].number string Required Invoice number (BT-1). data[].mode "live" | "test" Required — data[].status "generated" | "delivered" | "delivery_failed" Required — data[].templateVersion integer Required — data[].createdAt string Required — data[].validation object Required — data[].validation.summary string Required — data[].outputs object[] Required — data[].outputs[].kind "zugferd_pdf" | "xrechnung_xml" | "pdf" Required — data[].outputs[].sha256 string Required — data[].outputs[].bytes integer Required — data[].outputs[].url string Required Signed GET URL (expires in <= 15 minutes). nextCursor string | null Required — - 400
application/problem+jsonInvalid profile/cursor/from/to query parameter.
- 401
application/problem+jsonMissing or invalid API key (Authorization: Bearer <key>).
- 429
application/problem+jsonRate limit exceeded; see the Retry-After header.
/invoices/{id}Retrieve an invoice
Returns invoice metadata with freshly signed artifact URLs.
Parameters
| Field | In | Required | Description |
|---|---|---|---|
| id | path | Required | Resource id. |
Response
- 200
application/jsonThe invoice.
Field Type Required Description id string Required Invoice id. number string Required Invoice number (BT-1). mode "live" | "test" Required — status "generated" | "delivered" | "delivery_failed" Required — templateVersion integer Required — createdAt string Required — validation object Required — validation.summary string Required — outputs object[] Required — outputs[].kind "zugferd_pdf" | "xrechnung_xml" | "pdf" Required — outputs[].sha256 string Required — outputs[].bytes integer Required — outputs[].url string Required Signed GET URL (expires in <= 15 minutes). - 401
application/problem+jsonMissing or invalid API key (Authorization: Bearer <key>).
- 404
application/problem+jsonNo such invoice for this account.
- 429
application/problem+jsonRate limit exceeded; see the Retry-After header.
/templatesList templates
Unpaginated: org template counts are small (plan-limited).
Response
- 200
application/jsonThe account templates.
Field Type Required Description data PublicTemplate[] Required — data[].id string Required — data[].name string Required — data[].layoutId "classic" | "modern" | "compact" | "bold" | "minimal" Required — data[].version integer Required — data[].profileId string — Present only for profile-scoped templates. data[].createdAt string Required — - 401
application/problem+jsonMissing or invalid API key (Authorization: Bearer <key>).
- 429
application/problem+jsonRate limit exceeded; see the Retry-After header.
/templates/{id}Retrieve a template
Parameters
| Field | In | Required | Description |
|---|---|---|---|
| id | path | Required | Resource id. |
Response
- 200
application/jsonThe template.
Field Type Required Description id string Required — name string Required — layoutId "classic" | "modern" | "compact" | "bold" | "minimal" Required — version integer Required — profileId string — Present only for profile-scoped templates. createdAt string Required — - 401
application/problem+jsonMissing or invalid API key (Authorization: Bearer <key>).
- 404
application/problem+jsonNo such template for this account.
- 429
application/problem+jsonRate limit exceeded; see the Retry-After header.
/validateValidate an invoice document
Validates an XRechnung/UBL/CII XML or a ZUGFeRD PDF against KoSIT (Schematron) and veraPDF. Quota-exempt. Findings are translated to DE/EN.
Accepted content types
| Field | Type | Required | Description |
|---|---|---|---|
| file | string | Required | The XML or PDF document to validate. |
Response
- 200
application/jsonThe validation report.
Field Type Required Description validation object Required Raw compliance-engine validation result (engine-specific shape). versions object — — findings object[] Required — findings[].path string Required — findings[].bt string — — findings[].code string — — findings[].message object Required — findings[].message.de string Required — findings[].message.en string Required — findings[].untranslated boolean — — - 400
application/problem+jsonEmpty body, or the compliance engine could not decode it.
- 401
application/problem+jsonMissing or invalid API key (Authorization: Bearer <key>).
- 429
application/problem+jsonRate limit exceeded; see the Retry-After header.
- 502
application/problem+jsonThe compliance engine is unreachable.
/healthLiveness and dependency reachability
Public. Reports compliance-engine reachability and engine versions.
Response
- 200
application/jsonService health.
Field Type Required Description status "ok" | "degraded" Required — compliance "ok" | "unreachable" Required — versions object — —