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

# Submit a rental order

> Create a rental agreement and H17 or H19 claims inline or in stages, then finalize and verify the order.

## Outcome

You have a finalized rental order with a complete rental agreement and at
least one valid commercial (H17) or residential (H19) rental claim. The order
uses the same submission, review, withdrawal, and acceptance lifecycle as an
invoice order.

## Prerequisites

* A production or sandbox base URL and Bearer key.
* An entitled company UUID on every call when using a Partner key.
* A debtor UUID with sufficient address and notification data.
* The rental agreement facts: property address, monthly Warmmiete, contract
  conclusion date, and — per unpaid month — the H17 or H19 claim.
* One stable `Idempotency-Key` per logical `POST` command.

## Lifecycle context

A rental order follows the same draft → `submitted` → `accepted` lifecycle
as an invoice order. The only variant-specific pieces are the singleton
`rental_agreement`, which the order owns from creation, and the H17/H19
claims whose dates the agreement's due rule derives. Documents are base64
inline uploads or later multipart uploads below the agreement or claim; they
scan asynchronously and never block finalization.

## 0. Create or reuse the debtor

Create the reusable debtor first. Each example parses the exact returned UUID;
persist it as `DEBTOR_ID` and use that value in the order request. Reconcile
an ambiguous retry by the exact `your_reference`; never guess an ID.

<CodeGroup>
  ```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"
  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=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 payload = {
    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(payload),
    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 CreateDebtorRequest {
    public static void main(String[] args) throws IOException, InterruptedException {
      String json = "{\"your_reference\":\"quickstart-debtor-80000000-0000-4000-8000-000000000001\",\"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 CreateDebtorRequest {
      static async Task Main() {
          using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
          var payload = new {
              your_reference = "quickstart-debtor-80000000-0000-4000-8000-000000000001",
              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}
  status="$(curl --fail-with-body --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" \
    --output debtor-created.json --write-out '%{http_code}' \
    --data '{"your_reference":"quickstart-debtor-80000000-0000-4000-8000-000000000001","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}]}')"
  [ "$status" -eq 201 ] || exit 1
  DEBTOR_ID="$(jq -er '.id' debtor-created.json)" || exit 1
  ```
</CodeGroup>

## 1. Create inline

Use one [POST `/v2/orders/`](/api-docs/case-management-api/reference/orders/create-order)
when the agreement, claim, and files are ready together. The nested operation
is atomic.

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

  PAYWISE_API_URL = "https://api-sandbox.paywise.de"
  PAYWISE_API_KEY = "pw_sbx_your_api_key"
  ORDER_COMMAND_ID = "your-order-command-id"
  DEBTOR_ID = debtor_id  # Parsed from the immediately preceding create response.

  payload = {
      "type": "rental",
      "debtor_id": DEBTOR_ID,
      "additional_debtor_ids": [],
      "starting_approach": "extrajudicial",
      "creditor_obligation_fulfilled": True,
      "rental_agreement": {
          "property_address": {
              "street": "Rental Example Street 8",
              "postal_code": "10115",
              "city": "Berlin",
              "country": "DE"
          },
          "monthly_rent": {
              "value": "1200.00",
              "currency": "EUR"
          },
          "contract_date": "2026-01-10",
          "documents": [
              {
                  "type": "rental_agreement",
                  "filename": "rental-agreement.pdf",
                  "base64": "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo="
              }
          ]
      },
      "claims": [
          {
              "type": "receivable",
              "your_reference": "RENT-2026-02",
              "subject_matter": "Residential rent for February 2026",
              "document_date": "2026-02-01",
              "is_disputed": False,
              "principal_amount": {
                  "value": "1200.00",
                  "currency": "EUR"
              },
              "legal_basis": {
                  "claim_type_code": "H19"
              },
              "documents": [
                  {
                      "type": "claim_statement",
                      "filename": "rent-February-2026.pdf",
                      "base64": "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo="
                  }
              ]
          }
      ]
  }
  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=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 = order["id"]
  matching_claims = [claim for claim in order.get("claims", []) if claim.get("your_reference") == "RENT-2026-02"]
  if len(matching_claims) != 1:
      raise RuntimeError("Expected exactly one matching claim")
  claim_id = matching_claims[0]["id"]
  ```

  ```javascript JavaScript theme={null}
  const payload = {
    "type": "rental",
    "debtor_id": process.env.DEBTOR_ID,
      "additional_debtor_ids": [],
    "starting_approach": "extrajudicial",
    "creditor_obligation_fulfilled": true,
    "rental_agreement": {
      "property_address": {
        "street": "Rental Example Street 8",
        "postal_code": "10115",
        "city": "Berlin",
        "country": "DE"
      },
      "monthly_rent": {
        "value": "1200.00",
        "currency": "EUR"
      },
      "contract_date": "2026-01-10",
      "documents": [
        {
          "type": "rental_agreement",
          "filename": "rental-agreement.pdf",
          "base64": "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo="
        }
      ]
    },
    "claims": [
      {
        "type": "receivable",
        "your_reference": "RENT-2026-02",
        "subject_matter": "Residential rent for February 2026",
        "document_date": "2026-02-01",
        "is_disputed": false,
        "principal_amount": {
          "value": "1200.00",
          "currency": "EUR"
        },
        "legal_basis": {
          "claim_type_code": "H19"
        },
        "documents": [
          {
            "type": "claim_statement",
            "filename": "rent-February-2026.pdf",
            "base64": "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo="
          }
        ]
      }
    ]
  };
  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(payload),
    signal: AbortSignal.timeout(30000),
  });
  if (response.status !== 201) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  const order = await response.json();
  const orderId = order.id;
  const matchingClaims = order.claims.filter((claim) => claim.your_reference === payload.claims[0].your_reference);
  if (matchingClaims.length !== 1) throw new Error("Expected exactly one matching claim");
  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 RentalOrderRequest {
    static int closingDelimiter(String json, int opening, char open, char close) {
      int depth = 0;
      boolean inString = false;
      boolean escaped = false;
      for (int index = opening; index < json.length(); index++) {
        char character = json.charAt(index);
        if (inString) {
          if (escaped) escaped = false;
          else if (character == '\\') escaped = true;
          else if (character == '\"') inString = false;
        } else if (character == '\"') inString = true;
        else if (character == open) depth++;
        else if (character == close && --depth == 0) return index;
      }
      throw new IllegalArgumentException("Malformed JSON response");
    }
    static int closingQuote(String json, int opening) {
      boolean escaped = false;
      for (int index = opening + 1; index < json.length(); index++) {
        char character = json.charAt(index);
        if (escaped) escaped = false;
        else if (character == '\\') escaped = true;
        else if (character == '\"') return index;
      }
      throw new IllegalArgumentException("Malformed JSON string");
    }
    static int hexDigit(char character) {
      if (character >= '0' && character <= '9') return character - '0';
      if (character >= 'a' && character <= 'f') return character - 'a' + 10;
      if (character >= 'A' && character <= 'F') return character - 'A' + 10;
      throw new IllegalArgumentException("Malformed Unicode escape");
    }
    static int unicodeEscape(String json, int firstDigit, int closing) {
      if (firstDigit + 4 > closing) throw new IllegalArgumentException("Malformed Unicode escape");
      int value = 0;
      for (int index = firstDigit; index < firstDigit + 4; index++)
        value = value * 16 + hexDigit(json.charAt(index));
      return value;
    }
    static String decodeJsonString(String json, int opening, int closing) {
      StringBuilder decoded = new StringBuilder();
      for (int index = opening + 1; index < closing; index++) {
        char character = json.charAt(index);
        if (character == '\\') {
          if (++index >= closing) throw new IllegalArgumentException("Malformed JSON escape");
          char escape = json.charAt(index);
          switch (escape) {
            case '\"': decoded.append('\"'); break;
            case '\\': decoded.append('\\'); break;
            case '/': decoded.append('/'); break;
            case 'b': decoded.append('\b'); break;
            case 'f': decoded.append('\f'); break;
            case 'n': decoded.append('\n'); break;
            case 'r': decoded.append('\r'); break;
            case 't': decoded.append('\t'); break;
            case 'u':
              char unit = (char) unicodeEscape(json, index + 1, closing);
              index += 4;
              if (Character.isHighSurrogate(unit)) {
                if (index + 6 >= closing || json.charAt(index + 1) != '\\' || json.charAt(index + 2) != 'u')
                  throw new IllegalArgumentException("Missing low surrogate");
                char low = (char) unicodeEscape(json, index + 3, closing);
                if (!Character.isLowSurrogate(low)) throw new IllegalArgumentException("Invalid low surrogate");
                decoded.appendCodePoint(Character.toCodePoint(unit, low));
                index += 6;
              } else if (Character.isLowSurrogate(unit)) throw new IllegalArgumentException("Unexpected low surrogate");
              else decoded.append(unit);
              break;
            default: throw new IllegalArgumentException("Unsupported JSON escape");
          }
        } else {
          if (character < 0x20) throw new IllegalArgumentException("Unescaped control character");
          if (Character.isHighSurrogate(character)) {
            if (index + 1 >= closing || !Character.isLowSurrogate(json.charAt(index + 1)))
              throw new IllegalArgumentException("Missing raw low surrogate");
            decoded.append(character).append(json.charAt(++index));
          } else if (Character.isLowSurrogate(character)) throw new IllegalArgumentException("Unexpected raw low surrogate");
          else decoded.append(character);
        }
      }
      return decoded.toString();
    }
    static String directStringField(String object, String wanted) {
      int opening = 0;
      while (opening < object.length() && Character.isWhitespace(object.charAt(opening))) opening++;
      if (opening >= object.length() || object.charAt(opening) != '{')
        throw new IllegalArgumentException("Expected JSON object");
      int closing = closingDelimiter(object, opening, '{', '}');
      for (int cursor = opening + 1; cursor < closing;) {
        while (cursor < closing && (Character.isWhitespace(object.charAt(cursor)) || object.charAt(cursor) == ',')) cursor++;
        if (cursor >= closing) break;
        if (object.charAt(cursor) != '\"') throw new IllegalArgumentException("Malformed JSON field");
        int keyClosing = closingQuote(object, cursor);
        String key = decodeJsonString(object, cursor, keyClosing);
        cursor = keyClosing + 1;
        while (cursor < closing && Character.isWhitespace(object.charAt(cursor))) cursor++;
        if (cursor >= closing || object.charAt(cursor++) != ':') throw new IllegalArgumentException("Malformed JSON field");
        while (cursor < closing && Character.isWhitespace(object.charAt(cursor))) cursor++;
        if (cursor >= closing) throw new IllegalArgumentException("Missing JSON value");
        char valueStart = object.charAt(cursor);
        if (valueStart == '\"') {
          int valueClosing = closingQuote(object, cursor);
          if (key.equals(wanted)) return decodeJsonString(object, cursor, valueClosing);
          cursor = valueClosing + 1;
        } else if (valueStart == '{' || valueStart == '[') {
          char valueClose = valueStart == '{' ? '}' : ']';
          if (key.equals(wanted)) throw new IllegalArgumentException("Expected string field " + wanted);
          cursor = closingDelimiter(object, cursor, valueStart, valueClose) + 1;
        } else {
          if (key.equals(wanted)) throw new IllegalArgumentException("Expected string field " + wanted);
          while (cursor < closing && object.charAt(cursor) != ',') cursor++;
        }
      }
      return null;
    }
    static String topLevelId(String object) {
      String id = object.replaceFirst(
          "(?s)^\\s*\\{(?:(?!\\{).)*?\\\"id\\\"\\s*:\\s*\\\"([^\\\"]+)\\\".*$", "$1");
      if (id.equals(object)) throw new IllegalArgumentException("Missing top-level id");
      return id;
    }
    static String[] responseIds(String body, String reference) {
      if (reference == null) throw new IllegalArgumentException("Missing business reference");
      String orderId = topLevelId(body);
      java.util.regex.Matcher claims = java.util.regex.Pattern
          .compile("\\\"claims\\\"\\s*:\\s*\\[").matcher(body);
      if (!claims.find()) throw new IllegalArgumentException("Missing claims collection");
      int opening = body.indexOf('[', claims.start());
      int closing = closingDelimiter(body, opening, '[', ']');
      int matches = 0;
      String claimId = null;
      for (int cursor = opening + 1; cursor < closing;) {
        while (cursor < closing && (Character.isWhitespace(body.charAt(cursor)) || body.charAt(cursor) == ',')) cursor++;
        if (cursor >= closing) break;
        if (body.charAt(cursor) != '{') throw new IllegalArgumentException("Malformed claim object");
        int claimClosing = closingDelimiter(body, cursor, '{', '}');
        String claim = body.substring(cursor, claimClosing + 1);
        if (reference.equals(directStringField(claim, "your_reference"))) {
          matches++;
          claimId = directStringField(claim, "id");
          if (claimId == null) throw new IllegalArgumentException("Missing direct claim id");
        }
        cursor = claimClosing + 1;
      }
      if (matches != 1) throw new IllegalArgumentException("Expected exactly one matching claim");
      return new String[] {orderId, claimId};
    }
    public static void main(String[] args) throws IOException, InterruptedException {
      String json = "{\"type\":\"rental\",\"debtor_id\":\"" + System.getenv("DEBTOR_ID") + "\",\"additional_debtor_ids\":[],\"starti"
          + "ng_approach\":\"extrajudicial\",\"creditor_obligation_fulfilled\":true,\"renta"
          + "l_agreement\":{\"property_address\":{\"street\":\"Rental Example Street 8\",\"po"
          + "stal_code\":\"10115\",\"city\":\"Berlin\",\"country\":\"DE\"},\"monthly_rent\":{\"valu"
          + "e\":\"1200.00\",\"currency\":\"EUR\"},\"contract_date\":\"2026-01-10\",\"documents\":"
          + "[{\"type\":\"rental_agreement\",\"filename\":\"rental-agreement.pdf\",\"base64\":\""
          + "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5k"
          + "b2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4K"
          + "ZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3gg"
          + "WzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAow"
          + "MDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAw"
          + "MCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4Ngol"
          + "JUVPRgo=\"}]},\"claims\":[{\"type\":\"receivable\",\"your_reference\":\"RENT-2026-"
          + "02\",\"subject_matter\":\"Residential rent for February 2026\",\"document_date"
          + "\":\"2026-02-01\",\"is_disputed\":false,\"principal_amount\":{\"value\":\"1200.00\""
          + ",\"currency\":\"EUR\"},\"legal_basis\":{\"claim_type_code\":\"H19\"},\"documents\":["
          + "{\"type\":\"claim_statement\",\"filename\":\"rent-February-2026.pdf\",\"base64\":\""
          + "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5k"
          + "b2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4K"
          + "ZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3gg"
          + "WzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAow"
          + "MDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAw"
          + "MCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4Ngol"
          + "JUVPRgo=\"}]}]}";
      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());
      String[] ids = responseIds(response.body(), "RENT-2026-02");
      String orderId = ids[0];
      String claimId = ids[1];
    }
  }
  ```

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

  class RentalOrderRequest
  {
      static async Task Main()
      {
          using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
          var payload = new {
              type = "rental",
              debtor_id = Environment.GetEnvironmentVariable("DEBTOR_ID"), additional_debtor_ids = Array.Empty<string>(),
              starting_approach = "extrajudicial",
              creditor_obligation_fulfilled = true,
              rental_agreement = new {
                  property_address = new {
                      street = "Rental Example Street 8",
                      postal_code = "10115",
                      city = "Berlin",
                      country = "DE"
                  },
                  monthly_rent = new {
                      value = "1200.00",
                      currency = "EUR"
                  },
                  contract_date = "2026-01-10",
                  documents = new[] {
                      new {
                          type = "rental_agreement",
                          filename = "rental-agreement.pdf",
                          base64 = "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo="
                      }
                  }
              },
              claims = new[] {
                  new {
                      type = "receivable",
                      your_reference = "RENT-2026-02",
                      subject_matter = "Residential rent for February 2026",
                      document_date = "2026-02-01",
                      is_disputed = false,
                      principal_amount = new {
                          value = "1200.00",
                          currency = "EUR"
                      },
                      legal_basis = new {
                          claim_type_code = "H19"
                      },
                      documents = new[] {
                          new {
                              type = "claim_statement",
                              filename = "rent-February-2026.pdf",
                              base64 = "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo="
                          }
                      }
                  }
              }
          };
          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 orderId = order.RootElement.GetProperty("id").GetGuid();
          var matches = order.RootElement.GetProperty("claims").EnumerateArray()
              .Where(claim => claim.GetProperty("your_reference").GetString() == payload.claims[0].your_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 order-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
  {
    "type": "rental",
    "debtor_id": "$DEBTOR_ID",
      "additional_debtor_ids": [],
    "starting_approach": "extrajudicial",
    "creditor_obligation_fulfilled": true,
    "rental_agreement": {
      "property_address": {
        "street": "Rental Example Street 8",
        "postal_code": "10115",
        "city": "Berlin",
        "country": "DE"
      },
      "monthly_rent": {
        "value": "1200.00",
        "currency": "EUR"
      },
      "contract_date": "2026-01-10",
      "documents": [
        {
          "type": "rental_agreement",
          "filename": "rental-agreement.pdf",
          "base64": "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo="
        }
      ]
    },
    "claims": [
      {
        "type": "receivable",
        "your_reference": "RENT-2026-02",
        "subject_matter": "Residential rent for February 2026",
        "document_date": "2026-02-01",
        "is_disputed": false,
        "principal_amount": {
          "value": "1200.00",
          "currency": "EUR"
        },
        "legal_basis": {
          "claim_type_code": "H19"
        },
        "documents": [
          {
            "type": "claim_statement",
            "filename": "rent-February-2026.pdf",
            "base64": "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo="
          }
        ]
      }
    ]
  }
  JSON
  )"
  [ "$http_status" -eq 201 ] || exit 1
  ORDER_ID="$(jq -er '.id' order-response.json)" || exit 1
  CLAIM_ID="$(jq -er --arg ref "RENT-2026-02" '[.claims[] | select(.your_reference == $ref)] | if length == 1 then .[0].id else error("expected exactly one matching claim") end' order-response.json)" || exit 1
  ```
</CodeGroup>

`contract_date` is the date the rental contract was concluded. It is not the
move-in date or the beginning of the tenancy. `monthly_rent` is the recurring
Warmmiete including ancillary-cost prepayments; it provides agreement context
and does not cap or determine an individual claim amount.

## 2. Let dates derive—or override them deliberately

When `rental_agreement.due_rule` is omitted or `null`, both H17 and H19 claims
default to the third German business day of the rental month. German business
days are Monday through Friday excluding German federal holidays.

For each H17/H19 claim:

* `document_date` must be the first day of the claimed month.
* Omitted `legal_basis.contract_date` is copied from the agreement. A different
  supplied value returns `400`.
* Omitted `due_date` is derived from the agreement rule.
* Omitted `delay_date` becomes the day after the resolved due date.
* An explicit `delay_date` must be later than the resolved `due_date`.
* Explicit due and delay dates remain claim-level overrides. Patching either
  field to `null` restores server derivation.
* A later agreement due-rule change recomputes derived dates and preserves
  explicit overrides — with one exception: an explicit `delay_date` that
  falls on or before the recomputed `due_date` is reset to `due_date + 1`
  and stops being an override. Reminders are informational and do not change
  this rule.
* A rental-agreement `contract_date` (set inline or through a later
  agreement `PATCH`) is written to every rental claim's
  `legal_basis.contract_date`, so an order whose contract date arrives
  through the agreement satisfies readiness.
* Every date is `YYYY-MM-DD` in ASCII digits; `9999-12-30` is the latest
  accepted `due_date` (`delay_date` is its successor) and the latest accepted
  reminder `date`/`due_date`; a later value is `400` on that field with code
  `invalid`.

One order cannot mix H17 and H19 or contain two rental claims for the same
month. A mixed family returns `400`; a duplicate month returns `409`, so patch
the existing claim instead. Other `ReceivableClaim` entries may coexist with the
rental claims.

## 3. Build the draft in stages

Create the empty draft first, then use a distinct stable idempotency key for
this logical claim command. Preserve the exact key and body for replay, and
store the returned claim UUID after verifying its exact business reference.

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

  PAYWISE_API_URL = "https://api-sandbox.paywise.de"
  PAYWISE_API_KEY = "pw_sbx_your_api_key"
  CREATE_CLAIM_KEY = "your-distinct-create-claim-key"
  claim_payload = {
      "order_id": "20000000-0000-4000-8000-000000000001",
      "type": "receivable",
      "your_reference": "RENT-2026-02",
      "subject_matter": "Residential rent for February 2026",
      "document_date": "2026-02-01",
      "is_disputed": False,
      "principal_amount": {"value": "1200.00", "currency": "EUR"},
      "legal_basis": {"claim_type_code": "H19"},
      "documents": [{"type": "claim_statement", "filename": "rent-February-2026.pdf", "base64": "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo="}],
  }
  response = requests.post(
      f"{PAYWISE_API_URL}/v2/claims/",
      headers={"Authorization": f"Bearer {PAYWISE_API_KEY}", "Content-Type": "application/json", "Idempotency-Key": CREATE_CLAIM_KEY},
      json=claim_payload,
      timeout=(5, 30),
  )
  if response.status_code != 201:
      response.raise_for_status()
      raise RuntimeError(f"Expected 201, received {response.status_code}")
  claim = response.json()
  if claim.get("your_reference") != claim_payload["your_reference"]:
      raise RuntimeError("Claim business reference mismatch")
  claim_id = claim["id"]
  ```

  ```javascript JavaScript theme={null}
  const claimPayload = {
    order_id: "20000000-0000-4000-8000-000000000001",
    type: "receivable",
    your_reference: "RENT-2026-02",
    subject_matter: "Residential rent for February 2026",
    document_date: "2026-02-01",
    is_disputed: false,
    principal_amount: { value: "1200.00", currency: "EUR" },
    legal_basis: { claim_type_code: "H19" },
    documents: [{ type: "claim_statement", filename: "rent-February-2026.pdf", base64: "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo=" }],
  };
  const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/claims/`, {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.PAYWISE_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": process.env.CREATE_CLAIM_KEY },
    body: JSON.stringify(claimPayload),
    signal: AbortSignal.timeout(30000),
  });
  if (response.status !== 201) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  const claim = await response.json();
  if (claim.your_reference !== claimPayload.your_reference) throw new Error("Claim business reference mismatch");
  const claimId = claim.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 CreateClaimRequest {
    static String quoteJsonString(String value) {
      if (value == null) throw new IllegalArgumentException("Missing business reference");
      return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
    }
    static String topLevelId(String object) {
      String id = object.replaceFirst(
          "(?s)^\\s*\\{(?:(?!\\{).)*?\\\"id\\\"\\s*:\\s*\\\"([^\\\"]+)\\\".*$", "$1");
      if (id.equals(object)) throw new IllegalArgumentException("Missing top-level claim id");
      return id;
    }
    static String createdClaimId(String body, String reference) {
      String referencePattern = "(?s)^\\s*\\{(?:(?!\\{).)*?\\\"your_reference\\\"\\s*:\\s*"
          + java.util.regex.Pattern.quote(quoteJsonString(reference));
      if (!java.util.regex.Pattern.compile(referencePattern).matcher(body).find())
        throw new IllegalArgumentException("Claim business reference mismatch");
      return topLevelId(body);
    }
    public static void main(String[] args) throws IOException, InterruptedException {
      String json = "{\"order_id\":\"20000000-0000-4000-8000-000000000001\",\"type\":\"receivable\",\"your_reference\":\"RENT-2026-02\",\"subject_matter\":\"Residential rent for February 2026\",\"document_date\":\"2026-02-01\",\"is_disputed\":false,\"principal_amount\":{\"value\":\"1200.00\",\"currency\":\"EUR\"},\"legal_basis\":{\"claim_type_code\":\"H19\"},\"documents\":[{\"type\":\"claim_statement\",\"filename\":\"rent-February-2026.pdf\",\"base64\":\"JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo=\"}]}";
      HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
      HttpRequest request = HttpRequest.newBuilder()
          .uri(URI.create(System.getenv("PAYWISE_API_URL") + "/v2/claims/"))
          .timeout(Duration.ofSeconds(30))
          .header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY"))
          .header("Content-Type", "application/json")
          .header("Idempotency-Key", System.getenv("CREATE_CLAIM_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());
      String claimId = createdClaimId(response.body(), "RENT-2026-02");
    }
  }
  ```

  ```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 CreateClaimRequest {
      static async Task Main() {
          using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
          var payload = new {
              order_id = "20000000-0000-4000-8000-000000000001", type = "receivable",
              your_reference = "RENT-2026-02",
              subject_matter = "Residential rent for February 2026",
              document_date = "2026-02-01",
              is_disputed = false,
              principal_amount = new { value = "1200.00", currency = "EUR" },
              legal_basis = new { claim_type_code = "H19" },
              documents = new[] { new { type = "claim_statement", filename = "rent-February-2026.pdf", base64 = "JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo=" } }
          };
          using var request = new HttpRequestMessage(HttpMethod.Post, $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/claims/");
          request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
          request.Headers.Add("Idempotency-Key", Environment.GetEnvironmentVariable("CREATE_CLAIM_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());
          using var claim = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
          if (claim.RootElement.GetProperty("your_reference").GetString() != payload.your_reference) throw new InvalidOperationException("Claim business reference mismatch");
          var claimId = claim.RootElement.GetProperty("id").GetGuid();
      }
  }
  ```

  ```bash cURL theme={null}
  status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
    --request POST "$PAYWISE_API_URL/v2/claims/" \
    --header "Authorization: Bearer $PAYWISE_API_KEY" \
    --header "Content-Type: application/json" \
    --header "Idempotency-Key: $CREATE_CLAIM_KEY" \
    --output claim-created.json --write-out '%{http_code}' \
    --data '{"order_id":"20000000-0000-4000-8000-000000000001","type":"receivable","your_reference":"RENT-2026-02","subject_matter":"Residential rent for February 2026","document_date":"2026-02-01","is_disputed":false,"principal_amount":{"value":"1200.00","currency":"EUR"},"legal_basis":{"claim_type_code":"H19"},"documents":[{"type":"claim_statement","filename":"rent-February-2026.pdf","base64":"JVBERi0xLjQKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyAvUGFnZXMgMiAwIFIgPj4KZW5kb2JqCjIgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFszIDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgL1BhcmVudCAyIDAgUiAvTWVkaWFCb3ggWzAgMCAyMDAgMTAwXSA+PgplbmRvYmoKeHJlZgowIDQKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAwNTggMDAwMDAgbiAKMDAwMDAwMDExNSAwMDAwMCBuIAp0cmFpbGVyCjw8IC9TaXplIDQgL1Jvb3QgMSAwIFIgPj4Kc3RhcnR4cmVmCjE4NgolJUVPRgo="}]}')"
  [ "$status" -eq 201 ] || exit 1
  CLAIM_ID="$(jq -er --arg ref "RENT-2026-02" 'if .your_reference == $ref then .id else error("claim business reference mismatch") end' claim-created.json)" || exit 1
  ```
</CodeGroup>

For a commercial tenancy, create the same empty rental draft, update its
agreement as needed, and send a complete H17 claim to the same collection.
For example, the claim body still includes the draft UUID:

```json theme={null}
{
  "order_id": "20000000-0000-4000-8000-000000000001",
  "type": "receivable",
  "your_reference": "COMMERCIAL-RENT-2026-03",
  "subject_matter": "Commercial rent for March 2026",
  "document_date": "2026-03-01",
  "due_date": "2026-03-10",
  "delay_date": "2026-03-11",
  "is_disputed": false,
  "principal_amount": {"value": "2400.00", "currency": "EUR"},
  "legal_basis": {"claim_type_code": "H17"}
}
```

Keep one primary `rental_agreement` document on the agreement and one relevant
`claim_statement` on each rental claim. A second primary of the same type
conflicts with `409`; delete the old draft document before replacing it.
Agreement documents are optional at finalization, but every claim still needs
a non-empty `subject_matter` or a supporting document.

## 4. Finalize or remove the draft

Finalize with an empty-body
[POST `/v2/orders/{id}/finalize/`](/api-docs/case-management-api/reference/orders/finalize-order)
and a fresh `Idempotency-Key`. A rental draft is incomplete until it has a
complete property address, positive monthly rent, contract date, and at least
one H17/H19 claim. Missing readiness data returns `422` with public paths such
as `rental_agreement.contract_date`; invalid commands return `400`, and
conflicts return `409`.

Read the order after any ambiguous result. Once submitted, it may be withdrawn
while still eligible. If the draft should never be submitted, use
[DELETE `/v2/orders/{id}/`](/api-docs/case-management-api/reference/orders/delete-draft-order)
instead; draft deletion also removes its rental agreement.

## Runnable Python workflow

This orchestration proves the sandbox before any write, builds the rental
order in stages (empty draft → agreement → claim), uses one generated key per
logical POST, and never automatically repeats an ambiguous finalize call.

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

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

base_url = PAYWISE_API_URL
if not base_url.startswith("https://"):
    raise ValueError("PAYWISE_API_URL must be an HTTPS sandbox URL")
headers = {"Authorization": f"Bearer {PAYWISE_API_KEY}"}

probe = requests.get(
    f"{base_url}/v2/orders/",
    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")

# Stage 1: an empty rental draft. The order owns its rental agreement from
# creation, so the singleton can be completed in a separate step.
create_key = str(uuid.uuid4())
created = requests.post(
    f"{base_url}/v2/orders/",
    headers={**headers, "Idempotency-Key": create_key},
    json={
        "type": "rental",
        "debtor_id": DEBTOR_ID,
        "starting_approach": "extrajudicial",
        "creditor_obligation_fulfilled": True,
        "claims": [],
    },
    timeout=(5, 30),
)
if created.status_code != 201:
    created.raise_for_status()
    raise RuntimeError(f"Expected 201, received {created.status_code}")
order = created.json()
if order.get("type") != "rental" or order.get("status") != "draft":
    raise RuntimeError("Created order is not an editable rental draft")
order_id = order["id"]

# Stage 2: complete the agreement; omitted due_rule keeps the default
# third-business-day derivation for every rental claim.
agreement = requests.patch(
    f"{base_url}/v2/orders/{order_id}/rental-agreement/",
    headers=headers,
    json={
        "property_address": {
            "street": "Rental Example Street 8",
            "postal_code": "10115",
            "city": "Berlin",
            "country": "DE",
        },
        "monthly_rent": {"value": "1200.00", "currency": "EUR"},
        "contract_date": "2026-01-10",
    },
    timeout=(5, 30),
)
if agreement.status_code != 200:
    agreement.raise_for_status()
    raise RuntimeError(f"Expected 200, received {agreement.status_code}")

# Stage 3: one residential rental month; due and delay dates derive from
# the agreement, contract_date is copied from it.
claim_key = str(uuid.uuid4())
claim_created = requests.post(
    f"{base_url}/v2/claims/",
    headers={**headers, "Idempotency-Key": claim_key},
    json={
        "order_id": order_id,
        "type": "receivable",
        "your_reference": f"RENT-2026-02-{order_id}",
        "subject_matter": "Residential rent for February 2026",
        "document_date": "2026-02-01",
        "is_disputed": False,
        "principal_amount": {"value": "1200.00", "currency": "EUR"},
        "legal_basis": {"claim_type_code": "H19"},
    },
    timeout=(5, 30),
)
if claim_created.status_code != 201:
    claim_created.raise_for_status()
    raise RuntimeError(f"Expected 201, received {claim_created.status_code}")
claim = claim_created.json()
expected_reference = f"RENT-2026-02-{order_id}"
if claim.get("your_reference") != expected_reference:
    raise RuntimeError("Claim business reference mismatch")
claim_id = claim["id"]

finalize_key = str(uuid.uuid4())
try:
    finalized = requests.post(
        f"{base_url}/v2/orders/{order_id}/finalize/",
        headers={**headers, "Idempotency-Key": finalize_key},
        timeout=(5, 30),
    )
except requests.Timeout:
    recovered = requests.get(
        f"{base_url}/v2/orders/{order_id}/",
        headers=headers,
        timeout=(5, 30),
    )
    if recovered.status_code != 200:
        recovered.raise_for_status()
        raise RuntimeError(f"Expected 200, received {recovered.status_code}")
    order = recovered.json()
    if order.get("status") not in {
        "submitted",
        "awaiting_client_response",
        "accepted",
        "rejected",
        "withdrawn",
        "expired",
    }:
        raise RuntimeError(
            "Finalize outcome remains ambiguous; inspect the draft before deciding whether to retry with the same key"
        )
else:
    if finalized.status_code != 200:
        finalized.raise_for_status()
        raise RuntimeError(f"Expected 200, received {finalized.status_code}")
    order = finalized.json()

WORKFLOW_RESULT = {"order_id": order_id, "claim_id": claim_id, "status": order["status"]}
```

## Failure and recovery

* `400 validation_error` on create or claim commands: a mixed H17/H19
  family, a claim `document_date` that is not the first of the month, a
  `legal_basis.contract_date` that differs from the agreement, or a
  `delay_date` not later than the resolved `due_date`. Correct the field
  paths named in `errors[]` and issue a new logical command.
* `409` on a claim create: a rental claim for that month already exists —
  patch the existing claim instead of retrying. `409` on a document create:
  a primary `rental_agreement` or `claim_statement` already exists; delete
  the old draft document first.
* `422` on finalize: the readiness paths (for example
  `rental_agreement.contract_date`, `rental_agreement.property_address`,
  or `claims.<id>.subject_matter`) name what the draft still lacks. A
  claim whose stored `delay_date` is not after its `due_date` is reported
  as `claims.<id>.delay_date` with code `delay_before_due`
  (`delay_date must be after due_date.`). The draft stays editable;
  complete it and finalize with a fresh key.
* Finalize timeout, `500`, or `503`: read the order by ID before deciding
  on a retry. Only an observed `draft` justifies repeating finalize with the
  **same** key.
* `429`: wait at least the integer seconds in `Retry-After`, then retry the
  same command and key.

## Verify

<CodeGroup>
  ```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 ReadRentalOrderRequest {
    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 ReadRentalOrderRequest
  {
      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 --silent --show-error --connect-timeout 5 --max-time 30 \
    --request GET "$PAYWISE_API_URL/v2/orders/$ORDER_ID/" \
    --header "Authorization: Bearer $PAYWISE_API_KEY" \
    --output rental-order.json --write-out '%{http_code}')"
  [ "$http_status" -eq 200 ] || exit 1
  ```
</CodeGroup>

Require `.type` to be `rental`, `.status` to be `submitted` or `accepted`,
and `.rental_agreement.property_address` plus at least one claim with
`legal_basis.claim_type_code` `H17` or `H19` before recording the workflow
as successful.

## Reconcile acceptance by claim UUID

Webhook deliveries are at least once. On `order.accepted`, intersect the event's
`claim_ids` with the claim UUIDs stored by exact business reference. For every
match, refetch `/v2/claims/{claim_id}/` and trust the claim's current
`order_id` and `mandate_id`, even when the event order differs from the
originally submitted order. The claim UUID remains stable; do not scan orders
or reconstruct merge chains.

## Related reference

* [GET `/v2/orders/{order_id}/rental-agreement/`](/api-docs/case-management-api/reference/orders/get-rental-agreement)
* [PATCH `/v2/orders/{order_id}/rental-agreement/`](/api-docs/case-management-api/reference/orders/update-rental-agreement)
* [POST `/v2/orders/{order_id}/rental-agreement/documents/`](/api-docs/case-management-api/reference/orders/create-rental-agreement-document)
* [GET `/v2/orders/{id}/`](/api-docs/case-management-api/reference/orders/get-order)


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