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
Postman
Collection to import into Postman (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.

  1. 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.

  2. 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.

  3. 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”.

  4. 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}/invoices

    The 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.

  5. 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"
      }
    }
    JSON
  6. Output 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

POST/templates/{id}/invoices

Generate 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

FieldInRequiredDescription
idpathRequiredResource id.
outputquery—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-Keyheader—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

FieldTypeRequiredDescription
sellerobject—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.namestring—Seller name (BT-27)
seller.tradingNamestring—Seller trading name (BT-28)
seller.vatIdstring—Seller VAT identifier (BT-31)
seller.taxNumberstring—Seller Steuernummer (BT-32)
seller.electronicAddressstring—Seller electronic address, scheme EM (BT-34; mandatory for XRechnung)
seller.addressobject—Seller postal address (BG-5)
seller.address.line1string—Street line 1 (BT-35 / BT-50)
seller.address.line2string—Street line 2 (BT-36 / BT-51)
seller.address.citystring—City (BT-37 / BT-52; BR-DE-3/BR-DE-8)
seller.address.postCodestring—Post code (BT-38 / BT-53; BR-DE-4/BR-DE-9)
seller.address.countryCodestring—Country code (BT-40 / BT-55)
seller.contactobject—Seller contact (BG-6; mandatory per BR-DE-2)
seller.contact.namestring—Contact point (BT-41; BR-DE-5)
seller.contact.phonestring—Contact phone (BT-42; BR-DE-6)
seller.contact.emailstring—Contact email (BT-43; BR-DE-7/BR-DE-28)
buyerobjectRequired—
buyer.namestringRequiredBuyer name (BT-44)
buyer.vatIdstring—Buyer VAT identifier (BT-48)
buyer.legalRegistrationIdstring—Buyer legal registration id (BT-47)
buyer.electronicAddressstring—Buyer electronic address, scheme EM (BT-49; mandatory for XRechnung)
buyer.addressobjectRequiredBuyer postal address (BG-8)
buyer.address.line1stringRequiredStreet line 1 (BT-35 / BT-50)
buyer.address.line2string—Street line 2 (BT-36 / BT-51)
buyer.address.citystringRequiredCity (BT-37 / BT-52; BR-DE-3/BR-DE-8)
buyer.address.postCodestringRequiredPost code (BT-38 / BT-53; BR-DE-4/BR-DE-9)
buyer.address.countryCodestringRequiredCountry code (BT-40 / BT-55)
invoiceobjectRequired—
invoice.numberstringRequiredInvoice number (BT-1)
invoice.issueDatestringRequiredIssue date (BT-2)
invoice.dueDatestring—Payment due date (BT-9)
invoice.currency"EUR"RequiredInvoice currency (BT-5); v1 is EUR-only — non-EUR is a documented seam
invoice.buyerReferencestring—Buyer reference (BT-10); Leitweg-ID for B2G; mandatory for XRechnung (BR-DE-15)
invoice.paymentTermsstring—Payment terms free text (BT-20)
invoice.notesstring[]—Invoice notes (BT-22)
itemsobject[]Required—
items[].namestringRequiredItem name (BT-153)
items[].descriptionstring—Item description (BT-154)
items[].quantitystring | numberRequiredInvoiced quantity (BT-129); negative lines are permitted
items[].unit"H87" | "C62" | "HUR" | "DAY" | "KGM" | "MTR" | "MTK" | "MTQ" | "LTR" | "MON" | "KMT" | "XPP"RequiredUnit of measure, UN/ECE Rec 20/21 (BT-130), e.g. H87=piece, HUR=hour
items[].unitPricestring | numberRequiredItem net price (BT-146); must not be negative (BR-27)
items[].priceBaseQuantitystring | number—Price base quantity (BT-149), default 1
items[].vatRatestring | 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[].exemptionReasonstring—VAT exemption reason text (BT-120); required for category E, e.g. §19 UStG wording
items[].allowancesobject[]—Line allowances (BG-27)
items[].allowances[].amountstring | number—Allowance amount (BT-136)
items[].allowances[].percentstring | number—Allowance percentage of the line gross (BT-138)
items[].allowances[].reasonstring—Allowance reason (BT-139)
items[].chargesobject[]—Line charges (BG-28)
items[].charges[].amountstring | number—Charge amount (BT-141)
items[].charges[].percentstring | number—Charge percentage of the line gross (BT-143)
items[].charges[].reasonstring—Charge reason (BT-144)
allowancesobject[]—Document level allowances (BG-20)
allowances[].amountstring | numberRequiredDocument level allowance amount (BT-92)
allowances[].vatCategory"S" | "Z" | "E" | "AE" | "K" | "G"RequiredVAT category of the allowance (BT-95; required per BR-32)
allowances[].vatRatestring | number—VAT rate of the allowance (BT-96)
allowances[].reasonstring—Reason (BT-97)
allowances[].reasonCodestring—Reason code, UNTDID 5189 (BT-98)
chargesobject[]—Document level charges (BG-21)
charges[].amountstring | numberRequiredDocument level charge amount (BT-99)
charges[].vatCategory"S" | "Z" | "E" | "AE" | "K" | "G"RequiredVAT category of the charge (BT-102; required per BR-37)
charges[].vatRatestring | number—VAT rate of the charge (BT-103)
charges[].reasonstring—Reason (BT-104)
charges[].reasonCodestring—Reason code, UNTDID 7161 (BT-105)
paymentobject—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.ibanstring—Payment account IBAN (BT-84)
payment.bicstring—BIC (BT-86)
payment.accountNamestring—Payment account name (BT-85)
payment.referencestring—Remittance information (BT-83)
deliveryobject——
delivery.datestring—Actual delivery date (BT-72)
delivery.countryCodestring—Deliver-to country (BT-80)
totalsobject—Optional client-side totals; generation is rejected if they mismatch the computed totals
totals.taxExclusivestring | number—Client-asserted invoice total without VAT (BT-109)
totals.totalVatstring | number—Client-asserted total VAT (BT-110)
totals.taxInclusivestring | number—Client-asserted invoice total with VAT (BT-112)
deliverEmailDeliverEmail—Optional: email the generated invoice to these recipients. Delivery is attempted synchronously and NEVER fails the request — the invoice is archived either way.
deliverEmail.tostring[]RequiredRecipient addresses; lower-cased. IDN domains must be punycoded.
deliverEmail.ccstring[]—Carbon-copy addresses; lower-cased. IDN domains must be punycoded.
deliverEmail.subjectOverridestring—Replaces the generated subject line entirely.
deliverEmail.messagestring—Free-text note rendered above the standard body.

Response

  • 200application/json

    Idempotency-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.

    FieldTypeRequiredDescription
    idstringRequiredInvoice id.
    numberstringRequiredInvoice number (BT-1).
    mode"live" | "test"Required—
    status"generated" | "delivered" | "delivery_failed"Required—
    templateVersionintegerRequired—
    createdAtstringRequired—
    validationobjectRequired—
    validation.summarystringRequired—
    outputsobject[]Required—
    outputs[].kind"zugferd_pdf" | "xrechnung_xml" | "pdf"Required—
    outputs[].sha256stringRequired—
    outputs[].bytesintegerRequired—
    outputs[].urlstringRequiredSigned GET URL (expires in <= 15 minutes).
  • 201application/json

    Invoice generated. `deliverEmail` reports the delivery outcome, or is null.

    FieldTypeRequiredDescription
    idstringRequiredInvoice id.
    numberstringRequiredInvoice number (BT-1).
    mode"live" | "test"Required—
    status"generated" | "delivered" | "delivery_failed"Required—
    templateVersionintegerRequired—
    createdAtstringRequired—
    validationobjectRequired—
    validation.summarystringRequired—
    outputsobject[]Required—
    outputs[].kind"zugferd_pdf" | "xrechnung_xml" | "pdf"Required—
    outputs[].sha256stringRequired—
    outputs[].bytesintegerRequired—
    outputs[].urlstringRequiredSigned GET URL (expires in <= 15 minutes).
    deliverEmailDeliverEmailResult | nullRequirednull unless the request carried a `deliverEmail` block.
  • 400application/problem+json

    Malformed 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.

  • 401application/problem+json

    Missing or invalid API key (Authorization: Bearer <key>).

  • 402application/problem+json

    Monthly quota exceeded (only enforced when billing is enabled).

  • 403application/problem+json

    Recipient 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.

  • 404application/problem+json

    Template, template version, or profile not found.

  • 409application/problem+json

    Idempotency-Key reused with a different request body.

  • 422application/problem+json

    Compliance 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.

  • 429application/problem+json

    Rate limit exceeded; see the Retry-After header.

GET/invoices

List invoices

Cursor-paginated. Test-mode invoices are excluded unless mode=test.

Parameters

FieldInRequiredDescription
profilequery—Filter by profile id.
fromquery—Filter by record creation time (createdAt), lower bound, inclusive (any Date.parse-able value). NOT the invoice issue date (BT-2).
toquery—Filter by record creation time (createdAt), upper bound, inclusive (any Date.parse-able value). NOT the invoice issue date (BT-2).
modequery—live (default) or test.
cursorquery—Opaque pagination cursor from a previous nextCursor.
limitquery—Page size, clamped to [1, 100] (default 25).

Response

  • 200application/json

    A page of invoices.

    FieldTypeRequiredDescription
    dataPublicInvoice[]Required—
    data[].idstringRequiredInvoice id.
    data[].numberstringRequiredInvoice number (BT-1).
    data[].mode"live" | "test"Required—
    data[].status"generated" | "delivered" | "delivery_failed"Required—
    data[].templateVersionintegerRequired—
    data[].createdAtstringRequired—
    data[].validationobjectRequired—
    data[].validation.summarystringRequired—
    data[].outputsobject[]Required—
    data[].outputs[].kind"zugferd_pdf" | "xrechnung_xml" | "pdf"Required—
    data[].outputs[].sha256stringRequired—
    data[].outputs[].bytesintegerRequired—
    data[].outputs[].urlstringRequiredSigned GET URL (expires in <= 15 minutes).
    nextCursorstring | nullRequired—
  • 400application/problem+json

    Invalid profile/cursor/from/to query parameter.

  • 401application/problem+json

    Missing or invalid API key (Authorization: Bearer <key>).

  • 429application/problem+json

    Rate limit exceeded; see the Retry-After header.

GET/invoices/{id}

Retrieve an invoice

Returns invoice metadata with freshly signed artifact URLs.

Parameters

FieldInRequiredDescription
idpathRequiredResource id.

Response

  • 200application/json

    The invoice.

    FieldTypeRequiredDescription
    idstringRequiredInvoice id.
    numberstringRequiredInvoice number (BT-1).
    mode"live" | "test"Required—
    status"generated" | "delivered" | "delivery_failed"Required—
    templateVersionintegerRequired—
    createdAtstringRequired—
    validationobjectRequired—
    validation.summarystringRequired—
    outputsobject[]Required—
    outputs[].kind"zugferd_pdf" | "xrechnung_xml" | "pdf"Required—
    outputs[].sha256stringRequired—
    outputs[].bytesintegerRequired—
    outputs[].urlstringRequiredSigned GET URL (expires in <= 15 minutes).
  • 401application/problem+json

    Missing or invalid API key (Authorization: Bearer <key>).

  • 404application/problem+json

    No such invoice for this account.

  • 429application/problem+json

    Rate limit exceeded; see the Retry-After header.

GET/templates

List templates

Unpaginated: org template counts are small (plan-limited).

Response

  • 200application/json

    The account templates.

    FieldTypeRequiredDescription
    dataPublicTemplate[]Required—
    data[].idstringRequired—
    data[].namestringRequired—
    data[].layoutId"classic" | "modern" | "compact" | "bold" | "minimal"Required—
    data[].versionintegerRequired—
    data[].profileIdstring—Present only for profile-scoped templates.
    data[].createdAtstringRequired—
  • 401application/problem+json

    Missing or invalid API key (Authorization: Bearer <key>).

  • 429application/problem+json

    Rate limit exceeded; see the Retry-After header.

GET/templates/{id}

Retrieve a template

Parameters

FieldInRequiredDescription
idpathRequiredResource id.

Response

  • 200application/json

    The template.

    FieldTypeRequiredDescription
    idstringRequired—
    namestringRequired—
    layoutId"classic" | "modern" | "compact" | "bold" | "minimal"Required—
    versionintegerRequired—
    profileIdstring—Present only for profile-scoped templates.
    createdAtstringRequired—
  • 401application/problem+json

    Missing or invalid API key (Authorization: Bearer <key>).

  • 404application/problem+json

    No such template for this account.

  • 429application/problem+json

    Rate limit exceeded; see the Retry-After header.

POST/validate

Validate 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

application/xmlapplication/pdfmultipart/form-data
FieldTypeRequiredDescription
filestringRequiredThe XML or PDF document to validate.

Response

  • 200application/json

    The validation report.

    FieldTypeRequiredDescription
    validationobjectRequiredRaw compliance-engine validation result (engine-specific shape).
    versionsobject——
    findingsobject[]Required—
    findings[].pathstringRequired—
    findings[].btstring——
    findings[].codestring——
    findings[].messageobjectRequired—
    findings[].message.destringRequired—
    findings[].message.enstringRequired—
    findings[].untranslatedboolean——
  • 400application/problem+json

    Empty body, or the compliance engine could not decode it.

  • 401application/problem+json

    Missing or invalid API key (Authorization: Bearer <key>).

  • 429application/problem+json

    Rate limit exceeded; see the Retry-After header.

  • 502application/problem+json

    The compliance engine is unreachable.

GET/health

Liveness and dependency reachability

Public. Reports compliance-engine reachability and engine versions.

Response

  • 200application/json

    Service health.

    FieldTypeRequiredDescription
    status"ok" | "degraded"Required—
    compliance"ok" | "unreachable"Required—
    versionsobject——