> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paywise.de/llms.txt
> Use this file to discover all available pages before exploring further.

# Requests and responses

> Use the current paywise JSON, multipart, identifier, timestamp, money, and partial-update conventions.

JSON is the only request media type on all three APIs; document upload
operations additionally accept multipart data. Send the media type declared by
the current operation reference; unsupported types return `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](/api-docs/essentials/limits) 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.

| API prefix | Slashless GET / HEAD | Slashless other methods | Redirect behavior |
| - | - | - | - |
| `/v2/` | JSON `404` | JSON `404` | No redirect; no `Location` header |
| `/partner/v2/` | JSON `404` | JSON `404` | No redirect; no `Location` header |
| `/mahnservice/v1/` | `301` | `404` | GET/HEAD redirect to the canonical URL |

## Data conventions

* IDs are UUID strings named `id`. A path segment that is not a UUID answers
  `404` on every method, before authentication and without consuming
  rate-limit capacity.
* Timestamps such as `created_at` and `updated_at` use RFC 3339 in UTC: the
  Case Management and Partner APIs render `Z`, 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 (claim
  `items[].description`, `legal_basis.description`, `dispute_reason`, message
  `body`, request-to-client answers, the withdrawal `reason`) 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 withdrawal `reason` reject
  non-strings with `400 invalid`. Booleans such as
  `creditor_obligation_fulfilled`, `is_disputed`, and webhook `enabled` must
  be JSON `true`/`false` — `"true"`, `1`, and `""` are `400 invalid`.
* `YYYY-MM-DD` dates and numeric identifiers (postal codes, IBANs, `limit`,
  `offset`) accept ASCII digits only; other Unicode digits are `400 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`. `PUT` exists 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
  `400` instead of being ignored — on the Mahnservice API as well. Unknown
  query parameters are `400` too.

For Mahnservice amount, currency, address, email, and text formats, see its
[input field rules](/api-docs/mahnservice-api/concepts/invoice-lifecycle#input-field-rules).

## 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](/api-docs/essentials/pagination-and-synchronization) and
[public limits](/api-docs/essentials/limits).

## Idempotency keys

Every effectful `POST` 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:

```http theme={null}
POST /v2/orders/ HTTP/1.1
Host: api.paywise.de
Authorization: Bearer <api-key>
Content-Type: application/json
Idempotency-Key: 80000000-0000-4000-8000-000000000001
```

* 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
  typed `409`.
* 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`, and `DELETE` take 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 bump `updated_at`
  or the `ETag`).

The Mahnservice API takes no `Idempotency-Key` header; there, submitting an
invoice is idempotent on its `invoice_number` instead. See
[Errors and safe retries](/api-docs/essentials/errors-and-safe-retries) for
the full retry semantics, key scoping, and the idempotency error codes.

## Partial updates

With `PATCH`, 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.

```http theme={null}
PATCH /partner/v2/companies/10000000-0000-4000-8000-000000000001/ HTTP/1.1
Host: api.paywise.de
Authorization: Bearer <partner-api-key>
Content-Type: application/json

{"name": "Example GmbH"}

HTTP/1.1 200 OK
Content-Type: application/json
X-Paywise-Request-Id: 90000000-0000-4000-8000-000000000001
X-Paywise-Environment: production

{
  "id": "10000000-0000-4000-8000-000000000001",
  "customer_number": "5G0123",
  "name": "Example GmbH",
  "case_access": "available",
  "case_submission_readiness": {"ready": true, "issues": []},
  "created_at": "2026-08-01T08:00:00Z",
  "updated_at": "2026-08-27T10:15:30Z"
}
```

Every response, including an error, has fresh `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 `ETag` as `If-None-Match`. When
  the representation is unchanged the response is `304 Not Modified` with an
  empty body and the same `ETag`; otherwise you receive the full `200` body.
* **Guard a write against a concurrent change.** Send the `ETag` you last read
  as `If-Match` on `PATCH`, `PUT`, or `DELETE`. When the current representation
  no longer matches, the request fails with `412` and the code
  `precondition_failed`, and nothing is written. Re-read the resource, merge,
  and retry with the fresh validator. `If-Match: *` only requires that the
  resource exists.

```http theme={null}
GET /v2/debtors/30000000-0000-4000-8000-000000000001/ HTTP/1.1
Host: api.paywise.de
Authorization: Bearer <api-key>

HTTP/1.1 200 OK
ETag: "9f2c1d0e6a7b4c3d8e1f0a9b2c3d4e5f"

PATCH /v2/debtors/30000000-0000-4000-8000-000000000001/ HTTP/1.1
Host: api.paywise.de
Authorization: Bearer <api-key>
If-Match: "9f2c1d0e6a7b4c3d8e1f0a9b2c3d4e5f"
Content-Type: application/json

{ "your_reference": "K-2026-042" }
```

Both headers are optional; a request without them behaves as before. Weak
validators (`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

For `400`, 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](/api-docs/partner-api/reference/companies/update-a-company-patch)
for a JSON partial update and
[create a claim document](/api-docs/case-management-api/reference/documents/create-claim-document)
for a current upload operation.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.