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

# Case Management quickstart

> Create a debtor and invoice claim, finalize without a body, and find the accepted mandate.

This quickstart is designed for the sandbox and uses a direct Case key. The
Python examples use `https://api-sandbox.paywise.de` directly; replace the
inline `PAYWISE_API_KEY` placeholder with a sandbox key (`pw_sbx_…`) created
in the developer portal. Other language tabs read the matching environment
variables.
A Partner key must add `X-On-Behalf-Of-Company: <company-uuid>` to every Case
request instead.

Use the documented trailing slash on `/v2/` roots and resource routes.
Slashless requests such as `/v2/info` return JSON `404` for every method,
including `GET`, `HEAD`, `OPTIONS`, and writes, with no redirect or `Location`
header. Request `/v2/info/` directly. See
[canonical URLs](https://docs.paywise.de/api-docs/essentials/requests-and-responses.md#canonical-urls).

The request tabs below are complete, copyable examples for individual
operations. The final section runs the whole flow end to end once in Python.

This shortest path uses the default `invoice` order type. For subtype-specific
data and validation, follow [Submit a rental order](https://docs.paywise.de/api-docs/case-management-api/workflows/submit-a-rental-order.md)
or [Submit a titled order](https://docs.paywise.de/api-docs/case-management-api/workflows/submit-a-titled-order.md).

## 1. Create the debtor record

Use a fresh `DEBTOR_REFERENCE` and `DEBTOR_COMMAND_ID` for a new logical write.
Keep the same body and idempotency key when retrying an ambiguous result.

```python Python theme={null}
import uuid
import requests

PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
DEBTOR_COMMAND_ID = "your-debtor-command-id"
DEBTOR_REFERENCE = "quickstart-debtor-80000000-0000-4000-8000-000000000001"

debtor_payload = {
    "your_reference": DEBTOR_REFERENCE,
    "acting_as": "consumer",
    "person": {"salutation": "mx", "first_name": "Alex", "last_name": "Example"},
    "addresses": [{
        "street": "Example Street 12", "postal_code": "10115",
        "city": "Berlin", "country": "DE", "primary": True,
    }],
}
response = requests.post(
    f"{PAYWISE_API_URL}/v2/debtors/",
    headers={
        "Authorization": f"Bearer {PAYWISE_API_KEY}",
        "Content-Type": "application/json",
        "Idempotency-Key": DEBTOR_COMMAND_ID,
    },
    json=debtor_payload,
    timeout=(5, 30),
)
if response.status_code != 201:
    response.raise_for_status()
    raise RuntimeError(f"Expected 201, received {response.status_code}")
debtor_id = str(uuid.UUID(response.json()["id"]))
```

```javascript JavaScript theme={null}
const debtorPayload = {
  your_reference: process.env.DEBTOR_REFERENCE,
  acting_as: "consumer",
  person: { salutation: "mx", first_name: "Alex", last_name: "Example" },
  addresses: [{ street: "Example Street 12", postal_code: "10115", city: "Berlin", country: "DE", primary: true }],
};
const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/debtors/`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAYWISE_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": process.env.DEBTOR_COMMAND_ID,
  },
  body: JSON.stringify(debtorPayload),
  signal: AbortSignal.timeout(30000),
});
if (response.status !== 201) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
const debtorId = (await response.json()).id;
if (!/^[0-9a-f-]{36}$/i.test(debtorId)) throw new Error("Invalid debtor UUID");
```

```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 QuickstartRequest {
  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("DEBTOR_REFERENCE"))
        + ",\"acting_as\":\"consumer\",\"person\":{\"salutation\":\"mx\","
        + "\"first_name\":\"Alex\",\"last_name\":\"Example\"},\"addresses\":[{"
        + "\"street\":\"Example Street 12\",\"postal_code\":\"10115\","
        + "\"city\":\"Berlin\",\"country\":\"DE\",\"primary\":true}]}";
    HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(System.getenv("PAYWISE_API_URL") + "/v2/debtors/"))
        .timeout(Duration.ofSeconds(30))
        .header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY"))
        .header("Content-Type", "application/json")
        .header("Idempotency-Key", System.getenv("DEBTOR_COMMAND_ID"))
        .POST(HttpRequest.BodyPublishers.ofString(json)).build();
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    if (response.statusCode() != 201) throw new IOException(response.body());
    String debtorId = response.body().replaceFirst("(?s).*\\\"id\\\"\\s*:\\s*\\\"([0-9a-fA-F-]{36})\\\".*", "$1");
    if (debtorId.equals(response.body())) throw new IOException("Missing debtor UUID");
  }
}
```

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

class QuickstartRequest
{
    static async Task Main()
    {
        using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
        var payload = new {
            your_reference = Environment.GetEnvironmentVariable("DEBTOR_REFERENCE"),
            acting_as = "consumer",
            person = new { salutation = "mx", first_name = "Alex", last_name = "Example" },
            addresses = new[] { new { street = "Example Street 12", postal_code = "10115", city = "Berlin", country = "DE", primary = true } }
        };
        using var request = new HttpRequestMessage(HttpMethod.Post,
            $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/debtors/");
        request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
        request.Headers.Add("Idempotency-Key", Environment.GetEnvironmentVariable("DEBTOR_COMMAND_ID"));
        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());
        using var debtor = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
        var debtorId = debtor.RootElement.GetProperty("id").GetGuid();
    }
}
```

```bash cURL theme={null}
http_status="$(curl --fail-with-body --output quickstart-response.json --write-out '%{http_code}' --silent --show-error --connect-timeout 5 --max-time 30 \
  --request POST "$PAYWISE_API_URL/v2/debtors/" \
  --header "Authorization: Bearer $PAYWISE_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: $DEBTOR_COMMAND_ID" \
  --data @- <<JSON
{
  "your_reference": "$DEBTOR_REFERENCE",
  "acting_as": "consumer",
  "person": {"salutation": "mx", "first_name": "Alex", "last_name": "Example"},
  "addresses": [{
    "street": "Example Street 12",
    "postal_code": "10115",
    "city": "Berlin",
    "country": "DE",
    "primary": true
  }]
}
JSON
)"
[ "$http_status" -eq 201 ] || exit 1
DEBTOR_ID="$(jq -er '.id' quickstart-response.json)" || exit 1
```

The create response returns the debtor's `id` directly. Every language tab
above parses and retains that exact UUID; reuse it as `DEBTOR_ID` in step 2:

```json theme={null}
{"id": "acaf6cc1-f711-42c9-a385-7a3f537325cc", "created_at": "…", …}
```

If you lose the id, `GET /v2/debtors/?your_reference=<reference>` finds the
debtor again. The reference is not enforced unique and the filter is a
case-insensitive substring match, so compare `your_reference` exactly on the
results and treat more than one match as a reconciliation task.

## 2. Create the order draft

This is a `ReceivableClaim` in an invoice order, so the claim declares
`"type": "receivable"` (required on every claim). Omitting the order-level
`type` defaults the order to `invoice`; no rental-agreement or
enforceable-title fields belong in this payload. Choose `legal_basis.claim_type_code`
from the complete [German code catalog](https://docs.paywise.de/api-docs/case-management-api/concepts/claims.md#legal-basis-codes);
this example uses H05 for a service contract.

```python Python theme={null}
import requests

PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
CLAIM_REFERENCE = "quickstart-claim-80000000-0000-4000-8000-000000000001"
DEBTOR_ID = "40000000-0000-4000-8000-000000000001"
DOCUMENT_REFERENCE = "INV-80000000-0000-4000-8000-000000000001"
ORDER_COMMAND_ID = "your-order-command-id"

order_payload = {
    "debtor_id": DEBTOR_ID,
    "additional_debtor_ids": [],
    "starting_approach": "extrajudicial",
    "creditor_obligation_fulfilled": True,
    "claims": [{
        "type": "receivable",
        "your_reference": CLAIM_REFERENCE,
        "document_reference": DOCUMENT_REFERENCE,
        "subject_matter": "Consulting services for June 2026",
        "principal_amount": {"value": "125.50", "currency": "EUR"},
        "document_date": "2026-06-30", "due_date": "2026-07-14",
        "delay_date": "2026-07-20",
        "is_disputed": False, "items": [], "additional_charges": [],
        "reminders": [], "documents": [],
        "legal_basis": {"claim_type_code": "H05", "contract_date": "2026-06-01", "description": "Consulting agreement"},
    }],
}
response = requests.post(
    f"{PAYWISE_API_URL}/v2/orders/",
    headers={
        "Authorization": f"Bearer {PAYWISE_API_KEY}",
        "Content-Type": "application/json",
        "Idempotency-Key": ORDER_COMMAND_ID,
    },
    json=order_payload,
    timeout=(5, 30),
)
if response.status_code != 201:
    response.raise_for_status()
    raise RuntimeError(f"Expected 201, received {response.status_code}")
order = response.json()
order_id = str(uuid.UUID(order["id"]))
matching_claims = [
    claim for claim in order.get("claims", [])
    if claim.get("your_reference") == CLAIM_REFERENCE
]
if len(matching_claims) != 1:
    raise RuntimeError("Expected exactly one claim with the submitted business reference")
claim_id = str(uuid.UUID(matching_claims[0]["id"]))
```

```javascript JavaScript theme={null}
const orderPayload = {
  debtor_id: process.env.DEBTOR_ID,
  additional_debtor_ids: [], starting_approach: "extrajudicial",
  creditor_obligation_fulfilled: true,
  claims: [{
    type: "receivable",
    your_reference: process.env.CLAIM_REFERENCE,
    document_reference: process.env.DOCUMENT_REFERENCE,
    subject_matter: "Consulting services for June 2026",
    principal_amount: { value: "125.50", currency: "EUR" },
    document_date: "2026-06-30", due_date: "2026-07-14", delay_date: "2026-07-20",
    is_disputed: false,
    items: [], additional_charges: [], reminders: [], documents: [],
    legal_basis: { claim_type_code: "H05", contract_date: "2026-06-01", description: "Consulting agreement" },
  }],
};
const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/orders/`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAYWISE_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": process.env.ORDER_COMMAND_ID,
  },
  body: JSON.stringify(orderPayload),
  signal: AbortSignal.timeout(30000),
});
if (response.status !== 201) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
const order = await response.json();
const matchingClaims = order.claims.filter((claim) => claim.your_reference === process.env.CLAIM_REFERENCE);
if (matchingClaims.length !== 1) throw new Error("Expected exactly one claim with the submitted business reference");
const claimId = matchingClaims[0].id;
```

```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 QuickstartRequest {
  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 = "{\"debtor_id\":" + jsonString(System.getenv("DEBTOR_ID"))
        + ",\"additional_debtor_ids\":[],\"starting_approach\":\"extrajudicial\","
        + "\"creditor_obligation_fulfilled\":true,\"claims\":[{\"type\":\"receivable\",\"your_reference\":"
        + jsonString(System.getenv("CLAIM_REFERENCE")) + ",\"document_reference\":"
        + jsonString(System.getenv("DOCUMENT_REFERENCE")) + ",\"subject_matter\":\"Consulting services for June 2026\","
        + "\"principal_amount\":{\"value\":\"125.50\",\"currency\":\"EUR\"},"
        + "\"document_date\":\"2026-06-30\",\"due_date\":\"2026-07-14\","
        + "\"delay_date\":\"2026-07-20\",\"is_disputed\":false,"
        + "\"items\":[],\"additional_charges\":[],\"reminders\":[],\"documents\":[],"
        + "\"legal_basis\":{\"claim_type_code\":\"H05\",\"contract_date\":\"2026-06-01\","
        + "\"description\":\"Consulting agreement\"}}]}";
    HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(System.getenv("PAYWISE_API_URL") + "/v2/orders/"))
        .timeout(Duration.ofSeconds(30))
        .header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY"))
        .header("Content-Type", "application/json")
        .header("Idempotency-Key", System.getenv("ORDER_COMMAND_ID"))
        .POST(HttpRequest.BodyPublishers.ofString(json)).build();
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    if (response.statusCode() != 201) throw new IOException(response.body());
    // Parse the JSON response with your JSON library and require exactly one
    // claims[] entry whose your_reference equals CLAIM_REFERENCE; persist its id.
  }
}
```

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

class QuickstartRequest
{
    static async Task Main()
    {
        using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
        var payload = new {
            debtor_id = Environment.GetEnvironmentVariable("DEBTOR_ID"),
            additional_debtor_ids = Array.Empty<string>(), starting_approach = "extrajudicial",
            creditor_obligation_fulfilled = true,
            claims = new[] { new {
                type = "receivable",
                your_reference = Environment.GetEnvironmentVariable("CLAIM_REFERENCE"),
                document_reference = Environment.GetEnvironmentVariable("DOCUMENT_REFERENCE"),
                subject_matter = "Consulting services for June 2026",
                principal_amount = new { value = "125.50", currency = "EUR" },
                document_date = "2026-06-30", due_date = "2026-07-14",
                delay_date = "2026-07-20", is_disputed = false,
                items = Array.Empty<object>(), additional_charges = Array.Empty<object>(),
                reminders = Array.Empty<object>(), documents = Array.Empty<object>(),
                legal_basis = new { claim_type_code = "H05", contract_date = "2026-06-01", description = "Consulting agreement" }
            } }
        };
        using var request = new HttpRequestMessage(HttpMethod.Post,
            $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/orders/");
        request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
        request.Headers.Add("Idempotency-Key", Environment.GetEnvironmentVariable("ORDER_COMMAND_ID"));
        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());
        using var order = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
        var matches = order.RootElement.GetProperty("claims").EnumerateArray()
            .Where(claim => claim.GetProperty("your_reference").GetString() == Environment.GetEnvironmentVariable("CLAIM_REFERENCE"))
            .ToArray();
        if (matches.Length != 1) throw new InvalidOperationException("Expected exactly one matching claim");
        var claimId = matches[0].GetProperty("id").GetGuid();
    }
}
```

```bash cURL theme={null}
http_status="$(curl --fail-with-body --output quickstart-response.json --write-out '%{http_code}' --silent --show-error --connect-timeout 5 --max-time 30 \
  --request POST "$PAYWISE_API_URL/v2/orders/" \
  --header "Authorization: Bearer $PAYWISE_API_KEY" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: $ORDER_COMMAND_ID" \
  --data @- <<JSON
{
  "debtor_id": "$DEBTOR_ID",
  "additional_debtor_ids": [],
  "starting_approach": "extrajudicial",
  "creditor_obligation_fulfilled": true,
  "claims": [{
    "type": "receivable",
    "your_reference": "$CLAIM_REFERENCE",
    "document_reference": "$DOCUMENT_REFERENCE",
    "subject_matter": "Consulting services for June 2026",
    "principal_amount": {"value": "125.50", "currency": "EUR"},
    "document_date": "2026-06-30",
    "due_date": "2026-07-14",
    "delay_date": "2026-07-20",
    "is_disputed": false,
    "items": [],
    "additional_charges": [],
    "reminders": [],
    "documents": [],
    "legal_basis": {
      "claim_type_code": "H05",
      "contract_date": "2026-06-01",
      "description": "Consulting agreement"
    }
  }]
}
JSON
)"
[ "$http_status" -eq 201 ] || exit 1
ORDER_ID="$(jq -er '.id' quickstart-response.json)" || exit 1
CLAIM_ID="$(jq -er --arg ref "$CLAIM_REFERENCE" '[.claims[] | select(.your_reference == $ref)] | if length == 1 then .[0].id else error("expected one matching claim") end' quickstart-response.json)" || exit 1
```

A draft stays fully editable until it is finalized — `PATCH /v2/orders/{id}/`
corrects any field; omitted fields and arrays stay unchanged.

## 3. Finalize exactly once

Finalize accepts an empty body or exactly `{}`; any member is
`400 validation_error` with `unknown_field`. The examples send an empty body,
so they do not need a `Content-Type` header.

```python Python theme={null}
import requests

PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
FINALIZE_COMMAND_ID = "your-finalize-command-id"
ORDER_ID = "20000000-0000-4000-8000-000000000001"

response = requests.post(
    f"{PAYWISE_API_URL}/v2/orders/{ORDER_ID}/finalize/",
    headers={
        "Authorization": f"Bearer {PAYWISE_API_KEY}",
        "Idempotency-Key": FINALIZE_COMMAND_ID,
    },
    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/orders/${process.env.ORDER_ID}/finalize/`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.PAYWISE_API_KEY}`,
    "Idempotency-Key": process.env.FINALIZE_COMMAND_ID,
  },
  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 QuickstartRequest {
  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/orders/" + System.getenv("ORDER_ID") + "/finalize/"))
        .timeout(Duration.ofSeconds(30))
        .header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY"))
        .header("Idempotency-Key", System.getenv("FINALIZE_COMMAND_ID"))
        .POST(HttpRequest.BodyPublishers.noBody()).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 QuickstartRequest
{
    static async Task Main()
    {
        using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
        using var request = new HttpRequestMessage(HttpMethod.Post,
            $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/orders/{Environment.GetEnvironmentVariable("ORDER_ID")}/finalize/");
        request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
        request.Headers.Add("Idempotency-Key", Environment.GetEnvironmentVariable("FINALIZE_COMMAND_ID"));
        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 --output quickstart-response.json --write-out '%{http_code}' --silent --show-error --connect-timeout 5 --max-time 30 \
  --request POST "$PAYWISE_API_URL/v2/orders/$ORDER_ID/finalize/" \
  --header "Authorization: Bearer $PAYWISE_API_KEY" \
  --header "Idempotency-Key: $FINALIZE_COMMAND_ID"
)"
[ "$http_status" -eq 200 ] || exit 1
```

## 4. Accept the order in the sandbox

In production, paywise reviews and accepts a submitted order. The sandbox never
accepts one on its own, and there is no API call for it: you accept the order
in the sandbox portal. From the production portal, follow this path:

**Für Entwickler** → **Zur Sandbox** → **Mandate & Aufträge** →
**Aufträge in Prüfung** → open the order finalized in step 3 → open the
sandbox drawer (the amber **Sandbox-Tools** button at the lower right, or
`Alt+S`) → **Kontext** → **Auftrag annehmen** → **Bestätigen**

Your first **Zur Sandbox** entry activates the sandbox automatically.

The portal opens the newly created mandate. Later, the same drawer simulates
the debtor paying that mandate — *Zahlung des Schuldners an paywise*, in full
or as a partial amount — and every other counterparty step; see the
[sandbox drawer](https://docs.paywise.de/api-docs/case-management-api/concepts/sandbox.md#sandbox-drawer).

## 5. Verify acceptance and find the mandate

Acceptance freezes the order's claim set into exactly one mandate, and the
order names it: once `status` is `accepted`, the order's read-only `mandate`
field carries the mandate UUID.

The production pattern is push, not poll: subscribe a webhook to
`order.accepted` (see
[Consume Case webhooks](https://docs.paywise.de/api-docs/case-management-api/workflows/consume-webhooks.md)
for subscription creation, signature verification, and deduplication). The
event payload names both sides of the boundary: the order (`order_id`,
`order_url`) and the mandate its claims were frozen into (`mandate_id`,
`mandate_url`). Fetch `mandate_url` on your configured API host directly;
`mandate_id` equals the accepted order's `mandate` field:

```json order.accepted payload (data) theme={null}
{
  "company_id": "10000000-0000-4000-8000-000000000001",
  "order_id": "20000000-0000-4000-8000-000000000001",
  "order_url": "/v2/orders/20000000-0000-4000-8000-000000000001/",
  "claim_ids": ["30000000-0000-4000-8000-000000000001"],
  "mandate_id": "50000000-0000-4000-8000-000000000001",
  "mandate_url": "/v2/mandates/50000000-0000-4000-8000-000000000001/"
}
```

Webhook delivery is at least once. Match the stored claim UUID against
`claim_ids`, then refetch `/v2/claims/{claim_id}/`. Trust that response's
current `order_id` and nullable `mandate_id`, even when the event's `order_id`
differs from the order where you originally submitted the claim. Do not follow
an order merge chain to locate it.

In this quickstart no webhook endpoint is needed: the sandbox simulation in
step 4 accepts the order synchronously. Read the order once and take the
`mandate` UUID from the response:

```python Python theme={null}
import requests

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

response = requests.get(
    f"{PAYWISE_API_URL}/v2/orders/{ORDER_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/orders/${process.env.ORDER_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 QuickstartRequest {
  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/orders/" + System.getenv("ORDER_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 QuickstartRequest
{
    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/orders/{Environment.GetEnvironmentVariable("ORDER_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 --output quickstart-response.json --write-out '%{http_code}' --silent --show-error --connect-timeout 5 --max-time 30 \
  --request GET "$PAYWISE_API_URL/v2/orders/$ORDER_ID/" \
  --header "Authorization: Bearer $PAYWISE_API_KEY"
)"
[ "$http_status" -eq 200 ] || exit 1
```

Fetch the mandate by the exact UUID in the order response's `mandate` field:

```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"

response = requests.get(
    f"{PAYWISE_API_URL}/v2/mandates/{MANDATE_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/mandates/${process.env.MANDATE_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 QuickstartRequest {
  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/mandates/" + System.getenv("MANDATE_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 QuickstartRequest
{
    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/mandates/{Environment.GetEnvironmentVariable("MANDATE_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 --output quickstart-response.json --write-out '%{http_code}' --silent --show-error --connect-timeout 5 --max-time 30 \
  --request GET "$PAYWISE_API_URL/v2/mandates/$MANDATE_ID/" \
  --header "Authorization: Bearer $PAYWISE_API_KEY"
)"
[ "$http_status" -eq 200 ] || exit 1
```

## 6. Runnable end-to-end workflow

Install `requests`, replace the inline `PAYWISE_API_KEY` placeholder, then run
this program. The API URL is already set to the Sandbox host. Every id it
needs comes straight from a command response. Before writing, it checks HTTPS
and confirms the authenticated API reports the sandbox environment. After
finalizing the order it pauses: accept the order in the sandbox portal as in
step 4, and the program picks the acceptance up on its next poll.

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

import requests

PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"

TIMEOUT = (5, 30)


def normalized_https_origin(url):
    parts = urlsplit(url)
    if (parts.scheme.lower() != "https" or not parts.hostname
            or parts.username is not None or parts.password is not None
            or parts.port not in (None, 443) or parts.query or parts.fragment):
        raise ValueError("PAYWISE_API_URL must be an HTTPS sandbox URL")
    return (parts.scheme.lower(), parts.hostname.lower(), parts.port or 443)


def require_status(response, expected):
    if response.status_code != expected:
        response.raise_for_status()
        raise RuntimeError(f"Expected {expected}, received {response.status_code}")


def main():
    base_url = PAYWISE_API_URL
    normalized_https_origin(base_url)
    session = requests.Session()
    session.headers.update({
        "Authorization": f"Bearer {PAYWISE_API_KEY}",
        "Accept": "application/json",
    })

    response = session.get(f"{base_url}/v2/info/", timeout=TIMEOUT)
    require_status(response, 200)
    environment = next((value for key, value in response.headers.items()
                        if key.lower() == "x-paywise-environment"), None)
    if environment != "sandbox" or response.json().get("environment") != "sandbox":
        raise RuntimeError("This quickstart requires an authenticated sandbox API")

    run_id = str(uuid.uuid4())

    debtor_payload = {
        "your_reference": f"quickstart-debtor-{run_id}",
        "acting_as": "consumer",
        "person": {"salutation": "mx", "first_name": "Alex", "last_name": "Example"},
        "addresses": [{
            "street": "Example Street 12", "postal_code": "10115",
            "city": "Berlin", "country": "DE", "primary": True,
        }],
    }
    response = session.post(
        f"{base_url}/v2/debtors/",
        headers={"Content-Type": "application/json", "Idempotency-Key": str(uuid.uuid4())},
        json=debtor_payload,
        timeout=TIMEOUT,
    )
    require_status(response, 201)
    debtor_id = response.json()["id"]

    order_payload = {
        "debtor_id": debtor_id,
        "additional_debtor_ids": [],
        "starting_approach": "extrajudicial",
        "creditor_obligation_fulfilled": True,
        "claims": [{
            "type": "receivable",
            "your_reference": f"quickstart-claim-{run_id}",
            "document_reference": f"INV-{run_id}",
            "subject_matter": "Consulting services for June 2026",
            "principal_amount": {"value": "125.50", "currency": "EUR"},
            "document_date": "2026-06-30", "due_date": "2026-07-14",
            "delay_date": "2026-07-20",
            "is_disputed": False, "items": [], "additional_charges": [],
            "reminders": [], "documents": [],
            "legal_basis": {"claim_type_code": "H05", "contract_date": "2026-06-01", "description": "Consulting agreement"},
        }],
    }
    response = session.post(
        f"{base_url}/v2/orders/",
        headers={"Content-Type": "application/json", "Idempotency-Key": str(uuid.uuid4())},
        json=order_payload,
        timeout=TIMEOUT,
    )
    require_status(response, 201)
    order = response.json()
    order_id = order["id"]
    matching_claims = [
        claim for claim in order.get("claims", [])
        if claim.get("your_reference") == f"quickstart-claim-{run_id}"
    ]
    if len(matching_claims) != 1:
        raise RuntimeError("Expected exactly one claim with the submitted business reference")
    claim_id = matching_claims[0]["id"]

    response = session.post(
        f"{base_url}/v2/orders/{order_id}/finalize/",
        headers={"Idempotency-Key": str(uuid.uuid4())},
        timeout=TIMEOUT,
    )
    require_status(response, 200)

    # Sandbox only: nothing accepts a submitted order on its own, and there is
    # no API call for it. Accept the order in the sandbox portal (sandbox
    # drawer -> Kontext -> Auftrag annehmen -> Bestätigen) while this program
    # polls the order, at most 20 times, 15 seconds apart.
    print(
        f"Order {order_id} is submitted. Accept it in the sandbox portal: "
        "Mandate & Aufträge -> Aufträge in Prüfung -> open the order -> "
        "Sandbox-Tools -> Kontext -> Auftrag annehmen -> Bestätigen."
    )
    for attempt in range(20):
        if attempt:
            time.sleep(15)
        response = session.get(f"{base_url}/v2/orders/{order_id}/", timeout=TIMEOUT)
        require_status(response, 200)
        current = response.json()
        if current["status"] == "accepted":
            break
        if current["status"] != "submitted":
            raise RuntimeError(f"Expected submitted or accepted, order is {current['status']}")
    else:
        raise RuntimeError("Order was not accepted in the sandbox portal in time")
    mandate_id = current.get("mandate")
    if not mandate_id:
        raise RuntimeError("Accepted order did not return its mandate ID")

    # The same acceptance rule applies to webhook claim_ids: refetch the claim
    # and trust its current placement after review.
    response = session.get(f"{base_url}/v2/claims/{claim_id}/", timeout=TIMEOUT)
    require_status(response, 200)
    accepted_claim = response.json()
    if (accepted_claim.get("status") != "accepted" or accepted_claim.get("mandate_id") != mandate_id):
        raise RuntimeError("Claim acceptance projection mismatch")
    current_order_id = accepted_claim["order_id"]

    # Fetch the mandate and confirm it holds this run's exact claim.
    response = session.get(f"{base_url}/v2/mandates/{mandate_id}/", timeout=TIMEOUT)
    require_status(response, 200)
    mandate = response.json()
    if not any(
        claim["id"] == claim_id and claim["your_reference"] == f"quickstart-claim-{run_id}"
        for claim in mandate["claims"]
    ):
        raise RuntimeError("Mandate does not contain this run's exact claim")

    return {
        "order_id": order_id,
        "claim_id": claim_id,
        "current_order_id": current_order_id,
        "mandate_id": mandate_id,
    }


WORKFLOW_RESULT = main()
```

The accepted order names its mandate directly, so no list reconciliation is
needed: the program reads `mandate` from the accepted order, verifies that the
fetched mandate holds this run's exact claim, and never prints credentials.
Unexpected order states stop the program for inspection.

## Related reference

* [POST `/v2/debtors/`](https://docs.paywise.de/api-docs/case-management-api/reference/debtors/create-debtor)
* [GET `/v2/debtors/`](https://docs.paywise.de/api-docs/case-management-api/reference/debtors/list-debtors)
* [POST `/v2/orders/`](https://docs.paywise.de/api-docs/case-management-api/reference/orders/create-order)
* [PATCH `/v2/orders/{id}/`](https://docs.paywise.de/api-docs/case-management-api/reference/orders/update-order)
* [POST `/v2/orders/{id}/finalize/`](https://docs.paywise.de/api-docs/case-management-api/reference/orders/finalize-order)
* [GET `/v2/orders/{id}/`](https://docs.paywise.de/api-docs/case-management-api/reference/orders/get-order)
* [GET `/v2/mandates/`](https://docs.paywise.de/api-docs/case-management-api/reference/mandates/list-mandates)
* [GET `/v2/mandates/{id}/`](https://docs.paywise.de/api-docs/case-management-api/reference/mandates/get-mandate)


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