Outcome
You inspected every readiness issue, patched only contract-valid rejected Company fields, and re-read the exact company before any later Case finalization. Readiness remediation never changes Partner authorization. Incomplete companies remain readable and editable and may create Case drafts. Finalization re-evaluates readiness atomically. Evaluatecase_access
separately; green readiness does not prove consent or access.
1. Read the full current report
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_PARTNER_KEY = "pw_sbx_your_partner_key"
PAYWISE_COMPANY_ID = "20000000-0000-4000-8000-000000000001"
response = requests.get(
f"{PAYWISE_API_URL}/partner/v2/companies/{PAYWISE_COMPANY_ID}/",
headers={"Authorization": f"Bearer {PAYWISE_PARTNER_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(/\/$/, "")}/partner/v2/companies/${process.env.PAYWISE_COMPANY_ID}/`, {
method: "GET", headers: { Authorization: `Bearer ${process.env.PAYWISE_PARTNER_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 ReadinessRequest {
public static void main(String[] args) throws IOException, InterruptedException {
String url = System.getenv("PAYWISE_API_URL") + "/partner/v2/companies/" + System.getenv("PAYWISE_COMPANY_ID") + "/";
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(30))
.header("Authorization", "Bearer " + System.getenv("PAYWISE_PARTNER_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 ReadinessRequest {
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")}/partner/v2/companies/{Environment.GetEnvironmentVariable("PAYWISE_COMPANY_ID")}/");
request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_PARTNER_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());
}
}
status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
--request GET "$PAYWISE_API_URL/partner/v2/companies/$PAYWISE_COMPANY_ID/" \
--header "Authorization: Bearer $PAYWISE_PARTNER_KEY" --output company-before.json --write-out '%{http_code}')"
[ "$status" != "200" ] && exit 1
case_submission_readiness.issues array. Every field
starts with company. (for example company.payout_bank_account.account_holder);
strip that prefix and map the first remaining segment plus code to owned
input. Never stop at the first item or infer requirements from nullable
response fields.
A company.legal_representatives issue with code representative_required
names the missing role as an English code, for example
A legal representative with type "supervisory_board" is required. Resolve it
by sending the complete legal_representatives array with a representative
of that type; the array replaces the stored one.
2. Prove the sandbox before PATCH
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_PARTNER_KEY = "pw_sbx_your_partner_key"
response = requests.get(
f"{PAYWISE_API_URL}/partner/v2/info/",
headers={"Authorization": f"Bearer {PAYWISE_PARTNER_KEY}"}, timeout=(5, 30),
)
if response.status_code != 200:
response.raise_for_status()
raise RuntimeError(f"Expected 200, received {response.status_code}")
environment = next((value for name, value in response.headers.items() if name.lower() == "x-paywise-environment"), None)
if environment != "sandbox":
raise RuntimeError("Refusing Partner write outside the sandbox")
const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/partner/v2/info/`, {
method: "GET", headers: { Authorization: `Bearer ${process.env.PAYWISE_PARTNER_KEY}` }, signal: AbortSignal.timeout(30000),
});
if (response.status !== 200) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
const environment = response.headers.get("X-Paywise-Environment");
if (environment !== "sandbox") throw new Error("Refusing Partner write outside the sandbox");
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 ReadinessRequest {
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") + "/partner/v2/info/"))
.timeout(Duration.ofSeconds(30)).header("Authorization", "Bearer " + System.getenv("PAYWISE_PARTNER_KEY")).GET().build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new IOException(response.body());
String environment = response.headers().firstValue("X-Paywise-Environment").orElse("");
if (!"sandbox".equals(environment)) throw new IOException("Refusing Partner write outside the sandbox");
}
}
using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
class ReadinessRequest {
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")}/partner/v2/info/");
request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_PARTNER_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());
if (!response.Headers.TryGetValues("X-Paywise-Environment", out var environmentValues)
|| environmentValues.Count() != 1
|| !string.Equals(environmentValues.Single(), "sandbox", StringComparison.Ordinal))
throw new HttpRequestException("Refusing Partner write outside the sandbox");
}
}
proof="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
--request GET "$PAYWISE_API_URL/partner/v2/info/" \
--header "Authorization: Bearer $PAYWISE_PARTNER_KEY" \
--dump-header - --output /dev/null --write-out '\n%{http_code}')"
status="$(printf '%s\n' "$proof" | tail -n 1)"
[ "$status" != "200" ] && exit 1
environment="$(printf '%s\n' "$proof" | awk '
{
separator = index($0, ":")
if (!separator || tolower(substr($0, 1, separator - 1)) != "x-paywise-environment") next
count++
value = substr($0, separator + 1)
sub(/\r$/, "", value)
sub(/^[ \t]*/, "", value)
sub(/[ \t]*$/, "", value)
}
END { if (count != 1) exit 1; print value }
')" || exit 1
[ "$environment" != "sandbox" ] && exit 1
:
3. Patch only the rejected root field
An owned array replaces the entire array. Omitnotification_channels and
legal_representatives unless full replacement is intended. Re-sending an
object or array equal to the stored one — address, legal_representatives,
notification_channels, payout_bank_account, or an unchanged
data_sharing_basis — is a no-op: stored ids stay, and updated_at and the
ETag do not move. Company PATCH never accepts users; correct memberships
through their nested resources.
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_PARTNER_KEY = "pw_sbx_your_partner_key"
PAYWISE_COMPANY_ID = "20000000-0000-4000-8000-000000000001"
readiness_patch = {"payout_bank_account": {"account_holder": "Example Client GmbH", "iban": "DE89370400440532013000", "bic": None}}
response = requests.patch(
f"{PAYWISE_API_URL}/partner/v2/companies/{PAYWISE_COMPANY_ID}/",
headers={"Authorization": f"Bearer {PAYWISE_PARTNER_KEY}", "Content-Type": "application/json"},
json=readiness_patch, timeout=(5, 30),
)
if response.status_code != 200:
response.raise_for_status()
raise RuntimeError(f"Expected 200, received {response.status_code}")
const readinessPatch = { payout_bank_account: { account_holder: "Example Client GmbH", iban: "DE89370400440532013000", bic: null } };
const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/partner/v2/companies/${process.env.PAYWISE_COMPANY_ID}/`, {
method: "PATCH", headers: { Authorization: `Bearer ${process.env.PAYWISE_PARTNER_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(readinessPatch), 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 ReadinessRequest {
public static void main(String[] args) throws IOException, InterruptedException {
String json = "{\"payout_bank_account\":{\"account_holder\":\"Example Client GmbH\",\"iban\":\"DE89370400440532013000\",\"bic\":null}}";
String url = System.getenv("PAYWISE_API_URL") + "/partner/v2/companies/" + System.getenv("PAYWISE_COMPANY_ID") + "/";
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(30))
.header("Authorization", "Bearer " + System.getenv("PAYWISE_PARTNER_KEY")).header("Content-Type", "application/json")
.method("PATCH", HttpRequest.BodyPublishers.ofString(json)).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 ReadinessRequest {
static async Task Main() {
var payload = new { payout_bank_account = new { account_holder = "Example Client GmbH", iban = "DE89370400440532013000", bic = (string?)null } };
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
using var request = new HttpRequestMessage(HttpMethod.Patch, $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/partner/v2/companies/{Environment.GetEnvironmentVariable("PAYWISE_COMPANY_ID")}/");
request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_PARTNER_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 != 200) throw new HttpRequestException(await response.Content.ReadAsStringAsync());
}
}
status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
--request PATCH "$PAYWISE_API_URL/partner/v2/companies/$PAYWISE_COMPANY_ID/" \
--header "Authorization: Bearer $PAYWISE_PARTNER_KEY" --header "Content-Type: application/json" \
--output company-patched.json --write-out '%{http_code}' \
--data '{"payout_bank_account":{"account_holder":"Example Client GmbH","iban":"DE89370400440532013000","bic":null}}')"
[ "$status" != "200" ] && exit 1
4. Re-read current readiness before finalization
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_PARTNER_KEY = "pw_sbx_your_partner_key"
PAYWISE_COMPANY_ID = "20000000-0000-4000-8000-000000000001"
response = requests.get(
f"{PAYWISE_API_URL}/partner/v2/companies/{PAYWISE_COMPANY_ID}/",
headers={"Authorization": f"Bearer {PAYWISE_PARTNER_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(/\/$/, "")}/partner/v2/companies/${process.env.PAYWISE_COMPANY_ID}/`, {
method: "GET", headers: { Authorization: `Bearer ${process.env.PAYWISE_PARTNER_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 ReadinessRequest {
public static void main(String[] args) throws IOException, InterruptedException {
String url = System.getenv("PAYWISE_API_URL") + "/partner/v2/companies/" + System.getenv("PAYWISE_COMPANY_ID") + "/";
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(30))
.header("Authorization", "Bearer " + System.getenv("PAYWISE_PARTNER_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 ReadinessRequest {
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")}/partner/v2/companies/{Environment.GetEnvironmentVariable("PAYWISE_COMPANY_ID")}/");
request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_PARTNER_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());
}
}
status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
--request GET "$PAYWISE_API_URL/partner/v2/companies/$PAYWISE_COMPANY_ID/" \
--header "Authorization: Bearer $PAYWISE_PARTNER_KEY" --output company-current.json --write-out '%{http_code}')"
[ "$status" != "200" ] && exit 1
Runnable readiness workflow
Python workflow
import time
import uuid
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_PARTNER_KEY = "pw_sbx_your_partner_key"
PAYWISE_COMPANY_ID = "20000000-0000-4000-8000-000000000001"
base_url = PAYWISE_API_URL
if not base_url.startswith("https://"):
raise ValueError("PAYWISE_API_URL must be HTTPS")
company_id = str(uuid.UUID(PAYWISE_COMPANY_ID))
company_url = f"{base_url}/partner/v2/companies/{company_id}/"
session = requests.Session()
session.headers.update({"Authorization": f"Bearer {PAYWISE_PARTNER_KEY}"})
def read_company():
response = session.get(company_url, timeout=(5, 30))
if response.status_code != 200:
response.raise_for_status()
raise RuntimeError(f"Expected 200, received {response.status_code}")
value = response.json()
if value.get("id") != company_id:
raise RuntimeError("Company identity mismatch")
return value
def prove_sandbox():
response = session.get(f"{base_url}/partner/v2/info/", timeout=(5, 30))
if response.status_code != 200:
response.raise_for_status()
raise RuntimeError(f"Expected 200, received {response.status_code}")
environment = next((value for name, value in response.headers.items() if name.lower() == "x-paywise-environment"), None)
if environment != "sandbox":
raise RuntimeError("Refusing Partner write outside the sandbox")
before = read_company()
issues = list(before.get("case_submission_readiness", {}).get("issues", []))
issue_roots = {str(issue.get("field", "")).removeprefix("company.").split(".", 1)[0] for issue in issues}
allowed_fields = {"payout_bank_account"}
if not issue_roots <= allowed_fields:
raise RuntimeError(f"Unsupported readiness fields require a dedicated flow: {sorted(issue_roots - allowed_fields)}")
patch = {}
if "payout_bank_account" in issue_roots:
patch["payout_bank_account"] = {"account_holder": "Example Client GmbH", "iban": "DE89370400440532013000", "bic": None}
if patch:
prove_sandbox()
try:
patched = session.patch(company_url, headers={"Content-Type": "application/json"}, json=patch, timeout=(5, 30))
except requests.Timeout:
reconciled = None
for reconciliation_attempt in range(3):
candidate = read_company()
candidate_readiness = candidate.get("case_submission_readiness", {})
if (
candidate.get("payout_bank_account") == patch["payout_bank_account"]
and candidate_readiness.get("ready") is True
and candidate_readiness.get("issues") == []
):
reconciled = candidate
break
if reconciliation_attempt < 2:
time.sleep(reconciliation_attempt + 1)
if reconciled is None:
raise RuntimeError("Readiness PATCH outcome remains ambiguous; do not retry")
else:
if patched.status_code != 200:
patched.raise_for_status()
raise RuntimeError(f"Expected 200, received {patched.status_code}")
current = read_company()
readiness = current.get("case_submission_readiness", {})
if readiness.get("ready") is not True or readiness.get("issues") != []:
raise RuntimeError("Readiness remains unresolved; do not finalize")
WORKFLOW_RESULT = {"company_id": company_id, "ready": True, "issue_count": len(issues)}
Representative response
HTTP/1.1 200 OK
Content-Type: application/json
X-Paywise-Environment: sandbox
{
"id": "10000000-0000-4000-8000-000000000001",
"customer_number": "5G0123",
"name": "Example Client GmbH",
"onboarding_mode": "api_only",
"onboarding_status": "confirmation_not_required",
"legal_form": "gmbh",
"address": {
"street": "Musterstraße 1",
"postal_code": "10115",
"city": "Berlin",
"country": "DE"
},
"phone": "+49301234567",
"tax_treatment": "input_tax_deductible",
"vat_number": "DE129273398",
"default_claim_type": "H05",
"payout_bank_account": {
"account_holder": "Example Client GmbH",
"iban": "DE89370400440532013000",
"bic": null
},
"notification_channels": [{
"type": "email",
"value": "[email protected]",
"notifications": ["status_updates", "requests_to_client", "statements"]
}],
"legal_representatives": [{
"type": "managing_director",
"name": "Alex Example"
}],
"case_access": "available",
"case_submission_readiness": {"ready": true, "issues": []},
"users": [{
"id": "11000000-0000-4000-8000-000000000001",
"email": "[email protected]",
"first_name": "Alex",
"last_name": "Example",
"role": "admin",
"status": "pending_setup",
"invite_expires_at": "2026-09-06T10:00:00Z",
"revoked_at": null,
"revoked_by_token_id": null,
"revocation_reason": "",
"sandbox_origin": "sandbox_native",
"created_at": "2026-08-27T10:00:00Z",
"updated_at": "2026-08-27T10:05:00Z"
}],
"sandbox_origin": "sandbox_native",
"created_at": "2026-08-27T10:00:00Z",
"updated_at": "2026-08-27T10:05:00Z"
}
Failure and recovery
400 validation_error: branch on the returned field/code and send a new, minimal PATCH; never resend a stale full company.- A PATCH timeout is reconciled through the exact company URL before another correction. Company PATCH has no idempotency-key contract.
409 company_identity_lockedrequires the authorized non-API correction process for a real locked-field change.- Access unavailable is not a readiness-remediation endpoint. Stop delegated traffic and handle access separately.
