Kunden & Artikel

Kundenstamm und den wiederverwendbaren Positionskatalog mit Kundenpreisen verwalten.

Manage customers and the reusable line-item catalogue (positions).

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

Operationen

  1. GET /customers List customers (master data), ordered by customer number (paginated)
  2. POST /customers Create a customer (customerNumber is assigned by the server)
  3. GET /customers/{id} Get a customer by id
  4. PUT /customers/{id} Update a customer (full replace of editable fields)
  5. DELETE /customers/{id} Delete a customer (linked invoices keep their snapshot)
  6. GET /customers/{id}/positions This customer's COMPLETE set of position assignments (Zuordnungen)
  7. PUT /customers/{id}/positions Replace this customer's COMPLETE set of position assignments (Zuordnungen)
  8. GET /positions List the position catalog (Positionsvorlagen), ordered by description (paginated)
  9. POST /positions Create a position
  10. GET /positions/{id} Get a position by id (with its customer assignments)
  11. PUT /positions/{id} Update a position (full replace of editable fields)
  12. DELETE /positions/{id} Delete a position (ALWAYS succeeds — never 409)
  13. GET /customers/{id}/open-items Per-customer open-items statement (Kontoauszug / Offene Posten)

List customers (master data), ordered by customer number (paginated)

GET /customers

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

Parameter

Parameter In Typ Beschreibung
page Query integer

Page number (1-indexed).

Standard: 1
Bereich: ≥ 1
pageSize Query integer

Number of items per page.

Standard: 25
Bereich: ≥ 1, ≤ 100
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

    Paginated list of customers

    application/json object

  • 400

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

    application/json UnknownQueryParameterError | ValidationError

Create a customer (customerNumber is assigned by the server)

POST /customers

Honors the optional Idempotency-Key header so a retried creation is a replay, not a duplicate.

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

Parameter

Parameter In Typ Beschreibung
Idempotency-Key Header string

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

Länge: max. 255

Request-Body erforderlich

application/json CreateCustomer

Antworten

  • 201

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

    application/json Customer

  • 400

    Validation error

    application/json ValidationError

  • 409

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

    application/json object

Get a customer by id

GET /customers/{id}

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The customer

    application/json Customer

  • 404

    Not found

Update a customer (full replace of editable fields)

PUT /customers/{id}

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Request-Body erforderlich

application/json ReplaceCustomer

Antworten

  • 200

    The updated customer

    application/json Customer

  • 400

    Validation error

    application/json ValidationError

  • 404

    Not found

Delete a customer (linked invoices keep their snapshot)

DELETE /customers/{id}

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 204

    Deleted

  • 404

    Not found

This customer's COMPLETE set of position assignments (Zuordnungen)

GET /customers/{id}/positions

The read counterpart of the whole-set PUT below, and the ONLY sanctioned source for that PUT's body. COMPLETE BY CONTRACT. This response is never paginated and never truncated: there is no page/pageSize/cursor/limit parameter, and assignments.length IS the total. Should the stored set ever exceed the SAME ceiling the sibling PUT enforces on its body (ReplaceCustomerPositions.assignments.maxItems — one constant in code, not two numbers), this endpoint returns 409 assignment_set_too_large rather than a partial body. A client that checks nothing still cannot obtain a truncated set. Why it exists (and see the PUT, whose earlier reasoning this supersedes): deriving the assignment set by filtering a page of GET /positions is unsafe for ANY page size, because the PUT is a whole-set replace and an unloaded assignment reads as a deletion. Each entry embeds the position fields the Zuordnungen table renders (Bezeichnung, Katalogpreis, Einheit, Waehrung) so the screen needs no second lookup — but deliberately NOT the position's OTHER customers (Position.assignments): this is a read about ONE customer and must not return every other customer's name and price. Ordered by the position's description (the catalog's own order), with positionId as the deterministic tiebreak. Entity- and mode-scoped.

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The complete assignment set (an empty array when none).

    application/json CustomerPositionAssignments

  • 403

    Caller lacks the customers:read scope.

  • 404

    Customer not found. A customer with NO assignments is 200 with an empty array — never 404.

  • 409

    assignment_set_too_large — the stored set exceeds the contract ceiling, so a COMPLETE response cannot be returned and a partial one is refused. Unreachable through this API (the PUT caps what can be written); it exists so the completeness guarantee is enforced rather than assumed.

Replace this customer's COMPLETE set of position assignments (Zuordnungen)

PUT /customers/{id}/positions

Whole-set replace: the body IS the resulting assignment set. Any assignment not present in assignments is REMOVED. An empty array is valid and clears all of this customer's assignments. PUT (not PATCH/POST) because the request describes the complete desired state of a set, which is exactly PUT's semantics. This shape is chosen deliberately over per-row create/update/delete calls: the customer edit page is a single table with a single "Zuordnungen speichern" button, so one atomic call means one validation path and no partial-failure states to design a UI for. The write runs in a transaction — it either applies completely or not at all. It is scoped under the customer, not the position, because the assignment (and its price override, the "Kundenpreis") is a fact ABOUT THE CUSTOMER and is always read per customer. SUPERSEDED 2026-08-09. This description used to state that there was deliberately NO GET counterpart, because "the customer edit page must load the full catalog anyway, so a dedicated GET would be a second shape for data the client necessarily already holds." THE PREMISE WAS FALSE, AND ITS FALSENESS WAS THIS DEFECT. GET /positions is capped at pageSize 100 — a HARD maximum in the shared pagination schema, not a call-site choice — while this write accepts 500 assignments. So for any catalog above 100 entries the client did NOT hold the data: it silently omitted the unloaded assignments from this whole-set body, and this endpoint deleted them. No error, no warning, and the record read as if the user had removed them. The read counterpart is GET /customers/{id}/positions, and it is COMPLETE BY CONTRACT — never paginated, never truncated, 409 rather than a partial body. Build this PUT's body from that read. Never from a page of GET /positions, at any page size.

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Request-Body erforderlich

application/json ReplaceCustomerPositions

Antworten

  • 200

    The saved assignment set

    application/json object

  • 400

    Validation error, or invalid_position when a positionId is unknown.

    application/json ValidationError

  • 404

    Customer not found

List the position catalog (Positionsvorlagen), ordered by description (paginated)

GET /positions

Returns one PAGE of the catalog, each position carrying its customer assignments embedded (assignments). This is deliberate and is the endpoint's main design constraint: the invoice form must be able to resolve ANY customer's assigned positions and their effective unit price with ZERO extra network requests when the user changes the customer (the customer is chosen client-side inside an already-rendered form). Effective price for a customer = assignments[].unitPriceNetOverride when that customer has an assignment with a non-null override, else the position's own unitPriceNet (the Katalogpreis). assignments.length is also the "Kunden" count on the catalog list page — no separate aggregate endpoint is needed. Assignments are scoped to the positions on the current page only. The underlying implementation uses 2 constant queries per request. Sized for the target scale (tens of positions).

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

Parameter

Parameter In Typ Beschreibung
page Query integer

Page number (1-indexed).

Standard: 1
Bereich: ≥ 1
pageSize Query integer

Number of items per page.

Standard: 25
Bereich: ≥ 1, ≤ 100

Antworten

  • 200

    Paginated catalog, sorted by description, assignments embedded

    application/json object

  • 400

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

    application/json UnknownQueryParameterError | ValidationError

Create a position

POST /positions

Honors the optional Idempotency-Key header so a retried creation is a replay, not a duplicate.

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

Parameter

Parameter In Typ Beschreibung
Idempotency-Key Header string

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

Länge: max. 255

Request-Body erforderlich

application/json CreatePosition

Antworten

  • 201

    The created position (with an empty assignments array; or, on an Idempotency-Key replay, the recorded response)

    application/json Position

  • 400

    Validation error

    application/json ValidationError

  • 409

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

    application/json object

Get a position by id (with its customer assignments)

GET /positions/{id}

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 200

    The position

    application/json Position

  • 404

    Not found

Update a position (full replace of editable fields)

PUT /positions/{id}

Does NOT affect any existing invoice or subscription template. Those hold SNAPSHOTS of the values they were created with, never a reference to this row. Assignments are not touched here — replace them via PUT /customers/{id}/positions.

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Request-Body erforderlich

application/json ReplacePosition

Antworten

  • 200

    The updated position

    application/json Position

  • 400

    Validation error

  • 404

    Not found

Delete a position (ALWAYS succeeds — never 409)

DELETE /positions/{id}

There is NO "in use" check and no 409 by design. Existing invoices and subscription templates snapshot their line items, so no document can reference this row and nothing can be broken by removing it. Only the customer assignments (and their price overrides) cascade away.

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

Antworten

  • 204

    Deleted

  • 404

    Not found

Per-customer open-items statement (Kontoauszug / Offene Posten)

GET /customers/{id}/open-items

For one customer, every OPEN invoice — status='issued' AND payment_state <> 'paid', INCLUDING not-yet-due invoices (the all-open sibling of GET /dunning/overdue, which returns only the overdue subset). Per row, server-computed (never re-derived in web): effective due date, openAmountMinor (= totalGross − amountPaid − the same-currency partial credit notes), a due/overdue flag + daysOverdue, the current Mahnstufe, and the dunning nextAction descriptor — plus customer-level rollup totals (open + overdue-only subtotal). Bounded (a single customer's open set is small), not cursor-paginated. Mode-scoped. DEGRADES GRACEFULLY where dunning is enabledis off:dunningEnabled=falseand thenextAction/currentLevel*fields arenull`, while open amounts + due/overdue are still returned (never a 404 on the flag).

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

Parameter

Parameter In Typ Beschreibung
id erforderlich Pfad string (uuid)

The customer whose open items are returned.

Antworten

  • 200

    The customer's open-items statement (all fields server-derived).

    application/json CustomerOpenItems

  • 400

    id is not a UUID.

  • 403

    Caller lacks the documents:read scope.

Datenmodelle

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

Customer object

Customer master data (invoicing essentials only; GDPR-minimized).

Feld Typ Beschreibung
id string (uuid)
customerNumber string

Auto-assigned Kundennummer, e.g. "K-00001". Counted per company and per mode: each company's live customers and its sandbox customers each run their own series from K-00001, so the same number can belong to a different customer in another company or in the other mode. Unique within one company and mode; gaps are possible (a deleted customer's number is not reused).

name string
addressLine1 string | null
postalCode string | null
city string | null
countryCode string | null

ISO 3166-1 alpha-2

vatId string | null

USt-IdNr

email string (email) | null

Recipient email for invoice delivery

defaultBuyerReference string | null
defaultElectronicAddress string | null

EN 16931 BT-49 default for this customer — a Leitweg-ID or an email, prefilled onto new invoices and subscriptions. Bare value only: the scheme (0204 / EM) is derived at XRechnung generation time.

Länge: max. 200
documentTarget string

The customer's document-target intent (B2B / B2G), snapshotted onto every new invoice/subscription for this customer. Always present (defaults to 'b2b').

Werte: "b2b", "b2g"
ccEmails Array<string (email)> | null

Standing copy recipients („Kopie an"): every automatic invoice e-mail to this customer is also sent to these addresses, visibly in the Cc header. The invoice itself and its recipient are not affected. null when there are none, never an empty list.

Elemente: max. 5
bccEmails Array<string (email)> | null

Standing blind copy recipients („Blindkopie an"): every automatic invoice e-mail to this customer is also delivered to these addresses without naming them in any header. null when there are none, never an empty list.

Elemente: max. 5
capabilities CustomerCapabilities
restrictedDocumentCount integer

DERIVED rollup — count of this customer's documents currently DSGVO Art. 18 processing-restricted. Present only on the single-customer detail read (GET /customers/{id}), not the list. The invoice remains the single source of truth.

suppressedAddress object | null

Set when this customer's e-mail address is on the account's suppression list, because earlier mail to it was permanently refused by the receiving mail system. While it is set, no further mail is sent to that address; the remedy is to record a different address on this customer. Present on the single-customer detail read only — an address is not a list-scan fact and does not belong in a table. null when the address is not suppressed.

Felder
Feld Typ Beschreibung
address erforderlich string
suppressedAt erforderlich string (date-time)
reason erforderlich string

A coarse class, never the receiving mail system's own diagnostic text.

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

Verwendet von: GET /customers , POST /customers , GET /customers/{id} , PUT /customers/{id}

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 /customers , GET /positions

CustomerCapabilities object

What this deployment offers on the customer record. Present on the single-customer read and on the list envelope.

Feld Typ Beschreibung
copyRecipients erforderlich boolean

Whether ccEmails and bccEmails are accepted on create and replace, and applied to automatic invoice delivery.

Verwendet von: GET /customers

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 /customers , GET /positions

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 /customers , POST /customers , PUT /customers/{id} , PUT /customers/{id}/positions , GET /positions , POST /positions

CreateCustomer object

Create payload. customerNumber is NOT accepted — the server assigns it. On CREATE only, omitting the public-sector/business target records an ordinary business customer; on the replace it is REQUIRED (see ReplaceCustomer).

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

ISO 3166-1 alpha-2 (2 letters)

vatId string
email string (email)

Recipient email for invoice delivery

defaultBuyerReference string
defaultElectronicAddress string

EN 16931 BT-49 default — a Leitweg-ID or an email, prefilled onto new invoices and subscriptions. Send the bare value; the scheme (0204 / EM) is derived at XRechnung generation time.

Länge: max. 200
documentTarget string

Whether this customer is a public authority (b2g) or an ordinary business (b2b). It decides whether their invoices are later produced in the format public authorities require. On a CREATE, omitting it records an ordinary business customer. On the REPLACE it is REQUIRED — see ReplaceCustomer. The same property is KEPT rather than reset on the invoice and recurring-template replaces, which is why it is described here per property.

Werte: "b2b", "b2g"
ccEmails Array<string (email)> | null

Standing copy recipients („Kopie an") for every automatic invoice e-mail to this customer. Each entry is one plain address with no display name; the domain is lower-cased and the local part kept as sent. An address equal to email is dropped silently, and an address listed in both ccEmails and bccEmails is kept only here. At most 5 entries, else 400 validation_error naming ccEmails with code: too_many_addresses; an entry that is not one valid address is refused with code: invalid_address. An empty list or null stores none. Copies require email: a list without it is refused at email with code: copies_require_email. Accepted only where capabilities.copyRecipients is true; elsewhere a non-empty list is refused and stored lists are left unchanged.

Elemente: max. 5
bccEmails Array<string (email)> | null

Standing blind copy recipients („Blindkopie an"), under the same rules as ccEmails. A blind copy appears in no header of the sent message. Each automatic send records its blind copies permanently in the invoice's send history.

Elemente: max. 5

Verwendet von: POST /customers

ReplaceCustomer object

Replace payload for a customer. Identical to the create payload with one difference: the public-sector/business target is REQUIRED. This body describes the whole customer, so an omitted property states that the property has no value — and a customer is always one or the other, so there is nothing for an omission to mean. Until this became required, omitting it stored 'b2b', which silently converted a public authority into an ordinary business. The same whole-object rule applies to ccEmails and bccEmails: omitting a list clears the stored copy recipients, so a client must send back the lists it read.

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

ISO 3166-1 alpha-2 (2 letters)

vatId string
email string (email)

Recipient email for invoice delivery

defaultBuyerReference string
defaultElectronicAddress string

EN 16931 BT-49 default — a Leitweg-ID or an email, prefilled onto new invoices and subscriptions. Send the bare value; the scheme (0204 / EM) is derived at XRechnung generation time.

Länge: max. 200
documentTarget erforderlich string

Whether this customer is a public authority (b2g) or an ordinary business (b2b). It decides whether their invoices are later produced in the format public authorities require. On a CREATE, omitting it records an ordinary business customer. On the REPLACE it is REQUIRED — see ReplaceCustomer. The same property is KEPT rather than reset on the invoice and recurring-template replaces, which is why it is described here per property.

Werte: "b2b", "b2g"
ccEmails Array<string (email)> | null

Standing copy recipients („Kopie an") for every automatic invoice e-mail to this customer. Each entry is one plain address with no display name; the domain is lower-cased and the local part kept as sent. An address equal to email is dropped silently, and an address listed in both ccEmails and bccEmails is kept only here. At most 5 entries, else 400 validation_error naming ccEmails with code: too_many_addresses; an entry that is not one valid address is refused with code: invalid_address. An empty list or null stores none. Copies require email: a list without it is refused at email with code: copies_require_email. Accepted only where capabilities.copyRecipients is true; elsewhere a non-empty list is refused and stored lists are left unchanged.

Elemente: max. 5
bccEmails Array<string (email)> | null

Standing blind copy recipients („Blindkopie an"), under the same rules as ccEmails. A blind copy appears in no header of the sent message. Each automatic send records its blind copies permanently in the invoice's send history.

Elemente: max. 5

Verwendet von: PUT /customers/{id}

CustomerPositionAssignments object

One customer's COMPLETE set of position assignments (Zuordnungen). NOT paginated: assignments.length is the total. See GET /customers/{id}/positions for the completeness contract.

Feld Typ Beschreibung
customerId erforderlich string (uuid)
assignments erforderlich Array<CustomerPositionAssignmentDetail>

Verwendet von: GET /customers/{id}/positions

ReplaceCustomerPositions object

The COMPLETE desired assignment set for one customer. Anything absent is removed. An empty array clears all assignments. Duplicate positionId values are rejected (the resulting override would be ambiguous).

Feld Typ Beschreibung
assignments erforderlich Array<object>
Elemente: max. 500
Felder
Feld Typ Beschreibung
positionId erforderlich string (uuid)
unitPriceNetOverride DecimalString | null

Kundenpreis. Omit or send null for "no override" — the catalog price applies. Clearing the field in the UI IS the reset.

Verwendet von: PUT /customers/{id}/positions

CustomerPositionAssignment object

One saved assignment, as returned by PUT /customers/{id}/positions.

Feld Typ Beschreibung
positionId erforderlich string (uuid)
unitPriceNetOverride erforderlich DecimalString | null

null = no override = use the catalog price.

Verwendet von: PUT /customers/{id}/positions

Position object

One catalog entry ("Positionsvorlage") — a reusable invoice line item. SNAPSHOT SEMANTICS: inserting a position onto an invoice or subscription COPIES its values into the line item. No invoice line ever holds a reference back to a position, and the invoice create/update payload is completely unchanged by this feature. Editing or deleting a position therefore cannot alter any existing document.

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

Bezeichnung. Copied verbatim into the line item's description.

unitCode erforderlich string

EN 16931 BT-130 — the unit of measure this entry carries onto an invoice line, 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: catalogue entries saved 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

Katalogpreis — the fallback unit price. NUMERIC(15,4), as on the line item.

taxRatePercent erforderlich DecimalString
currency erforderlich string

ISO 4217. The client filters the picker to positions matching the invoice's currency; the api only stores and returns it.

defaultQuantity erforderlich DecimalString | null

Standardmenge, prefilled as the line's quantity on insert. null = NOT SET — the client falls back to "1".

assignments erforderlich Array<PositionAssignment>

Customers this position is assigned to. Always present (empty array when unassigned), on both the list and the detail endpoint.

createdAt erforderlich string (date-time)
updatedAt erforderlich string (date-time)
mode Mode

Verwendet von: GET /positions , POST /positions , GET /positions/{id} , PUT /positions/{id}

CreatePosition object

Create payload. Assignments are NOT part of this body — they are replaced per customer via PUT /customers/{id}/positions. On CREATE only, omitting the currency records EUR; on the replace it is REQUIRED (see ReplacePosition).

Feld Typ Beschreibung
description erforderlich string
Länge: min. 1, max. 500
unitCode erforderlich string

EN 16931 BT-130 — the unit of measure this catalogue entry carries onto an invoice line. 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
currency string

ISO 4217. This price is copied onto invoice lines, so the currency is copied with it. On a CREATE, omitting it records EUR. On the REPLACE it is REQUIRED — see ReplacePosition. Until it became required, omitting it on a replace silently repriced a foreign-currency catalogue entry in euros.

Länge: min. 3, max. 3
defaultQuantity DecimalString | null

Standardmenge. Omit or send null for "not set" (client falls back to "1").

Verwendet von: POST /positions

ReplacePosition object

Replace payload for a catalogue entry. Identical to the create payload with one difference: the currency is REQUIRED. This body describes the whole entry, so an omitted property states that the property has no value — and a price always has a currency, so there is nothing for an omission to mean. Until this became required, omitting it stored EUR, which silently repriced a foreign-currency entry.

Feld Typ Beschreibung
description erforderlich string
Länge: min. 1, max. 500
unitCode erforderlich string

EN 16931 BT-130 — the unit of measure this catalogue entry carries onto an invoice line. 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
currency erforderlich string

ISO 4217. This price is copied onto invoice lines, so the currency is copied with it. On a CREATE, omitting it records EUR. On the REPLACE it is REQUIRED — see ReplacePosition. Until it became required, omitting it on a replace silently repriced a foreign-currency catalogue entry in euros.

Länge: min. 3, max. 3
defaultQuantity DecimalString | null

Standardmenge. Omit or send null for "not set" (client falls back to "1").

Verwendet von: PUT /positions/{id}

CustomerOpenItems object

The per-customer open-items statement — every open invoice for one customer + the rollup totals. All fields server-derived.

Feld Typ Beschreibung
customerId erforderlich string (uuid)
dunningEnabled erforderlich boolean

Whether the dunning columns (nextAction/currentLevel*) are populated. false (degraded) ⇒ those fields are null on every item.

items erforderlich Array<OpenItem>
totals erforderlich OpenItemsTotals

Verwendet von: GET /customers/{id}/open-items

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: GET /customers , POST /customers , GET /customers/{id} , PUT /customers/{id} , GET /positions , POST /positions , GET /positions/{id} , PUT /positions/{id}

CustomerPositionAssignmentDetail object

One assignment, with the assigned position's own fields embedded so the Zuordnungen table renders without a second lookup. The embedded field set is exactly the fields an insert COPIES onto an invoice line item (the snapshot semantics of the catalogue copy). It is deliberately NOT the full Position: assignments (this position's OTHER customers) is omitted as a data-minimisation rule — this is a read about one customer — and createdAt/updatedAt are omitted because beside an assignment they read as the assignment's timestamps, which they are not.

Feld Typ Beschreibung
positionId erforderlich string (uuid)
description erforderlich string

The position's Bezeichnung.

unitCode erforderlich string

EN 16931 BT-130 — the unit of measure this entry carries onto an invoice line, 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: catalogue entries saved 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

Katalogpreis — the position's list price.

taxRatePercent erforderlich DecimalString
currency erforderlich string

ISO 4217, trimmed (the column is CHAR(3), blank-padded).

defaultQuantity erforderlich DecimalString | null

Standardmenge. null = NOT SET — the client falls back to "1".

unitPriceNetOverride erforderlich DecimalString | null

Kundenpreis. null = NO override = bill this customer unitPriceNet. Effective price = unitPriceNetOverride ?? unitPriceNet.

Verwendet von: GET /customers/{id}/positions

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: GET /customers/{id}/positions , PUT /customers/{id}/positions , GET /positions , POST /positions , GET /positions/{id} , PUT /positions/{id}

PositionAssignment object

A customer this position is assigned to (Zuordnung), with that customer's optional price override (Kundenpreis). The customer's number and name are joined in at READ time (never stored on the assignment, always fresh) so the catalog list, the position edit page and the customer edit page are each served without a second request.

Feld Typ Beschreibung
customerId erforderlich string (uuid)
customerNumber erforderlich string

Kundennummer, e.g. "K-00001" — counted per company and mode, unique only within them.

customerName erforderlich string
unitPriceNetOverride erforderlich DecimalString | null

Kundenpreis. null = NO override = bill this customer the catalog price (unitPriceNet). Effective price = unitPriceNetOverride ?? unitPriceNet.

Verwendet von: GET /positions , POST /positions , GET /positions/{id} , PUT /positions/{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 /positions , PUT /positions/{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 /positions , PUT /positions/{id}

OpenItem object

One open invoice on a customer's statement — status='issued' AND payment_state <> 'paid', due OR not-yet-due. Every field server-derived.

Feld Typ Beschreibung
invoiceId erforderlich string (uuid)
invoiceNumber erforderlich string | null
customerId erforderlich string (uuid) | null
buyerName erforderlich string
currency erforderlich string
effectiveDueDate erforderlich string (date)

COALESCE(due_date, issue_date + 30).

daysOverdue erforderlich integer

CURRENT_DATE − effectiveDueDate (whole days; NEGATIVE when not yet due).

overdue erforderlich boolean

True iff past the effective due date (daysOverdue > 0).

openAmountMinor erforderlich integer

What is still owed, in minor units: totalGross − amountPaid − the same-currency partial credit notes.

totalGrossMinor erforderlich integer

The invoice gross in minor units.

currentLevelLabel erforderlich string | null

The derived current Mahnstufe label; null when none sent OR dunning is off.

currentLevelSentAt erforderlich string (date-time) | null

When the current level was sent; null when none OR dunning is off.

nextAction erforderlich DunningNextAction | null

The server-computed next-action descriptor; null when dunning is not enabled.

Varianten

Verwendet von: GET /customers/{id}/open-items

OpenItemsTotals object

Customer-level rollup (all minor units).

Feld Typ Beschreibung
openAmountMinor erforderlich integer

Σ openAmountMinor over every open invoice.

openCount erforderlich integer

Count of open invoices.

overdueAmountMinor erforderlich integer

Σ openAmountMinor over the overdue subset only.

overdueCount erforderlich integer

Count of open invoices past their effective due date.

Verwendet von: GET /customers/{id}/open-items

DunningNextAction object

The SERVER-COMPUTED next-action descriptor for one overdue invoice. web renders it verbatim — the Mahnstufe schedule stays server-side. Produced by the shared send-precondition helper so a future send path cannot drift from what the row hint shows.

Feld Typ Beschreibung
nextLevelKey erforderlich string | null

The next level's key, or null when all reached levels are sent.

nextLevelLabel erforderlich string | null

The next level's label (the dynamic kebab item label), or null.

sendable erforderlich boolean

Whether the next level can be sent right now.

blockedReason erforderlich string | null

Why the next level is not sendable, or null when sendable is true. correction_chain_indeterminate is ranked FIRST of all: one of this invoice's own cancellation documents has itself been cancelled, so the amount still owed cannot be determined automatically. Every other reason implies an amount; this one is the absence of one, so the invoice is withheld from automated dunning and shown to a human instead (it stays on the per-customer open items). It never clears by waiting or by sending. cancelled_by_correction means an issued/paid 384, or a 381 that reverses this invoice IN FULL, settles it — it is ranked next, ahead of every remaining reason, because a cancelled invoice is not merely "not yet due". A 381 that reverses only PART of the invoice does not produce this state: it reduces the open amount instead, and the invoice stays dunnable for the remainder. not_sent is ranked SECOND: the platform has no record of ever sending this invoice, so it may not be dunned. It deliberately outranks all_sent and no_level_due — those read as "wait", and a delivery gap never clears by waiting. The row is still returned by the overdue read (it is still money owed); only the send is blocked, until the seller sends the invoice.

Werte: "cancelled_by_correction", "correction_chain_indeterminate", "not_sent", "no_recipient", "no_level_due", "all_sent", "processing_restricted"
nextLevelAvailableFrom erforderlich string (date) | null

ISO date the next level becomes due, when it is not yet due (no_level_due), else null.

Verwendet von: GET /customers/{id}/open-items

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