Rechnungen

Rechnungen und Gutschriften anlegen, ausstellen, versenden und als PDF oder XML abrufen – inklusive Validierung.

Create, issue, and manage invoices, credit notes, and their PDF/XML artifacts and validation.

25 Operationen · Basis-URL https://api.fortlauf.de/v1

Operationen

  1. GET /invoices List invoice summaries (newest first, paginated)
  2. POST /invoices Create an invoice (server computes all totals)
  3. POST /invoices/preview-totals Preview the totals for a set of line items (nothing is created)
  4. GET /invoices/next-number Preview the next auto-generated invoice number (no side effects)
  5. GET /invoices/{id} Get a full invoice by id
  6. PUT /invoices/{id} Replace a draft invoice (full replace of all mutable fields; server recomputes totals)
  7. DELETE /invoices/{id} Delete a draft invoice
  8. GET /invoices/{id}/pdf Download the stored ZUGFeRD PDF for an issued invoice
  9. GET /invoices/{id}/xml Download the stored XRechnung 3.0 CII XML for an issued invoice
  10. GET /invoices/{id}/preview.pdf Render a non-persisted, watermarked WYSIWYG preview PDF for a draft
  11. PATCH /invoices/{id}/retry-pdf Reset a failed PDF render to pending and retry generation
  12. PATCH /invoices/{id}/retry-xml Reset a failed XML render to pending and retry generation
  13. PATCH /invoices/{id}/processing-restriction Set or clear the DSGVO Art. 18 processing restriction
  14. PATCH /invoices/{id}/schedule Transition a draft to 'scheduled' with an auto-issuance date
  15. PATCH /invoices/{id}/finalize Issue a draft (compliance gate). Runs the KoSIT gate synchronously: assigns the invoice number (sticky — reused on a retry after a gate failure), renders + validates both XRechnung and ZUGFeRD in parallel, and either commits (artifacts + report + email outbox) or rejects with actionable findings. the validation gate in report-only mode (rollout default) never blocks — the commit runs regardless of verdict, with `validationState` recording the outcome; the validation gate in enforce mode blocks on any error-severity finding. The invoice date (`issueDate`) is set to the day of issue (Europe/Berlin), whatever date the draft carried.
  16. POST /invoices/{id}/credit-note Derive a Stornorechnung (credit note, UNTDID 381) from an issued invoice
  17. POST /invoices/{id}/validate Dry-run preflight (compliance gate). Draft or scheduled only. Runs the same local preflight + parallel render+validate calls as `PATCH /invoices/{id}/finalize`, but never assigns a number (a placeholder BT-1 is used when the invoice is unnumbered) and never commits — no state change on the invoice, regardless of verdict. The report is stored with `kind=preflight`.
  18. POST /validate Stateless Validation-as-a-service. Runs a submitted invoice through the SAME KoSIT + §14 compliance gate and returns the report — persisting NOTHING (no invoice, no stored report, no numbering, no metering). Requires the `validation:run` scope. Mounted only where standalone validation is enabled (off by default → 404).
  19. GET /invoices/{id}/validation-reports List validation report metadata for an invoice: every attempt (issue attempts, successful issuance, preflight dry-runs), verdict, kind, artifact/validator versions, and findings. Byte-exact report XML is not included — see `GET /invoices/{id}/validation-reports/{reportId}`.
  20. GET /invoices/{id}/validation-reports/{reportId} Retrieve a stored Prüfbericht (native KoSIT XML, byte-exact). 404 (not the report) if `reportId` belongs to a different invoice than `id` — no cross-invoice leakage.
  21. GET /invoices/{id}/section14 The typed §14 Abs. 4 UStG Pflichtangaben result for an invoice — the clean three-state `Section14Summary` the frontends bind to (the raw `kind='section14'` row also remains in the validation-reports list). For an issued document the stored result is returned (`evaluatedAt` = when it was computed); a lost post-commit persist is transparently recomputed over the immutable issued document and re-persisted (recompute-on-read), so the result is always retrievable. Advisory-only: `status` is `passed`/`has_warnings` (or `unavailable`) — NEVER `failed`; a `not_applicable` item is never counted as a warning. RLS-scoped — a cross-tenant request 404s (indistinguishable from missing). PII-free.
  22. POST /invoices/{id}/send Manually (re-)send an issued invoice's delivery email
  23. GET /invoices/{id}/sends The append-only send history of an invoice
  24. POST /invoices/{id}/external-send Record a dispatch you performed yourself
  25. POST /invoices/{id}/sends/{sendId}/retraction Withdraw a dispatch record

List invoice summaries (newest first, paginated)

GET /invoices

Erforderlicher Scope
documents:read
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
page Query integer

Page number (1-indexed).

Standard: 1
Bereich: ≥ 1
pageSize Query integer

Number of items per page.

Standard: 25
Bereich: ≥ 1, ≤ 100
status Query string

Filter by invoice status. Omit to return all statuses. void is RESERVED AND NEVER WRITTEN: §14c UStG / GoBD forbid voiding an original — a cancellation is a separate 381 referencing it — so ?status=void is accepted and returns an EMPTY page rather than a 400. To find cancelled documents use ?correctionState=cancelled.

Werte: "draft", "scheduled", "issued", "paid", "void"
correctionState Query string

Filter by the DERIVED correction state — computed from the document chain (the preceding-invoice link), never stored. cancelled = an issued/paid 381 (Storno) references the invoice; corrected = an issued/paid 384 (Berichtigung) does and no 381 does; none = neither. A DRAFT correction counts as none — it has cancelled nothing yet. The three values partition the result set. Omit for unchanged behaviour (no correction filtering).

Werte: "cancelled", "corrected", "none"
validationState Query string

Compliance-gate filter. failed surfaces documents (including scheduled invoices that reverted to draft) whose last issue attempt had an error-severity finding — the mitigation for scheduled issue failures having no other human in the loop.

Werte: "failed", "passed", "legacy"
customerId Query string (uuid)

Return only documents linked to this customer (exact match on the customer link). Documents created for an ad-hoc buyer carry no customer link and are therefore never returned when this filter is set.

q Query string

Free-text filter, case-insensitive substring match over a CLOSED per-resource field set (invoices: invoice number + buyer name; offers: offer number + buyer name; customers: name + email). Trimmed; an empty/whitespace-only value is treated as absent, not as an error. Results are bounded by the pagination limit and by your own entity's scope. Deliberately NOT a fuzzy/ranked search — it is a plain substring match, and it does not search line items, notes or amounts. The term is never written to logs, audit records or webhook payloads.

Länge: max. 100
transmissionProblem Query string

Return only documents whose latest system-performed dispatch is in a PROBLEM state — transmissionState undeliverable (the receiving mail system refused it permanently) or failed (nothing was ever handed over). This is deliberately a boolean rather than a transmissionState filter: the other states describe our own queue, which is an internal fact that changes meaning whenever the sending limits change. Only the literal value true is accepted; false is not a filter, it is the unfiltered list.

Werte: "true"

Antworten

  • 200

    Paginated list of invoice summaries

    application/json object

  • 400

    The query string was refused. Three distinguishable facts share this status, and a client should branch on the error code rather than on the status: unknown_query_parameter — the request named one or more query parameters this deployment does not define, and parameters lists their names. Nothing was filtered and nothing was written. This is a DEPLOY-ORDER signal: it is what a client released ahead of the API sees, and it replaces the older behaviour in which an unrecognised parameter was silently ignored and the request answered 200 OK with the UNFILTERED collection — indistinguishable, to the caller, from the filter having been applied. validation_error — a parameter this endpoint does define carried an unusable value (including a required one being absent or too short). issues[] names the parameter and what was wrong with it. invalid_cursor — on cursor-paginated endpoints, the pagination cursor is malformed. No response on this path ever echoes a submitted VALUE; only parameter names appear.

    application/json UnknownQueryParameterError | ValidationError

Create an invoice (server computes all totals)

POST /invoices

Honors the optional Idempotency-Key header so a retried DRAFT create is a replay, not a duplicate. NB: issuance (PATCH /invoices/{id}/finalize) is deliberately NOT idempotency-wrapped — it is idempotent by domain construction (numbering sticky-lock + one-per-finalize metering).

Erforderlicher Scope
documents:write
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
Idempotency-Key Header string

Optional client-supplied key that makes this mutating request safe to retry. The FIRST request runs and its outcome is recorded keyed by (tenant, route, key); a REPLAY (same key + byte-identical body, after the first completed) returns the recorded response verbatim WITHOUT re-executing — so a network-blip retry never creates a duplicate customer/subscription/key. A key reused with a DIFFERENT body is 409 IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_BODY; a key whose first request is still in flight is 409 REQUEST_IN_PROGRESS (retryable). Keys expire after 24h — a key reused after expiry is a fresh request, not a replay. Transparent when omitted (the request runs normally).

Länge: max. 255

Request-Body erforderlich

application/json CreateInvoice

Beispiel-Request
{
  "currency": "EUR",
  "invoiceTypeCode": "380",
  "buyer": {
    "name": "Achsfeld Transporte GmbH",
    "addressLine1": "Gewerbestraße 8",
    "postalCode": "59192",
    "city": "Bergkamen",
    "countryCode": "DE",
    "vatId": "DE245817096"
  },
  "buyerReference": "FP-2026-0219",
  "deliveryDate": "2026-07-31",
  "lineItems": [
    {
      "description": "Ladevorgänge Januar",
      "quantity": "1",
      "unitCode": "C62",
      "unitPriceNet": "420.00",
      "taxRatePercent": "19"
    }
  ]
}

Antworten

  • 201

    The created invoice (or, on an Idempotency-Key replay, the recorded response)

    application/json Invoice

  • 400

    Validation error (validation_error), including an invoiceTypeCode not on the configured allowlist, or a request body that is not valid JSON (invalid_json). Issues may carry a code: service_timing_conflict (deliveryDate: the body carries both a single service date and a service period), reverse_charge_buyer_vat_id_required (buyer.vatId), vat_id_prefix_unknown (buyer.vatId / seller.vatId), currency_code_unknown (currency), vat_category_unavailable (lineItems.N.vatCategory), vat_rate_category_mismatch (lineItems.N.taxRatePercent), vat_exemption_reason_required and vat_exemption_reason_not_allowed (lineItems.N.vatExemptionReasonText), and unit_code_unknown (lineItems.N.unitCode). The three *_unknown codes occur only where the deployment enforces that check.

    application/json ValidationError

  • 409

    Idempotency-Key conflict. IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_BODY — the key was already used with a different request body; REQUEST_IN_PROGRESS — the first request bearing this key is still in flight (retryable).

    application/json object

  • 422

    Either an unresolvable precedingInvoice reference (invoiceTypeCode="384" with a missing/dangling/draft/external reference) or a negative-amount line/total (UStAE 14.3 Abs. 2 Satz 7 — a reduction must be its own corrective document).

    application/problem+json InvalidPrecedingReferenceProblem | SplitRequiredProblem

Preview the totals for a set of line items (nothing is created)

POST /invoices/preview-totals

Returns the document totals, VAT breakdown and per-line net amounts that an invoice with these line items would get — computed by exactly the same server-side engine POST /invoices uses, so the previewed totals are byte-identical to the ones issuance persists for the same line items.

Nothing is created or consumed. No invoice, no invoice number, no numbering event, no metering record and no outbox entry — so no Idempotency-Key is accepted or needed, and the endpoint is safe to call as often, as concurrently and as out-of-order as a form's keystrokes demand. It is a read of would-be values, hence documents:read rather than documents:write, and it needs no persisted draft — which is the whole point: it previews an unsaved form.

Tax treatment is resolved on the server. Kleinunternehmer status (§ 19 UStG) is read from your entity's company profile; there is no field to send, and a client may not decide it. A Kleinunternehmer entity's preview therefore already shows the exempt (E) treatment its issued invoice will carry.

What this does NOT do: it is not a validation verdict (use POST /invoices/{id}/validate or POST /validate), and it cannot detect a mistyped amount — 1250 is a valid decimal and the server has no way to know 12.50 was meant. It shows the consequence of the numbers actually submitted, so a human can catch one that looks wrong.

Erforderlicher Scope
documents:read
Zugang
API-Schlüssel oder angemeldete Sitzung

Request-Body erforderlich

application/json PreviewTotalsRequest

Antworten

  • 200

    The totals these line items would produce.

    application/json PreviewTotals

  • 400

    Validation error (line-item shape / VAT-category rules). Issues may carry a code: vat_category_unavailable (lineItems.N.vatCategory), vat_rate_category_mismatch (lineItems.N.taxRatePercent), vat_exemption_reason_required and vat_exemption_reason_not_allowed (lineItems.N.vatExemptionReasonText), and unit_code_unknown (lineItems.N.unitCode); unit_code_unknown occurs only where the deployment enforces that check.

    application/json ValidationError

  • 422

    A line has a non-positive quantity or a negative unit price ( UStAE 14.3 Abs. 2 Satz 7 — a reduction must be its own corrective document). The same refusal POST /invoices gives.

    application/problem+json SplitRequiredProblem

Preview the next auto-generated invoice number (no side effects)

GET /invoices/next-number

Read-only preview — does NOT consume the counter. The number actually assigned at issue time (PATCH /invoices/{id}/finalize) can differ if another invoice is issued in between (the counter is only reserved atomically at issue time, not here).

Erforderlicher Scope
documents:read
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
issueDate Query string (date)

ISO date (YYYY-MM-DD) the number's date segment would use. Defaults to today.

Antworten

  • 200

    The previewed number, or null if no company profile is configured yet.

    application/json object

Get a full invoice by id

GET /invoices/{id}

Erforderlicher Scope
documents:read
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The invoice

    application/json Invoice

  • 404

    Not found

Replace a draft invoice (full replace of all mutable fields; server recomputes totals)

PUT /invoices/{id}

Draft only — always 409 on a non-draft invoice, regardless of which fields the request tries to change (numbers, artifacts, and totals of an issued document are immutable). Same request shape and the same validation as POST /invoices, including the invoiceTypeCode allowlist check (400) and the precedingInvoice/negative-amount checks (422, same as create), with one difference: the rule that a single service date (deliveryDate) and a service period (billingPeriodStart/billingPeriodEnd) are alternatives is judged on the RESULTING invoice — the stored values together with the ones you send — not on the request body alone. A save that would leave both on the invoice, or a body carrying both, is refused with 400 validation_error and one issue { path: deliveryDate, code: service_timing_conflict }; nothing is written and the stored values are unchanged. Remove the one you no longer want. A correction document (invoiceTypeCode 381/384, or one referencing a preceding invoice) may keep, unchanged, a date and a period it inherited from its original.

A replace writes the service period (billingPeriodStart, billingPeriodEnd) like every other mutable field: an omitted bound stores no value, so an omitted billingPeriodEnd leaves the period open-ended and omitting both clears it. An explicit null on deliveryDate, billingPeriodStart or billingPeriodEnd means the same as omitting it. One exception: on an invoice generated from a recurring template (subscriptionId set) the period belongs to the template. Omit it or resend the stored period unchanged (compared as calendar dates) and it is kept; a different period is refused with 400 validation_error and one issue { path: billingPeriodStart, code: billing_period_owned_by_subscription }, and nothing is written. To bill later periods differently, change the recurring template.

Rollout: a deployment that has not yet enabled this ignores billingPeriodStart/billingPeriodEnd on a replace and keeps the stored period. The response always shows the period that was stored; compare it with the one you sent. This note is removed once every deployment writes the period.

Erforderlicher Scope
documents:write
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Request-Body erforderlich

application/json CreateInvoice

Antworten

  • 200

    The updated invoice

    application/json Invoice

  • 400

    Validation error, including an invoiceTypeCode not on the configured allowlist, or an unknown customerId. Also service_timing_conflict (issue path deliveryDate): the invoice after this save would state both a single service date and a service period, or the body carries both (see the operation description). Also billing_period_owned_by_subscription (issue path billingPeriodStart): the invoice was generated from a recurring template and the request changes its period. Issues may also carry the other codes POST /invoices documents: reverse_charge_buyer_vat_id_required, vat_id_prefix_unknown, currency_code_unknown, vat_category_unavailable, vat_rate_category_mismatch, vat_exemption_reason_required, vat_exemption_reason_not_allowed and unit_code_unknown, with the same paths.

    application/json ValidationError

  • 404

    Not found

  • 409

    Not a draft (not_draft), or the supplied invoiceNo is already in use by another invoice (invoice_number_conflict).

    application/json ValidationError

  • 422

    Either an unresolvable precedingInvoice reference (invoiceTypeCode="384" with a missing/dangling/draft/external reference) or a negative-amount line/total (UStAE 14.3 Abs. 2 Satz 7 — a reduction must be its own corrective document).

    application/problem+json InvalidPrecedingReferenceProblem | SplitRequiredProblem

Delete a draft invoice

DELETE /invoices/{id}

Draft only. An issued/paid/void invoice is immutable (GoBD), so a delete on a non-draft is refused with 409 not_draft — the correction path for an issued document is a credit note (POST /invoices/{id}/credit-note), never a delete. A draft generated by a recurring invoice is refused with 409 subscription_draft_not_deletable while that recurring invoice is active or paused: the next billing run would create the draft again for the same period, so deleting it and billing the period by hand would invoice one supply twice. Edit the draft, or end the recurring invoice first; the body names the recurring invoice (subscriptionId). A draft still referenced by another record is refused with 409 invoice_delete_blocked. Idempotent from the caller's view: a missing invoice is a plain 404.

Erforderlicher Scope
documents:write
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 204

    Deleted (no content).

  • 404

    Invoice not found. ⚠ Indistinguishable, deliberately, from "an id belonging to a DIFFERENT entity": where row-scoped context resolution is enabled, the id is resolved to its own tenant/entity/mode before the handler runs, and an id this caller may not reach returns the same 404 as an unknown one. Distinguishing the two would leak the existence of another tenant's document.

    application/json ValidationError

  • 409

    Refused by the document's current state, nothing deleted. error is one of: not_draft — only draft invoices can be deleted; subscription_draft_not_deletable — the draft was generated by a recurring invoice that is still active or paused (the body carries subscriptionId); invoice_delete_blocked — another record still references the draft (the body carries referencedBy, a record type, never its contents).

    application/json ValidationError

Download the stored ZUGFeRD PDF for an issued invoice

GET /invoices/{id}/pdf

Streams the persisted artifact (invoices.pdf_key, written at issue time by the compliance-gate orchestrator). 404 whenever it is not ready to serve — draft, pdf_status not 'ready', or the object is unexpectedly missing from storage — the response body does not distinguish which. Readable with an API key carrying documents:read as well as with a dashboard session — this is the call an integration makes to hand the issued document on.

Erforderlicher Scope
documents:read
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The PDF/A-3b (ZUGFeRD) file, as attachment.

    application/pdf

  • 404

    Not found, not ready, or missing from storage

    application/json ValidationError

Download the stored XRechnung 3.0 CII XML for an issued invoice

GET /invoices/{id}/xml

Streams the persisted artifact (invoices.xml_key, written at issue time). 404 whenever it is not ready to serve — draft, xml_status not 'ready', or the object is unexpectedly missing from storage.

Erforderlicher Scope
documents:read
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The XRechnung 3.0 CII XML document, as attachment.

    application/xml

  • 404

    Not found, not ready, or missing from storage

    application/json ValidationError

Render a non-persisted, watermarked WYSIWYG preview PDF for a draft

GET /invoices/{id}/preview.pdf

Draft only (409 otherwise). Rendered on demand by the document renderer — the same template and renderer as the real PDF, so this is byte-faithful to what PATCH /invoices/{id}/finalize will produce. Nothing is stored; a fresh render happens on every call.

Erforderlicher Scope
documents:read
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The watermarked preview PDF, inline.

    application/pdf

  • 404

    Not found

    application/json ValidationError

  • 409

    Invoice is not a draft

    application/json ValidationError

  • 422

    buyer_address_incomplete — the buyer's postal address (street / postal code / city + a 2-letter country code) is incomplete, so no document can be rendered from this draft. missing[] names the offending request fields. Checked in api BEFORE the renderer is called, so an incomplete address is a clean, field-naming refusal rather than an opaque 5xx. A 502/503 here means the renderer itself failed or was unreachable, never that the data was wrong.

    application/json ValidationError

  • 502

    render_failed — the render service rejected the document.

  • 503

    renderer_unavailable — the render service is unreachable; retry.

Reset a failed PDF render to pending and retry generation

PATCH /invoices/{id}/retry-pdf

Requires an issued invoice whose pdf_status is 'failed'.

Erforderlicher Scope
documents:write
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The invoice with pdf_status reset to 'pending'; regeneration runs fire-and-forget.

    application/json Invoice

  • 404

    Not found

  • 409

    PDF retry requires an issued invoice with pdf_status = 'failed'

    application/json ValidationError

Reset a failed XML render to pending and retry generation

PATCH /invoices/{id}/retry-xml

Requires an issued invoice whose xml_status is 'failed'.

Erforderlicher Scope
documents:write
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The invoice with xml_status reset to 'pending'; regeneration runs fire-and-forget.

    application/json Invoice

  • 404

    Not found

  • 409

    XML retry requires an issued invoice with xml_status = 'failed'

    application/json ValidationError

Set or clear the DSGVO Art. 18 processing restriction

PATCH /invoices/{id}/processing-restriction

Document-level DSGVO Art. 18 processing restriction. Setting restricted: true requires a reason; it stamps restrictedAt/ restrictedBy and writes an audit-log entry documenting the conflict. Clearing (restricted: false) resets the reason/actor/timestamp. A restriction is metadata ABOUT the document — it never alters the issued content, and a restricted document still fully archives, exports, and appears in DATEV output (restriction limits processing, not existence). Role-gated owner/admin.

Erforderlicher Scope
documents:write
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Request-Body erforderlich

application/json object

Antworten

  • 200

    The invoice with its updated restriction metadata.

    application/json Invoice

  • 400

    Missing reason on a set, or an invalid reason value.

    application/json ValidationError

  • 403

    Insufficient role (owner/admin required).

  • 404

    Not found

Transition a draft to 'scheduled' with an auto-issuance date

PATCH /invoices/{id}/schedule

A scheduling worker (a separate process) polls for invoices whose scheduledIssuanceDate has arrived and issues them automatically. No number is assigned here — numbering only happens at issue time.

Erforderlicher Scope
documents:write
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Request-Body erforderlich

application/json object

Antworten

  • 200

    The invoice, now status = 'scheduled'

    application/json Invoice

  • 400

    Invalid or missing scheduledIssuanceDate

    application/json ValidationError

  • 404

    Not found

  • 409

    Only draft invoices can be scheduled

    application/json ValidationError

Issue a draft (compliance gate). Runs the KoSIT gate synchronously: assigns the invoice number (sticky — reused on a retry after a gate failure), renders + validates both XRechnung and ZUGFeRD in parallel, and either commits (artifacts + report + email outbox) or rejects with actionable findings. the validation gate in report-only mode (rollout default) never blocks — the commit runs regardless of verdict, with `validationState` recording the outcome; the validation gate in enforce mode blocks on any error-severity finding. The invoice date (`issueDate`) is set to the day of issue (Europe/Berlin), whatever date the draft carried.

PATCH /invoices/{id}/finalize

Erforderlicher Scope
documents:write
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    Issued. Artifacts and a validation report exist; the email outbox is enqueued when a recipient is configured. section14 is the advisory §14 Pflichtangaben summary computed for the issued document — passed/has_warnings/unavailable, never failed (§14 is advisory and never gates issuance). The durable §14 result is persisted strictly post-commit; a persist failure is self-healing (recomputed on read) and never affects issuance.

    application/json object

  • 402

    The plan tier does not include live issuance. Nothing was consumed — no invoice number, no document, no metering entry — and the draft is unchanged and still editable. Cleared by an upgrade, never by retrying.

    application/json EntitlementRequiredError

  • 404

    Not found

  • 409

    Not a draft, or issuing new documents is suspended for this account (issuing_suspended); nothing was issued and no number was used. A correction (credit note or corrected invoice) is never refused for that reason.

    application/json ValidationError

  • 422

    Gate failed (validation gate enforcing). Draft unchanged (number retained, still editable). Machine-readable findings. A B2G issue blocked solely by target-conditional B2G fields returns the distinct B2G_FIELD_REQUIRED variant instead.

    application/problem+json ValidationProblem | B2gFieldRequiredProblem

  • 503

    The document renderer was unavailable, unreachable, or timed out (15s budget). Retryable — the draft is intact and the sticky number (if already assigned) is unaffected.

    application/json ValidationError

Derive a Stornorechnung (credit note, UNTDID 381) from an issued invoice

POST /invoices/{id}/credit-note

Creates and returns a new DRAFT credit note (invoiceTypeCode="381") that references the given issued/paid invoice via the EN 16931 Preceding Invoice Reference (BG-3). Line items are copied from the original with POSITIVE amounts — a credit note states positive figures; the credit direction is carried by the type code, never by sign — and totals/tax are recomputed server-side. The original invoice is never modified. A full credit is returned; a partial credit is produced by editing the returned draft before issuing. The returned draft carries reversalScope="full" and is titled „Stornorechnung" on the rendered document. Editing it so it no longer covers the whole original flips reversalScope to "partial" and the title to „Rechnungskorrektur", so the printed word always matches what the document actually reverses. invoiceTypeCode stays "381" either way — the scope is a label, not a second document type, and nothing about booking, dunning or the permitted preceding references changes with it. Auth/scope/idempotency are identical to POST /invoices (honors the optional Idempotency-Key header so a retried derive is a replay, not a second draft).

Erforderlicher Scope
documents:write
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

The issued/paid invoice to credit.

Idempotency-Key Header string

Optional client-supplied key that makes this mutating request safe to retry. The FIRST request runs and its outcome is recorded keyed by (tenant, route, key); a REPLAY (same key + byte-identical body, after the first completed) returns the recorded response verbatim WITHOUT re-executing — so a network-blip retry never creates a duplicate customer/subscription/key. A key reused with a DIFFERENT body is 409 IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_BODY; a key whose first request is still in flight is 409 REQUEST_IN_PROGRESS (retryable). Keys expire after 24h — a key reused after expiry is a fresh request, not a replay. Transparent when omitted (the request runs normally).

Länge: max. 255

Antworten

  • 201

    The seeded 381 draft (same shape as POST /invoices; or, on an Idempotency-Key replay, the recorded response).

    application/json Invoice

  • 404

    Source invoice not found in the caller's scope

  • 409

    Idempotency-Key conflict. IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_BODY — the key was already used with a different request body; REQUEST_IN_PROGRESS — the first request bearing this key is still in flight (retryable).

    application/json object

  • 422

    INVALID_PRECEDING_REFERENCE — the source is not a legal credit-note target (draft/scheduled/void, or a type outside {380,381,384}).

    application/problem+json InvalidPrecedingReferenceProblem

Dry-run preflight (compliance gate). Draft or scheduled only. Runs the same local preflight + parallel render+validate calls as `PATCH /invoices/{id}/finalize`, but never assigns a number (a placeholder BT-1 is used when the invoice is unnumbered) and never commits — no state change on the invoice, regardless of verdict. The report is stored with `kind=preflight`.

POST /invoices/{id}/validate

Erforderlicher Scope
documents:read
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    Verdict (pass or fail) and the reports produced by this dry-run, plus the advisory §14 Pflichtangaben summary (section14) — evaluated pre-issue so a draft's missing Leistungsdatum etc. is visible with no document consumed. section14 never blocks: it is passed/has_warnings/unavailable, never failed.

    application/json object

  • 404

    Not found

  • 409

    Invoice is neither draft nor scheduled

    application/json ValidationError

  • 422

    Local field-completeness preflight failed (predates the KoSIT gate; unconditional on the validation gate). Nothing was rendered/consumed — reportIds is empty. A B2G dry-run blocked solely by target-conditional B2G fields returns the distinct B2G_FIELD_REQUIRED variant.

    ⚠ Two different bodies share this status, and they are told apart by CONTENT TYPE, not by a field. The compliance verdicts above are application/problem+json and carry code; an incomplete buyer address is the platform's standard envelope (application/json, discriminator error = buyer_address_incomplete, with missing[] naming the offending request fields) and carries no code at all. It is answered BEFORE the renderer is called, so it names the fields in this API's own vocabulary rather than the renderer's.

    application/problem+json ValidationProblem | B2gFieldRequiredProblem

    application/json ValidationError

  • 503

    The document renderer was unavailable, unreachable, or timed out.

    application/json ValidationError

Stateless Validation-as-a-service. Runs a submitted invoice through the SAME KoSIT + §14 compliance gate and returns the report — persisting NOTHING (no invoice, no stored report, no numbering, no metering). Requires the `validation:run` scope. Mounted only where standalone validation is enabled (off by default → 404).

POST /validate

Two request shapes, one verdict contract: (a) an invoice BODY (application/json, same shape as POST /invoices) is assembled in-memory (totals computed server-side, never trusted from input) and run through the api's KoSIT + §14 engine; (b) a SUBMITTED structured EN 16931 XML (application/xml) is proxied to zugferd's validate-only endpoint (no §14 — that reads an assembled model, not raw XML). The verdict is the SAME one issuance would gate (same engine) — it can never diverge.

200-with-findings (D-3): a fail verdict — KoSIT, §14, OR the local field-completeness preflight — is ordinary 200 response DATA, never an HTTP error. This NARROWLY diverges from the landed POST /invoices/{id}/validate dry-run, which throws 422 on its local preflight; this endpoint folds even that into the 200 findings. The two 422 boundaries are deliberately NOT reconciled (the dry-run's pre-flight-422 stays for its shipped consumers). 4xx here is reserved for a body that cannot be ASSEMBLED at all (path a) or an unknown formatVersion (path b).

Stateless fence: a submitted document is validated and forgotten — there is no landing zone, no field-extraction into invoices, no inbound receiving. Version is explicit (?profile=/?formatVersion= for path b; the api default for path a) and echoed as profile so the verdict is self-describing.

Erforderlicher Scope
validation:run
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
profile Query string

Path (b) only — the XRechnung/EN 16931 profile version to validate the submitted XML against (alias formatVersion). Echoed in the response.

Request-Body erforderlich

application/json CreateInvoice

application/xml string (binary)

Antworten

  • 200

    The verdict (pass/fail) + rule-referenced report(s), the echoed profile, and (path a only) the advisory §14 Pflichtangaben summary. A fail verdict is a successful evaluation, NOT an error.

    application/json object

  • 400

    The body cannot be assembled at all (path a — bad JSON / schema / customer / preceding reference) or an unknown formatVersion (path b). NOT a fail verdict (that is a 200).

    application/json ValidationError

  • 403

    The caller lacks the validation:run scope.

    application/json ValidationError

  • 503

    The document renderer was unavailable, unreachable, or timed out (path b, or path a render).

    application/json ValidationError

List validation report metadata for an invoice: every attempt (issue attempts, successful issuance, preflight dry-runs), verdict, kind, artifact/validator versions, and findings. Byte-exact report XML is not included — see `GET /invoices/{id}/validation-reports/{reportId}`.

GET /invoices/{id}/validation-reports

Erforderlicher Scope
documents:read
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

Retrieve a stored Prüfbericht (native KoSIT XML, byte-exact). 404 (not the report) if `reportId` belongs to a different invoice than `id` — no cross-invoice leakage.

GET /invoices/{id}/validation-reports/{reportId}

Erforderlicher Scope
documents:read
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)
reportId erforderlich Pfad string (uuid)

Antworten

  • 200

    The stored KoSIT Prüfbericht, byte-exact.

    application/xml

  • 404

    Not found (including a reportId belonging to a different invoice)

The typed §14 Abs. 4 UStG Pflichtangaben result for an invoice — the clean three-state `Section14Summary` the frontends bind to (the raw `kind='section14'` row also remains in the validation-reports list). For an issued document the stored result is returned (`evaluatedAt` = when it was computed); a lost post-commit persist is transparently recomputed over the immutable issued document and re-persisted (recompute-on-read), so the result is always retrievable. Advisory-only: `status` is `passed`/`has_warnings` (or `unavailable`) — NEVER `failed`; a `not_applicable` item is never counted as a warning. RLS-scoped — a cross-tenant request 404s (indistinguishable from missing). PII-free.

GET /invoices/{id}/section14

Erforderlicher Scope
documents:read
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The §14 Pflichtangaben summary for the invoice.

    application/json Section14Summary

  • 404

    Not found — a missing/cross-tenant invoice, or a draft never validated (no §14 result yet; render "nicht geprüft").

Manually (re-)send an issued invoice's delivery email

POST /invoices/{id}/send

The body is OPTIONAL and a request without one behaves exactly as it always has. In ONE transaction append an immutable immutable send record + RE-ARM the single an outbound-mail record of kind invoice (sent_at=NULL, status='pending') so the mail dispatcher re-sends the ALREADY-ISSUED ZUGFeRD PDF. The invoice row is byte-UNTOUCHED (0058 immutability): no render, no re-issue. A deliberate re-send (fresh Idempotency-Key) is allowed and writes a NEW send record + re-arms; a raced double-submit with the same key is a replay of the 201. Refuses (writing nothing) for a non-issued/paid status (409 invoice_not_sendable), no recipient (422 no_recipient_email — buyer email ∅ and the linked customer's email ∅), or no SMTP configured (422 email_settings_not_configured). sendeprotokolle is left untouched. Customer copy recipients are applied by automatic delivery; on this operation copies are sent only when named in the body.

Erforderlicher Scope
documents:write
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)
Idempotency-Key Header string

Request-Body optional

application/json object

Antworten

  • 201

    The created send.

    application/json InvoiceSend

  • 403

    Caller lacks the documents:write scope.

  • 404

    invoice_not_found — the invoice is not in scope.

  • 409

    invoice_not_sendable — status ∉ {issued, paid}.

  • 422

    no_recipient_email (no email on file AND no recipient supplied), email_settings_not_configured (no default SMTP config for the entity/mode), ad_hoc_recipient_invalid (an address is not a single valid address), ad_hoc_copy_cap_exceeded, ad_hoc_recipient_not_enabled (this deployment does not accept a chosen recipient — a body-less send is unaffected), or validation_error (the body's shape; details[] lists path and message, and an address entry that is too short or too long carries code: invalid_address), or sandbox_recipient_not_allowed (test mode, sent by the platform's test sender: a recipient — including a copy or blind copy — is neither a confirmed reply address of the company nor the address of an active member of the account; the body names no address). Nothing is written on any of them. The email_settings_not_configured body NAMES the gap and the fix — mode (which of the two mode-scoped configs is empty), missing: ["email_settings.default"] and remedy: "settings.delivery". The mode is load-bearing: delivery configuration is scoped per entity and mode, and a fresh session defaults to Test, so a tenant who has configured SMTP has very often configured the OTHER mode — which is exactly how a live invoice ends up issued and never mailed.

  • 429

    sandbox_mail_daily_cap_reached — test mode, sent by the platform's test sender: this company has reached its daily limit of test-mode e-mails sent by the platform (counted per company over the Europe/Berlin calendar day: e-mails sent today plus e-mails still waiting to be sent). Nothing was written and nothing was sent; a reminder level stays free. The limit resets at midnight Europe/Berlin; Retry-After gives the seconds until then and limit the number. E-mails sent through the company's own mail server are not counted.

    • Retry-After integer

      Seconds until the next business day starts (Europe/Berlin midnight).

    application/json object

The append-only send history of an invoice

GET /invoices/{id}/sends

Every (re-)send attempt of the invoice as an immutable send record (newest first) — both manual re-sends and the auto_issue delivery at issue. Drives the detail page's "Senden" vs "Erneut senden" label and the "zuletzt gesendet am" line.

Erforderlicher Scope
documents:read
Zugang
API-Schlüssel oder angemeldete Sitzung

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The send history.

    application/json object

  • 403

    Caller lacks the documents:read scope.

  • 404

    The invoice is not in scope.

Record a dispatch you performed yourself

POST /invoices/{id}/external-send

Records that a named person dispatched this invoice outside this application — by post, in person, or from their own mail client. It sends nothing and changes no document: no message is composed, no delivery is queued and the invoice is untouched.

The record is evidence of the STATEMENT, not of the dispatch. What it establishes is who said it, when they said it, and what they said. It establishes nothing about whether the document arrived.

Available to interactive sessions only. Programmatic credentials are refused, because the substance of the record is that a named person stated something.

Erforderlicher Scope
documents:write
Zugang
nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt (oidc-only)

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)
Idempotency-Key Header string

An optional client-generated key. A retry carrying the same key returns the recorded response instead of writing a second time.

Länge: max. 255

Request-Body erforderlich

application/json object

Antworten

  • 201

    The record was written.

    application/json InvoiceSendHistoryItem

  • 403

    Caller lacks the documents:write scope, or presents a programmatic credential.

  • 404

    The invoice is not in scope, or recording is unavailable in this deployment.

  • 409

    Only an issued or paid invoice can carry a dispatch record.

  • 422

    The stated date is out of range, or the channel is not accepted here.

Withdraw a dispatch record

POST /invoices/{id}/sends/{sendId}/retraction

Withdraws a dispatch record you entered. The original record is never edited and never removed: the withdrawal is written as its own entry beside it, and the history shows both.

Only a record you entered can be withdrawn. A dispatch this application actually performed cannot be — correct a message sent to the wrong address by sending it again to the right one.

Erforderlicher Scope
documents:write
Zugang
nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt (oidc-only)

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)
sendId erforderlich Pfad string (uuid)
Idempotency-Key Header string

An optional client-generated key. A retry carrying the same key returns the recorded response instead of writing a second time.

Länge: max. 255

Request-Body erforderlich

application/json object

Antworten

  • 201

    The withdrawal was written.

    application/json InvoiceSendHistoryItem

  • 403

    Caller lacks the documents:write scope, or presents a programmatic credential.

  • 404

    No such record on this invoice, or recording is unavailable in this deployment.

  • 409

    The record is not one that can be withdrawn, or it was already withdrawn.

Datenmodelle

Jedes Datenmodell dieser Gruppe einmal, mit allen Feldern – auch den verschachtelten. Verschachtelte Objekte sind aufklappbar; ein benannter Typ verweist auf seinen eigenen Abschnitt.

InvoiceSummary object

Feld Typ Beschreibung
id string (uuid)
invoiceNumber string | null

Null on a freshly created draft unless an explicit invoiceNo was supplied at creation (numbers are no longer auto-assigned at draft creation — only at issue time, sticky thereafter). Use GET /invoices/next-number to preview what will be assigned.

issueDate string (date)

EN 16931 BT-2, the invoice date. It is always the day the invoice is issued (Europe/Berlin): on a draft it is provisional and reads as the day of the latest save; issuing sets it to the issue day, and a scheduled issuance to the day it runs. Frozen at issue.

status string

⚠ void is RESERVED AND NEVER WRITTEN. §14c UStG / GoBD forbid voiding an issued original; a cancellation is a separate 381 document referencing it, so api never writes this status anywhere. It stays in the enum because removing it would be a breaking contract change for no benefit. To detect a cancelled invoice read correctionState, never status.

Werte: "draft", "scheduled", "issued", "paid", "void"
currency string

ISO 4217

buyerName string | null
totalGross DecimalString
validationState string | null

Compliance-gate outcome. null until a validation attempt has run. 'legacy' = issued before the gate existed (backfilled when the gate was introduced).

Werte: "failed", "passed", "legacy"
correctionState string | null

The DERIVED correction state, computed on read from the document chain and never stored. cancelled = an ISSUED/PAID 381 (Storno) that reverses this invoice IN FULL references it; corrected = an ISSUED/PAID 384 (Berichtigung) does and no full 381 does; null = neither, INCLUDING the case where the only correction is still a DRAFT (a half-made Storno has cancelled nothing and must not switch off collection) AND the case where the only correction is a PARTIAL 381 (it reduces the open amount rather than settling the invoice, so the invoice stays in the open items and is still dunned for the remainder). Cancellation wins over correction. Naming + the null-for-none convention follow dauerrechnungState. This — not status: void, which the law forbids the system from ever writing — is what a "Storniert" view keys on. An invoice with a non-null correctionState is excluded from /dunning/overdue, from the per-customer open items, and from payment matching, and POST /invoices/{id}/reminders refuses it with 409 cancelled_by_correction. An invoice reduced by PARTIAL credit notes keeps a null correctionState and stays in all three, at its reduced open amount, until that amount reaches zero.

Werte: "cancelled", "corrected"
transmissionState TransmissionState
transmissionProblemReason TransmissionProblemReason
transmissionLastEventAt string (date-time) | null

When we learned the fact transmissionState reports. null when there is no such fact.

mode Mode

Verwendet von: GET /invoices

PaginationMeta object

Pagination metadata returned alongside every paginated list.

Feld Typ Beschreibung
total erforderlich integer

Total number of records matching the current filter.

page erforderlich integer

Current page (1-indexed, echoes the request).

pageSize erforderlich integer

Records per page (echoes the request).

totalPages erforderlich integer

Total number of pages. Minimum 1, even when total is 0.

Verwendet von: GET /invoices

UnknownQueryParameterError object

The request named query parameters this endpoint does not define. A list endpoint refuses them rather than ignoring them, so a caller can never mistake an UNFILTERED collection for a filtered one.

Feld Typ Beschreibung
error erforderlich string
Werte: "unknown_query_parameter"
parameters erforderlich Array<string>

The unrecognised parameter NAMES, deduplicated and sorted. Bounded at ten entries, and a name longer than 64 characters is omitted — a parameter name is caller-supplied text, and this response never reflects unbounded caller input. The array may therefore be EMPTY while the refusal still stands: the refusal is decided by the request, not by the length of this list.

message string
requestId string

Verwendet von: GET /invoices

ValidationError object

A refused request body. error is validation_error when the body was read and failed validation (each problem is listed in issues), or invalid_json when the body is not valid JSON at all (then issues is absent). An unparsable body is answered 400 invalid_json on every operation that takes a JSON body. The submitted body is never echoed.

Feld Typ Beschreibung
error string
issues Array<object>
Felder
Feld Typ Beschreibung
path string
message string
code string

Optional machine-readable reason for the refusal, for a client that shows its own wording instead of message. Present only where an endpoint documents it; an issue without it is refused for the reason message states. Today: invalid_address (an entry of a copy list is not one valid address), too_many_addresses (more than 5 entries in one copy list), copies_require_email (copy recipients are set but email is empty), and service_timing_conflict (POST /invoices, PUT /invoices/{id}: the invoice would state both a single service date and a service period; the issue's path is deliveryDate). On the invoice saves and POST /invoices/preview-totals also reverse_charge_buyer_vat_id_required (a reverse-charge line needs the buyer's VAT identification number), vat_category_unavailable (the line's VAT category is not available), vat_rate_category_mismatch (the rate does not fit the line's VAT category), vat_exemption_reason_required / vat_exemption_reason_not_allowed (the exemption reason is missing, or not allowed for that category), and, only where the deployment enforces the check, unit_code_unknown, currency_code_unknown and vat_id_prefix_unknown (the same three also appear on quote and recurring-template saves, with the same paths). The set may grow, so treat an unknown value like an absent one. Never carries a submitted value.

Verwendet von: GET /invoices , POST /invoices , POST /invoices/preview-totals , PUT /invoices/{id} , DELETE /invoices/{id} , GET /invoices/{id}/pdf , GET /invoices/{id}/xml , GET /invoices/{id}/preview.pdf , PATCH /invoices/{id}/retry-pdf , PATCH /invoices/{id}/retry-xml , PATCH /invoices/{id}/processing-restriction , PATCH /invoices/{id}/schedule , PATCH /invoices/{id}/finalize , POST /invoices/{id}/validate , POST /validate

CreateInvoice object

Create payload. NOTE: no totals — the server computes them. Any totals present in the body are ignored.

Feld Typ Beschreibung
currency erforderlich string

ISO 4217 (3 letters)

invoiceTypeCode string

UNTDID 1001 (BT-3). Defaults to "380" if omitted. Allowlist- checked against INVOICE_TYPE_CODES (default "380,381,384") — an unlisted code is a 400 validation_error.

precedingInvoice object

EN 16931 BG-3 reference. REQUIRED when invoiceTypeCode="384" (correction); a missing, dangling, draft/scheduled, or otherwise unresolvable reference is a 422 INVALID_PRECEDING_REFERENCE. Ignored for any other invoiceTypeCode. Resolved server-side by id or invoiceNumber (at least one required) — the response's precedingInvoice.issueDate/invoiceNumber come from the resolved original, not from this input.

Felder
Feld Typ Beschreibung
id string (uuid)
invoiceNumber string
issueDate string (date)

Optional, and set by the server: the invoice date is always the day the invoice is issued, so this property is accepted only when it names today (Europe/Berlin). Any other day is refused with a validation_error naming issueDate. To state when the service was performed, use deliveryDate or billingPeriodStart and billingPeriodEnd. On a create or a replace the draft carries today as a provisional date, whether or not this property is sent; issuing sets the issue day.

dueDate string (date) | null

EN 16931 BT-9 payment due date. Optional; when omitted the render clients fall back to issueDate + 30 days. Frozen at issue by the 0058/0059 immutability trigger.

paymentTerms string | null

EN 16931 BT-20 free-text payment terms / Zahlungsziel. Optional; frozen at issue.

customerId string (uuid)

Optional link to a saved customer; buyer is still snapshotted

documentTarget string

Optional per-invoice override of whether the recipient is a public authority. Omitting it does NOT clear it: the server resolves the linked customer's setting, then this draft's own stored value, so a replace that does not mention it keeps what the document already says. Snapshotted onto the invoice and frozen at issue. Note that the same property behaves differently on the customer replace, where omitting it sets 'b2b'.

Werte: "b2b", "b2g"
status string

Desired initial status; defaults to "draft" when omitted. "issued" is NOT accepted — issued documents are reachable only via the finalize / internal-issue endpoints, which assign the invoice number and generate the artifacts.

Werte: "draft", "scheduled"
buyer erforderlich Party
seller Party

Optional; defaults to the configured seller

buyerReference string
buyerElectronicAddress string

EN 16931 BT-49 — the buyer's ELECTRONIC ROUTING address (a Leitweg-ID for B2G, or an email), used when generating XRechnung. Falls back to buyerEmail when absent; the format is validated at XRechnung generation time, not here.

Länge: max. 200
title string | null

Document title / Betreff, rendered above the line items. On a create, OMITTING the field applies the company-wide default title; an explicit null means "none — and do not apply the default".

Länge: max. 500
text string | null

Free introductory body text rendered above the line items. Distinct from notes, which renders below them. On a create, OMITTING the field applies the company-wide default text; an explicit null means "none — and do not apply the default".

Länge: max. 5000
buyerEmail string (email)

Recipient email (prefilled from the customer)

notes string | null

Note / footer text rendered below the line items. On a create, OMITTING the field applies the company-wide default note; an explicit null means "none — and do not apply the default".

Länge: max. 2000
invoiceNo string

Optional custom invoice number, used verbatim (uniqueness checked, counter NOT incremented). For a Dauerrechnung this is where the static, contract-derived Objektnummer goes (UStAE 14.5 Abs. 12), which satisfies §14 Abs. 4 Nr. 4. On a replace this property distinguishes the two cases explicitly: omitting it keeps the stored number, sending an empty string clears it.

billingPeriodStart string (date) | null

EN 16931 BT-73 — service/billing period start. For a Dauerrechnung this is the Leistungszeitraum "von" satisfying §14 Abs. 4 Nr. 6. Mutually exclusive with deliveryDate.

A replace writes this field like every other mutable field: omitting it, or sending null, stores no value. On an invoice generated from a recurring template (subscriptionId set) the period belongs to the template: omit it or resend the stored period unchanged; a different period is refused with billing_period_owned_by_subscription (issue path billingPeriodStart).

Rollout: a deployment that has not yet enabled this ignores billingPeriodStart/billingPeriodEnd on a replace and keeps the stored period. The response always shows the period that was stored; compare it with the one you sent. This note is removed once every deployment writes the period.

billingPeriodEnd string (date) | null

EN 16931 BT-74 — service/billing period end (Leistungszeitraum "bis"; leave unset for an open-ended continuous supply). Mutually exclusive with deliveryDate.

A replace writes this field like every other mutable field: omitting it, or sending null, stores no value. On an invoice generated from a recurring template (subscriptionId set) the period belongs to the template: omit it or resend the stored period unchanged; a different period is refused with billing_period_owned_by_subscription (issue path billingPeriodStart).

Rollout: a deployment that has not yet enabled this ignores billingPeriodStart/billingPeriodEnd on a replace and keeps the stored period. The response always shows the period that was stored; compare it with the one you sent. This note is removed once every deployment writes the period.

deliveryDate string (date) | null

EN 16931 BT-72 — actual delivery date (single-point Leistungsdatum) satisfying §14 Abs. 4 Nr. 6 for a one-off invoice whose service date differs from the issue date. Mutually exclusive with billingPeriodStart/billingPeriodEnd (BT-72 XOR BG-14) — provide a single date OR a period, not both. On a replace the rule is judged on the resulting invoice: a date sent while the draft stores a period is refused (service_timing_conflict). An explicit null means the same as omitting it. Null/omitted → the renderer keeps its issueDate fallback. Frozen at issue.

isDauerrechnung boolean

Mark this invoice as a Dauerrechnung (continuous-supply invoice, UStAE 14.5 Abs. 17 self-identifying branch). Orthogonal to invoiceTypeCode — the code stays "380" (or "384" on a reissue-as-Berichtigung referencing the source via precedingInvoice). Reuses billingPeriodStart/billingPeriodEnd for the Nr. 6 Leistungszeitraum and invoiceNo for the static Objektnummer. Defaults false; frozen at issue. Reissue is a fresh create+finalize, never a server-side action (no anchor table, no reissue-classification endpoint in v1).

lineItems erforderlich Array<CreateInvoiceLineItem>
Elemente: min. 1, max. 1000

Verwendet von: POST /invoices , PUT /invoices/{id} , POST /validate

Invoice object

Feld Typ Beschreibung
id string (uuid)
invoiceNumber string | null

Null on a freshly created draft unless an explicit invoiceNo was supplied at creation (numbers are no longer auto-assigned at draft creation — only at issue time, sticky thereafter). Use GET /invoices/next-number to preview what will be assigned.

issueDate string (date)

EN 16931 BT-2, the invoice date. It is always the day the invoice is issued (Europe/Berlin): on a draft it is provisional and reads as the day of the latest save; issuing sets it to the issue day, and a scheduled issuance to the day it runs. Frozen at issue.

status string

⚠ void is RESERVED AND NEVER WRITTEN. §14c UStG / GoBD forbid voiding an issued original; a cancellation is a separate 381 document referencing it, so api never writes this status anywhere. It stays in the enum because removing it would be a breaking contract change for no benefit. To detect a cancelled invoice read correctionState, never status.

Werte: "draft", "scheduled", "issued", "paid", "void"
currency string

ISO 4217

buyerName string | null
totalGross DecimalString
validationState string | null

Compliance-gate outcome. null until a validation attempt has run. 'legacy' = issued before the gate existed (backfilled when the gate was introduced).

Werte: "failed", "passed", "legacy"
correctionState string | null

The DERIVED correction state, computed on read from the document chain and never stored. cancelled = an ISSUED/PAID 381 (Storno) that reverses this invoice IN FULL references it; corrected = an ISSUED/PAID 384 (Berichtigung) does and no full 381 does; null = neither, INCLUDING the case where the only correction is still a DRAFT (a half-made Storno has cancelled nothing and must not switch off collection) AND the case where the only correction is a PARTIAL 381 (it reduces the open amount rather than settling the invoice, so the invoice stays in the open items and is still dunned for the remainder). Cancellation wins over correction. Naming + the null-for-none convention follow dauerrechnungState. This — not status: void, which the law forbids the system from ever writing — is what a "Storniert" view keys on. An invoice with a non-null correctionState is excluded from /dunning/overdue, from the per-customer open items, and from payment matching, and POST /invoices/{id}/reminders refuses it with 409 cancelled_by_correction. An invoice reduced by PARTIAL credit notes keeps a null correctionState and stays in all three, at its reduced open amount, until that amount reaches zero.

Werte: "cancelled", "corrected"
transmissionState TransmissionState
transmissionProblemReason TransmissionProblemReason
transmissionLastEventAt string (date-time) | null

When we learned the fact transmissionState reports. null when there is no such fact.

mode Mode
sendState InvoiceSendState

What is on record about this invoice having gone out. Present on this read only.

transmission Transmission | null

What the transport reported about the latest system-performed dispatch of this invoice, and the remedies offered for it. Present on this read only; null when there is no system-performed dispatch to report on.

capabilities object

What this deployment currently accepts on this invoice. It rides inside this read so a client can offer only what would be accepted, without a second request.

Felder
Feld Typ Beschreibung
externalSendRecord erforderlich boolean

Whether a dispatch performed outside this application can be recorded.

adHocRecipient erforderlich boolean

Whether the send accepts a recipient and copies chosen for this one message.

dueDate string (date) | null

EN 16931 BT-9 payment due date. Frozen at issue; render clients fall back to issueDate + 30 days when null.

paymentTerms string | null

EN 16931 BT-20 payment terms / Zahlungsziel. Frozen at issue.

invoiceTypeCode string

UNTDID 1001 (BT-3), e.g. "380" (commercial invoice). Write requests are allowlist-checked (INVOICE_TYPE_CODES, default "380,381,384"); anything else is a 400. Frozen after issue — a PUT on a non-draft invoice is always rejected regardless of which fields it tries to change.

reversalScope nur lesbar string | null

Derived, read-only: whether a credit note (invoiceTypeCode="381") reverses its referenced original in full or only in part. A FULL reversal is titled „Stornorechnung" on the rendered document; a PARTIAL one is titled „Rechnungskorrektur". Re-derived on every draft edit and frozen at issue. "full" means all of: the same currency, numerically equal net, tax and gross totals, and the same set of value-bearing line facts (quantity, unit code, unit net price, VAT rate, VAT category). Free text — descriptions and exemption reason texts — is deliberately not part of the comparison, and the comparison is against the referenced original document, not against whatever remains outstanding on it after earlier corrections. null for every other document type, and for a credit note whose original could not be resolved. This field affects the printed title ONLY. It is never a second document type: a partial credit note keeps invoiceTypeCode="381", so how it is booked, whether it is dunned, and which preceding references it may carry are all unchanged.

Werte: "full", "partial"
precedingInvoice PrecedingInvoiceRef | null

EN 16931 BG-3 (BT-25 number / BT-26 issue date). Set only on a correction (invoiceTypeCode="384") that references an in-system original; both fields are snapshotted server-side from the resolved original at create/update time — never trusted from client input directly. null for any other invoiceTypeCode.

correctedBy Array<string (uuid)>

Derived, read-only: ids of correction invoices (invoiceTypeCode="384") whose precedingInvoice points at THIS invoice. Computed on every read via the preceding-invoice link — never stored, so an original is never mutated by a correction that targets it. Only populated on GET /invoices/{id} (empty on create/update/ finalize responses).

sourceOfferId string (uuid) | null

The INTERNAL backlink to the source Angebot this invoice was derived from (POST /offers/{id}/convert-to-invoice). NULL for every non-converted invoice. NOT the EN 16931 preceding-invoice reference — a quote is not a preceding invoice.

sourceOffer SourceOfferRef | null

The reverse projection of sourceOfferId — the source Angebot's id + number, for the invoice-detail "Entstanden aus Angebot AN-…" reference line. Resolved on every read (no migration) from the offer named by sourceOfferId; null for a non-converted invoice (or an offer no longer visible under scope). Only populated on GET /invoices/{id} (null on create/update/finalize responses).

correctionDocuments Array<CorrectionDocumentRef>

The reverse-link LABEL projection — the ISSUED/paid corrections (invoiceTypeCode="384") and credit notes / Stornos ("381") whose precedingInvoice points at THIS invoice, enriched with number + type code + issue date so a client can render "storniert durch (381) …" vs "korrigiert durch (384) …" without an N-fetch. The issued subset of the same reverse lookup as correctedBy (which stays the full, unfiltered id list). Computed on every read via the preceding-invoice link — never stored. Only populated on GET /invoices/{id} (empty on create/update/ finalize responses).

customerId string (uuid) | null

Soft link to the source customer; buyer is snapshotted

documentTarget string

The document-target (Rechnungsintent) snapshotted onto this invoice at creation (override > linked customer > 'b2b'), re-resolved on draft edit, frozen at issue. 'b2b' renders ZUGFeRD (EN 16931) with the German XRechnung CIUS rules (BR-DE-, e.g. BT-10) advisory; 'b2g' renders XRechnung + ZUGFeRD with BR-DE- as hard requirements.

Werte: "b2b", "b2g"
seller Party
sellerIsKleinunternehmer boolean

§19 UStG snapshot: whether the seller was a Kleinunternehmer at this invoice's CREATION, captured from the company profile's Kleinunternehmer setting and frozen on the invoice (like seller.taxNumber). Read-only; the §14 evaluator reads this immutable snapshot, never the live company flag.

isDauerrechnung boolean

The Dauerrechnung marker — an ORTHOGONAL invoice-level flag, NOT a new invoiceTypeCode (a Dauerrechnung stays a "380", or "384" on a reissue-as-Berichtigung). Set at create/draft, frozen at issue. The only consumer that branches on it is the §14 evaluator (Nr. 4 static-Objektnummer exemption; Nr. 6 period-satisfies). DATEV never branches on it — a Dauerrechnung books identically to its underlying 380/384.

dauerrechnungState string | null

DERIVED, read-only standing state. active = a Dauerrechnung chain head (no successor references it); superseded = a later invoice references it via precedingInvoice; null = not a Dauerrechnung (isDauerrechnung=false). Computed from the correctedBy chain, never stored (no anchor table). superseded only ever surfaces on GET /invoices/{id} (where correctedBy is resolved); a Dauerrechnung on a create/update/finalize response reads active.

Werte: "active", "superseded"
buyer Party
buyerReference string | null
buyerEmail string (email) | null

Snapshot recipient email

notes string | null
lineItems Array<InvoiceLineItem>
totals InvoiceTotals
taxBreakdown Array<TaxBreakdownEntry>
issuedAt string (date-time) | null

Set at issue time. Approximate (from updatedAt) for 'legacy' rows backfilled when the field was introduced; null for drafts.

processingRestricted boolean

DSGVO Art. 18 processing restriction. When true, the document is flagged restricted — it is still fully archived, exported, and DATEV-included (restriction limits processing, not existence). Set/cleared via PATCH /invoices/{id}/processing-restriction.

processingRestrictionReason string | null

Reason for the restriction; null when not restricted.

Werte: "article_18_gdpr_request", "legal_dispute", "disputed_accuracy"
restrictedAt string (date-time) | null

When the restriction was set (denormalized render-copy).

restrictedBy string (uuid) | null

users.id that set the restriction (denormalized render-copy).

createdAt string (date-time)
updatedAt string (date-time)

Verwendet von: POST /invoices , GET /invoices/{id} , PUT /invoices/{id} , PATCH /invoices/{id}/retry-pdf , PATCH /invoices/{id}/retry-xml , PATCH /invoices/{id}/processing-restriction , PATCH /invoices/{id}/schedule , POST /invoices/{id}/credit-note

InvalidPrecedingReferenceProblem object

POST /invoices / PUT /invoices/{id} with invoiceTypeCode="384" and a precedingInvoice that is missing, dangling, points at a draft/scheduled invoice, or is otherwise unresolvable in-system (external originals are rejected in v1).

Feld Typ Beschreibung
type string
title string
status integer
detail string
code erforderlich string
Werte: "INVALID_PRECEDING_REFERENCE"

Verwendet von: POST /invoices , PUT /invoices/{id} , POST /invoices/{id}/credit-note

SplitRequiredProblem object

POST /invoices / PUT /invoices/{id} with a non-positive line quantity, a negative line unitPriceNet, or negative document totals (UStAE 14.3 Abs. 2 Satz 7: a reduction must be its own corrective document, not a negative amount mixed into this one). detail cites the paragraph verbatim.

Feld Typ Beschreibung
type string
title string
status integer
detail string
code erforderlich string
Werte: "SPLIT_REQUIRED"

Verwendet von: POST /invoices , POST /invoices/preview-totals , PUT /invoices/{id}

PreviewTotalsRequest object

The line items to price. Nothing else is accepted: no buyer, no currency, no dates, no document type — and, deliberately, no tax-treatment flag (Kleinunternehmer status is read from your company profile, never from the request). Any totals present in the body are ignored; the server computes them.

Feld Typ Beschreibung
lineItems erforderlich Array<CreateInvoiceLineItem>
Elemente: min. 1, max. 1000

Verwendet von: POST /invoices/preview-totals

PreviewTotals object

The totals an invoice with these line items would get — identical to what POST /invoices computes for the same line items. Nothing was created.

Feld Typ Beschreibung
totals erforderlich InvoiceTotals
taxBreakdown erforderlich Array<TaxBreakdownEntry>
lines erforderlich Array<object>

Per-line net amounts, in the same order as the submitted lineItems.

Felder
Feld Typ Beschreibung
lineNetAmount erforderlich DecimalString

Verwendet von: POST /invoices/preview-totals

DecimalString string

A fixed-precision decimal as a string, e.g. "9542.50". Read shapes deliberately publish no scale: a document issued before the request scales below were introduced may carry more decimals than a new request may send, and such a document is served from the archive rather than re-created.

Beispiel: "9542.50"

Verwendet von: PATCH /invoices/{id}/finalize

TransmissionState string | null

What the TRANSPORT reported about this document's latest system-performed dispatch. A separate axis from sendState (who says it was sent) and from the sender kind (whose infrastructure carried it), and the three are never merged. transmitted is the highest claim this rail can ever make: a receiving mail system ACCEPTED the bytes. There is no delivered value and there will never be one — SMTP cannot report that a message reached a person, and a value that claimed it would be a fabricated fact on a retained document trail. A future rail that genuinely carries a delivery confirmation gets its own, different value. failed and undeliverable are different facts and are never merged: failed means nothing was ever handed over, undeliverable means it was and it came back. null means this document has no system-performed dispatch at all — a hand-recorded dispatch, a draft, or a document that was never mailable. It never means "unknown" and must not be rendered as a problem.

  • Werte: "not_sent", "queued", "held", "transmitted", "delayed", "undeliverable", "failed"

Verwendet von: PATCH /invoices/{id}/finalize

TransmissionProblemReason string | null

Why a document is in a delayed, undeliverable or failed state. null in every other state. ⚠ It carries a CLASS and never the report's own diagnostic text: that text is a fragment of third-party correspondence about somebody else's invoice and is neither stored nor published anywhere in this platform.

  • Werte: "hard_bounce", "soft_bounce", "smtp_rejected", "attempts_exhausted"

Verwendet von: PATCH /invoices/{id}/finalize

Mode string

Mode echo ("unmissable live/test indicator"): the row's own live/test mode, echoed on every scoped-resource representation so a live vs. test record is unmissable. Read from the record's mode column (never re-derived from the request credential); live for legacy rows created before mode existed.

  • Werte: "live", "test"

Beispiel: "live"

Verwendet von: PATCH /invoices/{id}/finalize

InvoiceSendState object

What this application knows about the invoice leaving the building. It is a class rather than a bare date, because a date alone cannot say whether we dispatched the document or somebody told us they had.

Feld Typ Beschreibung
class erforderlich string
Werte: "none", "system_transmitted", "user_asserted"
recordedAt string (date-time) | null

When we wrote the record down. Null when there is no record.

assertedOn string (date) | null

The date stated by the person who recorded it. Present only for user_asserted, and then always together with recordedAt.

Verwendet von: PATCH /invoices/{id}/finalize

Transmission object

What the transport reported about one document's latest system-performed dispatch, with the remedies this API offers for it. Present on the single-invoice read; null when there is no system-performed dispatch to report on.

Feld Typ Beschreibung
state erforderlich TransmissionState
heldReason erforderlich TransmissionHeldReason
problemReason erforderlich TransmissionProblemReason
lastEventAt erforderlich string (date-time) | null
sender erforderlich object | null

The sender identity the message actually went out under, read from the send record's own snapshot. It is never re-derived from current settings: a sender identity can be changed, and re-reading it would show today's sender beside yesterday's message.

Felder
Feld Typ Beschreibung
kind erforderlich string
Werte: "tenant_smtp", "platform"
fromAddress erforderlich string | null
replyTo erforderlich string | null
recipientSuppressed erforderlich boolean

The recipient address is currently on this account's suppression list, so no further mail is sent to it until the address is changed.

actions erforderlich Array<string>

The remedies this API offers for this state, in the order they should be presented. The list is decided by the server and a client renders it as offered — a client that decides for itself whether a re-send is possible will eventually offer one the server refuses.

Verwendet von: PATCH /invoices/{id}/finalize

PrecedingInvoiceRef object

EN 16931 BG-3 snapshot (BT-25 number / BT-26 issue date) — the original invoice a correction (invoiceTypeCode="384") restates.

Feld Typ Beschreibung
id erforderlich string (uuid)
invoiceNumber erforderlich string
issueDate erforderlich string (date)

Verwendet von: PATCH /invoices/{id}/finalize

SourceOfferRef object

The source Angebot an invoice was derived from via POST /offers/{id}/convert-to-invoice — id + number, for the invoice-detail "Entstanden aus Angebot AN-…" reference line. NOT an EN 16931 preceding-invoice reference (a quote is not an invoice).

Feld Typ Beschreibung
id erforderlich string (uuid)
offerNumber erforderlich string

Angebotsnummer (e.g. AN-00042)

Verwendet von: PATCH /invoices/{id}/finalize

CorrectionDocumentRef object

One issued correction / credit-note that references an invoice as its BG-3 preceding reference — number + UNTDID 1001 type code + issue date, so a client can label the reverse link ("storniert durch (381)" vs "korrigiert durch (384)").

Feld Typ Beschreibung
id erforderlich string (uuid)
invoiceNumber erforderlich string
invoiceTypeCode erforderlich string

UNTDID 1001

reversalScope erforderlich nur lesbar string | null

The referencing document's own reversal scope, so a client can label this reverse link with the same word that document carries on itself („Stornorechnung" when full, „Rechnungskorrektur" when partial). null for a correction ("384") and for a credit note whose scope was never recorded.

Werte: "full", "partial"
issueDate erforderlich string (date)

Verwendet von: PATCH /invoices/{id}/finalize

Party object

Seller or buyer (EN 16931 BG-4 / BG-7, simplified).

Feld Typ Beschreibung
name erforderlich string
addressLine1 string | null
postalCode string | null
city string | null
countryCode string | null

ISO 3166-1 alpha-2

vatId string | null

USt-IdNr (EN 16931 BT-31 / BT-48)

taxNumber string | null

Steuernummer (seller snapshot on invoices)

Verwendet von: PATCH /invoices/{id}/finalize

InvoiceLineItem object

A line (EN 16931 BG-25, simplified).

Feld Typ Beschreibung
id erforderlich string (uuid)
description erforderlich string
quantity erforderlich DecimalString
unitCode erforderlich string

EN 16931 BT-130 — the unit of measure of the invoiced quantity, from the UN/ECE Recommendation 20 code list with the Recommendation 21 extension (rule BR-CL-23). E.g. C62 (piece), HUR (hour), KGM.

NO PATTERN IS PUBLISHED ON THIS READ, deliberately: documents issued before the input boundary existed may carry a value outside the code list, and those documents are immutable and are streamed from the archive rather than re-rendered. Publishing a pattern here would make our own historical responses fail a client's schema validation.

unitPriceNet erforderlich DecimalString
lineNetAmount erforderlich DecimalString

Computed = quantity × unitPriceNet

taxRatePercent erforderlich DecimalString
itemDescription string | null

EN 16931 BT-154 — optional longer item description, distinct from the item name (description, BT-153). Renders as a PDF sub-line and serialises to CII ram:Description.

vatCategory string

EN 16931 BT-118 VAT category. O omitted per owner. A Kleinunternehmer seller's lines resolve to E server-side.

Werte: "S", "Z", "E", "AE", "K", "G"
Standard: "S"
vatExemptionReasonCode string | null

EN 16931 BT-121 VATEX exemption reason code (late-binding / StB-gated).

vatExemptionReasonText string | null

EN 16931 BT-120 exemption reason free-text.

Verwendet von: PATCH /invoices/{id}/finalize

InvoiceTotals object

Document totals (EN 16931 BG-22, simplified).

Feld Typ Beschreibung
totalNet DecimalString
totalTax DecimalString
totalGross DecimalString

Verwendet von: PATCH /invoices/{id}/finalize

TaxBreakdownEntry object

One VAT group (EN 16931 BG-23, simplified).

Feld Typ Beschreibung
taxRatePercent DecimalString
taxableAmount DecimalString
taxAmount DecimalString

Verwendet von: PATCH /invoices/{id}/finalize

ValidationSummary object

Feld Typ Beschreibung
verdict erforderlich string
Werte: "pass", "fail"
reports erforderlich Array<ValidationReportMeta>

Verwendet von: PATCH /invoices/{id}/finalize

Section14Summary object

The advisory §14 Abs. 4 UStG Pflichtangaben result — the three-state UI contract. Embedded inline under section14 in the POST /invoices/{id}/validate dry-run response (and the finalize/issue response) and returned by GET /invoices/{id}/section14. status is passed/has_warnings for a computed result (NEVER failed — the §14 layer never fails a document), or unavailable when the §14 layer degraded (a guard that can never affect issuance/validation).

Feld Typ Beschreibung
status erforderlich string
Werte: "passed", "has_warnings", "unavailable"
rulesetVersion erforderlich string

The versioned Section14Ruleset the result was evaluated under.

evaluatedAt erforderlich string (date-time)
items erforderlich Array<Section14Item>

Verwendet von: PATCH /invoices/{id}/finalize , POST /invoices/{id}/validate , POST /validate , GET /invoices/{id}/section14

ValidationFinding object

Feld Typ Beschreibung
ruleId erforderlich string
severity erforderlich string
Werte: "error", "warning"
message erforderlich string
location string

XPath where provided by the validator

Verwendet von: PATCH /invoices/{id}/finalize , POST /invoices/{id}/validate , POST /validate

EntitlementRequiredError object

402 body when the tenant's plan tier does not include the capability the request needs. Cleared by an upgrade, never by the caller — which is what distinguishes it from entity_limit_reached (409), a conflict with current state that the same caller clears by deactivating an entity. Nothing already issued is affected: an entitlement change never touches a document, never touches the archive and never revokes a read.

Feld Typ Beschreibung
error erforderlich string
Werte: "entitlement_required"
message string
capability erforderlich string

The capability the current plan does not include.

Werte: "live_issuance", "live_api_keys"
planTier erforderlich string
Werte: "launch", "entry", "platform", "sovereign"
upgradeUrl string

Relative dashboard hint to the plan surface.

Verwendet von: PATCH /invoices/{id}/finalize

ValidationProblem object

Feld Typ Beschreibung
type string
title string
status integer
detail string
code erforderlich string
Werte: "VALIDATION_FAILED"
findings erforderlich Array<ValidationFinding>
reportIds erforderlich Array<string (uuid)>

Ids of the persisted issue-attempt validation reports. Empty when the local preflight blocked before any KoSIT call ("fast 422, nothing consumed").

Verwendet von: PATCH /invoices/{id}/finalize , POST /invoices/{id}/validate

B2gFieldRequiredProblem object

A B2G (Behörde / XRechnung) issue or dry-run blocked SOLELY by target-conditional B2G fields (e.g. BT-10 buyer reference). A DISTINCT, stable code so the client can offer the two remedies — fill the field, or switch the invoice's target to B2B (the same fields are advisory for B2B). Emitted only when EVERY blocking finding is a B2G-conditional rule; a mixed EN-core/KoSIT failure stays VALIDATION_FAILED.

Feld Typ Beschreibung
type string
title string
status integer
detail string
code erforderlich string
Werte: "B2G_FIELD_REQUIRED"
documentTarget erforderlich string
Werte: "b2g"
missing erforderlich Array<string>

The BT/field tokens the B2G document is missing.

remedies erforderlich Array<string>
findings erforderlich Array<ValidationFinding>
reportIds erforderlich Array<string (uuid)>

Verwendet von: PATCH /invoices/{id}/finalize , POST /invoices/{id}/validate

ValidationReportMeta object

Feld Typ Beschreibung
id string (uuid)
format string
Werte: "xrechnung-cii", "xrechnung-ubl", "zugferd-cii", "section14"
formatVersion string
artifactSetId string
validatorName string
validatorVersion string
verdict string
Werte: "pass", "fail"
kind string
Werte: "issue", "issue_attempt", "preflight", "section14"
findings Array<ValidationFinding>
validatedAt string (date-time)

Verwendet von: POST /invoices/{id}/validate , POST /validate , GET /invoices/{id}/validation-reports

InvoiceSend object

One invoice send — an append-only fact recorded per (re-)send attempt. The POST /invoices/{id}/send response carries trigger:manual; GET /invoices/{id}/sends also lists auto_issue rows.

Feld Typ Beschreibung
id erforderlich string (uuid)
invoiceId erforderlich string (uuid)
invoiceNumber erforderlich string | null
toEmail erforderlich string

The recipient at send time.

subject erforderlich string

The api-rendered email subject.

bodyHtml erforderlich string

The api-rendered email body (HTML).

trigger erforderlich string

What triggered the send (a manual re-send, or auto-delivery at issue).

Werte: "manual", "auto_issue"
createdAt erforderlich string (date-time)

Verwendet von: POST /invoices/{id}/send

InvoiceSendHistoryItem object

One entry in an invoice's send history. The list carries two kinds of entry and evidence is what tells them apart: a dispatch this application performed, and a dispatch a named person recorded having performed themselves. A withdrawal of a record is a third entry referencing the one it withdraws.

Feld Typ Beschreibung
id erforderlich string (uuid)
invoiceId erforderlich string (uuid)
trigger erforderlich string
Werte: "manual", "auto_issue"
evidence erforderlich string

system_transmitted — this application composed a message and accepted it into its own outbound queue. user_asserted — a named person stated that they dispatched the document themselves. The second establishes who said it and when; it establishes nothing about whether the document arrived.

Werte: "system_transmitted", "user_asserted"
channel string | null

The medium. Absent on a withdrawal, which names none.

Werte: "email", "post", "handover", "external_email", "other"
toEmail string | null

The recipient at send time. Absent on a record and on a withdrawal.

cc Array<string> | null
bcc Array<string> | null

Blind-copy recipients, recorded permanently for the document's retention period.

sentOn string (date) | null

The date the person states they dispatched it. Present only on a record, and always alongside createdAt — the two are never merged, so a reader always sees both what was claimed and when we wrote it down.

note string | null
Länge: max. 280
reversesSendId string (uuid) | null

Set on a withdrawal; names the entry being withdrawn.

retractionReason string | null
Werte: "wrong_invoice", "not_actually_sent", "wrong_date", "duplicate_entry", "other"
retractedAt string (date-time) | null

When this entry was withdrawn. Absent while it stands.

recordedBy erforderlich object

Who the entry belongs to, resolved when the history is read rather than stored — so a corrected e-mail address repairs every historical entry at once. status says what kind of answer this is, so a caller can word every case without inventing a subject.

Felder
Feld Typ Beschreibung
userId erforderlich string (uuid) | null
name erforderlich string | null

Null when there is no name to give — an automatic delivery, or a closed account, where nothing is reconstructed.

status erforderlich string
Werte: "system", "active", "former", "erased", "unknown"
createdAt erforderlich string (date-time)

When this application wrote the entry down.

Verwendet von: GET /invoices/{id}/sends , POST /invoices/{id}/external-send , POST /invoices/{id}/sends/{sendId}/retraction

CreateInvoiceLineItem object

Feld Typ Beschreibung
description erforderlich string
quantity erforderlich QuantityString
unitCode erforderlich string

EN 16931 BT-130 — the unit of measure of the invoiced quantity. This is a CODED element: the value must come from the UN/ECE Recommendation 20 code list with the Recommendation 21 extension, as enforced by rule BR-CL-23, and NOT from your own vocabulary. Codes are UPPERCASE and are never corrected for you — "hur" is not "HUR". Common values: C62 (piece), HUR (hour), DAY, KGM (kilogram), MTR (metre), LTR (litre), MTK (square metre), KWH. The full accepted set is much wider than these examples; anything matching the published pattern and present in the code list is accepted.

ANNOUNCED NARROWING: this endpoint currently ACCEPTS values outside the code list and records them, so an existing integration keeps working. Refusal is a separate, later change. Send a listed code now; the pattern above is what will be enforced.

Länge: min. 2, max. 3
Muster: ^[A-Z0-9]{2,3}$
unitPriceNet erforderlich MoneyString
taxRatePercent erforderlich PercentString
itemDescription string

EN 16931 BT-154 — optional longer item description, distinct from the item name (description).

vatCategory string

EN 16931 BT-118. Increment 1 accepts only S from users; non-S is rejected (422). Exposure of E/AE/K/G/Z is Increment 2 (StB + Sprint-13 gated).

Werte: "S"

Verwendet von: POST /invoices , POST /invoices/preview-totals , PUT /invoices/{id} , POST /validate

TransmissionHeldReason string | null

Why the queue is holding this message back. Set only in the held state, null otherwise. A held message consumes no delivery attempt and is re-tried automatically once the condition clears.

  • Werte: "tenant_paused", "entity_cap", "domain_cap", "recipient_suppressed"

Verwendet von: POST /invoices , GET /invoices/{id} , PUT /invoices/{id} , PATCH /invoices/{id}/retry-pdf , PATCH /invoices/{id}/retry-xml , PATCH /invoices/{id}/processing-restriction , PATCH /invoices/{id}/schedule , PATCH /invoices/{id}/finalize , POST /invoices/{id}/credit-note

Section14Item object

One row of the §14 Abs. 4 UStG Pflichtangaben checklist. Advisory only — severity is warning at most, NEVER an error/blocking outcome. A not_applicable item is legitimately excused by an exemption (records exemptionRule) and is unambiguously distinct from missing, so it is never mistaken for — nor counted as — a warning. PII-free by construction (per-Nr code/status/rule/label only, never invoice contents).

Feld Typ Beschreibung
code erforderlich string

Stable machine code (primary signal), e.g. section14.abs4.nr6.

nr erforderlich integer

The §14 Abs. 4 Satz 1 Nr. (1–8) this item checks.

Bereich: ≥ 1, ≤ 8
label erforderlich string

Static German label for the Nr. (no invoice PII).

status erforderlich string
Werte: "present", "missing", "not_applicable"
severity erforderlich string | null

warning iff status=missing; otherwise null. Never error.

Werte: "warning"
exemptionRule erforderlich string | null

The statute that excused a not_applicable item (e.g. §33 UStDV, §34a UStDV, UStAE 14.5 Abs. 12); null otherwise.

message erforderlich string

The evaluator's per-Nr human hint (secondary to code) — lets a missing row render a per-Nr hint, not just the static label. PII-free by construction (no invoice contents; never the dry-run number placeholder).

Verwendet von: PATCH /invoices/{id}/finalize , POST /invoices/{id}/validate , POST /validate , GET /invoices/{id}/section14

QuantityString string

An invoiced quantity as a string, with at most four decimal places. Unlike a price, a quantity is not rounded before use — the line total is price × quantity, rounded once — so a fractional quantity such as "0.25" is ordinary and carries a wider scale than a monetary amount.

  • Muster: ^\d+(\.\d{1,4})?$

Beispiel: "0.25"

Verwendet von: POST /invoices , POST /invoices/preview-totals , PUT /invoices/{id} , POST /validate

MoneyString string

A monetary amount as a string, with at most two decimal places — the scale amounts are billed in. A finer value is refused rather than rounded: rounding it would change the number the caller sent, and because a unit price is rounded to whole cents before being multiplied by the quantity, that change is then multiplied too. A price of "12.999" at quantity 1000 billed 13000.00 against an honest 12999.00; "0.005" at the same quantity billed double.

  • Muster: ^\d+(\.\d{1,2})?$

Beispiel: "9542.50"

Verwendet von: POST /invoices , POST /invoices/preview-totals , PUT /invoices/{id} , POST /validate

PercentString string

A VAT rate as a string, with at most one decimal place — the scale the rendered document can carry. One decimal covers every German rate in use: the standard rates and the agricultural and forestry averages. A finer rate is refused rather than printed, because the document would then state a rate that does not produce the tax amount printed beside it.

  • Muster: ^\d+(\.\d{1})?$

Beispiel: "19"

Verwendet von: POST /invoices , POST /invoices/preview-totals , PUT /invoices/{id} , POST /validate

Nach oben · Alle Ressourcengruppen · OpenAPI-Spezifikation (YAML)