Konto & Abrechnung

Tarif, Abrechnung, Nutzung, Audit-Protokoll und Kontoschließung.

Plans, billing, usage, audit log, and account closure.

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

Operationen

  1. GET /me/account/close-disclosure The counsel-owned retention disclosure shown before account closure
  2. GET /me/account/close-blockers Preconditions that must be resolved before the caller can close their account
  3. POST /me/account/close Close the caller's account and erase their identity (Art. 17)
  4. GET /legal/current-versions The legal documents currently published, and the sentence each is accepted by
  5. GET /tenants/{tenantId}/audit-log Tenant-scoped audit log (cursor-paginated, owner-only)
  6. GET /tenants/{tenantId}/usage Usage snapshot for the period (finalized-document rollup, admin/owner)
  7. GET /tenants/{tenantId}/billing The tenant's billing status (tier + effective price + masked account + usage)
  8. POST /tenants/{tenantId}/billing/subscribe Start a subscription — obtain permission to charge this account
  9. POST /tenants/{tenantId}/billing/payment-method/replace Replace the payment method this account already holds
  10. GET /tenants/{tenantId}/billing-recovery The billing status read that still answers a SUSPENDED account
  11. POST /tenants/{tenantId}/billing-recovery/subscribe Start a subscription from a SUSPENDED account whose mandate is revoked
  12. POST /tenants/{tenantId}/billing-recovery/payment-method/replace Replace the payment method from a SUSPENDED account whose mandate is active
  13. POST /tenants/{tenantId}/legal-acceptances Accept the currently published legal documents
  14. POST /tenants/{tenantId}/processing-instructions Submit an instruction or an objection to the processor
  15. GET /tenants/{tenantId}/processing-instructions List this account's instructions and objections, newest first
  16. GET /tenants/{tenantId}/processing-instructions/{id} One instruction or objection with its full text
  17. POST /feedback Send feedback about the screen you are on
  18. GET /tenants/{tenantId}/legal-acceptances/{document}/document Retrieve the record of an accepted legal document
  19. GET /tenants/{tenantId}/legal-acceptance-status Which legal documents this account still has to accept
  20. GET /billing/plans The self-serve plan catalog (unauth-safe)
  21. POST /billing/provider-webhook Inbound payment-provider webhook receiver (POC)

The counsel-owned retention disclosure shown before account closure

GET /me/account/close-disclosure

The honesty core of the account-closure flow. Returns the structured, api-owned retention disclosure the UI renders VERBATIM (no client-side legal mapping, no hard-coded legal claim in the frontend). Ships a CONSERVATIVE placeholder — retentionYears: null, legalBasis: null, classification: "undetermined" — until legal counsel rules the exact values (a one-line config change, no re-model). The endpoint enforces the single hard invariant IN CODE: it never emits a hard-coded retention number while classification !== "ruled", and never emits a "fully deleted" claim (neverFullErasure is always true), so the UI cannot regress the honesty invariant.

Available only where self-service account closure is enabled (off by default); 404 when not.

Erforderlicher Scope
keiner
Zugang
nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt (oidc-only)
Verfügbarkeit
noch nicht freigeschaltet (not-enabled)

Antworten

  • 200

    The retention disclosure (conservative placeholder until counsel rules).

    application/json AccountCloseDisclosure

  • 401

    Missing/invalid OIDC token

  • 404

    The account-closure surface is not enabled.

Preconditions that must be resolved before the caller can close their account

GET /me/account/close-blockers

The authoritative pre-close read the UI renders as a resolvable checklist (never a bare 409, never a client-side last-ownership computation — the api is authoritative). Self-only: the subject is the verified token's user (unspoofable — no path/body/query userId). Returns last_owner_tenant for every tenant the caller is the SOLE active owner of, and active_subscription for every owned tenant with a live Fortlauf billing mandate. The subscription check is FEATURE-DETECTED: when billing is not enabled (the default) it collapses to empty. An empty blockers array means the caller may proceed to close.

Available only where self-service account closure is enabled (off by default); 404 when not.

Erforderlicher Scope
keiner
Zugang
nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt (oidc-only)
Verfügbarkeit
noch nicht freigeschaltet (not-enabled)

Antworten

  • 200

    The caller's unresolved close preconditions (possibly empty).

    application/json AccountCloseBlockers

  • 401

    Missing/invalid OIDC token

  • 404

    The account-closure surface is not enabled.

Close the caller's account and erase their identity (Art. 17)

POST /me/account/close

The self-service Art. 17 identity-erasure path. Self-only / unspoofable: the subject is the verified token user (no path/body/query userId). One auditable workflow: re-check blockers (→ 409 with the machine-readable blocker body, no side effect) → revoke the identity-provider login (deactivate, never delete) → revoke every membership → SCRUB identity PII (email→NULL, sub→deleted:<id> tombstone, status='closed') and write the erasure-evidence record in ONE atomic transaction. users.id is KEPT — erasure is tombstone+scrub, never a hard DELETE (the retained audit trail references it). On a GoBD-bound platform the issued invoices + WORM archive are legally retained and are NOT erased — the disclosure (GET /me/account/close-disclosure) states this honestly.

Idempotent: an already-closed (or absent) subject is a no-op already_closed. Available only where self-service account closure is enabled (404 when not).

Erforderlicher Scope
keiner
Zugang
nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt (oidc-only)
Verfügbarkeit
noch nicht freigeschaltet (not-enabled)

Antworten

  • 200

    The account was closed, or was already closed (idempotent no-op).

    application/json AccountCloseResult

  • 401

    Missing/invalid OIDC token

  • 404

    The account-closure surface is not enabled.

  • 409

    Unresolved preconditions (last-owner tenant / active subscription). The body carries the machine-readable blockers checklist.

    application/json object

The legal documents currently published, and the sentence each is accepted by

GET /legal/current-versions

What a customer is being asked to agree to right now. Answers for the PLATFORM, not for the caller — it is deliberately not "what does this account still owe", which is a tenant-scoped question and a different one. A signed-in caller who belongs to no account may read it: they are exactly who is about to create one.

An empty list means nothing is published and there is no requirement, so a client renders no acceptance control at all. That is a 200, never a 404: a 404 could not be told apart from a deployment that does not have this route yet.

acceptanceSentence is the exact wording the customer agrees to and is echoed verbatim from the published version. A client must render it rather than substituting its own copy — the version identifier is what pins the words, and paraphrasing breaks that. documentUrl is omitted when no address is configured, in which case there is no link and therefore no control.

Erforderlicher Scope
keiner
Zugang
angemeldete Sitzung oder API-Schlüssel (authenticated)

Antworten

  • 200

    The currently published documents, possibly none.

    application/json object

  • 401

    No valid credential.

Tenant-scoped audit log (cursor-paginated, owner-only)

GET /tenants/{tenantId}/audit-log

Append-only authorization-event trail for one tenant. Owner role only (requireTenantContext + requireRole('owner') — stricter than the admin gate on api-keys/webhooks; the audit log answers "who did what and when" for security/DSGVO). Cursor-paginated ({ data, nextCursor }, keyset over (timestamp, id) DESC — newest first), the same envelope as the webhook delivery log. Metadata only: the hashed IP is NEVER returned (the raw IP is never stored — hashed at the repository boundary, DSGVO), and tenantId is omitted (implied by the path). Entries are scoped to the resolved tenant; the path :tenantId must equal the caller's active tenant or the request is 403 tenant_mismatch. Consumed by the Übersicht activity feed and the Team surface.

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

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant whose audit log to read. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

cursor Query string

Opaque keyset cursor from a prior page's nextCursor. Reset it on a filter change.

limit Query integer

Page size for cursor-paginated reads.

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

Filter by action, e.g. entity.created, api_key.revoked, user.login. Open string (NOT an enum) — the log carries event types from many subsystems (api_key.*, webhook.*, and auth/tenancy events).

actorId Query string (uuid)

Filter by actor (users.id) — the Team "who did this" lens.

resourceType Query string

Filter by target kind, e.g. entity, invitation, api_key.

from Query string (date-time)

Inclusive timestamp lower bound (RFC3339).

to Query string (date-time)

Inclusive timestamp upper bound (RFC3339).

Antworten

  • 200

    A page of audit records (metadata only).

    application/json AuditLogPage

  • 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 is not owner (insufficient_role), or the path tenantId is not the caller's active tenant (tenant_mismatch).

Usage snapshot for the period (finalized-document rollup, admin/owner)

GET /tenants/{tenantId}/usage

The billable-usage snapshot the Übersicht card shows: the count of finalized documents (invoice_finalized / credit_note_finalized / correction_finalized) for the period, scoped to the caller's resolved tenant/entity. Admin/owner only (requireTenantContext + requireRole('admin')); a key-bearing caller carries no role and is refused (403 insufficient_role). Metadata only — COUNTS, never document contents. Live only — sandbox (mode='test') documents are excluded at the query layer (the BillingRollupQuery boundary). A tenant sees only its own counts; the path :tenantId must equal the caller's active tenant or the request is 403 tenant_mismatch. Defaults to the current calendar month (UTC) when no from/to is given.

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

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant whose usage to read. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

from Query string (date-time)

Inclusive lower bound of the reporting window (RFC3339, half-open [from, to)). Must be supplied together with to. Omit both to get the default: the current calendar month (UTC).

to Query string (date-time)

Exclusive upper bound (RFC3339). Must be supplied together with from.

Antworten

  • 200

    The usage snapshot for the resolved period.

    application/json UsageSnapshot

  • 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 admin role (insufficient_role), or the path tenantId is not the caller's active tenant (tenant_mismatch).

The tenant's billing status (tier + effective price + masked account + usage)

GET /tenants/{tenantId}/billing

The self-serve billing-status read the "Abrechnung" surface consumes: the tenant's plan tier, the resolved plan (label + entitlements + its EFFECTIVE price), the MASKED billing account, and the current-period usage. Read open to ANY active member (requireTenantContext, no role required) — it mirrors GET .../plan; the path :tenantId must equal the caller's active tenant (else 403 tenant_mismatch). Reads-only — no Mollie/provider call, no issued-invoice mutation. Key invariants: plan.price is resolved through the override-aware resolveEffectivePricing seam (NOT PLAN_CATALOG_V1 directly), so a future gated per-tenant price override flows here with no consumer change; every €-figure is server-sourced and is the price that applies; usage.billableUnits is BYTE-EQUAL to GET .../usage for the same period (the §7 bill == usage invariant); account is null when billing is not enabled or no account row exists yet, and NEVER exposes providerCustomerId/mandateRef.

Erforderlicher Scope
keiner
Zugang
angemeldete Sitzung oder API-Schlüssel (authenticated)

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant whose billing status to read. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

Antworten

  • 200

    The tenant's billing status.

    application/json TenantBillingStatus

  • 403

    Path tenantId ≠ active tenant (tenant_mismatch), or the caller has no active membership (no_tenant_membership).

Start a subscription — obtain permission to charge this account

POST /tenants/{tenantId}/billing/subscribe

Begins the collection of a payment mandate: the standing permission that lets this account be charged for its subscription. On a rail that hosts the payment step this mints a hosted checkout and returns its address; the caller sends the person there and the mandate comes into existence only once that first payment settles, which is reported asynchronously. On a rail that captures the instrument inline there is no address and the mandate is active on return.

The response is always 200 with the destination in the body, and NEVER a redirect: the caller performs the hand-off itself, so a server-side call from a front end cannot follow the redirect and strand the person's browser. The destination is produced by the payment provider and by nothing in the request — no request property, query parameter or header can influence where a person is sent.

Restricted to an interactive owner session. An API credential may not start a subscription: the credential belongs to the caller's software, the subscription to their company. Idempotency-Key is REQUIRED, because there is no natural once-only guard on starting a subscription and a retried request must never mint a second payment page.

Only an already-ACTIVE mandate is refused (409). An account that has never had one, is still waiting on one, had one refused, or had one withdrawn may all start again. When the billing feature is switched off this path does not exist at all (404), which is how a client detects the capability without holding a switch of its own.

Billing details must be on file before a subscription can start, because German VAT law requires the recipient's full name and address on every invoice we issue. Send them as billingParty, or omit that property if they have already been supplied. Three separate refusals distinguish the cases — none supplied and none held, supplied but incomplete, and a country we cannot yet bill — so a caller can tell a person exactly what to do next. Every one of them refuses before the payment provider is contacted: nothing is stored and no payment page is minted.

Erforderlicher Scope
keiner
Zugang
angemeldete Sitzung oder API-Schlüssel (authenticated)

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant whose billing status to read. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

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 optional

application/json StartSubscription

Antworten

  • 200

    The hand-off destination, or null on a rail that captured the instrument inline.

    application/json SubscriptionStart

  • 400

    The body carried a property this operation does not define, or an unsupported instrument family, or no Idempotency-Key was sent.

  • 403

    Path tenantId ≠ active tenant (tenant_mismatch), the caller is not an owner, or an API credential was presented. Self-serve card registration may also be closed on this system (self_serve_billing_closed); that one is a PER-TENANT gate rather than a missing capability, which is why it is a 403 and not the 404 the neighbouring billing switches return — the route exists and other tenants may be using it, so a client must feature-detect on the error code and never on the route's absence. Nothing was stored, no payment page was minted, the payment provider was never contacted, and the refusal is NOT recorded by the idempotency layer: the same Idempotency-Key works once the gate is opened.

  • 409

    This account already holds an active mandate (mandate_already_active). Nothing was created; a second payment page is never minted.

  • 422

    The billing details are missing, incomplete, or name a country we cannot yet bill. The error code distinguishes the three: billing_party_required (none supplied and none on file), billing_party_incomplete (a field is absent or blank — a whitespace-only value counts as absent), and billing_party_country_unsupported. Or the tier named is not a plan that can be ordered on this system (tier_not_purchasable). Nothing was stored, no payment page was minted, and the payment provider was never contacted. After tier_not_purchasable the same Idempotency-Key may be used again with the corrected request.

  • 502

    The payment provider could not be reached, or did not return a usable destination (provider_unavailable). NOTHING was stored — the caller may retry with a fresh Idempotency-Key.

  • 503

    The payment credential is not configured on this deployment (config_error). Every other surface keeps working.

Replace the payment method this account already holds

POST /tenants/{tenantId}/billing/payment-method/replace

Starts a SECOND payment-method capture while the account keeps the mandate it currently holds. The held mandate stays chargeable for the whole of the hand-off, and is withdrawn only after the replacement has been confirmed by this platform's own authenticated read — so abandoning the hosted page leaves the account exactly as it was, still able to be charged.

The response is always 200 with the destination in the body and NEVER a redirect; the caller performs the hand-off itself. The destination comes from the payment provider and from nothing in the request: no request property, query parameter or header can influence where a person is sent.

Restricted to an interactive owner session; an API credential may not change a company's payment method. Idempotency-Key is REQUIRED, and it identifies the intent rather than the attempt: repeating a request with the same key returns the same hand-off destination, while pressing the control again with a new key SUPERSEDES the earlier attempt — the earlier one can no longer complete, and a payment method it registers late is withdrawn automatically.

This operation defines no request properties at all. In particular there is no choice of instrument family: the replacement reuses the family the account already uses.

Only an account that HOLDS an active payment method may replace one (409 otherwise) — the mirror of the refusal on starting a subscription, which refuses an account that already holds one. When the capability is switched off this path does not exist at all (404), which is how a client detects it without holding a switch of its own.

Erforderlicher Scope
keiner
Zugang
angemeldete Sitzung oder API-Schlüssel (authenticated)
Verfügbarkeit
noch nicht freigeschaltet (not-enabled)

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant whose billing status to read. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

Idempotency-Key Header string

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

Länge: max. 255

Antworten

  • 200

    The hand-off destination and the identifier of this replacement.

    application/json PaymentMethodReplacementStart

  • 400

    No Idempotency-Key was sent, or the body carried a property this operation does not define.

  • 403

    Path tenantId ≠ active tenant (tenant_mismatch), the caller is not an owner, or an API credential was presented. Self-serve card registration may also be closed on this system (self_serve_billing_closed); that one is a PER-TENANT gate rather than a missing capability, which is why it is a 403 and not the 404 the neighbouring billing switches return — the route exists and other tenants may be using it, so a client must feature-detect on the error code and never on the route's absence. Nothing was stored, no payment page was minted, the payment provider was never contacted, and the refusal is NOT recorded by the idempotency layer: the same Idempotency-Key works once the gate is opened.

  • 409

    This account holds no active payment method (no_mandate_to_replace). There is nothing to replace; the account starts one through .../billing/subscribe instead. Nothing was created.

  • 502

    The payment provider could not be reached, or returned no usable destination (provider_unavailable). NOTHING was stored and the account is unchanged — it still holds the payment method it had.

  • 503

    The payment credential is not configured on this deployment (config_error). Every other surface keeps working.

The billing status read that still answers a SUSPENDED account

GET /tenants/{tenantId}/billing-recovery

Byte-identical in shape to GET /tenants/{tenantId}/billing, on a prefix that ADMITS a suspended tenant — the one door a suspended customer may walk through to restore a means of payment. Every other route keeps refusing a suspended tenant (403 tenant_suspended), which is how a client feature-detects the suspension itself.

Restricted to an interactive (OIDC) session: an API credential is refused outright, because this door exists so a PERSON can restore a payment method. Open to any active member of the tenant. Paying never lifts a suspension — reactivation stays a human act with a recorded reason — so a renderer of this surface must not promise that a successful payment restores access. When the capability is switched off this prefix does not exist at all (404).

Erforderlicher Scope
keiner
Zugang
nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt (oidc-only)
Verfügbarkeit
noch nicht freigeschaltet (not-enabled)

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant whose billing status to read. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

Antworten

  • 200

    The tenant's billing status (same shape as the ordinary read).

    application/json TenantBillingStatus

  • 403

    Path tenantId ≠ active tenant (tenant_mismatch), the caller has no active membership (no_tenant_membership), or an API credential was presented (credential_not_accepted).

Start a subscription from a SUSPENDED account whose mandate is revoked

POST /tenants/{tenantId}/billing-recovery/subscribe

The recovery twin of POST /tenants/{tenantId}/billing/subscribe, on the prefix that admits a suspended tenant: for the suspended account whose payment mandate is revoked, this is the only way to establish a new one. Contract, refusals and response are the ordinary subscribe operation's — see that operation for the hand-off semantics, the billingParty requirements and the error vocabulary. Restricted to an interactive owner session; Idempotency-Key is REQUIRED. When the capability is switched off this path does not exist at all (404).

Erforderlicher Scope
keiner
Zugang
nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt (oidc-only)
Verfügbarkeit
noch nicht freigeschaltet (not-enabled)

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant whose billing status to read. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

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 optional

application/json StartSubscription

Antworten

  • 200

    The hand-off destination, or null on a rail that captured the instrument inline.

    application/json SubscriptionStart

  • 400

    The body carried a property this operation does not define, or an unsupported instrument family, or no Idempotency-Key was sent.

  • 403

    Path tenantId ≠ active tenant (tenant_mismatch), the caller is not an owner, or an API credential was presented.

  • 409

    This account already holds an active mandate (mandate_already_active) — the replace operation is the route for that population. Nothing was created.

  • 422

    The billing details are missing, incomplete, or name a country we cannot yet bill (billing_party_required / billing_party_incomplete / billing_party_country_unsupported). Nothing was stored and the payment provider was never contacted.

  • 502

    The payment provider could not be reached (provider_unavailable). NOTHING was stored — the caller may retry with a fresh Idempotency-Key.

  • 503

    The payment credential is not configured on this deployment (config_error).

Replace the payment method from a SUSPENDED account whose mandate is active

POST /tenants/{tenantId}/billing-recovery/payment-method/replace

The recovery twin of POST /tenants/{tenantId}/billing/payment-method/replace, on the prefix that admits a suspended tenant: for the suspended account that still HOLDS an active mandate (a reissued card, say), this replaces it while the held mandate stays chargeable for the whole hand-off. Contract, refusals and response are the ordinary replace operation's — see that operation. Both money verbs exist on this prefix deliberately: a suspended tenant whose mandate is revoked needs subscribe, one whose mandate is active needs replace, and admitting only one of them rebuilds the trap for half the population. Restricted to an interactive owner session; Idempotency-Key is REQUIRED. When the capability is switched off this path does not exist at all (404).

Erforderlicher Scope
keiner
Zugang
nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt (oidc-only)
Verfügbarkeit
noch nicht freigeschaltet (not-enabled)

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant whose billing status to read. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

Idempotency-Key Header string

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

Länge: max. 255

Antworten

  • 200

    The hand-off destination and the identifier of this replacement.

    application/json PaymentMethodReplacementStart

  • 400

    No Idempotency-Key was sent, or the body carried a property this operation does not define.

  • 403

    Path tenantId ≠ active tenant (tenant_mismatch), the caller is not an owner, or an API credential was presented.

  • 409

    This account holds no active payment method (no_mandate_to_replace); it starts one through .../billing-recovery/subscribe instead. Nothing was created.

  • 502

    The payment provider could not be reached (provider_unavailable). NOTHING was stored and the account is unchanged.

  • 503

    The payment credential is not configured on this deployment (config_error).

Accept the currently published legal documents

POST /tenants/{tenantId}/legal-acceptances

Records that this account accepts the named documents, and answers with the post-state for every document this platform asks accounts to accept — not only the ones named, so an account that accepted one of two still sees the other outstanding rather than reading the answer as completion.

The version in the body is a confirmation of what was on screen, not the value recorded: the version written is the one read as current inside the same transaction, so a stale tab cannot conclude an agreement to a version nobody saw. A version that is no longer current is refused and nothing is written.

Accepting a version already held is an ordinary success. No second record is made and no second confirmation is sent.

Who accepted, when, and the counterparty as it was shown at the time are taken from the verified session and from the account; there are no fields for them. The accepting person must be named on their identity token.

⛔ All or nothing: if any named document is refused, none is recorded.

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

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant being managed. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

Request-Body erforderlich

application/json object

Antworten

  • 200

    The post-state, one entry per document this platform asks accounts to accept — the same shape the status read answers in.

    application/json object

  • 400

    The body could not be read (validation_error), or a named document has no published version (invalid_legal_version) — which is what an account is told wherever nothing has been published yet.

    application/json ValidationError

  • 403

    tenant_mismatch (the path tenant is not the active one), insufficient_role (member/readonly), or credential_not_accepted (an API key).

  • 409

    A named version is no longer the published one (legal_acceptance_superseded, with the current state per document), or the accepting person is not named on their identity token — a refusal the caller can clear by completing their profile.

    application/json object

Submit an instruction or an objection to the processor

POST /tenants/{tenantId}/processing-instructions

Records a documented instruction (AVV § 2 Abs. 3) or an objection to a sub-processor change (AVV § 7 Abs. 3) for this account, write-once, and sends a copy to the processor's data-protection contact and a receipt to the person submitting. Who submitted and when are taken from the verified session and the server clock; there are no fields for them. Available to account owners and admins in a signed-in session; API keys are refused. Nothing is executed by this call.

The record cannot be changed or deleted afterwards. A change of mind is a new record.

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

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant being managed. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

Idempotency-Key Header string

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

Länge: max. 255

Request-Body erforderlich

application/json ProcessingInstructionCreate

Antworten

  • 201

    The stored record (or, on an Idempotency-Key replay, the recorded response).

    application/json ProcessingInstruction

  • 400

    validation_error, naming the fields and never repeating a submitted value. Includes relatedEntityId naming no company of this account, and a carriage return in subject or body.

    application/json ValidationError

  • 403

    tenant_mismatch (the path tenant is not the active one), credential_not_accepted (an API key), or insufficient_role (member, readonly).

  • 409

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

    application/json object

  • 422

    submitter_name_missing: the session's identity token carries no name, so the record would name nobody. Nothing was recorded.

List this account's instructions and objections, newest first

GET /tenants/{tenantId}/processing-instructions

Cursor-paginated ({ data, nextCursor }), newest first. Summaries carry no text; open a record for its text. No status is recorded or reported.

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

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant being managed. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

limit Query integer
Standard: 50
Bereich: ≥ 1, ≤ 100
cursor Query string

Opaque; the nextCursor of the previous page.

Antworten

  • 200

    One page. No text bodies.

    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

    tenant_mismatch · credential_not_accepted · insufficient_role

One instruction or objection with its full text

GET /tenants/{tenantId}/processing-instructions/{id}

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

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant being managed. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

id erforderlich Pfad string (uuid)

Antworten

  • 200

    The record.

    application/json ProcessingInstruction

  • 403

    tenant_mismatch · credential_not_accepted · insufficient_role

  • 404

    not_found, also for another account's record id.

Send feedback about the screen you are on

POST /feedback

Records one feedback report from a signed-in person, with the screen's context and, optionally, a screenshot, and answers with a short reference. Every member role may send feedback, including read-only members. API keys are refused: feedback comes from a person. Who sent it (account, company, person, role and environment) is taken from the signed-in session; the body has no fields for it, and naming one is refused.

The request is multipart/form-data of at most 512 KiB, refused before it is read when the declared length is larger. The part submission (JSON, at most 16 KiB) is required; the part screenshot is optional and must be a WebP, JPEG or PNG image of at most 400 KiB whose longest side is at most 4096 px. When the screenshot part is present, the submission.screenshot metadata must be present too, and only then. No IP address, browser identification or page address is stored. The contact address is stored only when contactOptIn is true, and it is the session's verified address, never a value from the body.

With an Idempotency-Key, a retry with the same content (the validated submission and the screenshot bytes) answers with the first report's receipt and records nothing; the same key with different content is a conflict. A person may send 20 reports and an account 100 per calendar day (Europe/Berlin); a replay does not count. The endpoint is not enabled on every deployment; where it is not, it answers 404.

Erforderlicher Scope
keiner
Zugang
nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt (oidc-only)
Verfügbarkeit
noch nicht freigeschaltet (not-enabled)

Parameter

Parameter In Typ Beschreibung
Idempotency-Key Header string

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

Länge: max. 255

Request-Body erforderlich

multipart/form-data object

Antworten

  • 201

    The report was recorded (or, on an Idempotency-Key replay with the same content, the first report's receipt).

    application/json FeedbackReceipt

  • 400

    validation_error: a part or field is missing, unknown or out of range — including a field naming the account, company, person, role or environment, a submission part over 16 KiB, a message outside 1–4000 characters, a route or entityRef.id that fails its pattern, or contactOptIn on a session without an address. The issues name paths and reason codes, never a submitted value.

    application/json FeedbackRefusal

  • 403

    credential_not_accepted (an API key), no_tenant_membership, or tenant_suspended (the account is fully suspended; partial suspension measures do not refuse feedback).

  • 409

    IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_BODY: this key was already used for a report with different content. Nothing was recorded.

    application/json object

  • 413

    payload_too_large: the request is over 512 KiB (refused unread when declared), or the screenshot part is over 400 KiB.

  • 415

    unsupported_media_type: the request is not multipart/form-data, or the screenshot's bytes are not the image type it declares, are SVG, or its longest side is over 4096 px.

    application/json FeedbackRefusal

  • 429

    feedback_rate_limited: 20 reports by this person or 100 by this account today (Europe/Berlin). Nothing was recorded.

    • Retry-After integer

      Seconds until the next midnight in Europe/Berlin.

Retrieve the record of an accepted legal document

GET /tenants/{tenantId}/legal-acceptances/{document}/document

Returns the conclusion document for one legal document this account has accepted: the version, the instant with its time zone, the accepting person by name and e-mail, their role, the counterparty as it was shown at the time, and the acceptance sentence in the wording of that version.

Available at any time to an owner or admin, so a customer who deleted the confirmation e-mail — or whose accepting colleague has left — never has to ask for it. member and readonly are refused: the record names a colleague and carries their address.

The bytes are the same record the confirmation e-mail carries. It is a self-contained HTML document with no scripts and no external resources, so it can be saved, printed and reopened unchanged.

Values are those recorded AT the acceptance and are never re-resolved: a later change to a person's details does not alter a record of what they declared, and a newer published version does not alter the wording of an earlier acceptance.

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

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

Must be the caller's active tenant.

document erforderlich Pfad string

Which document, e.g. avv or agb.

Antworten

  • 200

    The conclusion document.

    text/html string

  • 400

    The path named no document this API has.

    application/json ValidationError

  • 403

    tenant_mismatch (the path tenant is not the active one), insufficient_role (member/readonly), or credential_not_accepted (an API key).

  • 404

    This account has not accepted that document, so there is no record to return (error: legal_acceptance_not_recorded). Never answered with an empty document.

    application/json object

Which legal documents this account still has to accept

GET /tenants/{tenantId}/legal-acceptance-status

One answer covering every document, so an account owing more than one meets a single prompt rather than a sequence of them.

outstanding is the whole question: true when the currently published version has not been accepted. When a newer version has superseded the one this account agreed to, outstanding is true AND acceptedVersion still names the older one — both are needed to say that the agreement has changed rather than that nothing was ever agreed.

When nothing is published, currentVersion is null and outstanding is false for everyone: there is no requirement, so there is nothing to ask about.

⛔ This is a read, not a gate. Nothing is refused on the strength of it — an act that requires an acceptance checks for itself. Any active member may read it, including a read-only one; whether to PROMPT is a separate decision for the application showing it.

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

Parameter

Parameter In Typ Beschreibung
tenantId erforderlich Pfad string (uuid)

The tenant being managed. Must equal the caller's active tenant (the resolved auth context) — a mismatch is 403 tenant_mismatch.

Antworten

  • 200

    One entry per document this platform asks accounts to accept.

    application/json object

  • 403

    Path tenantId is not the active tenant, or an API credential was presented.

The self-serve plan catalog (unauth-safe)

GET /billing/plans

The public plan/pricing catalog, served VERBATIM from the PLAN_CATALOG_V1 code constant with its entitlements and price points. UNAUTHENTICATED-SAFE — pure marketing/onboarding data with NO tenant data, mandate, or usage — so it is an explicit exact-path carve-out of the default-deny gate and is served UNCONDITIONALLY (it does NOT require billing to be enabled; the catalog is always readable). Every price is the price that applies; no €-figure originates in a client.

Returns the self-serve tiers only: the plan this endpoint tells you about is the plan you can actually buy. A tier that is not self-serve is still a valid plan_tier value, still resolvable, and still billed normally for tenants already on it — it simply is not offered here and is not offerable at checkout. Consumers must therefore treat tier as an open enum on read and must not assume every catalog tier appears.

Erforderlicher Scope
keiner
Zugang
ohne Anmeldung (public)

Antworten

  • 200

    The plan catalog.

    application/json PlanCatalog

Inbound payment-provider webhook receiver (POC)

POST /billing/provider-webhook

SIGNATURE-authed inbound receiver for our billing provider's payment events. NOT OIDC / not a tenant header — the caller is the provider, so this is an explicit carve-out of the default-deny gate, authenticated by the provider's HMAC-SHA256 signature over the RAW body (Webhook-Signature + Webhook-Timestamp + Webhook-Id). Verifies BEFORE any parse; dedupes on the (provider, provider_event_id) natural key (a redelivery is a 200 no-op); on a verified charge.settled flips our invoice issued → paid in one transaction; ALWAYS 200s a well-formed known event so the provider stops retrying. Mounted only when billing; the only wired provider is the mock (no real money).

Erforderlicher Scope
keiner
Zugang
ohne Anmeldung (public)

Parameter

Parameter In Typ Beschreibung
Webhook-Id erforderlich Header string

Stable per-message id, identical across every retry of the same message. It is an HMAC input, so it cannot be altered in transit; dedupe on it.

Webhook-Signature erforderlich Header string

v1,<base64> — HMAC-SHA256 over {Webhook-Id}.{Webhook-Timestamp}.{raw body}. May carry several space-delimited signatures during a secret rotation; accept if ANY verifies.

Webhook-Timestamp erforderlich Header string

Unix seconds. It is INSIDE the signature, so a captured message cannot be re-stamped and replayed — reject anything outside your tolerance (we use 5 minutes).

Request-Body erforderlich

application/json object

Antworten

  • 200

    Processed, deduped (redelivery), or ignored (unknown/irrelevant event) — the provider must stop retrying.

  • 401

    Missing/invalid provider signature (invalid_signature) — rejected before parse.

  • 413

    The request body exceeded this receiver's cap (payload_too_large). Refused BEFORE the body was buffered — a declared oversize length is refused without reading the stream at all, and an undeclared or chunked body is read incrementally and cancelled at the cap. Nothing was read, nothing was written, nothing was fetched.

  • 429

    Too many requests from this caller (rate_limited). This receiver carries its own flood guard, keyed on the resolved client address and sized so that only a flood reaches it. The refusal is RETRYABLE and carries Retry-After — retry after the stated delay; nothing was read, written or confirmed.

  • 503

    We could not complete a call back to the payment provider, so this notification was neither confirmed nor recorded (confirmation_unavailable). RETRYABLE, and it carries Retry-After — nothing was written, and a redelivery of the same message is processed normally rather than treated as a duplicate. This is deliberately not answered 200 (which would state we hold something we never confirmed) nor 401 (which is terminal and would end your retries).

Datenmodelle

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

AccountCloseDisclosure object

The counsel-owned retention disclosure served to the account page (GET /me/account/close-disclosure). The UI renders it verbatim. Conservative placeholder until counsel rules: retentionYears/legalBasis are null and classification is undetermined. neverFullErasure is always true — on a GoBD-bound platform, closing an account never wipes the legally-retained records; they are restricted and deleted on expiry.

Feld Typ Beschreibung
retentionYears erforderlich integer | null

The counsel-ruled retention period in years; null until ruled (never a guessed number).

legalBasis erforderlich string | null

The counsel-ruled legal-basis citation; null until ruled.

minimizationNote erforderlich string

The honest, number-free disclosure copy (access closed + PII minimized where permitted + records retained-and-un-deletable in the window). Never claims full erasure.

classification erforderlich string
Werte: "undetermined", "ruled"
neverFullErasure erforderlich boolean

Always true — the flow never performs a full erasure of retained records.

Werte: true

Verwendet von: GET /me/account/close-disclosure

AccountCloseBlockers object

Feld Typ Beschreibung
blockers erforderlich Array<AccountCloseBlocker>

Verwendet von: GET /me/account/close-blockers

AccountCloseResult object

The result of a completed (or idempotent no-op) account close.

Feld Typ Beschreibung
status erforderlich string
Werte: "closed", "already_closed"
userId erforderlich string (uuid)

The (retained, tombstoned) pseudonymous actor key — never hard-deleted.

Verwendet von: POST /me/account/close

AccountCloseBlocker object

One machine-readable precondition that must be resolved before the caller can close their account. A discriminated union on type. The UI renders a resolvable checklist from these, never a bare 409, and never computes last-ownership client-side (the api is authoritative).

Feld Typ Beschreibung
type erforderlich string
Werte: "last_owner_tenant", "active_subscription"
tenantId erforderlich string (uuid)
tenantName string

Present when resolvable — the tenant's display name for the checklist.

periodEnd string (date-time)

(active_subscription only) the current billing period end, when known.

Verwendet von: POST /me/account/close

AuditLogPage object

A cursor-paginated page of audit records ({ data, nextCursor }, keyset over (timestamp, id)).

Feld Typ Beschreibung
data erforderlich Array<AuditLogEntry>
nextCursor erforderlich string | null

Opaque cursor for the next page; null on the last page.

Verwendet von: GET /tenants/{tenantId}/audit-log

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 /tenants/{tenantId}/audit-log , GET /tenants/{tenantId}/usage , GET /tenants/{tenantId}/processing-instructions

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 /tenants/{tenantId}/audit-log , GET /tenants/{tenantId}/usage , POST /tenants/{tenantId}/legal-acceptances , POST /tenants/{tenantId}/processing-instructions , GET /tenants/{tenantId}/processing-instructions , GET /tenants/{tenantId}/legal-acceptances/{document}/document

UsageSnapshot object

The billable-usage snapshot the Übersicht card consumes: the reporting period, the flat total of billable (live-only, finalized) documents, and the per-event-type breakdown. Metadata-only (counts).

Feld Typ Beschreibung
period erforderlich object

The half-open [from, to) reporting window (RFC3339 UTC instants).

Felder
Feld Typ Beschreibung
from erforderlich string (date-time)
to erforderlich string (date-time)
totalBillableUnits erforderlich integer

Total live finalized documents in the period (sum of byEventType).

Bereich: ≥ 0
byEventType erforderlich object

Billable-unit count keyed by finalize event type (always all three keys).

Felder
Feld Typ Beschreibung
invoice_finalized erforderlich integer
Bereich: ≥ 0
credit_note_finalized erforderlich integer
Bereich: ≥ 0
correction_finalized erforderlich integer
Bereich: ≥ 0

Verwendet von: GET /tenants/{tenantId}/usage

TenantBillingStatus object

The self-serve billing-status read the "Abrechnung" surface consumes. plan.price is the EFFECTIVE (override-aware) price resolved through resolveEffectivePricing. usage.billableUnits is BYTE-EQUAL to GET .../usage for the same period (the §7 bill == usage invariant). entitlement is the effective allowance + this period's within/over-allowance verdict, resolved through the SAME override-aware seam (reports, never enforces). account is null when billing is not enabled or no account row exists yet.

Feld Typ Beschreibung
tier erforderlich string
Werte: "launch", "entry", "platform", "sovereign"
plan erforderlich object

The resolved plan for this tenant's tier — label + entitlements + EFFECTIVE price.

Felder
Feld Typ Beschreibung
label erforderlich string
entitlements erforderlich PlanEntitlements
price erforderlich ProvisionalPrice
account erforderlich MaskedBillingAccount | null

The masked billing account, or null (feature-detected).

usage erforderlich object

The current-period usage — billableUnits byte-equal to GET .../usage.

Felder
Feld Typ Beschreibung
period erforderlich object

The billing period as inclusive date-only bounds (YYYY-MM-DD).

Felder
Feld Typ Beschreibung
start erforderlich string (date)
end erforderlich string (date)
billableUnits erforderlich integer

Live finalized documents in the period (byte-equal to GET .../usage).

Bereich: ≥ 0
includedDocumentsPerPeriod erforderlich integer
Bereich: ≥ 0
overageUnits erforderlich integer

max(0, billableUnits − includedDocumentsPerPeriod).

Bereich: ≥ 0
entitlement erforderlich TenantEntitlement
cancellation erforderlich object

The subscription-cancellation state of this account. Always present — a client feature-detects on available, never on a key's presence.

Felder
Feld Typ Beschreibung
available erforderlich boolean

Whether cancelling the subscription is offered on this deployment. When false no cancellation control applies, whatever the other fields say.

prospectiveEffectiveAt erforderlich string (date-time) | null

The exact instant a cancellation requested now would take effect: the end of the period already paid for, from the same derivation the cancellation itself copies its stored date from. null when no billed period exists yet (there is no stateable date, and a cancellation request would be refused), and null once a cancellation has been recorded — the stored effectiveAt is then the only date a renderer may use.

requestedAt erforderlich string (date-time) | null

When the cancellation was requested; null while none is recorded. Stored, never recomputed.

effectiveAt erforderlich string (date-time) | null

The stored instant the recorded cancellation takes effect — copied once at the request and immutable thereafter; null while none is recorded. A renderer must show this value verbatim and must not promise that no further charge will follow it: a final usage charge for a supply that happened before this instant may lawfully be collected after it.

paymentMethodChange PaymentMethodChange

Present ONLY while a payment-method replacement is under way or has failed. Absent otherwise, including after one has completed.

Verwendet von: GET /tenants/{tenantId}/billing , GET /tenants/{tenantId}/billing-recovery

StartSubscription object

What the caller may choose when starting a subscription. Deliberately tiny: the caller may name WHICH plan it orders (tier), but there is no amount, price or currency here — what an account pays is resolved server-side from the plan and its effective pricing, and a request field would be a second source of that number. There is no destination of any kind either.

Feld Typ Beschreibung
tier string

The paid plan being ordered, by its tier value from GET /billing/plans — never by its label. Optional: omitted means platform. On the recovery operation, omitted means the paid plan the account held before collection stopped, else platform. A plan not currently offered is refused with 422 tier_not_purchasable before anything is stored or the payment provider is contacted.

Werte: "entry", "platform"
method string

The instrument family to collect. Omitted means the platform default. A closed set on purpose: a provider-native method name here would put one provider's vocabulary on this contract.

Werte: "card", "sepa"
billingParty BillingParty

Verwendet von: POST /tenants/{tenantId}/billing/subscribe , POST /tenants/{tenantId}/billing-recovery/subscribe

SubscriptionStart object

The result of starting a subscription.

Feld Typ Beschreibung
checkoutUrl erforderlich string | null

Where to send the person to complete the payment step. null on a rail that captured the instrument inline, in which case the mandate is already active. Produced by the payment provider; validate it against the hosts you expect before following it.

mandateStatus erforderlich MandateState

Verwendet von: POST /tenants/{tenantId}/billing/subscribe , POST /tenants/{tenantId}/billing-recovery/subscribe

PaymentMethodReplacementStart object

The result of starting a payment-method replacement.

Feld Typ Beschreibung
checkoutUrl erforderlich string (uri)

Where to send the person to complete the new payment method. Produced by the payment provider; validate it against the hosts you expect before following it.

replacementId erforderlich string (uuid)

This replacement attempt's own identifier. It is a correlation value, not a credential and not a provider resource, and it is stable for a given Idempotency-Key.

Verwendet von: POST /tenants/{tenantId}/billing/payment-method/replace , POST /tenants/{tenantId}/billing-recovery/payment-method/replace

ProcessingInstructionCreate object

Feld Typ Beschreibung
kind erforderlich string
Werte: "instruction", "objection"
subject erforderlich string

Not blank; no line breaks or other control characters.

Länge: min. 1, max. 200
body erforderlich string

Stored and fingerprinted exactly as sent; not blank. Line breaks must be sent as LF only: a carriage return is refused (400), never rewritten, because the fingerprint covers the exact bytes and the same visible text must not have two fingerprints. Lengths count Unicode code points.

Länge: min. 1, max. 20000
relatedEntityId string (uuid)

Optional. A company of this account; a label only.

Verwendet von: POST /tenants/{tenantId}/processing-instructions

ProcessingInstruction object

Feld Typ Beschreibung
id erforderlich string (uuid)
kind erforderlich string
Werte: "instruction", "objection"
channel erforderlich string

Always product in this version.

Werte: "product", "email", "letter", "oral"
subject erforderlich string
bodyHash erforderlich string

SHA-256 over the UTF-8 bytes of the text exactly as stored.

Muster: ^sha256:[0-9a-f]{64}$
submittedBy erforderlich object

The person who submitted it, as captured at submission.

Felder
Feld Typ Beschreibung
name erforderlich string
email erforderlich string
role erforderlich string | null
Werte: "owner", "admin", "member", "readonly"
receivedAt erforderlich string (date-time)
relatedEntity erforderlich object | null
Felder
Feld Typ Beschreibung
id erforderlich string (uuid)
name erforderlich string

The company's current name, resolved at read time.

body erforderlich string

Verwendet von: POST /tenants/{tenantId}/processing-instructions , GET /tenants/{tenantId}/processing-instructions/{id}

ProcessingInstructionSummary object

Feld Typ Beschreibung
id erforderlich string (uuid)
kind erforderlich string
Werte: "instruction", "objection"
channel erforderlich string

Always product in this version.

Werte: "product", "email", "letter", "oral"
subject erforderlich string
bodyHash erforderlich string

SHA-256 over the UTF-8 bytes of the text exactly as stored.

Muster: ^sha256:[0-9a-f]{64}$
submittedBy erforderlich object

The person who submitted it, as captured at submission.

Felder
Feld Typ Beschreibung
name erforderlich string
email erforderlich string
role erforderlich string | null
Werte: "owner", "admin", "member", "readonly"
receivedAt erforderlich string (date-time)
relatedEntity erforderlich object | null
Felder
Feld Typ Beschreibung
id erforderlich string (uuid)
name erforderlich string

The company's current name, resolved at read time.

Verwendet von: GET /tenants/{tenantId}/processing-instructions

FeedbackSubmission object

The submission part of POST /feedback. Every level refuses unknown fields, so a field naming the account, company, person, role or environment is a 400, never merged.

Feld Typ Beschreibung
category erforderlich string

broken — something does not work; dislike — something is unpleasant; idea — a wish.

Werte: "broken", "dislike", "idea"
message erforderlich string
Länge: min. 1, max. 4000
contactOptIn erforderlich boolean

true lets the vendor reply. The session's verified address is then stored with the report; it is never taken from the body.

context erforderlich FeedbackContext
screenshot FeedbackScreenshotMeta

Verwendet von: POST /feedback

FeedbackReceipt object

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

The short reference to quote when following up.

Muster: ^FB-[A-Z2-7]{8}$
receivedAt erforderlich string (date-time)

Verwendet von: POST /feedback

FeedbackRefusal object

A refusal of POST /feedback. Names paths and reason codes, never a submitted value.

Feld Typ Beschreibung
error erforderlich string
Werte: "validation_error", "payload_too_large", "unsupported_media_type"
issues erforderlich Array<object>
Felder
Feld Typ Beschreibung
path erforderlich string
code erforderlich string

Verwendet von: POST /feedback

PlanCatalog object

The unauth-safe plan catalog — the SELF-SERVE tiers with their entitlements and price points, served VERBATIM from the PLAN_CATALOG_V1 code constant. Every entry's deprecated price.provisional is false; no €-figure originates in the FE. A tier absent from this array is not "removed": it may still be a valid plan_tier, resolvable and billed normally, and simply is not self-serve.

Feld Typ Beschreibung
catalogVersion erforderlich string
Werte: "v1"
plans erforderlich Array<PlanCatalogEntry>

Verwendet von: GET /billing/plans

AuditLogEntry object

One audit-log record projected for the read API — METADATA ONLY. The hashed IP and tenantId are deliberately absent (the raw IP is never stored; tenantId is implied by the path).

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

The action, e.g. user.login, entity.created, api_key.revoked.

actorId erforderlich string

The acting user (users.id), or service:{name} for a worker-initiated event. Resolvable to a member for the Team feed.

entityId erforderlich string (uuid) | null

The entity in scope, or null for tenant-level events.

resourceType erforderlich string

The kind of the affected resource, e.g. user, entity, api_key.

resourceId erforderlich string

Polymorphic id of the affected resource (target varies by resourceType).

timestamp erforderlich string (date-time)

When the event occurred (UTC).

requestId erforderlich string

Correlation id for the originating request.

Verwendet von: GET /tenants/{tenantId}/audit-log

PlanEntitlements object

Feld Typ Beschreibung
includedDocumentsPerPeriod erforderlich integer

Finalized live documents included per billing period before overage.

Bereich: ≥ 0

Verwendet von: GET /tenants/{tenantId}/billing , GET /tenants/{tenantId}/billing-recovery , GET /billing/plans

ProvisionalPrice object

A price point. All amounts are INTEGER MINOR UNITS (cents), never a float. Every price returned is the price that applies; the price shown before ordering binds.

Feld Typ Beschreibung
provisional erforderlich veraltet boolean

Deprecated. Always false since 2026-10-06: the prices returned here are binding, and the price shown before ordering is the price that applies. The property stays so that existing clients keep parsing; do not branch on it. It will be removed only in a major version.

Werte: false
currency erforderlich string
Werte: "EUR"
baseMonthlyMinor erforderlich integer

Base fee per billing period, in cents.

Bereich: ≥ 0
overagePerDocumentMinor erforderlich integer

Price per document beyond the included allotment, in cents. A single FLAT rate — there is no bracketed ladder, no prepaid credit balance and no volume step.

Bereich: ≥ 0
capMonthlyMinor erforderlich integer | null

A ceiling on the whole monthly amount, in cents. null — no tier carries a ceiling: the amount for a period is baseMonthlyMinor + overagePerDocumentMinor × max(0, billableUnits − includedDocumentsPerPeriod). The property is kept so that existing clients keep parsing; do not render a ceiling from it.

Bereich: ≥ 0

Verwendet von: GET /tenants/{tenantId}/billing , GET /tenants/{tenantId}/billing-recovery , GET /billing/plans

MaskedBillingAccount object

The tenant's billing account, MASKED for the wire — it NEVER carries providerCustomerId/mandateRef (the same masking rule as every other billing read). null on the parent read when billing is not enabled or no account row exists yet.

Feld Typ Beschreibung
provider erforderlich string
Werte: "mock", "stripe", "mollie"
mandateStatus erforderlich MandateState
defaultMethod erforderlich string | null
Werte: "sepa", "card"

Verwendet von: GET /tenants/{tenantId}/billing , GET /tenants/{tenantId}/billing-recovery

TenantEntitlement object

The entitlement-on-authorisation view: the tenant's EFFECTIVE allowance (override-aware, via resolveEffectivePricing — the same seam as plan/usage) + this period's usage verdict against it. status REPORTS within/over allowance; it never blocks issuance (no overage enforcement — the owner-reserved pricing question). Entitlement is granted on authorisation (cards at launch; the launch tier is €0/no-card).

Feld Typ Beschreibung
includedDocumentsPerPeriod erforderlich integer

EFFECTIVE included documents (override-aware); byte-consistent with the catalog when no override.

Bereich: ≥ 0
billableUnits erforderlich integer

Live finalized documents in the period (same figure as usage/GET .../usage).

Bereich: ≥ 0
overageUnits erforderlich integer

max(0, billableUnits − includedDocumentsPerPeriod). Reported, never enforced.

Bereich: ≥ 0
status erforderlich string

The allowance verdict for the period. Reporting only.

Werte: "within_allowance", "over_allowance"
liveIssuance erforderlich boolean

Whether this plan issues live documents at all. false means no live document can be issued on this plan — a capability statement, not an exhausted allowance, and the two are rendered differently: a plan with no live issuance has no allowance to count against, so no meter, no proportion and no remaining figure applies to it. Read this field rather than comparing the allowance with zero.

band erforderlich string

The resolved state for this period, computed by the server so that every client renders the same state for the same account on the same day. none — nothing needs to be shown. approaching_allowance — the included allowance is nearly used up, and additional documents will be charged. overage_priced — the allowance is used up and additional documents are being charged at the published rate. cap_reached — reserved; not emitted while no plan carries a ceiling (which is the case for every plan today). not_included — this plan does not issue live documents. Clients must not compute this from the numbers themselves: the thresholds are a commercial rule and they are deliberately not published as a formula.

Werte: "none", "approaching_allowance", "overage_priced", "cap_reached", "not_included"
consequence erforderlich string

What happens to the NEXT live document on this plan — included (covered by the allowance), priced_as_overage (charged at the published per-document rate) or refused (this plan issues no live documents). Distinct from band, which is a display state.

Werte: "included", "priced_as_overage", "refused"
periodResetsOn erforderlich string (date)

The date on which the next period begins and the counts return to zero. A date, never a rule — clients print it and do not derive a period boundary of their own.

Verwendet von: GET /tenants/{tenantId}/billing , GET /tenants/{tenantId}/billing-recovery

PaymentMethodChange object

A payment-method replacement that is under way or that did not complete. Present on the billing status ONLY in those two cases. Its ABSENCE is the normal state and must not be read as "no replacement has ever happened": a completed replacement is simply the account's current payment method, so it is deliberately indistinguishable from one that never started. The reason a replacement did not complete is not published, here or on any other read.

Feld Typ Beschreibung
state erforderlich string

pending — a replacement is under way; no second hand-off is offered while it is. failed — it did not complete, and the account is UNCHANGED: it still holds the payment method it had, and it is still chargeable.

Werte: "pending", "failed"
startedAt erforderlich string (date-time)

When this replacement was started.

Verwendet von: GET /tenants/{tenantId}/billing , GET /tenants/{tenantId}/billing-recovery

BillingParty object

Who receives the invoices we issue for this subscription. German VAT law requires the recipient's full name and address on an invoice, so this must be on file before a subscription can start. It may be omitted once it has already been supplied; sending it again replaces what is held, and the replacement applies to the next invoice rather than to invoices already issued.

It is never taken from the company profile used for the invoices this account itself issues. That profile describes the account as a SELLER, and reusing it would mean editing a letterhead silently changed who we bill and where.

Feld Typ Beschreibung
companyName string

The legal name of the company we invoice.

addressLine1 string

Street and house number.

postalCode string
city string
countryCode string

ISO 3166-1 alpha-2. Only DE is accepted today; anything else is refused with billing_party_country_unsupported.

vatId string | null

VAT identification number. Genuinely optional — a small business exempt from VAT has none. Absent is accepted; an empty string is not.

billingEmail string (email)

Where the invoices we issue are sent. Format-checked ONLY when it carries a value: a malformed address is a 400 bad request, while blank and absent both fall through to the named 422 billing_party_incomplete refusal the client switches on. The two answers mean different things and a client may rely on that.

Verwendet von: POST /tenants/{tenantId}/billing/subscribe , POST /tenants/{tenantId}/billing-recovery/subscribe

MandateState string

The state of an account's standing permission to be charged. ONE definition, referenced everywhere this value is published, so the set can never differ between two reads. failed means an attempt to obtain that permission did not succeed — as distinct from revoked, which means permission was held and later withdrawn; the two differ in what a customer should do next. The reason a capture did not succeed is deliberately not published, here or on any other read. Only active prevents starting a new subscription attempt.

  • Werte: "none", "pending", "active", "revoked", "failed"

Verwendet von: GET /tenants/{tenantId}/billing , POST /tenants/{tenantId}/billing/subscribe , GET /tenants/{tenantId}/billing-recovery , POST /tenants/{tenantId}/billing-recovery/subscribe

FeedbackContext object

The screen the report was sent from. A route template, never a page address.

Feld Typ Beschreibung
surface erforderlich string

The application the report was sent from, set by its server.

Werte: "web", "dashboard"
buildSha erforderlich string

The build of the application's server.

Muster: ^[0-9a-f]{7,40}$
clientBuildSha string

The build the browser was running, when it differs.

Muster: ^[0-9a-f]{7,40}$
route erforderlich string

The route TEMPLATE (for example /(app)/rechnungen/[id]), never a concrete path, query or fragment.

Muster: ^/[A-Za-z0-9_()\[\]/.-]{0,200}$
pageLabel string
Länge: max. 80
entityRef object

An opaque reference to the record on screen. It is stored and shown as text; nothing opens it.

Felder
Feld Typ Beschreibung
type erforderlich string
Länge: min. 1, max. 40
id erforderlich string
Muster: ^[A-Za-z0-9_-]{1,64}$
dialogSurface string | null
Länge: max. 80
sidebar string
Werte: "collapsed", "expanded"
viewport object
Felder
Feld Typ Beschreibung
width erforderlich integer
Bereich: ≥ 0, ≤ 100000
height erforderlich integer
Bereich: ≥ 0, ≤ 100000
devicePixelRatio erforderlich number
Bereich: > 0, ≤ 16
device string

A coarse device class derived in the browser, never the raw browser identification.

Länge: max. 120
locale string
Länge: max. 35
timeZone string

An IANA time-zone name.

Länge: max. 64
clientTimestamp erforderlich string (date-time)

Verwendet von: POST /feedback

FeedbackScreenshotMeta object

Required exactly when the screenshot part is present. Asserted by the client: it shows that masking ran and proves nothing about the pixels.

Feld Typ Beschreibung
maskedCount erforderlich integer
Bereich: ≥ 0, ≤ 100000
manualHiddenCount erforderlich integer
Bereich: ≥ 0, ≤ 1000
highlightCount erforderlich integer
Bereich: ≥ 0, ≤ 1000
width erforderlich integer
Bereich: ≥ 1, ≤ 4096
height erforderlich integer
Bereich: ≥ 1, ≤ 4096

Verwendet von: POST /feedback

PlanCatalogEntry object

One tier of the plan catalog (served verbatim from PLAN_CATALOG_V1).

Feld Typ Beschreibung
tier erforderlich string

The plan_tier correlation key. The full vocabulary is listed because it is the persisted enum; only the SELF-SERVE subset is ever returned by GET /billing/plans. Map figures by this value, never by label. Treat a value you do not recognise as a plan you cannot describe rather than as an error: the set may grow.

Werte: "launch", "entry", "platform", "sovereign"
label erforderlich string

Pricing-card label (German-first).

entitlements erforderlich PlanEntitlements
price erforderlich ProvisionalPrice
supportResponseClass erforderlich string

What we promise about answering you, as a class. ⚠ Enforced by a calendar, not by code — nothing in the API measures, alerts on or reports against it, and there is no SLA clock, ticket surface or status page behind it.

Werte: "community", "one_business_day"
supportResponseNote string

An optional operator note qualifying supportResponseClass (e.g. a known period of delayed replies), echoed verbatim. ABSENT when the operator has set none — never an empty string.

Verwendet von: GET /billing/plans

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