Angebote

Angebote erstellen, versenden, annehmen oder ablehnen und in Rechnungen umwandeln.

Create, send, accept/decline, and convert quotes (Angebote) into invoices.

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

Operationen

  1. POST /offers Create an Angebot draft
  2. GET /offers List Angebote (offset-paginated, derived expiry)
  3. GET /offers/next-number Preview the next Angebotsnummer
  4. GET /offers/{id} Get an Angebot
  5. PUT /offers/{id} Replace a draft/sent Angebot's content
  6. DELETE /offers/{id} Delete a draft Angebot
  7. GET /offers/settings Read the Angebotsnummer format
  8. PUT /offers/settings Update the Angebotsnummer format (admin)
  9. POST /offers/{id}/mark-sent Record a draft→sent transition
  10. POST /offers/{id}/accept Record the accepted outcome
  11. POST /offers/{id}/decline Record the declined outcome
  12. POST /offers/{id}/convert-to-invoice Convert an Angebot into a NEW invoice draft
  13. GET /offers/{id}/pdf Render the Angebot as a plain PDF
  14. POST /offers/{id}/send Send the Angebot as a plain-PDF email

Create an Angebot draft

POST /offers

Create a draft offer. Assigns the Angebotsnummer at creation from the mode-scoped, gap-permissible offer counter (NOT the invoice counter). Snapshots buyer + seller identity; totals are computed server-side via the SAME computeInvoice() engine invoices use (any totals in the body are ignored). Honors Idempotency-Key (a retried create is a replay). A readonly caller 403s via the scope.

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

Parameter

Parameter In Typ Beschreibung
Idempotency-Key Header string

Request-Body erforderlich

application/json CreateOffer

Antworten

  • 201

    The created draft offer (with the assigned Angebotsnummer + derived expiry).

    application/json Offer

  • 400

    Validation error, or invalid_customer (unknown customerId).

  • 403

    Caller lacks the documents:write scope.

  • 404

    Offers are not enabled — the surface is not mounted.

  • 422

    invalid_line_amount — a line has a non-positive quantity or negative price.

List Angebote (offset-paginated, derived expiry)

GET /offers

Offset-paginated list of the entity+mode-scoped offers, newest first (offer_date DESC, id DESC). Each row carries the DERIVED effectiveStatus (sent past validUntil → expired) and the signed daysUntilExpiry, computed server-side (DB clock) so web never diffs dates. status filters the STORED status; expired=1 filters the derived expired overlay.

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

Parameter

Parameter In Typ Beschreibung
page Query integer
Standard: 1
Bereich: ≥ 1
pageSize Query integer
Standard: 25
Bereich: ≥ 1, ≤ 100
status Query string

Filter by STORED status (expired is derived — use expired).

Werte: "draft", "sent", "accepted", "declined"
expired Query string

When truthy, return only the DERIVED expired set (sent + past validUntil).

Werte: "0", "1", "true", "false"
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

Antworten

  • 200

    A page of offer summaries + pagination metadata.

    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

  • 403

    Caller lacks the documents:read scope.

  • 404

    Offers are not enabled — the surface is not mounted.

Preview the next Angebotsnummer

GET /offers/next-number

The Angebotsnummer a create would assign next, WITHOUT consuming the counter (the form preview; the GET /invoices/next-number sibling).

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

Antworten

  • 200

    The next Angebotsnummer.

    application/json object

  • 403

    Caller lacks the documents:read scope.

  • 404

    Offers are not enabled — the surface is not mounted.

Get an Angebot

GET /offers/{id}

Full detail: line items, computed totals, the derived effectiveStatus/daysUntilExpiry, and the convertedInvoiceId backlink. A missing / cross-scope id returns 404 (never leaks existence).

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The offer.

    application/json Offer

  • 403

    Caller lacks the documents:read scope.

  • 404

    Offer not in scope, or offers are not enabled.

Replace a draft/sent Angebot's content

PUT /offers/{id}

Full replace of the mutable fields of a draft/sent offer; totals are recomputed. Editability is an APPLICATION-level soft freeze (NOT the 0058 GoBD trigger): an accepted/declined or already-converted offer returns 409 offer_not_editable. offer_number/status/converted_* are never changed here.

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 ReplaceOffer

Antworten

  • 200

    The updated offer.

    application/json Offer

  • 400

    Validation error, or invalid_customer.

  • 403

    Caller lacks the documents:write scope.

  • 404

    Offer not in scope, or offers are not enabled.

  • 409

    offer_not_editable — the offer is accepted/declined or already converted.

  • 422

    invalid_line_amount — a line has a non-positive quantity or negative price.

Delete a draft Angebot

DELETE /offers/{id}

Delete a draft offer (leaves a numbering gap — fine; there is no gap-free obligation on a quote). A sent/accepted/declined offer is a communicated record and returns 409 offer_not_deletable.

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 204

    Deleted.

  • 403

    Caller lacks the documents:write scope.

  • 404

    Offer not in scope, or offers are not enabled.

  • 409

    offer_not_deletable — only draft offers can be deleted.

Read the Angebotsnummer format

GET /offers/settings

The Angebotsnummer prefix + zero-padding (from the company profile), defaulting to AN-/5 when no profile exists yet. A separate config from invoice numbering.

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

Antworten

  • 200

    The Angebotsnummer format settings.

    application/json OfferSettings

  • 403

    Caller lacks the documents:read scope.

  • 404

    Offers are not enabled — the surface is not mounted.

Update the Angebotsnummer format (admin)

PUT /offers/settings

Set the Angebotsnummer prefix/padding. Requires the admin role (numbering config). The next GET /offers/next-number reflects the change. Never touches invoice numbering.

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

Request-Body erforderlich

application/json OfferSettings

Antworten

  • 200

    The updated settings.

    application/json OfferSettings

  • 400

    Validation error.

  • 403

    Caller lacks the documents:write scope or the admin role.

  • 404

    Offers are not enabled — the surface is not mounted.

  • 409

    company_profile_not_configured — configure the profile first.

Record a draft→sent transition

POST /offers/{id}/mark-sent

Record an offline/optional draft → sent transition. Idempotent from sent. Illegal from accepted/declined/converted → 409 offer_invalid_transition.

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The updated offer.

    application/json Offer

  • 403

    Caller lacks the documents:write scope.

  • 404

    Offer not in scope, or offers are not enabled.

  • 409

    offer_invalid_transition.

Record the accepted outcome

POST /offers/{id}/accept

Record the seller-observed accepted outcome. Idempotent + reversible (a mis-mark is corrected by re-marking). Legal from sent/accepted/declined; refused from draft or a converted offer → 409 offer_invalid_transition.

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The updated offer.

    application/json Offer

  • 403

    Caller lacks the documents:write scope.

  • 404

    Offer not in scope, or offers are not enabled.

  • 409

    offer_invalid_transition.

Record the declined outcome

POST /offers/{id}/decline

Record the seller-observed declined outcome. Idempotent + reversible. Refused from draft or a converted offer → 409 offer_invalid_transition.

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The updated offer.

    application/json Offer

  • 403

    Caller lacks the documents:write scope.

  • 404

    Offer not in scope, or offers are not enabled.

  • 409

    offer_invalid_transition.

Convert an Angebot into a NEW invoice draft

POST /offers/{id}/convert-to-invoice

Seeds and returns a NEW DRAFT invoice (invoiceTypeCode defaults to 380) from the offer's copied line items, buyer/seller snapshot, currency and texts, via the SAME prefill machinery as POST /invoices. Totals are recomputed server-side (equal to the offer's by construction). The offer is NOT consumed: it keeps its Angebotsnummer + lifecycle, is marked accepted/converted, and is backlinked to the derived invoice via an INTERNAL reference (source_offer_id) — NOT the EN 16931 preceding-invoice reference (a quote is not a preceding invoice). Converting an already-converted offer returns the existing invoice (200, idempotent). A draft source → 409.

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

Antworten

  • 200

    The offer was already converted — the existing invoice draft is returned.

    application/json Invoice

  • 201

    The seeded invoice draft (same shape as POST /invoices).

    application/json Invoice

  • 403

    Caller lacks the documents:write scope.

  • 404

    Offer not in scope, or offers are not enabled.

  • 409

    offer_not_convertible — the offer is still a draft.

Render the Angebot as a plain PDF

GET /offers/{id}/pdf

On-demand, non-persisted plain-business-document PDF of the CURRENT offer state (rendered as a plain business document — NO ZUGFeRD/XML, NO KoSIT/EN 16931 validation). Always correct because a quote is editable.

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The plain-PDF render.

    application/pdf string (binary)

  • 403

    Caller lacks the documents:read scope.

  • 404

    Offer not in scope, or offers are not enabled.

  • 422

    buyer_address_incomplete — the buyer address (street/postal code/city + a 2-letter country) is incomplete. Validated in api BEFORE calling the renderer, so a missing address is a clean 422 here, never an opaque 502.

  • 502

    render_unavailable — the render service failed.

Send the Angebot as a plain-PDF email

POST /offers/{id}/send

Render + freeze the per-send artifact, then in ONE transaction append the immutable immutable offer-send record + enqueue the an outbound-mail record of kind offer row (fetch-and-attach; the mail dispatcher defers if the bytes are not yet landed) + flip a draft offer to sent. Refuses (writing nothing) for a terminal/converted offer (409), no recipient (422 no_recipient_email), or no SMTP configured (422); a render failure aborts before any write (502). Honors Idempotency-Key (double-submit backstop). A re-send writes a NEW immutable send row.

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

Antworten

  • 201

    The created send.

    application/json OfferSend

  • 403

    Caller lacks the documents:write scope.

  • 404

    Offer not in scope, or offers are not enabled.

  • 409

    offer_not_sendable — the offer is terminal/converted.

  • 422

    buyer_address_incomplete (the buyer address is incomplete — street/postal code/city + a 2-letter country; validated in api before render, so it is a clean 422, never a 502), no_recipient_email (no email on file), email_settings_not_configured, 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 these.

  • 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

  • 502

    render_unavailable — the render service failed.

Datenmodelle

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

CreateOffer object

Create payload. NOTE: no totals — the server computes them (any totals in the body are ignored). The quote number is assigned by the server at creation. On CREATE only, the title, the body text and the note fall back to the company-wide defaults when you omit them; on the replace (see ReplaceOffer) they do not.

Feld Typ Beschreibung
currency erforderlich string

ISO 4217 (3 letters)

customerId string (uuid)

Optional link to a saved customer; buyer is still snapshotted.

buyer erforderlich Party
seller Party

Omitted, the seller identity is taken from the company profile as it stands at the moment of the save. That applies to a replace as well, so send the seller you want recorded if it must not follow later changes to the company profile.

buyerReference string
buyerElectronicAddress string
buyerEmail string (email)
offerDate string (date)

Quote date. On a create it defaults to today. On a replace it is REQUIRED — see ReplaceOffer.

validUntil string (date) | null

"Gültig bis"; omit or null = no deadline ("ohne Frist").

title string | null

Quote title / subject. On a create, omitting it takes the company-wide default title, while an explicit null means "none — and do not apply the default". On a replace, omitting it stores nothing — that is how a title is cleared.

text string | null

Body text. On a create, omitting it takes the company-wide default, while an explicit null means "none — and do not apply the default". On a replace, omitting it stores nothing.

notes string | null

Note. On a create, omitting it takes the company-wide default, while an explicit null means "none — and do not apply the default". On a replace, omitting it stores nothing — deleting the note and saving clears it, and the company default does not come back.

bindingExclusionNote string
lineItems erforderlich Array<CreateOfferLineItem>
Elemente: min. 1, max. 1000

Verwendet von: POST /offers

Offer object

Feld Typ Beschreibung
id erforderlich string (uuid)
offerNumber erforderlich string
status erforderlich OfferStatus
effectiveStatus erforderlich OfferEffectiveStatus
offerDate erforderlich string (date)
validUntil erforderlich string (date) | null

"Gültig bis"; null = "ohne Frist" (no deadline).

daysUntilExpiry erforderlich integer | null

Signed whole-days from CURRENT_DATE (DB clock) to validUntil: positive = future, 0 = today, negative = past. null when validUntil is null. Computed server-side so web never diffs dates.

currency erforderlich string
customerId erforderlich string (uuid) | null
buyerName erforderlich string
totalGross erforderlich DecimalString
convertedInvoiceId erforderlich string (uuid) | null

The INTERNAL backlink to the derived invoice; null until converted.

mode string
Werte: "live", "test"
seller Party
buyer Party
buyerReference string | null
buyerElectronicAddress string | null
buyerEmail string | null
title string | null
text string | null
notes string | null
bindingExclusionNote string | null

Optional civil-law "freibleibend/unverbindlich" wording (§145 BGB).

totals InvoiceTotals
lineItems Array<OfferLineItem>
convertedAt string (date-time) | null
createdAt string (date-time)
updatedAt string (date-time)

Verwendet von: POST /offers , GET /offers/{id} , PUT /offers/{id} , POST /offers/{id}/mark-sent , POST /offers/{id}/accept , POST /offers/{id}/decline

OfferSummary object

One row of the offers list — expiry fields are server-derived (no client computation).

Feld Typ Beschreibung
id erforderlich string (uuid)
offerNumber erforderlich string
status erforderlich OfferStatus
effectiveStatus erforderlich OfferEffectiveStatus
offerDate erforderlich string (date)
validUntil erforderlich string (date) | null

"Gültig bis"; null = "ohne Frist" (no deadline).

daysUntilExpiry erforderlich integer | null

Signed whole-days from CURRENT_DATE (DB clock) to validUntil: positive = future, 0 = today, negative = past. null when validUntil is null. Computed server-side so web never diffs dates.

currency erforderlich string
customerId erforderlich string (uuid) | null
buyerName erforderlich string
totalGross erforderlich DecimalString
convertedInvoiceId erforderlich string (uuid) | null

The INTERNAL backlink to the derived invoice; null until converted.

mode string
Werte: "live", "test"

Verwendet von: GET /offers

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 /offers

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 /offers

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 /offers

ReplaceOffer object

Replace payload for a quote. Identical to the create payload with one difference: the quote date is REQUIRED. This body describes the whole quote, so an omitted property states that the property has no value — and a quote cannot have no date. The free-text title, body and note therefore CLEAR when omitted here; they do not fall back to the company-wide defaults the way they do on a create.

Feld Typ Beschreibung
currency erforderlich string

ISO 4217 (3 letters)

customerId string (uuid)

Optional link to a saved customer; buyer is still snapshotted.

buyer erforderlich Party
seller Party

Omitted, the seller identity is taken from the company profile as it stands at the moment of the save. That applies to a replace as well, so send the seller you want recorded if it must not follow later changes to the company profile.

buyerReference string
buyerElectronicAddress string
buyerEmail string (email)
offerDate erforderlich string (date)

Quote date. On a create it defaults to today. On a replace it is REQUIRED — see ReplaceOffer.

validUntil string (date) | null

"Gültig bis"; omit or null = no deadline ("ohne Frist").

title string | null

Quote title / subject. On a create, omitting it takes the company-wide default title, while an explicit null means "none — and do not apply the default". On a replace, omitting it stores nothing — that is how a title is cleared.

text string | null

Body text. On a create, omitting it takes the company-wide default, while an explicit null means "none — and do not apply the default". On a replace, omitting it stores nothing.

notes string | null

Note. On a create, omitting it takes the company-wide default, while an explicit null means "none — and do not apply the default". On a replace, omitting it stores nothing — deleting the note and saving clears it, and the company default does not come back.

bindingExclusionNote string
lineItems erforderlich Array<CreateOfferLineItem>
Elemente: min. 1, max. 1000

Verwendet von: PUT /offers/{id}

OfferSettings object

The Angebotsnummer format. Separate from invoice numbering.

Feld Typ Beschreibung
offerNumberPrefix erforderlich string
offerNumberPadding erforderlich integer
Bereich: ≥ 1, ≤ 12

Verwendet von: GET /offers/settings , PUT /offers/settings

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 /offers/{id}/convert-to-invoice

OfferSend object

One Angebot send — an append-only fact with a frozen artifact.

Feld Typ Beschreibung
id erforderlich string (uuid)
offerId erforderlich string (uuid)
offerNumber erforderlich string
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).

artifactKey erforderlich string

Deterministic object key of the frozen per-send PDF.

createdAt erforderlich string (date-time)

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

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: POST /offers , GET /offers/{id} , PUT /offers/{id} , POST /offers/{id}/mark-sent , POST /offers/{id}/accept , POST /offers/{id}/decline , POST /offers/{id}/convert-to-invoice

CreateOfferLineItem object

A quote line as written. A key this schema does not list (for example vatCategory) is refused with 400 validation_error; the issue's path is lineItems.N.<key>.

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

EN 16931 BT-130 — the unit of measure of the quoted 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, BT-153). The same field and bound as CreateInvoiceLineItem.itemDescription, because a quote line becomes an invoice line on conversion. Leading and trailing whitespace is trimmed. Omitting it on a replace (PUT /offers/{id}) stores no description for that line.

Länge: max. 1000

Verwendet von: POST /offers , PUT /offers/{id}

OfferStatus string

The STORED lifecycle status. expired is DERIVED (never stored); converted is not a status (it is convertedInvoiceId != null).

  • Werte: "draft", "sent", "accepted", "declined"

Verwendet von: POST /offers , GET /offers , GET /offers/{id} , PUT /offers/{id} , POST /offers/{id}/mark-sent , POST /offers/{id}/accept , POST /offers/{id}/decline

OfferEffectiveStatus string

The DERIVED effective status: the stored status, except a sent offer whose validUntil is in the past surfaces as expired.

  • Werte: "draft", "sent", "accepted", "declined", "expired"

Verwendet von: POST /offers , GET /offers , GET /offers/{id} , PUT /offers/{id} , POST /offers/{id}/mark-sent , POST /offers/{id}/accept , POST /offers/{id}/decline

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: POST /offers , GET /offers , GET /offers/{id} , PUT /offers/{id} , POST /offers/{id}/mark-sent , POST /offers/{id}/accept , POST /offers/{id}/decline , POST /offers/{id}/convert-to-invoice

InvoiceTotals object

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

Feld Typ Beschreibung
totalNet DecimalString
totalTax DecimalString
totalGross DecimalString

Verwendet von: POST /offers , GET /offers/{id} , PUT /offers/{id} , POST /offers/{id}/mark-sent , POST /offers/{id}/accept , POST /offers/{id}/decline , POST /offers/{id}/convert-to-invoice

OfferLineItem object

A quote line (invoice-item shape, verbatim; snapshot-on-insert).

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

EN 16931 BT-130 — the unit of measure of the quoted 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: quotes stored before this input boundary existed may carry a value outside the code list, and they are returned exactly as they were saved.

unitPriceNet erforderlich DecimalString
taxRatePercent erforderlich DecimalString
lineNetAmount erforderlich DecimalString

Computed = quantity × unitPriceNet

itemDescription string

EN 16931 BT-154 — optional longer item description, distinct from the item name (description, BT-153), returned exactly as stored. Absent when the line has none. Renders as a sub-line on the quote PDF and is carried onto the invoice line when the quote is converted.

Länge: max. 1000

Verwendet von: POST /offers , GET /offers/{id} , PUT /offers/{id} , POST /offers/{id}/mark-sent , POST /offers/{id}/accept , POST /offers/{id}/decline

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: POST /offers/{id}/convert-to-invoice

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: POST /offers/{id}/convert-to-invoice

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: POST /offers/{id}/convert-to-invoice

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: POST /offers/{id}/convert-to-invoice

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: POST /offers/{id}/convert-to-invoice

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: POST /offers/{id}/convert-to-invoice

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: POST /offers/{id}/convert-to-invoice

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: POST /offers/{id}/convert-to-invoice

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: POST /offers/{id}/convert-to-invoice

TaxBreakdownEntry object

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

Feld Typ Beschreibung
taxRatePercent DecimalString
taxableAmount DecimalString
taxAmount DecimalString

Verwendet von: POST /offers/{id}/convert-to-invoice

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 /offers , PUT /offers/{id}

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 /offers , PUT /offers/{id}

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 /offers , PUT /offers/{id}

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 /offers/{id}/convert-to-invoice

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