> ## 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.

# Report payments

> Report payments you receive directly from debtors against a claim or mandate, reconcile your reports, and recover safely.

## Outcome

A positive EUR payment you received directly from a debtor is reported against
the intended claim or mandate. You can reconcile your report through the
top-level read-only payment collection.

Payments received by paywise are reflected in the mandate's balance, published
status updates, and statements. See
[Payments](/api-docs/case-management-api/concepts/payments#following-payments-received-by-paywise)
for how to follow those receipts.

## Prerequisites

* A Bearer key with `case:payments:write` (listing a claim's payments needs
  `case:payments:read`); the default `*` grant includes both.
* The exact claim UUID before acceptance, or the exact mandate or subcase UUID
  after acceptance. Keep the claim UUID even if review changes its order.
* A non-future `value_date`, positive decimal amount, and stable external
  `your_reference`.
* A stable idempotency key per payment-report command.

## Lifecycle context

During order creation and submission, use claim payments to record partial
payments (**Teilzahlungen**) you received before handing the case over to
paywise. Include them inline when creating the claim or add them to the
draft through the claim-scoped endpoint below. See
[Partial payments before handover](/api-docs/case-management-api/concepts/payments#partial-payments-before-handover-teilzahlungen)
for how to preserve the original invoice amount and record receipts separately.

Before acceptance, report against the claim. While the order is `draft`,
`submitted`, or `awaiting_client_response`, the payment is recorded on that
claim, reduces its total, and is visible to the review team; it is deletable
only while the order is still `draft`. After acceptance, report against the exact mandate or subcase.
The accepted-claim route remains supported for compatibility: it delegates to
accepted-case reporting, preserves exact claim attribution, and returns `201`.
A rejected or cancelled claim, or a mandate whose processing has ended,
returns `409 conflict`.
Payment reported below an accepted claim, mandate, or subcase is
immediately immutable. Top-level payments are read-only and there is no
reversal or negative-payment operation. While the order is still `draft`, a
payment that would exceed the claim's open value is rejected with
`400 overpayment`; on an accepted case overpayment is accepted and reconciled
by paywise. See [Payments](/api-docs/case-management-api/concepts/payments#overpayment-and-duplicates)
for the duplicate guards on both routes.

## 1. Choose exactly one target

Generate one per-command business reference and body before choosing a target
branch:

```bash theme={null}
export PAYMENT_REFERENCE="BANK-$(uuidgen)"
jq -n --arg ref "$PAYMENT_REFERENCE" '{
  your_reference: $ref,
  amount: {value: "50.00", currency: "EUR"},
  value_date: "2026-08-26"
}' > payment.json
```

For a claim before acceptance (or the supported accepted-claim compatibility route):

<CodeGroup>
  ```python Python theme={null}
  import requests

  PAYWISE_API_URL = "https://api-sandbox.paywise.de"
  PAYWISE_API_KEY = "pw_sbx_your_api_key"
  CLAIM_ID = "40000000-0000-4000-8000-000000000001"
  PAYMENT_KEY = "your-payment-key"
  PAYMENT_REFERENCE = "BANK-80000000-0000-4000-8000-000000000001"

  payment_payload = {"your_reference": PAYMENT_REFERENCE, "amount": {"value": "50.00", "currency": "EUR"}, "value_date": "2026-08-26"}
  response = requests.post(
      f"{PAYWISE_API_URL}/v2/claims/{CLAIM_ID}/payments/",
      headers={"Authorization": f"Bearer {PAYWISE_API_KEY}", "Content-Type": "application/json", "Idempotency-Key": PAYMENT_KEY},
      json=payment_payload, timeout=(5, 30),
  )
  if response.status_code != 201:
      response.raise_for_status()
      raise RuntimeError(f"Expected 201, received {response.status_code}")
  ```

  ```javascript JavaScript theme={null}
  const paymentPayload = { your_reference: process.env.PAYMENT_REFERENCE, amount: { value: "50.00", currency: "EUR" }, value_date: "2026-08-26" };
  const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/claims/${process.env.CLAIM_ID}/payments/`, {
    method: "POST", headers: { Authorization: `Bearer ${process.env.PAYWISE_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": process.env.PAYMENT_KEY },
    body: JSON.stringify(paymentPayload), signal: AbortSignal.timeout(30000),
  });
  if (response.status !== 201) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  ```

  ```java Java theme={null}
  import java.io.IOException;
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.time.Duration;

  class ReportClaimPaymentRequest {
    static String jsonString(String value) {
      if (value == null) throw new IllegalArgumentException("Missing environment value");
      StringBuilder escaped = new StringBuilder("\"");
      for (int index = 0; index < value.length(); index++) {
        char character = value.charAt(index);
        switch (character) {
          case '\"': escaped.append("\\\""); break;
          case '\\': escaped.append("\\\\"); break;
          case '\b': escaped.append("\\b"); break;
          case '\f': escaped.append("\\f"); break;
          case '\n': escaped.append("\\n"); break;
          case '\r': escaped.append("\\r"); break;
          case '\t': escaped.append("\\t"); break;
          default:
            if (character < 0x20) escaped.append(String.format("\\u%04x", (int) character));
            else escaped.append(character);
        }
      }
      return escaped.append('\"').toString();
    }
    public static void main(String[] args) throws IOException, InterruptedException {
      String json = "{\"your_reference\":" + jsonString(System.getenv("PAYMENT_REFERENCE")) + ",\"amount\":{\"value\":\"50.00\",\"currency\":\"EUR\"},\"value_date\":\"2026-08-26\"}";
      HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
      String url = System.getenv("PAYWISE_API_URL") + "/v2/claims/" + System.getenv("CLAIM_ID") + "/payments/";
      HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(30))
          .header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY")).header("Content-Type", "application/json")
          .header("Idempotency-Key", System.getenv("PAYMENT_KEY")).POST(HttpRequest.BodyPublishers.ofString(json)).build();
      HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
      if (response.statusCode() != 201) throw new IOException(response.body());
    }
  }
  ```

  ```csharp C# theme={null}
  using System;
  using System.Net.Http;
  using System.Net.Http.Json;
  using System.Threading;
  using System.Threading.Tasks;

  class ReportClaimPaymentRequest
  {
      static async Task Main()
      {
          using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
          var payload = new { your_reference = Environment.GetEnvironmentVariable("PAYMENT_REFERENCE"), amount = new { value = "50.00", currency = "EUR" }, value_date = "2026-08-26" };
          using var request = new HttpRequestMessage(HttpMethod.Post, $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/claims/{Environment.GetEnvironmentVariable("CLAIM_ID")}/payments/");
          request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
          request.Headers.Add("Idempotency-Key", Environment.GetEnvironmentVariable("PAYMENT_KEY"));
          request.Content = JsonContent.Create(payload);
          using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
          using var response = await client.SendAsync(request, cancellation.Token);
          if ((int)response.StatusCode != 201) throw new HttpRequestException(await response.Content.ReadAsStringAsync());
      }
  }
  ```

  ```bash cURL theme={null}
  http_status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
    --request POST "$PAYWISE_API_URL/v2/claims/$CLAIM_ID/payments/" \
    --header "Authorization: Bearer $PAYWISE_API_KEY" --header "Content-Type: application/json" \
    --header "Idempotency-Key: $PAYMENT_KEY" --output payment-write-response.json --write-out '%{http_code}' --data @- <<JSON
  {"your_reference":"$PAYMENT_REFERENCE","amount":{"value":"50.00","currency":"EUR"},"value_date":"2026-08-26"}
  JSON
  )"
  [ "$http_status" -eq 201 ] || exit 1
  ```
</CodeGroup>

For an accepted mandate or subcase, use the same body on that target with its
one stable logical command key:

<CodeGroup>
  ```python Python theme={null}
  import requests

  PAYWISE_API_URL = "https://api-sandbox.paywise.de"
  PAYWISE_API_KEY = "pw_sbx_your_api_key"
  MANDATE_ID = "50000000-0000-4000-8000-000000000001"
  PAYMENT_KEY = "your-payment-key"
  PAYMENT_REFERENCE = "BANK-80000000-0000-4000-8000-000000000001"

  payment_payload = {"your_reference": PAYMENT_REFERENCE, "amount": {"value": "50.00", "currency": "EUR"}, "value_date": "2026-08-26"}
  response = requests.post(
      f"{PAYWISE_API_URL}/v2/mandates/{MANDATE_ID}/payments/",
      headers={"Authorization": f"Bearer {PAYWISE_API_KEY}", "Content-Type": "application/json", "Idempotency-Key": PAYMENT_KEY},
      json=payment_payload, timeout=(5, 30),
  )
  if response.status_code != 201:
      response.raise_for_status()
      raise RuntimeError(f"Expected 201, received {response.status_code}")
  ```

  ```javascript JavaScript theme={null}
  const paymentPayload = { your_reference: process.env.PAYMENT_REFERENCE, amount: { value: "50.00", currency: "EUR" }, value_date: "2026-08-26" };
  const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/mandates/${process.env.MANDATE_ID}/payments/`, {
    method: "POST", headers: { Authorization: `Bearer ${process.env.PAYWISE_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": process.env.PAYMENT_KEY },
    body: JSON.stringify(paymentPayload), signal: AbortSignal.timeout(30000),
  });
  if (response.status !== 201) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  ```

  ```java Java theme={null}
  import java.io.IOException;
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.time.Duration;

  class ReportMandatePaymentRequest {
    static String jsonString(String value) {
      if (value == null) throw new IllegalArgumentException("Missing environment value");
      StringBuilder escaped = new StringBuilder("\"");
      for (int index = 0; index < value.length(); index++) {
        char character = value.charAt(index);
        switch (character) {
          case '\"': escaped.append("\\\""); break;
          case '\\': escaped.append("\\\\"); break;
          case '\b': escaped.append("\\b"); break;
          case '\f': escaped.append("\\f"); break;
          case '\n': escaped.append("\\n"); break;
          case '\r': escaped.append("\\r"); break;
          case '\t': escaped.append("\\t"); break;
          default:
            if (character < 0x20) escaped.append(String.format("\\u%04x", (int) character));
            else escaped.append(character);
        }
      }
      return escaped.append('\"').toString();
    }
    public static void main(String[] args) throws IOException, InterruptedException {
      String json = "{\"your_reference\":" + jsonString(System.getenv("PAYMENT_REFERENCE")) + ",\"amount\":{\"value\":\"50.00\",\"currency\":\"EUR\"},\"value_date\":\"2026-08-26\"}";
      HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
      String url = System.getenv("PAYWISE_API_URL") + "/v2/mandates/" + System.getenv("MANDATE_ID") + "/payments/";
      HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(30))
          .header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY")).header("Content-Type", "application/json")
          .header("Idempotency-Key", System.getenv("PAYMENT_KEY")).POST(HttpRequest.BodyPublishers.ofString(json)).build();
      HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
      if (response.statusCode() != 201) throw new IOException(response.body());
    }
  }
  ```

  ```csharp C# theme={null}
  using System;
  using System.Net.Http;
  using System.Net.Http.Json;
  using System.Threading;
  using System.Threading.Tasks;

  class ReportMandatePaymentRequest
  {
      static async Task Main()
      {
          using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
          var payload = new { your_reference = Environment.GetEnvironmentVariable("PAYMENT_REFERENCE"), amount = new { value = "50.00", currency = "EUR" }, value_date = "2026-08-26" };
          using var request = new HttpRequestMessage(HttpMethod.Post, $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/mandates/{Environment.GetEnvironmentVariable("MANDATE_ID")}/payments/");
          request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
          request.Headers.Add("Idempotency-Key", Environment.GetEnvironmentVariable("PAYMENT_KEY"));
          request.Content = JsonContent.Create(payload);
          using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
          using var response = await client.SendAsync(request, cancellation.Token);
          if ((int)response.StatusCode != 201) throw new HttpRequestException(await response.Content.ReadAsStringAsync());
      }
  }
  ```

  ```bash cURL theme={null}
  http_status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
    --request POST "$PAYWISE_API_URL/v2/mandates/$MANDATE_ID/payments/" \
    --header "Authorization: Bearer $PAYWISE_API_KEY" --header "Content-Type: application/json" \
    --header "Idempotency-Key: $PAYMENT_KEY" --output mandate-payment-write-response.json --write-out '%{http_code}' --data @- <<JSON
  {"your_reference":"$PAYMENT_REFERENCE","amount":{"value":"50.00","currency":"EUR"},"value_date":"2026-08-26"}
  JSON
  )"
  [ "$http_status" -eq 201 ] || exit 1
  ```
</CodeGroup>

Execute one target branch, not both. The URL supplies the target; never send
claim or mandate IDs in the body. Retain the generated `PAYMENT_REFERENCE`,
target kind, target ID, body, and command key together until the outcome is
known.

You can add payment-specific `metadata` to either creation body, for example
`[{"type": "transaction:reference", "value": "BANK-TX-0042"}]`. Include it
in the persisted body and keep it identical on same-key retries. The payment
read returns this context; existing v1 reports expose their stored metadata
without being reported again. See
[Payment metadata](/api-docs/case-management-api/concepts/payments#payment-metadata).

## 2. Resolve the canonical payment

The `201` body is the payment read resource, including its `id` and a
`Location` header — store that `id` directly. Use the collection lookup below
only to recover when the response was lost (timeout, crash) before the `id` was
persisted: filter by the selected claim or mandate and the generated reference,
then follow every exact `next` URL before selecting a match:

<CodeGroup>
  ```python Python theme={null}
  import requests

  PAYWISE_API_URL = "https://api-sandbox.paywise.de"
  PAYWISE_API_KEY = "pw_sbx_your_api_key"
  PAYMENT_REFERENCE = "BANK-80000000-0000-4000-8000-000000000001"
  PAYMENT_TARGET_ID = "40000000-0000-4000-8000-000000000001"
  PAYMENT_TARGET_KIND = "claim"

  response = requests.get(
      f"{PAYWISE_API_URL}/v2/payments/",
      headers={"Authorization": f"Bearer {PAYWISE_API_KEY}"},
      params={PAYMENT_TARGET_KIND: PAYMENT_TARGET_ID, "your_reference": PAYMENT_REFERENCE, "limit": 100},
      timeout=(5, 30),
  )
  if response.status_code != 200:
      response.raise_for_status()
      raise RuntimeError(f"Expected 200, received {response.status_code}")
  ```

  ```javascript JavaScript theme={null}
  const url = new URL("/v2/payments/", process.env.PAYWISE_API_URL);
  url.search = new URLSearchParams({ [process.env.PAYMENT_TARGET_KIND]: process.env.PAYMENT_TARGET_ID, your_reference: process.env.PAYMENT_REFERENCE, limit: "100" });
  const response = await fetch(url, { method: "GET", headers: { Authorization: `Bearer ${process.env.PAYWISE_API_KEY}` }, signal: AbortSignal.timeout(30000) });
  if (response.status !== 200) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  ```

  ```java Java theme={null}
  import java.io.IOException;
  import java.net.URI;
  import java.net.URLEncoder;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.nio.charset.StandardCharsets;
  import java.time.Duration;

  class ListPaymentsRequest {
    static String encode(String value) { return URLEncoder.encode(value, StandardCharsets.UTF_8); }
    public static void main(String[] args) throws IOException, InterruptedException {
      String query = encode(System.getenv("PAYMENT_TARGET_KIND")) + "=" + encode(System.getenv("PAYMENT_TARGET_ID")) + "&your_reference=" + encode(System.getenv("PAYMENT_REFERENCE")) + "&limit=100";
      HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
      HttpRequest request = HttpRequest.newBuilder().uri(URI.create(System.getenv("PAYWISE_API_URL") + "/v2/payments/?" + query))
          .timeout(Duration.ofSeconds(30)).header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY")).GET().build();
      HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
      if (response.statusCode() != 200) throw new IOException(response.body());
    }
  }
  ```

  ```csharp C# theme={null}
  using System;
  using System.Net.Http;
  using System.Threading;
  using System.Threading.Tasks;

  class ListPaymentsRequest
  {
      static async Task Main()
      {
          using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
          var kind = Uri.EscapeDataString(Environment.GetEnvironmentVariable("PAYMENT_TARGET_KIND")!);
          var target = Uri.EscapeDataString(Environment.GetEnvironmentVariable("PAYMENT_TARGET_ID")!);
          var reference = Uri.EscapeDataString(Environment.GetEnvironmentVariable("PAYMENT_REFERENCE")!);
          using var request = new HttpRequestMessage(HttpMethod.Get, $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/payments/?{kind}={target}&your_reference={reference}&limit=100");
          request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
          using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
          using var response = await client.SendAsync(request, cancellation.Token);
          if ((int)response.StatusCode != 200) throw new HttpRequestException(await response.Content.ReadAsStringAsync());
      }
  }
  ```

  ```bash cURL theme={null}
  http_status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
    --request GET --get "$PAYWISE_API_URL/v2/payments/" \
    --header "Authorization: Bearer $PAYWISE_API_KEY" \
    --data-urlencode "$PAYMENT_TARGET_KIND=$PAYMENT_TARGET_ID" \
    --data-urlencode "your_reference=$PAYMENT_REFERENCE" --data-urlencode 'limit=100' \
    --output payments-page.json --write-out '%{http_code}')"
  [ "$http_status" -eq 200 ] || exit 1
  ```
</CodeGroup>

The runnable workflow below follows each opaque `next` URL, validates every
candidate's exact target shape, and then switches to the server `id` for all
later reads or deletion. It accepts only relative or absolute pagination links
on the exact API HTTPS origin and stops after 50 pages.

## 3. Delete a mistaken draft payment only

If the order is still `draft`, the claim-scoped payment may be deleted:

<CodeGroup>
  ```python Python theme={null}
  import requests

  PAYWISE_API_URL = "https://api-sandbox.paywise.de"
  PAYWISE_API_KEY = "pw_sbx_your_api_key"
  CLAIM_ID = "40000000-0000-4000-8000-000000000001"
  PAYMENT_ID = "70000000-0000-4000-8000-000000000001"

  response = requests.delete(
      f"{PAYWISE_API_URL}/v2/claims/{CLAIM_ID}/payments/{PAYMENT_ID}/",
      headers={"Authorization": f"Bearer {PAYWISE_API_KEY}"}, timeout=(5, 30),
  )
  if response.status_code != 204:
      response.raise_for_status()
      raise RuntimeError(f"Expected 204, received {response.status_code}")
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/claims/${process.env.CLAIM_ID}/payments/${process.env.PAYMENT_ID}/`, {
    method: "DELETE", headers: { Authorization: `Bearer ${process.env.PAYWISE_API_KEY}` }, signal: AbortSignal.timeout(30000),
  });
  if (response.status !== 204) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  ```

  ```java Java theme={null}
  import java.io.IOException;
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.time.Duration;

  class DeletePaymentRequest {
    public static void main(String[] args) throws IOException, InterruptedException {
      String url = System.getenv("PAYWISE_API_URL") + "/v2/claims/" + System.getenv("CLAIM_ID") + "/payments/" + System.getenv("PAYMENT_ID") + "/";
      HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
      HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(30)).header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY")).DELETE().build();
      HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
      if (response.statusCode() != 204) throw new IOException(response.body());
    }
  }
  ```

  ```csharp C# theme={null}
  using System;
  using System.Net.Http;
  using System.Threading;
  using System.Threading.Tasks;

  class DeletePaymentRequest
  {
      static async Task Main()
      {
          using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
          using var request = new HttpRequestMessage(HttpMethod.Delete, $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/claims/{Environment.GetEnvironmentVariable("CLAIM_ID")}/payments/{Environment.GetEnvironmentVariable("PAYMENT_ID")}/");
          request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
          using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
          using var response = await client.SendAsync(request, cancellation.Token);
          if ((int)response.StatusCode != 204) throw new HttpRequestException(await response.Content.ReadAsStringAsync());
      }
  }
  ```

  ```bash cURL theme={null}
  http_status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
    --request DELETE "$PAYWISE_API_URL/v2/claims/$CLAIM_ID/payments/$PAYMENT_ID/" \
    --header "Authorization: Bearer $PAYWISE_API_KEY" --output /dev/null --write-out '%{http_code}')"
  [ "$http_status" -eq 204 ] || exit 1
  ```
</CodeGroup>

Success is `204` with no body. After finalization, deletion returns `409`.
Mandate payments are never deletable through the API.

## Representative response

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json

{
  "count": 1,
  "next": null,
  "previous": null,
  "results": [
    {
      "id": "90000000-0000-4000-8000-000000000001",
      "claim": {
        "id": "40000000-0000-4000-8000-000000000001",
        "your_reference": "client-claim-42",
        "document_reference": "INV-2026-0042"
      },
      "mandate": null,
      "your_reference": "BANK-TRANSACTION-99812",
      "amount": {"value": "50.00", "currency": "EUR"},
      "value_date": "2026-08-26",
      "metadata": [],
      "created_at": "2026-08-27T12:30:00Z",
      "updated_at": "2026-08-27T12:30:00Z"
    }
  ]
}
```

This is a complete response matching the `PaginatedPaymentReadList` schema.

## Runnable Python workflow

```python Python workflow theme={null}
import uuid
from urllib.parse import urljoin, urlsplit, urlunsplit

import requests

PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
CLAIM_ID = "40000000-0000-4000-8000-000000000001"
DELETE_DRAFT_PAYMENT = "false"
MANDATE_ID = None  # Set this and clear CLAIM_ID for a mandate payment.
PAYMENT_TARGET_KIND = "claim"

MAX_PAGINATION_PAGES = 50


def normalized_https_origin(url):
    if not isinstance(url, str) or not url or url.strip() != url:
        raise ValueError("URL must be a non-empty HTTPS URL")
    try:
        parts = urlsplit(url)
        port = parts.port
    except (TypeError, ValueError) as error:
        raise ValueError("URL is malformed") from error
    if (
        parts.scheme.lower() != "https"
        or not parts.hostname
        or parts.username is not None
        or parts.password is not None
    ):
        raise ValueError("URL must use HTTPS without user information")
    return ("https", parts.hostname.lower(), port if port is not None else 443)


def canonical_page_url(value, current_url, expected_origin):
    if not isinstance(value, str) or not value or value.strip() != value:
        raise RuntimeError("Refusing a malformed pagination URL")
    try:
        resolved = urljoin(current_url, value)
        parts = urlsplit(resolved)
        origin = normalized_https_origin(resolved)
        port = parts.port if parts.port is not None else 443
    except (TypeError, ValueError) as error:
        raise RuntimeError("Refusing a malformed pagination URL") from error
    if origin != expected_origin or parts.fragment:
        raise RuntimeError("Refusing a pagination URL outside the API HTTPS origin")
    host = parts.hostname.lower()
    canonical_host = f"[{host}]" if ":" in host else host
    canonical_netloc = canonical_host if port == 443 else f"{canonical_host}:{port}"
    return urlunsplit(("https", canonical_netloc, parts.path or "/", parts.query, ""))


def paginated(session, base_url, initial_url, *, params=None):
    expected_origin = normalized_https_origin(base_url)
    current_url = canonical_page_url(initial_url, f"{base_url}/", expected_origin)
    seen_urls = set()
    pages_read = 0
    while True:
        if current_url in seen_urls:
            raise RuntimeError("Pagination cycle detected")
        if pages_read >= MAX_PAGINATION_PAGES:
            raise RuntimeError("Pagination page budget exhausted")
        seen_urls.add(current_url)
        pages_read += 1
        response = session.get(current_url, params=params, timeout=(5, 30))
        if response.status_code != 200:
            response.raise_for_status()
            raise RuntimeError(f"Expected 200, received {response.status_code}")
        payload = response.json()
        yield from payload["results"]
        next_url = payload.get("next")
        if next_url is None:
            return
        current_url = canonical_page_url(next_url, current_url, expected_origin)
        params = None


base_url = PAYWISE_API_URL
normalized_https_origin(base_url)
headers = {"Authorization": f"Bearer {PAYWISE_API_KEY}"}
session = requests.Session()
session.headers.update(headers)

probe = requests.get(
    f"{base_url}/v2/payments/", headers=headers, params={"limit": 1}, timeout=(5, 30)
)
if probe.status_code != 200:
    probe.raise_for_status()
    raise RuntimeError(f"Expected 200, received {probe.status_code}")
if probe.headers.get("X-Paywise-Environment", "").lower() != "sandbox":
    raise RuntimeError("Refusing writes without authenticated sandbox proof")

target_kind = PAYMENT_TARGET_KIND
claim_id = CLAIM_ID
mandate_id = MANDATE_ID
if target_kind == "claim":
    if not claim_id or mandate_id:
        raise ValueError("Claim target requires CLAIM_ID only")
    target_id = claim_id
    create_url = f"{base_url}/v2/claims/{claim_id}/payments/"
elif target_kind == "mandate":
    if not mandate_id or claim_id:
        raise ValueError("Mandate target requires MANDATE_ID only")
    target_id = mandate_id
    create_url = f"{base_url}/v2/mandates/{mandate_id}/payments/"
else:
    raise ValueError("PAYMENT_TARGET_KIND must be claim or mandate")

payment_reference = f"BANK-{uuid.uuid4()}"
payment_payload = {
    "your_reference": payment_reference,
    "amount": {"value": "50.00", "currency": "EUR"},
    "value_date": "2026-08-26",
}
created = requests.post(
    create_url,
    headers={**headers, "Idempotency-Key": str(uuid.uuid4())},
    json=payment_payload,
    timeout=(5, 30),
)
if created.status_code != 201:
    created.raise_for_status()
    raise RuntimeError(f"Expected 201, received {created.status_code}")

params = {target_kind: target_id, "your_reference": payment_reference, "limit": 100}
matches_by_id = {}
for candidate in paginated(
    session, base_url, f"{base_url}/v2/payments/", params=params
):
    claim = candidate.get("claim")
    mandate = candidate.get("mandate")
    exact_target = (
        claim is not None and claim.get("id") == target_id
        if target_kind == "claim"
        else mandate is not None and mandate.get("id") == target_id and claim is None
    )
    if candidate.get("your_reference") == payment_reference and exact_target:
        matches_by_id[candidate["id"]] = candidate

if len(matches_by_id) != 1:
    raise RuntimeError(f"Expected one server payment ID, found {sorted(matches_by_id)}")
payment_id = next(iter(matches_by_id))
payment_response = requests.get(
    f"{base_url}/v2/payments/{payment_id}/", headers=headers, timeout=(5, 30)
)
if payment_response.status_code != 200:
    payment_response.raise_for_status()
    raise RuntimeError(f"Expected 200, received {payment_response.status_code}")
payment = payment_response.json()
if payment.get("id") != payment_id or payment.get("your_reference") != payment_reference:
    raise RuntimeError("Payment read did not preserve the reconciled server identity")

if DELETE_DRAFT_PAYMENT == "true":
    if target_kind != "claim":
        raise RuntimeError("Mandate payments are immutable")
    claim_response = requests.get(
        f"{base_url}/v2/claims/{claim_id}/", headers=headers, timeout=(5, 30)
    )
    if claim_response.status_code != 200:
        claim_response.raise_for_status()
        raise RuntimeError(f"Expected 200, received {claim_response.status_code}")
    if claim_response.json().get("status") != "draft":
        raise RuntimeError("Only a draft claim payment may be deleted")
    deleted = requests.delete(
        f"{create_url}{payment_id}/", headers=headers, timeout=(5, 30)
    )
    if deleted.status_code != 204:
        deleted.raise_for_status()
        raise RuntimeError(f"Expected 204, received {deleted.status_code}")

WORKFLOW_RESULT = {
    "payment_id": payment_id,
    "target_kind": target_kind,
    "target_id": target_id,
    "deleted": DELETE_DRAFT_PAYMENT == "true",
}
```

## Failure and recovery

* `400`: reject zero, negative, non-EUR, malformed, or future-dated input;
  correct it before starting a new logical command.
* `400 overpayment` (draft claim only): the payment exceeds the claim's open
  value. Check the claim's `total_amount`; correct the amount or the claim.
* `409 duplicate_payment` (draft claim) or `409 duplicate_payment_report`
  (accepted case, with `existing_payment_id`): the same payment was already
  reported. The 24-hour window covers the one mandate you report against, so
  the same payment on a main case and on one of its subcases is two separate
  reports. A repeat with the same `your_reference` is a duplicate too.
  Reconcile against the existing payment ID; if you lost the original
  response, replay that request with its original `Idempotency-Key`.
* `409 order_expired`: the draft order expired; it accepts no payment report
  or deletion any more, and the daily cleanup deletes it.
* `403 permission_denied`: the key lacks `case:payments:write` (or
  `case:payments:read` for the list); use a key with the payment permissions.
* `409 conflict` on create: the target is dead — the claim was rejected or
  cancelled, or the mandate is
  closed. A mandate whose `state.processing.code` is `ended`,
  `canceled_by_client`, or `canceled_by_service_provider`, or whose
  `archived` flag is `true`, answers
  `Cannot report a payment on a closed mandate.` Do not
  retry against another target; queue the payment for manual reconciliation. `409` on
  delete: the order finalized while the command was in flight; the payment
  stands. Do not attempt a negative compensating payment.
* Timeout, `500`, or `503`: repeat the same target, body, and idempotency key.
* `429`: wait for `Retry-After`, then retry with the same key.
* Duplicate webhook or replayed command: upsert by server payment `id`. The
  generated reference, target, amount, and date help reconcile one ambiguous
  retry outcome, but are not a uniqueness key. If multiple server IDs share a
  business tuple, preserve every ID and investigate; never collapse them.

For an incorrect immutable payment, retain the payment ID and request ID and
contact paywise support; payment reversal is outside the contract.

## Verify

<CodeGroup>
  ```python Python theme={null}
  import requests

  PAYWISE_API_URL = "https://api-sandbox.paywise.de"
  PAYWISE_API_KEY = "pw_sbx_your_api_key"
  PAYMENT_ID = "70000000-0000-4000-8000-000000000001"

  response = requests.get(
      f"{PAYWISE_API_URL}/v2/payments/{PAYMENT_ID}/",
      headers={"Authorization": f"Bearer {PAYWISE_API_KEY}"}, timeout=(5, 30),
  )
  if response.status_code != 200:
      response.raise_for_status()
      raise RuntimeError(f"Expected 200, received {response.status_code}")
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/payments/${process.env.PAYMENT_ID}/`, {
    method: "GET", headers: { Authorization: `Bearer ${process.env.PAYWISE_API_KEY}` }, signal: AbortSignal.timeout(30000),
  });
  if (response.status !== 200) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  ```

  ```java Java theme={null}
  import java.io.IOException;
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.time.Duration;

  class ReadPaymentRequest {
    public static void main(String[] args) throws IOException, InterruptedException {
      HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
      HttpRequest request = HttpRequest.newBuilder().uri(URI.create(System.getenv("PAYWISE_API_URL") + "/v2/payments/" + System.getenv("PAYMENT_ID") + "/"))
          .timeout(Duration.ofSeconds(30)).header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY")).GET().build();
      HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
      if (response.statusCode() != 200) throw new IOException(response.body());
    }
  }
  ```

  ```csharp C# theme={null}
  using System;
  using System.Net.Http;
  using System.Threading;
  using System.Threading.Tasks;

  class ReadPaymentRequest
  {
      static async Task Main()
      {
          using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
          using var request = new HttpRequestMessage(HttpMethod.Get, $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/payments/{Environment.GetEnvironmentVariable("PAYMENT_ID")}/");
          request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
          using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
          using var response = await client.SendAsync(request, cancellation.Token);
          if ((int)response.StatusCode != 200) throw new HttpRequestException(await response.Content.ReadAsStringAsync());
      }
  }
  ```

  ```bash cURL theme={null}
  http_status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
    --request GET "$PAYWISE_API_URL/v2/payments/$PAYMENT_ID/" \
    --header "Authorization: Bearer $PAYWISE_API_KEY" --output payment.json --write-out '%{http_code}')"
  [ "$http_status" -eq 200 ] || exit 1
  ```
</CodeGroup>

Also confirm exactly one of `claim` and `mandate` identifies the intended
target.

## Related reference

* [POST `/v2/claims/{claim_id}/payments/`](/api-docs/case-management-api/reference/claims/create-claim-payment)
* [DELETE `/v2/claims/{claim_id}/payments/{id}/`](/api-docs/case-management-api/reference/claims/delete-claim-payment)
* [POST `/v2/mandates/{mandate_id}/payments/`](/api-docs/case-management-api/reference/mandates/create-mandate-payment)
* [GET `/v2/payments/`](/api-docs/case-management-api/reference/payments/list-payments)
* [GET `/v2/payments/{id}/`](/api-docs/case-management-api/reference/payments/get-payment)


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