415. Encode JSON
as UTF-8: a Content-Type charset parameter is honoured only for the UTF-8,
UTF-16, and UTF-32 family, any other charset is 415 unsupported_media_type.
A request body is parsed strictly: invalid JSON, nesting deeper than 64
levels, non-finite numbers, unpaired surrogate escapes, and duplicate object
members are 400 parse_error, and a declared Content-Length above the
body ceiling is 413. Always send a
Content-Length; a chunked request body without one is 411 length_required.
Commands that declare no request body (rotate-secret, redeliver, Partner
membership resend and cancel, the Mahnservice invoice actions) and every
DELETE on the Case Management and Partner APIs reject one with
400 unexpected_body — including {}, [], and null; only an empty body is
accepted, and a rejected DELETE deletes nothing. finalize is the exception:
it accepts an empty body or exactly {}, and any member is
400 validation_error with unknown_field.
Canonical URLs
Send the documented trailing slash on every Case Management (/v2/) and
Partner (/partner/v2/) request. Slashless roots and routes return JSON 404
for every method, including GET, HEAD, OPTIONS, and writes, with no
redirect and no Location header. For example, /v2/info and
/partner/v2/info are unavailable; use /v2/info/ and /partner/v2/info/.
This contract is the same in production and the sandbox.
Data conventions
- IDs are UUID strings named
id. A path segment that is not a UUID answers404on every method, before authentication and without consuming rate-limit capacity. - Timestamps such as
created_atandupdated_atuse RFC 3339 in UTC: the Case Management and Partner APIs renderZ, the Mahnservice API+00:00. Accept both when parsing. - Single-line text fields (names, references, subjects, labels) reject
control characters — including the line separators U+2028 and U+2029 —
with
400 control_characters; invisible format characters are stripped and the text is NFC-normalized before validation. Multi-line fields (claimitems[].description,legal_basis.description,dispute_reason, messagebody, request-to-client answers, the withdrawalreason) allow TAB, LF, and CR, store every line break as\n, and reject other control characters. - Most string fields (
title,body,text,your_reference,street,issuing_authority,file_number) accept a JSON number and store its decimal text; the multi-line claim fields and the withdrawalreasonreject non-strings with400 invalid. Booleans such ascreditor_obligation_fulfilled,is_disputed, and webhookenabledmust be JSONtrue/false—"true",1, and""are400 invalid. YYYY-MM-DDdates and numeric identifiers (postal codes, IBANs,limit,offset) accept ASCII digits only; other Unicode digits are400 invalid(query parameters:invalid_parameter).- Calendar-date field names end in
_date. - Money is a decimal-string value in EUR, for example
{ "value": "125.50", "currency": "EUR" }. - Collection members are updated with
PATCH.PUTexists only for a singleton sub-resource that you create or replace in place, currently the enforceable title of a claim in a titled order (PUT /v2/claims/{claim_id}/enforceable-title/). - Unknown fields, read-only fields, and unknown nested fields are rejected with
400instead of being ignored — on the Mahnservice API as well. Unknown query parameters are400too.
Mahnservice request details
Use the trailing slash on/mahnservice/v1/ routes. A GET or HEAD
without it redirects with 301; a write without it returns 404.
All Mahnservice responses, including schema and documentation responses and
the JSON 404 for an unmatched route, include X-Paywise-Request-Id,
X-Paywise-Environment, and Cache-Control: private, no-store.
Unknown body fields return 400 unknown_field, read-only fields
400 read_only_field, and unknown query parameters 400 unknown_parameter.
For pagination and rate limits, use the shared
pagination guide and
public limits.
Idempotency keys
Every effectfulPOST on the Case Management and Partner APIs requires an
Idempotency-Key header. The key marks one logical command, so that a retry
after a timeout or an ambiguous outcome can never create a second order,
payment, or webhook subscription:
- Generate one opaque key (a fresh UUID works) per logical command and retain it until the outcome is known.
- Retrying an ambiguous result means resending the same key with the same
body. A completed command replays its original response with
Idempotency-Replayed: true; the same key with a different body returns a typed409. - A new logical command always gets a new key — reusing yesterday’s key for today’s write turns a real command into a replay.
GET,PATCH,PUT, andDELETEtake no key and ignore one that is sent: reads are naturally safe to repeat, and a partial update resent unchanged converges on the same state (it does not even bumpupdated_ator theETag).
Idempotency-Key header; there, submitting an
invoice is idempotent on its invoice_number instead. See
Errors and safe retries for
the full retry semantics, key scoping, and the idempotency error codes.
Partial updates
WithPATCH, an omitted field remains unchanged. null clears only a field
documented as nullable. An empty object still counts as supplied and is
validated. When an owned array is supplied, it replaces the complete array;
children omitted from the replacement are deleted if nothing else references
them. Omit the array to leave it unchanged.
X-Paywise-Request-Id and
X-Paywise-Environment headers. Authenticated data uses
Cache-Control: private, no-store; only the immutable legal-form catalog may
be cached privately for one day.
Conditional requests
On the Case Management and Partner APIs every single-resource read (GET …/{id}/) and every PATCH or PUT response carries a strong ETag
that identifies the returned representation. Lists, POST responses,
downloads, and idempotent replays carry none. The Mahnservice API publishes
no ETag and ignores If-Match and If-None-Match.
- Skip an unchanged re-read. Send the last
ETagasIf-None-Match. When the representation is unchanged the response is304 Not Modifiedwith an empty body and the sameETag; otherwise you receive the full200body. - Guard a write against a concurrent change. Send the
ETagyou last read asIf-MatchonPATCH,PUT, orDELETE. When the current representation no longer matches, the request fails with412and the codeprecondition_failed, and nothing is written. Re-read the resource, merge, and retry with the fresh validator.If-Match: *only requires that the resource exists.
W/"…") never satisfy If-Match; the validator is compared
literally against the current representation, so keep the quotes exactly as
received. If-None-Match is evaluated on GET only. On orders, claims, the
enforceable title, the rental agreement, and messages, the If-Match check
runs again after the write has locked the resource, so of two writers that
both hold the current ETag the second gets 412. On every other resource the
check is optimistic: it runs before the write is locked, so two such writers
can both succeed and the last one wins — re-read after a 200 if you need to
confirm the final state. A write route whose members are
read elsewhere (DELETE /v2/claims/{claim_id}/payments/{id}/
reads at GET /v2/payments/{id}/) has no representation to compare and answers
If-Match with 412; omit the header there.
Recover from invalid requests
For400, read the machine-readable top-level code and each nested field
error, correct only the rejected fields, and submit a new request. For 415,
switch to the media type named by the operation. Do not turn a rejected PUT
into a full-object PATCH; send only the intended changes.
See update a Partner company
for a JSON partial update and
create a claim document
for a current upload operation.