Konto & Abrechnung
Tarif, Abrechnung, Nutzung, Audit-Protokoll und Kontoschließung.
Plans, billing, usage, audit log, and account closure.
Operationen
-
GET/me/account/close-disclosureThe counsel-owned retention disclosure shown before account closure -
GET/me/account/close-blockersPreconditions that must be resolved before the caller can close their account -
POST/me/account/closeClose the caller's account and erase their identity (Art. 17) -
GET/legal/current-versionsThe legal documents currently published, and the sentence each is accepted by -
GET/tenants/{tenantId}/audit-logTenant-scoped audit log (cursor-paginated, owner-only) -
GET/tenants/{tenantId}/usageUsage snapshot for the period (finalized-document rollup, admin/owner) -
GET/tenants/{tenantId}/billingThe tenant's billing status (tier + effective price + masked account + usage) -
POST/tenants/{tenantId}/billing/subscribeStart a subscription — obtain permission to charge this account -
POST/tenants/{tenantId}/billing/payment-method/replaceReplace the payment method this account already holds -
GET/tenants/{tenantId}/billing-recoveryThe billing status read that still answers a SUSPENDED account -
POST/tenants/{tenantId}/billing-recovery/subscribeStart a subscription from a SUSPENDED account whose mandate is revoked -
POST/tenants/{tenantId}/billing-recovery/payment-method/replaceReplace the payment method from a SUSPENDED account whose mandate is active -
POST/tenants/{tenantId}/legal-acceptancesAccept the currently published legal documents -
POST/tenants/{tenantId}/processing-instructionsSubmit an instruction or an objection to the processor -
GET/tenants/{tenantId}/processing-instructionsList this account's instructions and objections, newest first -
GET/tenants/{tenantId}/processing-instructions/{id}One instruction or objection with its full text -
POST/feedbackSend feedback about the screen you are on -
GET/tenants/{tenantId}/legal-acceptances/{document}/documentRetrieve the record of an accepted legal document -
GET/tenants/{tenantId}/legal-acceptance-statusWhich legal documents this account still has to accept -
GET/billing/plansThe self-serve plan catalog (unauth-safe) -
POST/billing/provider-webhookInbound 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
-
200The retention disclosure (conservative placeholder until counsel rules).
application/jsonAccountCloseDisclosure -
401Missing/invalid OIDC token
-
404The 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
-
200The caller's unresolved close preconditions (possibly empty).
application/jsonAccountCloseBlockers -
401Missing/invalid OIDC token
-
404The 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
-
200The account was closed, or was already closed (idempotent no-op).
application/jsonAccountCloseResult -
401Missing/invalid OIDC token
-
404The account-closure surface is not enabled.
-
409Unresolved preconditions (last-owner tenant / active subscription). The body carries the machine-readable
blockerschecklist.application/jsonobject
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
-
200The currently published documents, possibly none.
application/jsonobject -
401No 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 |
cursor | Query | string | Opaque keyset cursor from a prior page's |
limit | Query | integer | Page size for cursor-paginated reads. Standard: 25 Bereich: ≥ 1, ≤ 100 |
eventType | Query | string | Filter by action, e.g. |
actorId | Query | string (uuid) | Filter by actor ( |
resourceType | Query | string | Filter by target kind, e.g. |
from | Query | string (date-time) | Inclusive |
to | Query | string (date-time) | Inclusive |
Antworten
-
200A page of audit records (metadata only).
application/jsonAuditLogPage -
400The query string was refused. Three distinguishable facts share this status, and a client should branch on the
errorcode rather than on the status:unknown_query_parameter— the request named one or more query parameters this deployment does not define, andparameterslists their names. Nothing was filtered and nothing was written. This is a DEPLOY-ORDER signal: it is what a client released ahead of the API sees, and it replaces the older behaviour in which an unrecognised parameter was silently ignored and the request answered200 OKwith the UNFILTERED collection — indistinguishable, to the caller, from the filter having been applied.validation_error— a parameter this endpoint does define carried an unusable value (including a required one being absent or too short).issues[]names the parameter and what was wrong with it.invalid_cursor— on cursor-paginated endpoints, the pagination cursor is malformed. No response on this path ever echoes a submitted VALUE; only parameter names appear.application/jsonUnknownQueryParameterError | ValidationError -
403Caller is not
owner(insufficient_role), or the pathtenantIdis 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 |
from | Query | string (date-time) | Inclusive lower bound of the reporting window (RFC3339, half-open |
to | Query | string (date-time) | Exclusive upper bound (RFC3339). Must be supplied together with |
Antworten
-
200The usage snapshot for the resolved period.
application/jsonUsageSnapshot -
400The query string was refused. Three distinguishable facts share this status, and a client should branch on the
errorcode rather than on the status:unknown_query_parameter— the request named one or more query parameters this deployment does not define, andparameterslists their names. Nothing was filtered and nothing was written. This is a DEPLOY-ORDER signal: it is what a client released ahead of the API sees, and it replaces the older behaviour in which an unrecognised parameter was silently ignored and the request answered200 OKwith the UNFILTERED collection — indistinguishable, to the caller, from the filter having been applied.validation_error— a parameter this endpoint does define carried an unusable value (including a required one being absent or too short).issues[]names the parameter and what was wrong with it.invalid_cursor— on cursor-paginated endpoints, the pagination cursor is malformed. No response on this path ever echoes a submitted VALUE; only parameter names appear.application/jsonUnknownQueryParameterError | ValidationError -
403Caller lacks the admin role (
insufficient_role), or the pathtenantIdis 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 |
Antworten
-
200The tenant's billing status.
application/jsonTenantBillingStatus -
403Path
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 |
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 Länge: max. 255 |
Request-Body optional
application/json StartSubscription
Antworten
-
200The hand-off destination, or
nullon a rail that captured the instrument inline.application/jsonSubscriptionStart -
400The body carried a property this operation does not define, or an unsupported instrument family, or no
Idempotency-Keywas sent. -
403Path
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 theerrorcode 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 sameIdempotency-Keyworks once the gate is opened. -
409This account already holds an active mandate (
mandate_already_active). Nothing was created; a second payment page is never minted. -
422The billing details are missing, incomplete, or name a country we cannot yet bill. The
errorcode 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), andbilling_party_country_unsupported. Or thetiernamed 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. Aftertier_not_purchasablethe sameIdempotency-Keymay be used again with the corrected request. -
502The payment provider could not be reached, or did not return a usable destination (
provider_unavailable). NOTHING was stored — the caller may retry with a freshIdempotency-Key. -
503The 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 |
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 Länge: max. 255 |
Antworten
-
200The hand-off destination and the identifier of this replacement.
application/jsonPaymentMethodReplacementStart -
400No
Idempotency-Keywas sent, or the body carried a property this operation does not define. -
403Path
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 theerrorcode 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 sameIdempotency-Keyworks once the gate is opened. -
409This account holds no active payment method (
no_mandate_to_replace). There is nothing to replace; the account starts one through.../billing/subscribeinstead. Nothing was created. -
502The 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. -
503The 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 |
Antworten
-
200The tenant's billing status (same shape as the ordinary read).
application/jsonTenantBillingStatus -
403Path
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 |
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 Länge: max. 255 |
Request-Body optional
application/json StartSubscription
Antworten
-
200The hand-off destination, or
nullon a rail that captured the instrument inline.application/jsonSubscriptionStart -
400The body carried a property this operation does not define, or an unsupported instrument family, or no
Idempotency-Keywas sent. -
403Path
tenantId≠ active tenant (tenant_mismatch), the caller is not an owner, or an API credential was presented. -
409This account already holds an active mandate (
mandate_already_active) — the replace operation is the route for that population. Nothing was created. -
422The 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. -
502The payment provider could not be reached (
provider_unavailable). NOTHING was stored — the caller may retry with a freshIdempotency-Key. -
503The 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 |
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 Länge: max. 255 |
Antworten
-
200The hand-off destination and the identifier of this replacement.
application/jsonPaymentMethodReplacementStart -
400No
Idempotency-Keywas sent, or the body carried a property this operation does not define. -
403Path
tenantId≠ active tenant (tenant_mismatch), the caller is not an owner, or an API credential was presented. -
409This account holds no active payment method (
no_mandate_to_replace); it starts one through.../billing-recovery/subscribeinstead. Nothing was created. -
502The payment provider could not be reached (
provider_unavailable). NOTHING was stored and the account is unchanged. -
503The 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 |
Request-Body erforderlich
application/json object
Antworten
-
200The post-state, one entry per document this platform asks accounts to accept — the same shape the status read answers in.
application/jsonobject -
400The 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/jsonValidationError -
403tenant_mismatch(the path tenant is not the active one),insufficient_role(member/readonly), orcredential_not_accepted(an API key). -
409A 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/jsonobject
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 |
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
-
201The stored record (or, on an Idempotency-Key replay, the recorded response).
application/jsonProcessingInstruction -
400validation_error, naming the fields and never repeating a submitted value. IncludesrelatedEntityIdnaming no company of this account, and a carriage return insubjectorbody.application/jsonValidationError -
403tenant_mismatch(the path tenant is not the active one),credential_not_accepted(an API key), orinsufficient_role(member,readonly). -
409Idempotency-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/jsonobject -
422submitter_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 |
limit | Query | integer | Standard: 50 Bereich: ≥ 1, ≤ 100 |
cursor | Query | string | Opaque; the |
Antworten
-
200One page. No text bodies.
application/jsonobject -
400The query string was refused. Three distinguishable facts share this status, and a client should branch on the
errorcode rather than on the status:unknown_query_parameter— the request named one or more query parameters this deployment does not define, andparameterslists their names. Nothing was filtered and nothing was written. This is a DEPLOY-ORDER signal: it is what a client released ahead of the API sees, and it replaces the older behaviour in which an unrecognised parameter was silently ignored and the request answered200 OKwith the UNFILTERED collection — indistinguishable, to the caller, from the filter having been applied.validation_error— a parameter this endpoint does define carried an unusable value (including a required one being absent or too short).issues[]names the parameter and what was wrong with it.invalid_cursor— on cursor-paginated endpoints, the pagination cursor is malformed. No response on this path ever echoes a submitted VALUE; only parameter names appear.application/jsonUnknownQueryParameterError | ValidationError -
403tenant_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 |
id erforderlich | Pfad | string (uuid) |
Antworten
-
200The record.
application/jsonProcessingInstruction -
403tenant_mismatch·credential_not_accepted·insufficient_role -
404not_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
-
201The report was recorded (or, on an
Idempotency-Keyreplay with the same content, the first report's receipt).application/jsonFeedbackReceipt -
400validation_error: a part or field is missing, unknown or out of range — including a field naming the account, company, person, role or environment, asubmissionpart over 16 KiB, a message outside 1–4000 characters, arouteorentityRef.idthat fails its pattern, orcontactOptInon a session without an address. The issues name paths and reason codes, never a submitted value.application/jsonFeedbackRefusal -
403credential_not_accepted(an API key),no_tenant_membership, ortenant_suspended(the account is fully suspended; partial suspension measures do not refuse feedback). -
409IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_BODY: this key was already used for a report with different content. Nothing was recorded.application/jsonobject -
413payload_too_large: the request is over 512 KiB (refused unread when declared), or the screenshot part is over 400 KiB. -
415unsupported_media_type: the request is notmultipart/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/jsonFeedbackRefusal -
429feedback_rate_limited: 20 reports by this person or 100 by this account today (Europe/Berlin). Nothing was recorded.-
Retry-AfterintegerSeconds 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. |
Antworten
-
200The conclusion document.
text/htmlstring -
400The path named no document this API has.
application/jsonValidationError -
403tenant_mismatch(the path tenant is not the active one),insufficient_role(member/readonly), orcredential_not_accepted(an API key). -
404This 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/jsonobject
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 |
Antworten
-
200One entry per document this platform asks accounts to accept.
application/jsonobject -
403Path 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
-
200The plan catalog.
application/jsonPlanCatalog
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 |
|
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
-
200Processed, deduped (redelivery), or ignored (unknown/irrelevant event) — the provider must stop retrying.
-
401Missing/invalid provider signature (
invalid_signature) — rejected before parse. -
413The 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. -
429Too 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 carriesRetry-After— retry after the stated delay; nothing was read, written or confirmed. -
503We could not complete a call back to the payment provider, so this notification was neither confirmed nor recorded (
confirmation_unavailable). RETRYABLE, and it carriesRetry-After— nothing was written, and a redelivery of the same message is processed normally rather than treated as a duplicate. This is deliberately not answered200(which would state we hold something we never confirmed) nor401(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
|
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 Felder
| ||||||||||||
totalBillableUnits erforderlich | integer | Total live finalized documents in the period (sum of Bereich: ≥ 0 | ||||||||||||
byEventType erforderlich | object | Billable-unit count keyed by finalize event type (always all three keys). Felder
|
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
| ||||||||||||||||||||||||
account erforderlich | MaskedBillingAccount | null | The masked billing account, or | ||||||||||||||||||||||||
usage erforderlich | object | The current-period usage — Felder
| ||||||||||||||||||||||||
entitlement erforderlich | TenantEntitlement | |||||||||||||||||||||||||
cancellation erforderlich | object | The subscription-cancellation state of this account. Always present — a client feature-detects on Felder
| ||||||||||||||||||||||||
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 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. |
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 |
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 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
| ||||||||||||
receivedAt erforderlich | string (date-time) | |||||||||||||
relatedEntity erforderlich | object | null | Felder
| ||||||||||||
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 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
| ||||||||||||
receivedAt erforderlich | string (date-time) | |||||||||||||
relatedEntity erforderlich | object | null | Felder
|
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 |
Werte: "broken", "dislike", "idea" |
message erforderlich | string | Länge: min. 1, max. 4000 |
contactOptIn erforderlich | boolean |
|
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
|
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. |
actorId erforderlich | string | The acting user ( |
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. |
resourceId erforderlich | string | Polymorphic id of the affected resource (target varies by |
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 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. 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 Bereich: ≥ 0 |
overageUnits erforderlich | integer |
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. |
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. Werte: "none", "approaching_allowance", "overage_priced", "cap_reached", "not_included" |
consequence erforderlich | string | What happens to the NEXT live document on this plan — 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 |
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 |
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 |
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 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
| ||||||||||||
dialogSurface | string | null | Länge: max. 80 | ||||||||||||
sidebar | string | Werte: "collapsed", "expanded" | ||||||||||||
viewport | object | Felder
| ||||||||||||
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 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 |
Verwendet von:
GET /billing/plans
Nach oben · Alle Ressourcengruppen · OpenAPI-Spezifikation (YAML)