Outcome
You have a validated, immutablesubmitted order and retained identifiers for
the order and its claims. You can then wait for a client request, rejection,
withdrawal, or acceptance into a mandate.
This workflow covers the standard invoice submission and uses
ReceivableClaim entries, each declaring the required claim
"type": "receivable". Omitting the order-level type defaults to invoice. For subtype resources
and readiness rules, use Submit a rental order
or Submit a titled 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.
- One stable
Idempotency-Keyper logicalPOSTcommand.
Lifecycle context
Onlydraft orders are editable and finalizable. Finalize has no body and
makes the aggregate immutable with public state submitted, including during
internal review. Acceptance creates or extends a mandate and freezes the legal
debtor snapshot. A submitted order may be withdrawn only before staff review
starts.
0. Create or reuse the debtor
Create the reusable debtor first. Each example parses the exact returned UUID; persist it asDEBTOR_ID and use that value in the order request. Reconcile
an ambiguous retry by the exact your_reference; never guess an ID.
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"]))
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");
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");
}
}
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();
}
}
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
1. Create an aggregate draft
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"
CREATE_ORDER_KEY = "your-create-order-key"
DEBTOR_ID = debtor_id # Parsed from the immediately preceding create response.
DOCUMENT_REFERENCE = "INV-80000000-0000-4000-8000-000000000001"
order_payload = {
"debtor_id": DEBTOR_ID,
"additional_debtor_ids": [],
"starting_approach": "extrajudicial",
"creditor_obligation_fulfilled": False,
"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": CREATE_ORDER_KEY,
},
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 = 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 matching claim")
claim_id = matching_claims[0]["id"]
const orderPayload = {
debtor_id: process.env.DEBTOR_ID,
additional_debtor_ids: [], starting_approach: "extrajudicial",
creditor_obligation_fulfilled: false,
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.CREATE_ORDER_KEY },
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 orderId = order.id;
const matchingClaims = order.claims.filter(
(claim) => claim.your_reference === process.env.CLAIM_REFERENCE,
);
if (matchingClaims.length !== 1) throw new Error("Expected exactly one matching claim");
const claimId = matchingClaims[0].id;
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 SubmitOrderRequest {
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();
}
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 = "{\"debtor_id\":" + jsonString(System.getenv("DEBTOR_ID"))
+ ",\"additional_debtor_ids\":[],\"starting_approach\":\"extrajudicial\","
+ "\"creditor_obligation_fulfilled\":false,\"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("CREATE_ORDER_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[] ids = responseIds(response.body(), System.getenv("CLAIM_REFERENCE"));
String orderId = ids[0];
String claimId = ids[1];
}
}
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 SubmitOrderRequest
{
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 = false,
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("CREATE_ORDER_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 order = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var orderId = order.RootElement.GetProperty("id").GetString();
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").GetString();
}
}
order_payload="$(jq -n \
--arg debtor "$DEBTOR_ID" \
--arg claim_reference "$CLAIM_REFERENCE" \
--arg document_reference "$DOCUMENT_REFERENCE" '
{
debtor_id: $debtor,
additional_debtor_ids: [],
starting_approach: "extrajudicial",
creditor_obligation_fulfilled: false,
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"
}
}]
}
')" || exit 1
http_status="$(curl --fail-with-body --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: $CREATE_ORDER_KEY" \
--output order-response.json --write-out '%{http_code}' \
--data "$order_payload")"
[ "$http_status" -eq 201 ] || exit 1
ORDER_ID="$(jq -er '.id' order-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 exactly one matching claim") end' order-response.json)" || exit 1
ReceivableClaim entries. Do not send
calculated total fields, interest configuration, rental-agreement or
enforceable-title fields, or order_flow_type.
Create or update debtor details only through the debtor resource. Order writes
contain debtor UUIDs, never debtor objects. Create new claims inline as complete
claim request objects, or attach them to a draft through the stable claim
collection shown below.
Every claim needs a default date (delay_date) before finalization. Send it
explicitly as above, or let the API derive it: from the claim’s reminders
for a consumer debtor, or from document_date plus 32 days for a business
debtor. A consumer claim with neither delay_date nor reminders stays a
draft — finalization reports claims.<id>.delay_date as missing.
Staged claim alternative
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.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": "quickstart-claim-80000000-0000-4000-8000-000000000001",
"document_reference": "INV-80000000-0000-4000-8000-000000000001",
"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/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"]
const claimPayload = {
order_id: "20000000-0000-4000-8000-000000000001",
type: "receivable",
your_reference: "quickstart-claim-80000000-0000-4000-8000-000000000001",
document_reference: "INV-80000000-0000-4000-8000-000000000001",
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/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;
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\":\"quickstart-claim-80000000-0000-4000-8000-000000000001\",\"document_reference\":\"INV-80000000-0000-4000-8000-000000000001\",\"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/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(), "quickstart-claim-80000000-0000-4000-8000-000000000001");
}
}
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 = "quickstart-claim-80000000-0000-4000-8000-000000000001",
document_reference = "INV-80000000-0000-4000-8000-000000000001",
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/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();
}
}
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":"quickstart-claim-80000000-0000-4000-8000-000000000001","document_reference":"INV-80000000-0000-4000-8000-000000000001","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"}}')"
[ "$status" -eq 201 ] || exit 1
CLAIM_ID="$(jq -er --arg ref "quickstart-claim-80000000-0000-4000-8000-000000000001" 'if .your_reference == $ref then .id else error("claim business reference mismatch") end' claim-created.json)" || exit 1
2. Correct the draft
Patch only what changed. To change the additional debtors, sendadditional_debtor_ids to
PATCH /v2/orders/{id}/.
The array replaces the complete membership: include the UUID of every debtor
you want to keep. Omit the field to preserve the membership; [] unlinks everyone without
deleting debtor records. The same 10-debtor limit and duplicate checks apply as
at creation. This operation is available only while the order is a draft.
Existing debtor and claim data remain unchanged on unrelated patches. Patch a
draft claim by its stable top-level claim UUID; replacing a claim’s complete
additional_charges array requires every desired replacement charge.
The order response embeds debtor summaries. Use their IDs with
GET /v2/debtors/{id}/
for full details, and
PATCH /v2/debtors/{id}/
to correct debtor information. When updating membership based on a previous
order read, send its ETag in If-Match to avoid overwriting a concurrent edit.
For a claim correction, patch the stable top-level claim resource directly:
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
CLAIM_ID = "40000000-0000-4000-8000-000000000001"
ORDER_ID = "30000000-0000-4000-8000-000000000001"
response = requests.patch(
f"{PAYWISE_API_URL}/v2/claims/{CLAIM_ID}/",
headers={"Authorization": f"Bearer {PAYWISE_API_KEY}", "Content-Type": "application/json"},
json={"subject_matter": "Consulting services for June 2026"},
timeout=(5, 30),
)
if response.status_code != 200:
response.raise_for_status()
raise RuntimeError(f"Expected 200, received {response.status_code}")
const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/claims/${process.env.CLAIM_ID}/`, {
method: "PATCH",
headers: { Authorization: `Bearer ${process.env.PAYWISE_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ subject_matter: "Consulting services for June 2026" }),
signal: AbortSignal.timeout(30000),
});
if (response.status !== 200) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
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 CorrectClaimRequest {
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/claims/" + System.getenv("CLAIM_ID") + "/"))
.timeout(Duration.ofSeconds(30)).header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY"))
.header("Content-Type", "application/json")
.method("PATCH", HttpRequest.BodyPublishers.ofString("{\"subject_matter\":\"Consulting services for June 2026\"}"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new IOException(response.body());
}
}
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
class CorrectClaimRequest
{
static async Task Main()
{
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
using var request = new HttpRequestMessage(HttpMethod.Patch,
$"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/claims/{Environment.GetEnvironmentVariable("CLAIM_ID")}/");
request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
request.Content = JsonContent.Create(new { subject_matter = "Consulting services for June 2026" });
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());
}
}
http_status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
--request PATCH "$PAYWISE_API_URL/v2/claims/$CLAIM_ID/" \
--header "Authorization: Bearer $PAYWISE_API_KEY" \
--header "Content-Type: application/json" \
--output claim-response.json --write-out '%{http_code}' \
--data '{"subject_matter":"Consulting services for June 2026"}')"
[ "$http_status" -eq 200 ] || exit 1
3. Finalize without a request body
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
FINALIZE_KEY = "your-finalize-key"
ORDER_ID = "30000000-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_KEY},
timeout=(5, 30),
)
if response.status_code != 200:
response.raise_for_status()
raise RuntimeError(f"Expected 200, received {response.status_code}")
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_KEY },
signal: AbortSignal.timeout(30000),
});
if (response.status !== 200) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
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 FinalizeOrderRequest {
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_KEY"))
.POST(HttpRequest.BodyPublishers.noBody()).build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new IOException(response.body());
}
}
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
class FinalizeOrderRequest
{
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_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());
}
}
http_status="$(curl --fail-with-body --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_KEY" \
--output finalized-order.json --write-out '%{http_code}')"
[ "$http_status" -eq 200 ] || exit 1
{}; the cURL example uses the empty
form, so it sends neither --data nor Content-Type. After a timeout, fetch
the order by ID first. Record any observed submitted, awaiting_client_response,
accepted, rejected, withdrawn, or expired outcome. If it is still
draft, make an explicit retry decision with the same FINALIZE_KEY; never
issue a second uncontrolled finalize write.
Representative response
HTTP/1.1 200 OK
Content-Type: application/json
{
"confirmation_email": null,
"expires_at": null,
"rejection": null,
"id": "20000000-0000-4000-8000-000000000001",
"type": "invoice",
"status": "submitted",
"mandate": null,
"merged_into": null,
"your_reference": "client-claim-42",
"starting_approach": "extrajudicial",
"creditor_obligation_fulfilled": true,
"debtor": {
"id": "10000000-0000-4000-8000-000000000001",
"your_reference": "quickstart-customer-1001",
"acting_as": "consumer",
"person": {
"salutation": "mx",
"first_name": "Alex",
"last_name": "Example",
"birth_date": null
},
"organization": null,
"legal_form": null,
"metadata": [],
"events": []
},
"additional_debtors": [],
"claims": [
{
"id": "30000000-0000-4000-8000-000000000001",
"order_id": "20000000-0000-4000-8000-000000000001",
"status": "submitted",
"mandate_id": null,
"debtor_id": "10000000-0000-4000-8000-000000000001",
"additional_debtor_ids": [],
"payments": [],
"type": "receivable",
"your_reference": "client-claim-42",
"document_reference": "INV-2026-0042",
"subject_matter": "Consulting services for June 2026",
"is_disputed": false,
"dispute_reason": null,
"principal_amount": {"value": "125.50", "currency": "EUR"},
"items": [],
"additional_charges": [],
"additional_charges_amount": {"value": "0.00", "currency": "EUR"},
"total_amount": {"value": "125.50", "currency": "EUR"},
"document_date": "2026-06-30",
"due_date": "2026-07-14",
"reminders": [],
"delay_date": "2026-07-20",
"legal_basis": {
"claim_type_code": "H05",
"contract_date": "2026-06-01",
"description": "Consulting agreement"
},
"documents": [],
"metadata": [],
"events": [],
"created_at": "2026-08-27T10:00:00Z",
"updated_at": "2026-08-27T10:04:00Z"
}
],
"totals": {
"order_value": {"value": "125.50", "currency": "EUR"},
"main_claims": {"value": "125.50", "currency": "EUR"},
"charges": {"value": "0.00", "currency": "EUR"},
"payments": {"value": "0.00", "currency": "EUR"}
},
"created_at": "2026-08-27T10:00:00Z",
"updated_at": "2026-08-27T10:05:00Z"
}
Order response schema.
Runnable Python workflow
This orchestration proves the sandbox before any write, uses one generated key per logical POST, and never automatically repeats an ambiguous finalize call.Python workflow
import uuid
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 = "10000000-0000-4000-8000-000000000001"
DOCUMENT_REFERENCE = "INV-80000000-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")
order_payload = {
"debtor_id": DEBTOR_ID,
"additional_debtor_ids": [],
"starting_approach": "extrajudicial",
"creditor_obligation_fulfilled": False,
"claims": [{
"type": "receivable",
"your_reference": CLAIM_REFERENCE,
"document_reference": DOCUMENT_REFERENCE,
"subject_matter": "Consulting services for May 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,
}],
}
create_key = str(uuid.uuid4())
created = requests.post(
f"{base_url}/v2/orders/",
headers={**headers, "Idempotency-Key": create_key},
json=order_payload,
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("status") != "draft":
raise RuntimeError("Created order is not editable")
order_id = 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 = matching_claims[0]["id"]
corrected = requests.patch(
f"{base_url}/v2/claims/{claim_id}/",
headers=headers,
json={"subject_matter": "Consulting services for June 2026"},
timeout=(5, 30),
)
if corrected.status_code != 200:
corrected.raise_for_status()
raise RuntimeError(f"Expected 200, received {corrected.status_code}")
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: correct the reported field paths on the draft, then finalize again with a new logical-command key. The failed validation did not finalize the order. Common causes on create andPATCH: a missingdebtor_id(debtor_id/required), a non-string, malformed, or empty UUID (debtor_id/invalid, oradditional_debtor_ids[i]/invalid), a duplicate party (additional_debtor_ids/duplicate),creditor_obligation_fulfilledoris_disputedsent as a string or number instead of a JSON boolean (invalid), a date with non-ASCII digits (invalid), or a control character in a text field (control_characters). An unknown or other-company debtor UUID is a neutral404, not a400field error.403 terms_acceptance_requiredon finalize: the credential’s current API terms have not been accepted. Accept them in the developer portal; the draft stays editable and finalize again with a fresh key.- Finalize timeout, transient
500, or503: fetch the exact order by ID before making any retry decision. Record every valid non-draft lifecycle outcome; only an observeddraftcan be considered for an explicit retry with the same key. Do not immediately repeat finalize. - For other ambiguous idempotent POST outcomes, reconcile the resource first; if a retry is still required, repeat the same request with the same key.
409 idempotency_request_in_progress: wait forRetry-After, then retry the same request and key.idempotency_key_expiredis terminal; reconcile by fetching the order. Reusing a key for a different operation or body also returns409and requires a new logical command.- Lifecycle
409: refetch the order. It may already be finalized (Order is already finalized.), withdrawn or decided (Order is already finalized or withdrawn.— also when finalize raced a withdrawal), or merged into another order (Order was merged into another order., on every write; followmerged_into). Another actor may have changed it. - To withdraw an eligible submitted order, use a fresh command key:
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
ORDER_ID = "30000000-0000-4000-8000-000000000001"
WITHDRAW_KEY = "your-withdraw-key"
response = requests.post(
f"{PAYWISE_API_URL}/v2/orders/{ORDER_ID}/withdraw/",
headers={"Authorization": f"Bearer {PAYWISE_API_KEY}", "Content-Type": "application/json", "Idempotency-Key": WITHDRAW_KEY},
json={"reason": "Submitted duplicate invoice in error"},
timeout=(5, 30),
)
if response.status_code != 200:
response.raise_for_status()
raise RuntimeError(f"Expected 200, received {response.status_code}")
const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/orders/${process.env.ORDER_ID}/withdraw/`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PAYWISE_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": process.env.WITHDRAW_KEY },
body: JSON.stringify({ reason: "Submitted duplicate invoice in error" }),
signal: AbortSignal.timeout(30000),
});
if (response.status !== 200) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
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 WithdrawOrderRequest {
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") + "/withdraw/"))
.timeout(Duration.ofSeconds(30)).header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY"))
.header("Content-Type", "application/json").header("Idempotency-Key", System.getenv("WITHDRAW_KEY"))
.POST(HttpRequest.BodyPublishers.ofString("{\"reason\":\"Submitted duplicate invoice in error\"}"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new IOException(response.body());
}
}
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
class WithdrawOrderRequest
{
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")}/withdraw/");
request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
request.Headers.Add("Idempotency-Key", Environment.GetEnvironmentVariable("WITHDRAW_KEY"));
request.Content = JsonContent.Create(new { reason = "Submitted duplicate invoice in error" });
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());
}
}
http_status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
--request POST "$PAYWISE_API_URL/v2/orders/$ORDER_ID/withdraw/" \
--header "Authorization: Bearer $PAYWISE_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: $WITHDRAW_KEY" \
--output withdrawn-order.json --write-out '%{http_code}' \
--data '{"reason":"Submitted duplicate invoice in error"}')"
[ "$http_status" -eq 200 ] || exit 1
409 after staff review starts and for draft, accepted,
rejected, expired, or already withdrawn orders. Refetch and stop; do not loop.
Verify
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
ORDER_ID = "30000000-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}")
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()}`);
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 ReadOrderRequest {
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());
}
}
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
class ReadOrderRequest
{
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());
}
}
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 current-order.json --write-out '%{http_code}')"
[ "$http_status" -eq 200 ] || exit 1
.status to be submitted or accepted before recording the
workflow as successful.
Also retain X-Paywise-Request-Id for support and the finalization key until
the outcome is durably recorded.
Reconcile acceptance by claim UUID
Webhook deliveries are at least once. Onorder.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.
