Angebote
Angebote erstellen, versenden, annehmen oder ablehnen und in Rechnungen umwandeln.
Create, send, accept/decline, and convert quotes (Angebote) into invoices.
Operationen
-
POST/offersCreate an Angebot draft -
GET/offersList Angebote (offset-paginated, derived expiry) -
GET/offers/next-numberPreview the next Angebotsnummer -
GET/offers/{id}Get an Angebot -
PUT/offers/{id}Replace a draft/sent Angebot's content -
DELETE/offers/{id}Delete a draft Angebot -
GET/offers/settingsRead the Angebotsnummer format -
PUT/offers/settingsUpdate the Angebotsnummer format (admin) -
POST/offers/{id}/mark-sentRecord a draft→sent transition -
POST/offers/{id}/acceptRecord the accepted outcome -
POST/offers/{id}/declineRecord the declined outcome -
POST/offers/{id}/convert-to-invoiceConvert an Angebot into a NEW invoice draft -
GET/offers/{id}/pdfRender the Angebot as a plain PDF -
POST/offers/{id}/sendSend 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
-
201The created draft offer (with the assigned Angebotsnummer + derived expiry).
application/jsonOffer -
400Validation error, or
invalid_customer(unknown customerId). -
403Caller lacks the
documents:writescope. -
404Offers are not enabled — the surface is not mounted.
-
422invalid_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 ( 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
-
200A page of offer summaries + pagination metadata.
application/jsonobject -
400The query string was refused. Three distinguishable facts share this status, and a client should branch on the
errorcode rather than on the status:unknown_query_parameter— the request named one or more query parameters this deployment does not define, andparameterslists 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 answered200 OKwith 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/jsonUnknownQueryParameterError | ValidationError -
403Caller lacks the
documents:readscope. -
404Offers 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
-
200The next Angebotsnummer.
application/jsonobject -
403Caller lacks the
documents:readscope. -
404Offers 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
-
200The offer.
application/jsonOffer -
403Caller lacks the
documents:readscope. -
404Offer 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
-
200The updated offer.
application/jsonOffer -
400Validation error, or
invalid_customer. -
403Caller lacks the
documents:writescope. -
404Offer not in scope, or offers are not enabled.
-
409offer_not_editable— the offer is accepted/declined or already converted. -
422invalid_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
-
204Deleted.
-
403Caller lacks the
documents:writescope. -
404Offer not in scope, or offers are not enabled.
-
409offer_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
-
200The Angebotsnummer format settings.
application/jsonOfferSettings -
403Caller lacks the
documents:readscope. -
404Offers 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
-
200The updated settings.
application/jsonOfferSettings -
400Validation error.
-
403Caller lacks the
documents:writescope or theadminrole. -
404Offers are not enabled — the surface is not mounted.
-
409company_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
-
200The updated offer.
application/jsonOffer -
403Caller lacks the
documents:writescope. -
404Offer not in scope, or offers are not enabled.
-
409offer_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
-
200The updated offer.
application/jsonOffer -
403Caller lacks the
documents:writescope. -
404Offer not in scope, or offers are not enabled.
-
409offer_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
-
200The updated offer.
application/jsonOffer -
403Caller lacks the
documents:writescope. -
404Offer not in scope, or offers are not enabled.
-
409offer_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
-
200The offer was already converted — the existing invoice draft is returned.
application/jsonInvoice -
201The seeded invoice draft (same shape as POST /invoices).
application/jsonInvoice -
403Caller lacks the
documents:writescope. -
404Offer not in scope, or offers are not enabled.
-
409offer_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
-
200The plain-PDF render.
application/pdfstring (binary) -
403Caller lacks the
documents:readscope. -
404Offer not in scope, or offers are not enabled.
-
422buyer_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. -
502render_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
-
201The created send.
application/jsonOfferSend -
403Caller lacks the
documents:writescope. -
404Offer not in scope, or offers are not enabled.
-
409offer_not_sendable— the offer is terminal/converted. -
422buyer_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, orsandbox_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. -
429sandbox_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-Aftergives the seconds until then andlimitthe number. E-mails sent through the company's own mail server are not counted.-
Retry-AfterintegerSeconds until the next business day starts (Europe/Berlin midnight).
application/jsonobject -
-
502render_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 |
text | string | null | Body text. On a create, omitting it takes the company-wide default, while an explicit |
notes | string | null | Note. On a create, omitting it takes the company-wide default, while an explicit |
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 |
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 |
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
|
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 |
text | string | null | Body text. On a create, omitting it takes the company-wide default, while an explicit |
notes | string | null | Note. On a create, omitting it takes the company-wide default, while an explicit |
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 | |||||||||
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 | ⚠ 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. Werte: "cancelled", "corrected" | |||||||||
transmissionState | TransmissionState | ||||||||||
transmissionProblemReason | TransmissionProblemReason | ||||||||||
transmissionLastEventAt | string (date-time) | null | When we learned the 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; | |||||||||
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
| |||||||||
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 ( | |||||||||
reversalScope nur lesbar | string | null | Derived, read-only: whether a credit note ( Werte: "full", "partial" | |||||||||
precedingInvoice | PrecedingInvoiceRef | null | EN 16931 BG-3 (BT-25 number / BT-26 issue date). Set only on a correction ( | |||||||||
correctedBy | Array<string (uuid)> | Derived, read-only: ids of correction invoices ( | |||||||||
sourceOfferId | string (uuid) | null | The INTERNAL backlink to the source Angebot this invoice was derived from ( | |||||||||
sourceOffer | SourceOfferRef | null | The reverse projection of | |||||||||
correctionDocuments | Array<CorrectionDocumentRef> | The reverse-link LABEL projection — the ISSUED/paid corrections ( | |||||||||
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 | |||||||||
isDauerrechnung | boolean | The Dauerrechnung marker — an ORTHOGONAL invoice-level flag, NOT a new | |||||||||
dauerrechnungState | string | null | DERIVED, read-only standing state. 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 | |||||||||
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 ( 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 ( 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 |
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
| ||||||||||||
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). 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 ( |
vatCategory | string | EN 16931 BT-118 VAT category. 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)