# WireBloom Banking Platform API > One versioned REST API (`/v1`, OpenAPI 3.1.0) for balances, payments, recipients, exchanges and webhooks of a WireBloom customer account. Contract version 1.0.0-draft.14. Authenticate with a bearer API key and send `X-Tenant-Id`. Production base URL: https://bank.wirebloom.com/v1. Sandbox base URL: https://bank.wirebloom.com/v1 (`wb_test_` keys). ## Guides - [Getting started](https://developers.wirebloom.com/md/docs/getting-started.md): Environments, your first API key and your first request. - [Authentication and API keys](https://developers.wirebloom.com/md/docs/authentication.md): Bearer API keys, scopes, IP allow-lists, expiry and rotation. - [Tenancy](https://developers.wirebloom.com/md/docs/tenancy.md): The X-Tenant-Id header and how keys are bound to one customer. - [Idempotency](https://developers.wirebloom.com/md/docs/idempotency.md): Safe retries with the Idempotency-Key header. - [Pagination, sorting and filtering](https://developers.wirebloom.com/md/docs/pagination.md): Cursor pagination, sort and filter parameters on list operations. - [Errors](https://developers.wirebloom.com/md/docs/errors.md): RFC 9457 problem details and how to handle them. - [Rate limits](https://developers.wirebloom.com/md/docs/rate-limits.md): Per-key request buckets and the RateLimit headers. - [Webhooks and signature verification](https://developers.wirebloom.com/md/docs/webhooks.md): Register endpoints, verify signatures, retries, pause and secret rotation. - [Sandbox and simulation](https://developers.wirebloom.com/md/docs/sandbox.md): Deterministic sandbox bank rules and the simulation endpoints. - [Versioning and deprecation](https://developers.wirebloom.com/md/docs/versioning.md): What changes without notice, what needs a new major version, and how deprecations are announced. - [Migrating from the Integrated Finance API](https://developers.wirebloom.com/md/docs/migrating-from-integrated-finance.md): Resource and convention mapping for integrations built against Integrated Finance. - [Integration and usage](https://developers.wirebloom.com/md/docs/integration-usage.md): Where operators and developers see API keys, per-key usage and webhook health. ## API reference - [tenants](https://developers.wirebloom.com/md/reference/tenants.md): Operators, customers, people (memberships), invitations, teams and customer settings (currencies, addresses, alerts, approved domains, approval policies, primary owner, closure). - [onboarding](https://developers.wirebloom.com/md/reference/onboarding.md): Customer KYB case: wizard data, persons (directors, UBOs), documents, submission, KYC provider SDK tokens, operator review queue and decisions (D-22). - [accounts](https://developers.wirebloom.com/md/reference/accounts.md): Balances (ledger accounts per currency and sub-balances), account details per rail, journal with running balance, statements, currencies. - [recipients](https://developers.wirebloom.com/md/reference/recipients.md): Recipients (beneficiaries): requirements per currency/country, CRUD, Confirmation/Verification of Payee, approval (customer tier or operator queue, D-14). - [payments](https://developers.wirebloom.com/md/reference/payments.md): Payments, internal transfers, add money, exchanges and quotes, forward contracts and held rates, scheduled payments, direct debits (mandates, collections), bulk payments, approvals and funds requests. - [activity](https://developers.wirebloom.com/md/reference/activity.md): Unified transactions (ledger view) with filters, search and exports; transaction detail with updates, annotations, attachments and confirmations; documents and asynchronous jobs. - [providers](https://developers.wirebloom.com/md/reference/providers.md): Operator banking-partner configuration: provider connections (write-only credentials), routing rules, KYC connections, inbound provider webhooks and the provider event log. - [public-api](https://developers.wirebloom.com/md/reference/public-api.md): Integrator access: API keys with scopes and integrator webhooks (HMAC-signed callbacks). - [developer](https://developers.wirebloom.com/md/reference/developer.md): Self-serve developer sandboxes (D-42, FR-PLAT-07): registration (open mode) with email verification and abuse limits, the developer workspace, and the operator / platform controls (invite or open per operator, capped per environment). - [simulations](https://developers.wirebloom.com/md/reference/simulations.md): Sandbox simulations (SC-38, P-89): every asynchronous outcome on demand — incoming payments and debits, payment status, returns and recalls, inbound recalls, exchange settlement, FX rate moves, direct-debit outcomes, card events, KYC outcomes, integrator events, statements, scheduled runs and provider outages. ## Optional - [OpenAPI contract](https://developers.wirebloom.com/openapi.json): the machine-readable public integrator contract - [Error catalogue](https://developers.wirebloom.com/errors): every problem type with status and retryability - [Event catalogue](https://developers.wirebloom.com/events): webhook events and payload schemas - [Changelog](https://developers.wirebloom.com/changelog): contract releases - [Deprecations](https://developers.wirebloom.com/deprecations): deprecated operations and sunset dates - [Full text](https://developers.wirebloom.com/llms-full.txt): every guide and the compact reference in one file # Getting started The WireBloom Banking Platform API connects an ERP, accounting or treasury system to a WireBloom customer account. One versioned API (`/v1`) serves the web app, the mobile app and integrators, so everything the web app shows is available to your system within the scopes of your key. ## Environments | Environment | Base URL | Keys | Money | | ----------- | ---------------------------------------------- | ----------- | ------------------------------------ | | Production | `https://api.banking.wirebloom.com/v1` | `wb_live_…` | real (banking partners) | | Staging | `https://api.staging.banking.wirebloom.com/v1` | `wb_test_…` | simulated ([sandbox](/docs/sandbox)) | | Local | `http://localhost:3001/v1` | `wb_test_…` | simulated | JSON over HTTPS (UTF-8). Money is `{ "amountMinor": "125000", "currency": "EUR" }`: integer minor units as a string, never floats. Timestamps are RFC 3339 UTC (`…Z`). Clients must ignore unknown fields and tolerate new enum values. ## Your first request 1. Ask a customer Owner or Admin to create a `wb_test_` key on the sandbox customer (Settings → API keys, see [Authentication and API keys](/docs/authentication)). 2. Store the key in your secrets manager: it is shown once. 3. List the balances of the customer: ```sh curl https://api.staging.banking.wirebloom.com/v1/balances \ -H "Authorization: Bearer $WIREBLOOM_API_KEY" \ -H "X-Tenant-Id: $WIREBLOOM_CUSTOMER_ID" ``` `X-Tenant-Id` is the id of the customer the key belongs to ([Tenancy](/docs/tenancy)). The response is a paginated list ([Pagination](/docs/pagination)); the operation is [`listBalances`](/reference/accounts#listBalances). ## Next steps - Send money with [`createPayment`](/reference/payments#createPayment) and an `Idempotency-Key` ([Idempotency](/docs/idempotency)). - Receive events instead of polling: [Webhooks](/docs/webhooks). - Try every asynchronous outcome in the [sandbox](/docs/sandbox) before going live. - Read the [error catalogue](/errors) and the [rate limits](/docs/rate-limits). ## Sample integration `platform/examples/integration-client` (TypeScript, Node 22) uses the typed client generated from the contract and includes a webhook receiver that verifies signatures. It lists balances, registers itself as an endpoint, sends a test event, simulates an incoming payment and prints the events it receives, then removes the endpoint. ```sh pnpm --filter @wirebloom/example-client build WIREBLOOM_API_URL=http://localhost:3001/v1 WIREBLOOM_API_KEY=wb_test_… \ WIREBLOOM_TENANT_ID= pnpm --filter @wirebloom/example-client start ``` ## Security checklist - Keep keys and webhook secrets in a secrets manager; never in code, logs or URLs. - One key per system, least scopes, IP allow-list and expiry where possible; rotate regularly. - Verify every webhook signature and timestamp; de-duplicate on the event id. - Treat webhook payloads as hints: confirm state with the API before acting on money. - Report suspected key exposure to your WireBloom administrator and revoke the key at once. # Authentication and API keys Send the key as a bearer token on every request: ```http GET /v1/balances HTTP/1.1 Host: api.banking.wirebloom.com Authorization: Bearer wb_live_7Hk2cQab… X-Tenant-Id: 0192a6f0-0000-7000-8000-0000000000c1 ``` ## Creating a key A customer Owner or Admin creates keys in the web app (Settings → API keys) or with `createApiKey` (`POST /v1/api-keys`, an interactive-session operation, not part of the public API-key reference) from an interactive session; a fresh step-up verification (`api_key.create`) is required. The full key is shown **once** in the response (`secret`); WireBloom stores only its SHA-256 hash and a display prefix (`wb_live_7Hk2cQab`). Store it in your secrets manager; if it is lost, rotate or create a new one. ```json POST /v1/api-keys { "name": "ERP sync", "environment": "live", "scopes": ["balances:read", "transactions:read", "payments:write"], "ipAllowlist": ["203.0.113.0/24"], "expiresAt": "2027-09-30T00:00:00Z" } ``` ## Scopes A key reaches only the operations whose contract lists its scope (`x-api-key-scopes`, shown on every operation in the [reference](/reference)); anything else is `403` [`insufficient-scope`](/errors#insufficient-scope). | Scope | Grants | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `balances:read` | balances, balance details, holds, enabled currencies | | `transactions:read` | transactions, journal, exports and jobs, annotations and attachments (read) | | `statements:read` | statements and PDF confirmations | | `recipients:read` | recipients and field requirements | | `recipients:write` | create, edit, delete and verify recipients | | `payments:read` | payments, transfers, scheduled payments, mandates, collections, bulk payments, approvals, funds requests | | `payments:write` | create/submit/cancel payments, transfers, add money, scheduled payments, mandates, collections, bulk payments, funds requests | | `exchanges:read` | quotes, exchanges, forward contracts | | `exchanges:write` | quotes, exchanges, forward contracts and drawdowns | | `webhooks:manage` | integrator webhook endpoints and deliveries ([Webhooks](/docs/webhooks)) | Keys can never approve payments, manage people, settings, cards or API keys. Payments created with a key follow the same limits and approval tiers as the web app (a payment may wait in `pending_approval` for a human approver). ## Restrictions - `ipAllowlist` (IPv4/IPv6 addresses or CIDR blocks, up to 20): requests from other addresses get `403` [`ip-not-allowed`](/errors#ip-not-allowed). - `expiresAt`: after it the key is `401` [`invalid-api-key`](/errors#invalid-api-key). - A key is bound to one customer: another `X-Tenant-Id` is `403` [`tenant-forbidden`](/errors#tenant-forbidden). - `wb_test_` keys work only in environments with the [sandbox](/docs/sandbox). ## Rotation `rotateApiKey` (`POST /v1/api-keys/{id}/rotate`, from an interactive session, optional `{ "gracePeriodHours": 24 }`, default 24, max 168, `0` revokes the old key at once) returns a new key with the same settings; the old key keeps working until the end of the grace period. Deploy the new key, confirm traffic, then revoke the old one (`revokeApiKey`, `DELETE /v1/api-keys/{id}`, immediate). `lastUsedAt` on the key and the per-key usage in the console ([Integration and usage](/docs/integration-usage)) help confirm the switch. Rotation needs step-up `api_key.create`. # Tenancy Every tenant-scoped call carries `X-Tenant-Id` = the id of the customer the key belongs to. The contract marks each operation with `x-tenant` (`customer`, `operator` or `any`), shown as a badge in the [reference](/reference). - A customer API key is bound to exactly one customer. Another `X-Tenant-Id` is `403` [`tenant-forbidden`](/errors#tenant-forbidden). - Ids of other customers are `404` [`not-found`](/errors#not-found): the API never reveals whether a resource of another tenant exists. - Operators run many customers on one platform; each operator's data is isolated by row-level security in the database, so a key can never read another operator's or another customer's data even through a bug in a query. # Idempotency Send `Idempotency-Key` (8–255 characters `[A-Za-z0-9._:-]`, a UUID v4 is ideal) on POSTs; it is **required** on money-moving operations (payments, transfers, add money, exchanges, scheduled payments, mandates, collections, bulk payments, recipients, funds requests). Operations that require it show the **Idempotency-Key required** badge in the [reference](/reference). - One key per logical operation; reuse the same key when you retry the same request. - Same key + same body within 24 h: the stored response is returned with `Idempotency-Replayed: true`; nothing happens twice. - Same key + different body: `422` [`idempotency-key-reused`](/errors#idempotency-key-reused). While the first request still runs: `409` [`idempotency-key-in-use`](/errors#idempotency-key-in-use) with `Retry-After`. - `5xx` responses are not stored: retry with the same key. - Responses that contain a one-time secret (`POST /api-keys`, `POST /integrator-webhooks`) are never replayed; do not retry those blindly: list and delete duplicates instead. ## Optimistic concurrency Updates of versioned resources need `If-Match` with the `ETag` from your last read. A stale value is `412` [`precondition-failed`](/errors#precondition-failed); a missing header is `428` [`precondition-required`](/errors#precondition-required). # Pagination, sorting and filtering Lists return: ```json { "data": [], "page": { "nextCursor": "…", "prevCursor": null, "limit": 25, "total": 120 } } ``` - Pass `cursor=` until it is `null`. - `limit` 1–100 (default 25). - `sort=-createdAt` (prefix `-` for descending; the allowed fields are listed per operation). - `filter[field]=a,b` (comma = OR, different fields = AND). Unknown filters are `400` [`unknown-filter`](/errors#unknown-filter). - `q` for free text where supported. - Operations that support it take up to 5 `filter[metadata.]=` parameters (ANDed) on integrator-owned `metadata`. Cursors are opaque and valid only with the same `sort` and `filter`; a cursor from another query is `400` [`invalid-cursor`](/errors#invalid-cursor). ## Long-running work Exports, custom statements, reports and bulk submissions return `202` with a `Job` and `Location: /v1/jobs/{jobId}`. Poll [`getJob`](/reference/activity#getJob) until it reports `succeeded`, then download from `resultUrl` (pre-signed, 15 minutes). # Errors Errors are RFC 9457 `application/problem+json`: ```json { "type": "https://api.banking.wirebloom.com/problems/validation-error", "title": "Validation failed", "status": 422, "code": "validation_error", "detail": "1 field is invalid.", "requestId": "req_01J8Z6Q7R2", "errors": [ { "field": "/amount/amountMinor", "code": "too_small", "message": "Amount must be greater than zero." } ] } ``` - Branch on `code` (stable, snake_case; the last segment of `type`). Every code is listed in the [error catalogue](/errors) with its status and whether a retry can succeed. - `errors[].field` is a JSON Pointer into the body (or `query.` / `header.`). - Quote `requestId` (also the `X-Request-Id` response header) to support. Common codes for integrators: `invalid_api_key` (401), `insufficient_scope`, `ip_not_allowed`, `tenant_forbidden` (403), `not_found` (404), `insufficient_funds`, `idempotency_key_in_use` (409), `precondition_failed` (412, stale `If-Match`), `validation_error`, `idempotency_key_reused`, `limit_exceeded` (422), `rate_limited` (429), `provider_error` / `provider_timeout` (502/504). ## Retrying Retry only what can succeed later: `409 idempotency-key-in-use`, `429 rate-limited` (after `Retry-After`) and `5xx` with the same `Idempotency-Key`. Other `4xx` need a change on your side first. # Rate limits | Bucket | Limit | | -------------------------- | -------------- | | Requests per key | 600 per minute | | Money-moving POSTs per key | 60 per minute | Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` (seconds) for the tightest bucket; `429` [`rate-limited`](/errors#rate-limited) adds `Retry-After`. Back off exponentially with jitter and honour `Retry-After`. Each key has its own buckets; rate-limit hits per key are visible in the console ([Integration and usage](/docs/integration-usage)). # Webhooks and signature verification WireBloom notifies your HTTPS endpoint when something happens, so you do not have to poll. The payload schema of every event is in the [event catalogue](/events). ## Register an endpoint ```json POST /v1/integrator-webhooks (scope webhooks:manage) { "url": "https://erp.example.com/wirebloom/webhooks", "events": ["payment.status_changed", "incoming_payment.received"], "description": "ERP payments" } ``` The response of [`createIntegratorWebhook`](/reference/public-api#createIntegratorWebhook) contains `secret` (`whsec_…`, **shown once**) for verifying signatures. Endpoint rules: `https://` only, a public host name or address (no private, loopback, link-local, internal names, no credentials in the URL); every delivery re-checks the resolved address. Up to 10 endpoints per customer. [`testIntegratorWebhook`](/reference/public-api#testIntegratorWebhook) sends a signed test event right away (`data.test: true`, header `WireBloom-Test: true`) and returns the attempt. ## Events | Event | When | | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | | `payment.created` | a payment was submitted (left draft) | | `payment.status_changed` | any later status change (`previousStatus` → `status`) | | `payment.completed` / `payment.failed` / `payment.returned` | the corresponding terminal change (also sent as `payment.status_changed`) | | `transfer.completed` | an internal transfer between own balances completed | | `incoming_payment.received` | money arrived on a balance | | `exchange.completed` | a currency exchange completed | | `recipient.status_changed` | a recipient was added or its approval status changed | | `approval.requested` / `approval.decided` | a payment or recipient needs / got an approval decision | | `funds_request.status_changed` | a funds request changed status | | `recipient.verification_completed`, `statement.available`, `balance.low` | defined; delivery arrives with later releases (the sandbox can simulate them) | ## Payload and headers Payloads are thin: ids, statuses and amounts. Fetch the full resource with the API (`GET /payments/{id}` …) when you need details. ```http POST /wirebloom/webhooks HTTP/1.1 Content-Type: application/json User-Agent: WireBloom-Webhooks/1.0 WireBloom-Event-Id: 0192a6f3-0000-7000-8000-0000000e0001 WireBloom-Event-Type: payment.status_changed WireBloom-Timestamp: 1790161320 WireBloom-Signature: v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd {"id":"0192a6f3-0000-7000-8000-0000000e0001","type":"payment.status_changed", "createdAt":"2026-09-23T11:02:00Z","apiVersion":"v1","tenantId":"0192a6f0-0000-7000-8000-0000000000c1", "data":{"object":"payment","id":"0192a6f1-3333-7000-8000-00000000c001","status":"completed", "previousStatus":"processing","amount":{"amountMinor":"100000","currency":"EUR"}}} ``` ## Verify every delivery `WireBloom-Signature` is `v1=` + hex HMAC-SHA256 of `WireBloom-Timestamp + "." + rawBody` with your endpoint secret. During a secret rotation it carries two entries (`v1=…, v1=…`); accept either. ```ts import { createHmac, timingSafeEqual } from 'node:crypto'; export function verify( rawBody: Buffer, timestamp: string, signatures: string, secret: string, ): boolean { if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; const expected = createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest(); return signatures .split(',') .map((s) => s.trim()) .filter((s) => s.startsWith('v1=')) .some((s) => { const candidate = Buffer.from(s.slice(3), 'hex'); return candidate.length === expected.length && timingSafeEqual(candidate, expected); }); } ``` ```python import hashlib, hmac, time def verify(raw_body: bytes, timestamp: str, signatures: str, secret: str) -> bool: if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > 300: return False expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest() return any(hmac.compare_digest(s.strip()[3:], expected) for s in signatures.split(",") if s.strip().startswith("v1=")) ``` Receiver rules: 1. Verify against the **raw** body bytes, before JSON parsing; compare in constant time. 2. Reject timestamps more than 5 minutes from your clock (replay protection). 3. De-duplicate on `WireBloom-Event-Id` (deliveries are at-least-once; an event id is the same for retries, redeliveries and every endpoint that receives it). 4. Answer `2xx` within 10 seconds, then process asynchronously. Redirects are not followed. 5. Do not rely on ordering: use `status` / `previousStatus` and re-read the resource. ## Retries, pause and recovery Any non-`2xx`, timeout or connection error is retried after 1 min, 5 min, 30 min, 2 h, 6 h, then every 12 h, for up to 72 h after the event. After 20 consecutive failed attempts the endpoint is **paused**; after 72 h without a successful delivery it is **disabled**. Both notify the customer's admins. Paused or disabled endpoints receive no new events. To recover: fix the receiver, [`updateIntegratorWebhook`](/reference/public-api#updateIntegratorWebhook) with `"enabled": true` (send `If-Match`), then list [`listWebhookDeliveries`](/reference/public-api#listWebhookDeliveries) with `filter[succeeded]=false` and redeliver what you missed ([`redeliverWebhook`](/reference/public-api#redeliverWebhook), same event id). You can also disable an endpoint yourself with `"enabled": false`. ## Secret rotation [`rotateIntegratorWebhookSecret`](/reference/public-api#rotateIntegratorWebhookSecret) returns a new secret; for 24 h deliveries are signed with both the new and the old secret. Deploy the new secret within that window. # Sandbox and simulation Staging (and local stacks) run a sandbox bank. Use a `wb_test_` key on your sandbox customer. ## Deterministic rules - Payments settle instantly and deterministically; amounts ending in `77` stay pending. - Account-holder verification returns a match unless the name contains `NOMATCH`, `CLOSE` or `UNAVAILABLE`. - FX uses fixed fictional rates. ## Simulation endpoints These exist only in sandbox environments (`403` with live keys, `404` [`feature-disabled`](/errors#feature-disabled) in production). | Operation | Request | Effect | | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | [`simulateSandboxIncomingPayment`](/reference/public-api#simulateSandboxIncomingPayment) | `POST /v1/sandbox/incoming-payments` `{ "balanceId", "amount", "senderName"?, "reference"? }` (`payments:write`) | incoming funds from the sandbox bank: balance credited, `incoming_payment.received` webhook | | [`simulateSandboxPaymentStatus`](/reference/public-api#simulateSandboxPaymentStatus) | `POST /v1/sandbox/payments/{paymentId}/status` `{ "status": "processing" \| "completed" \| "rejected" \| "cancelled" \| "returned" }` (`payments:write`) | a bank status update for a payment you sent | | [`simulateSandboxWebhookEvent`](/reference/public-api#simulateSandboxWebhookEvent) | `POST /v1/sandbox/webhook-events` `{ "type", "objectId"?, "status"?, "amount"? }` (`webhooks:manage`) | any webhook event, delivered to your subscribed endpoints with `data.sandbox: true` | | [`resetSandbox`](/reference/public-api#resetSandbox) | `POST /v1/sandbox/reset` (`webhooks:manage`) | clears webhook delivery history and re-enables endpoints paused by failures | Simulations answer `202 { "simulationId", "status": "queued" }` and are processed by the same pipeline as real bank events (seconds). They accept `Idempotency-Key`. Balances are never reset (the ledger is append-only): top up again with an incoming payment. # Versioning and deprecation The major version is in the path (`/v1`). The contract's `info.version` follows semver; the deployed build version is returned by `GET /health`. ## Non-breaking changes Shipped without notice: new endpoints, new optional request fields, new response fields, new enum values in responses, new problem types and new webhook event types (delivered only when subscribed). Clients must ignore unknown fields and tolerate unknown enum values. ## Breaking changes Removing or renaming fields or endpoints, changing types or semantics, making optional input required, tightening validation, changing defaults and changing auth or scope requirements. Breaking changes ship only in a new major version (`/v2`) and are marked **BREAKING** in the [changelog](/changelog). ## Deprecation A deprecated operation is marked `deprecated: true` in the contract and keeps working until its sunset date. Its responses carry: ```http Deprecation: @1790121600 Sunset: Wed, 31 Mar 2027 00:00:00 GMT Link: ; rel="deprecation" ``` - `Deprecation` (RFC 9745): when the operation was deprecated, as `@`. - `Sunset` (RFC 8594): when it stops working, at least **6 months** after the deprecation for integrator-facing operations. - `Link` with `rel="deprecation"`: the entry on the [deprecations page](/deprecations), with the migration path. Usage of deprecated operations by API keys is reported to the operator. Webhook payloads carry `apiVersion`. ## Contract releases Each contract release is listed in the [changelog](/changelog) with what integrators must do. The published contract is downloadable as [`openapi.json`](/openapi.json) and is also served by every environment at `GET /v1/openapi.json`. # Migrating from the Integrated Finance API Operators that move to WireBloom from a direct Integrated Finance (IF) integration can port their integration resource by resource. WireBloom sits in front of the banking partner: your system talks only to the WireBloom API, whichever partner the operator runs. ## Resources | Integrated Finance | WireBloom | Reference | | ----------------------------------------------------- | ------------------------------- | ---------------------------------------------------------- | | `client` | customer (`X-Tenant-Id`) | [Tenancy](/docs/tenancy) | | `bankAccount` / `transactionAccount` | balance | [`listBalances`](/reference/accounts#listBalances) | | `outgoingTransfer` | payment | [`createPayment`](/reference/payments#createPayment) | | `exchange` | exchange (quote, then exchange) | [`createExchange`](/reference/payments#createExchange) | | `beneficiary` | recipient | [`createRecipient`](/reference/recipients#createRecipient) | | webhook envelope `{webhook, data, connect, metadata}` | `IntegratorEvent` | [Event catalogue](/events) | ## Conventions | Topic | Integrated Finance | WireBloom | | -------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------- | | Idempotency | `X-Idempotency-Key`, `X-Idempotency-Status` | `Idempotency-Key`, `Idempotency-Replayed: true` ([Idempotency](/docs/idempotency)) | | Pagination | 0-based `metadata.page` | opaque cursors `page.nextCursor` ([Pagination](/docs/pagination)) | | Filters | dotted, e.g. `data.client.status=` | `filter[status]=` | | Errors | not catalogued | RFC 9457 problem details, [error catalogue](/errors) | | Webhook signatures | Ed25519 with published keys | HMAC-SHA256 with your endpoint secret ([Webhooks](/docs/webhooks)) | | Webhook registration | two fixed URLs per instance, set by IF | self-service, up to 10 endpoints per customer | | Custom data | `attributes` | `metadata` (20 keys, 500 characters per value) | ## Steps 1. Map your stored IF ids to WireBloom ids (customers, balances, recipients); keep both during the cut-over. 2. Replace polling of IF transfer statuses with the `payment.status_changed` webhook and re-read the payment. 3. Replace IF webhook verification with the HMAC check and de-duplicate on `WireBloom-Event-Id`. 4. Run the whole flow against the [sandbox](/docs/sandbox) with a `wb_test_` key before switching production keys. # Integration and usage The WireBloom console shows how an integration behaves, so you can confirm a key rotation, spot errors and see webhook health without asking support. ## Operators: the Integration page Operator staff open **Integration** in the operator console for each tenant. It shows: - the active banking connection and its environment and health; - the provider webhook registration state; - every API key with its usage per key: requests per day, error rate, last used and rate-limit hits; - the status of the customer's integrator webhook endpoints; - links to this portal and to the sandbox. ## Developers: your keys only Customer users with API-key management (Settings → Integrations) see a developer view with **their own keys and their usage only**: the same per-key figures and the last 100 requests of each key over 7 days (method, route, status, time taken, request id), never other customers' data. The same data is available from `GET /v1/developer/api-usage` and `GET /v1/developer/api-keys/{apiKeyId}/requests`. ## How usage is counted Every API request made with a key is counted per key and per day (UTC). Usage is aggregated daily; today and yesterday are computed live from the request log, so a call appears within a few seconds. The request log is kept 7 days, daily figures 400 days. Request and response bodies are never stored for usage. Rate-limit hits are `429` [`rate-limited`](/errors#rate-limited) responses. # Error catalogue | Status | Code | Title | Retryable | | --- | --- | --- | --- | | 400 | `bad_request` | Bad request | no | | 400 | `invalid_cursor` | Invalid cursor | no | | 400 | `refresh_token_reused` | Refresh token reused | no | | 400 | `unknown_filter` | Unknown filter | no | | 401 | `invalid_api_key` | Invalid API key | no | | 401 | `invalid_credentials` | Invalid credentials | no | | 401 | `invalid_signature` | Invalid signature | no | | 401 | `mfa_required` | Multi-factor authentication required | no | | 401 | `session_expired` | Session expired | no | | 401 | `unauthenticated` | Authentication required | no | | 403 | `access_rejected` | Access rejected | no | | 403 | `dual_control` | Dual control required | no | | 403 | `forbidden` | Forbidden | no | | 403 | `impersonation_restricted` | Not allowed while impersonating | no | | 403 | `insufficient_scope` | Insufficient scope | no | | 403 | `ip_not_allowed` | IP address not allowed | no | | 403 | `reason_required` | Access reason required | no | | 403 | `step_up_required` | Step-up authentication required | no | | 403 | `tenant_forbidden` | Tenant not allowed | no | | 403 | `terms_outdated` | Terms acceptance required | no | | 404 | `feature_disabled` | Feature disabled | no | | 404 | `not_found` | Not found | no | | 405 | `method_not_allowed` | Method not allowed | no | | 409 | `activation_blocked` | Activation checklist incomplete | no | | 409 | `conflict` | Conflict | no | | 409 | `connection_in_use` | Connection in use | no | | 409 | `domain_not_approved` | Email domain not approved | no | | 409 | `duplicate_recipient` | Duplicate recipient | no | | 409 | `funds_requests_disabled` | Funds requests disabled | no | | 409 | `idempotency_key_in_use` | Idempotency key in use | yes | | 409 | `insufficient_funds` | Insufficient funds | no | | 409 | `invalid_transition` | Invalid state transition | no | | 409 | `last_mfa_factor` | Last MFA factor | no | | 409 | `not_cancellable` | Not cancellable | no | | 409 | `onboarding_not_open` | Onboarding not open | no | | 409 | `primary_owner_protected` | Primary owner protected | no | | 409 | `provider_unsupported` | Not available on this banking connection | no | | 409 | `quote_expired` | Quote expired | no | | 412 | `precondition_failed` | Precondition failed | no | | 413 | `payload_too_large` | Payload too large | no | | 415 | `unsupported_media_type` | Unsupported media type | no | | 422 | `currency_not_enabled` | Currency not enabled | no | | 422 | `document_not_clean` | Document not clean | no | | 422 | `idempotency_key_reused` | Idempotency key reused | no | | 422 | `limit_exceeded` | Limit exceeded | no | | 422 | `recipient_not_approved` | Recipient not approved | no | | 422 | `validation_error` | Validation failed | no | | 422 | `verification_unavailable` | Verification unavailable | no | | 428 | `precondition_required` | Precondition required | no | | 429 | `rate_limited` | Too many requests | yes | | 500 | `internal_error` | Internal error | yes | | 501 | `not_implemented` | Not implemented | no | | 502 | `provider_error` | Provider error | yes | | 503 | `service_unavailable` | Service unavailable | yes | | 504 | `provider_timeout` | Provider timeout | yes | # Webhook events - `payment.status_changed`: Integrator callback: payment.status_changed - `incoming_payment.received`: Integrator callback: incoming_payment.received - `exchange.completed`: Integrator callback: exchange.completed - `recipient.status_changed`: Integrator callback: recipient.status_changed - `approval.requested`: Integrator callback: approval.requested - `statement.available`: Integrator callback: statement.available # tenants Operators, customers, people (memberships), invitations, teams and customer settings (currencies, addresses, alerts, approved domains, approval policies, primary owner, closure). ## GET /customers operationId: `listCustomers` Summary: List customers (operator) API key scopes: `customers:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query), `q` (query), `withTotal` (query) Responses: 200 `CustomerPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `customers:read` (operator-level keys only, `X-Tenant-Id` = the operator id). **Metadata filter:** `filter[metadata.]=` (exact match; at most 5; AND). **Batch look-up (P12-T07):** `filter[id]=,,…` (at most 100) returns those rows only, e.g. to resolve names shown next to ids; combine with `limit` ≥ the number of ids. ## POST /customers operationId: `createCustomer` Summary: Create a customer (operator-initiated) API key scopes: `customers:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `CustomerCreate` Responses: 201 `Customer`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Creates a `draft` customer. With `primaryOwner` a primary-owner invitation is emailed; without it (console sessions only) no invitation is sent. Creates a draft customer and invites the primary owner. Self-registration uses `/auth/register`. **API key scope:** `customers:write` (operator-level keys only, `X-Tenant-Id` = the operator id). **API keys (review W3-02):** an operator key needs `customers:write`, the owner address must be on the operator settings' `apiKeyInvitationDomains` (`422 invitation_domains_required` / `domain_not_approved` before anything is written) and each key may create 10 customers a minute (`429`). ## GET /customers/{customerId} operationId: `getCustomer` Summary: Get customer API key scopes: `customers:read` Parameters: `customerId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-None-Match` (header) Responses: 200 `Customer`, 304, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` `X-Tenant-Id` is the customer itself or its operator. `customer.view` is held by operator staff roles and by every customer role (own customer only). **API key scope:** `customers:read` (operator-level keys only, `X-Tenant-Id` = the operator id). ## GET /customers/{customerId}/settings/currencies operationId: `listCustomerCurrencies` Summary: Account currencies (offered and enabled) API key scopes: `balances:read` Parameters: `customerId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-None-Match` (header) Responses: 200, 304, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `balances:read`. # onboarding Customer KYB case: wizard data, persons (directors, UBOs), documents, submission, KYC provider SDK tokens, operator review queue and decisions (D-22). ## GET /customers/{customerId}/kyb operationId: `getKybCase` Summary: Get KYB case (wizard state) API key scopes: `onboarding:read` Parameters: `customerId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-None-Match` (header) Responses: 200 `KybCase`, 304, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `onboarding:read` (operator-level keys only, `X-Tenant-Id` = the operator id). # accounts Balances (ledger accounts per currency and sub-balances), account details per rail, journal with running balance, statements, currencies. ## GET /balances operationId: `listBalances` Summary: List balances API key scopes: `balances:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query), `q` (query) Responses: 200 `BalancePage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` Balances tab (All / Favourites / per currency). Amounts are real-time from `ledger.account_balances`. **Operator staff** (`x-tenant: any`): with `X-Tenant-Id` set to a customer they read that customer; with their operator id they read every customer of the operator (RLS operator scope), optionally narrowed with `filter[customerId]`. **API key scope:** `balances:read`. **Metadata filter:** `filter[metadata.]=` (exact match; at most 5; AND). **Batch look-up (P12-T07):** `filter[id]=,,…` (at most 100) returns those rows only, e.g. to resolve names shown next to ids; combine with `limit` ≥ the number of ids. ## GET /balances/{balanceId} operationId: `getBalance` Summary: Get balance API key scopes: `balances:read` Parameters: `balanceId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-None-Match` (header) Responses: 200 `Balance`, 304, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `balances:read`. ## GET /balances/{balanceId}/details operationId: `getBalanceAccountDetails` Summary: Account details (local and SWIFT) API key scopes: `balances:read` Parameters: `balanceId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `rail` (query) Responses: 200, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` `status: pending` until the partner has provisioned the account. **API key scope:** `balances:read`. ## GET /balances/{balanceId}/holds operationId: `listBalanceHolds` Summary: Pending holds on a balance API key scopes: `balances:read` Parameters: `balanceId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query) Responses: 200 `HoldPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `balances:read`. **Review holds (P10R-01):** a hold placed while a payment is reviewed is listed with subject type `payment_review`, its own id as subject id and a neutral description; the amount counts against the available balance like any other hold. ## GET /balances/{balanceId}/journal operationId: `listBalanceJournal` Summary: Journal lines with running balance API key scopes: `transactions:read` Parameters: `balanceId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query) Responses: 200 `JournalLinePage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `transactions:read`. ## GET /balances/{balanceId}/statements operationId: `listStatements` Summary: Monthly statements API key scopes: `statements:read` Parameters: `balanceId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query) Responses: 200 `StatementPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `statements:read`. ## POST /balances/{balanceId}/statements operationId: `requestStatement` Summary: Generate a custom-period statement (async) API key scopes: `statements:read` Parameters: `balanceId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Request body: `StatementRequest` Responses: 202 `Job`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Step-up:** requires a verified step-up challenge for action `statement.full_export` within the last 5 minutes (session/bearer only). Otherwise `403` with problem type `step-up-required`; the step-up check runs before body validation. **API key scope:** `statements:read`. ## GET /balances/{balanceId}/statements/{statementId}/download operationId: `downloadStatement` Summary: Download statement API key scopes: `statements:read` Parameters: `balanceId` (path, required), `statementId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `format` (query) Responses: 200 `DownloadLink`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `statements:read`. ## GET /currencies operationId: `listCurrencies` Summary: Currencies offered by the operator (with enabled flag) API key scopes: `balances:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `enabled` (query) Responses: 200, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `balances:read`. # recipients Recipients (beneficiaries): requirements per currency/country, CRUD, Confirmation/Verification of Payee, approval (customer tier or operator queue, D-14). ## GET /recipients operationId: `listRecipients` Summary: List recipients API key scopes: `recipients:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query), `q` (query), `withTotal` (query) Responses: 200 `RecipientPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `recipients:read`. **Metadata filter:** `filter[metadata.]=` (exact match; at most 5; AND). **Batch look-up (P12-T07):** `filter[id]=,,…` (at most 100) returns those rows only, e.g. to resolve names shown next to ids; combine with `limit` ≥ the number of ids. ## POST /recipients operationId: `createRecipient` Summary: Add recipient API key scopes: `recipients:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `RecipientCreate` Responses: 201 `Recipient`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Validates against requirements (IBAN mod-97, BIC, national formats), de-duplicates (`409 duplicate-recipient`), runs CoP/VoP where available and applies the approval mode (D-14): customer approval tier or operator queue. API keys: no step-up; recipients created by API keys always require approval by a human per the customer policy. **Step-up:** requires a verified step-up challenge for action `recipient.create` within the last 5 minutes (session/bearer only). Otherwise `403` with problem type `step-up-required`; the step-up check runs before body validation. **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **API key scope:** `recipients:write`. ## GET /recipients/{recipientId} operationId: `getRecipient` Summary: Get recipient (full details) API key scopes: `recipients:read` Parameters: `recipientId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-None-Match` (header) Responses: 200 `Recipient`, 304, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `recipients:read`. ## PATCH /recipients/{recipientId} operationId: `updateRecipient` Summary: Edit recipient API key scopes: `recipients:write` Parameters: `recipientId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-Match` (header, required) Request body: `RecipientUpdate` Responses: 200 `Recipient`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 412 `Problem`, 422 `Problem`, 428 `Problem`, 429 `Problem`, default `Problem` **Step-up:** requires a verified step-up challenge for action `recipient.update` within the last 5 minutes (session/bearer only). Otherwise `403` with problem type `step-up-required`; the step-up check runs before body validation. **Concurrency:** `If-Match` with the current `ETag` is required (`428` if absent, `412` if stale). **API key scope:** `recipients:write`. ## DELETE /recipients/{recipientId} operationId: `deleteRecipient` Summary: Delete recipient (soft) API key scopes: `recipients:write` Parameters: `recipientId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-Match` (header, required) Responses: 204, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 412 `Problem`, 428 `Problem`, 429 `Problem`, default `Problem` `409` if scheduled payments or pending payments reference it. **Concurrency:** `If-Match` with the current `ETag` is required (`428` if absent, `412` if stale). **API key scope:** `recipients:write`. ## POST /recipients/{recipientId}/verify operationId: `verifyRecipient` Summary: Run Confirmation / Verification of Payee API key scopes: `recipients:write` Parameters: `recipientId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `RecipientVerification`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` `422 verification-unavailable` if no routed provider supports CoP/VoP for this currency/country. **API key scope:** `recipients:write`. ## GET /recipients/requirements operationId: `getRecipientRequirements` Summary: Field requirements per currency and country API key scopes: `recipients:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `currency` (query, required), `country` (query, required), `type` (query) Responses: 200 `RecipientRequirements`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `recipients:read`. # payments Payments, internal transfers, add money, exchanges and quotes, forward contracts and held rates, scheduled payments, direct debits (mandates, collections), bulk payments, approvals and funds requests. ## POST /add-money operationId: `addMoney` Summary: Add money (deposit details or conversion order) API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `AddMoneyRequest` Responses: 200 `AddMoneyResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **API key scope:** `payments:write`. ## GET /approvals operationId: `listApprovals` Summary: Approvals inbox API key scopes: `payments:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query), `withTotal` (query), `If-None-Match` (header) Responses: 200 `ApprovalPage`, 304, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` Customer members see their customer's approvals. **Operator staff** (`x-tenant: any`): with `X-Tenant-Id` set to a customer they read that customer; with their operator id they read every customer of the operator (RLS operator scope) (approvals oversight); staff never decide customer approvals, so `mine` returns nothing for them and `canDecide` is false. `filter[subjectId]` lists the approvals of one payment, recipient or bulk upload. **API key scope:** `payments:read`. **Conditional GET (P12-T07):** the response carries a weak `ETag` of its content; send it back as `If-None-Match` when polling and the API answers `304` without a body while nothing changed. ## GET /approvals/{approvalId} operationId: `getApproval` Summary: Get approval API key scopes: `payments:read` Parameters: `approvalId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `Approval`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` Read access as `GET /approvals` (operator staff: any customer of the operator). **API key scope:** `payments:read`. ## GET /bulk-payments operationId: `listBulkPayments` Summary: List bulk uploads API key scopes: `payments:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query) Responses: 200 `BulkPaymentPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:read`. ## POST /bulk-payments operationId: `createBulkPayment` Summary: Upload bulk payments file for validation API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `BulkPaymentCreate` Responses: 202 `BulkPayment`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **API key scope:** `payments:write`. ## GET /bulk-payments/{bulkPaymentId} operationId: `getBulkPayment` Summary: Bulk upload status API key scopes: `payments:read` Parameters: `bulkPaymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `BulkPayment`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:read`. ## POST /bulk-payments/{bulkPaymentId}/cancel operationId: `cancelBulkPayment` Summary: Cancel bulk upload API key scopes: `payments:write` Parameters: `bulkPaymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `BulkPayment`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:write`. **Stalled submissions (re-review R-05):** a batch in `submitting` whose background run has stopped (no progress for two minutes) can be cancelled: rows not yet submitted become `failed` with code `cancelled`, payments already created stay and the batch ends `submitted` / `partially_submitted` (or `cancelled` when none was created, which also closes its batch approval). A batch that is still progressing answers `409 not-cancellable`. Batches that stay stalled are closed by the platform after 30 minutes (row code `submission_stalled`), and rows not yet submitted when the batch approval expires fail with code `approval_expired`. ## GET /bulk-payments/{bulkPaymentId}/rows operationId: `listBulkPaymentRows` Summary: Per-row validation results API key scopes: `payments:read` Parameters: `bulkPaymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query) Responses: 200 `BulkPaymentRowPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:read`. ## POST /bulk-payments/{bulkPaymentId}/submit operationId: `submitBulkPayment` Summary: Submit validated rows as payments API key scopes: `payments:write` Parameters: `bulkPaymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Responses: 202 `BulkPaymentSubmitAccepted`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` Asynchronous (scalability audit X-12): moves the batch to `submitting`, opens the batch approval when the total falls in an approval tier, and returns `202` with the job (`Location: /v1/jobs/{jobId}`). Every valid row then becomes a payment through the single-payment path, each with its row status in one transaction, so an interrupted run resumes without duplicates. Submitting a batch that is already `submitting` returns the same job (and resumes it when it stalled, for the original submitter). `429 rate-limited` when too many batches of the operator are being submitted. Decisions on the batch approval are refused (`409`) until the batch leaves `submitting`. **Step-up:** requires a verified step-up challenge for action `payment.above_threshold` within the last 5 minutes (session/bearer only). Otherwise `403` with problem type `step-up-required`; the step-up check runs before body validation. **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **API key scope:** `payments:write`. **Expired batch approval (re-review R-05):** re-submitting a batch in `submitting` whose batch approval is no longer pending answers `409 invalid-transition`; the remaining rows are closed as `failed` (`approval_expired`). ## GET /collections operationId: `listCollections` Summary: List direct debit collections API key scopes: `payments:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query) Responses: 200 `CollectionPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **Feature flag:** `payments.direct_debits`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `payments:read`. ## POST /collections operationId: `createCollection` Summary: Request a collection on a mandate API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `CollectionCreate` Responses: 201 `Collection`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **Feature flag:** `payments.direct_debits`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `payments:write`. ## GET /collections/{collectionId} operationId: `getCollection` Summary: Get collection API key scopes: `payments:read` Parameters: `collectionId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `Collection`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **Feature flag:** `payments.direct_debits`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `payments:read`. ## POST /collections/{collectionId}/cancel operationId: `cancelCollection` Summary: Cancel collection (before submission) API key scopes: `payments:write` Parameters: `collectionId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `Collection`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` **Feature flag:** `payments.direct_debits`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `payments:write`. ## GET /exchanges operationId: `listExchanges` Summary: List exchanges API key scopes: `exchanges:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query), `q` (query) Responses: 200 `ExchangePage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `exchanges:read`. ## POST /exchanges operationId: `createExchange` Summary: Execute exchange from a quote API key scopes: `exchanges:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `ExchangeCreate` Responses: 201 `Exchange`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Exchanges are subject to payment approval policies when enabled. **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **API key scope:** `exchanges:write`. ## GET /exchanges/{exchangeId} operationId: `getExchange` Summary: Get exchange API key scopes: `exchanges:read` Parameters: `exchangeId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `Exchange`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `exchanges:read`. ## GET /exchanges/{exchangeId}/confirmation operationId: `getExchangeConfirmation` Summary: Exchange order confirmation (PDF) API key scopes: `statements:read` Parameters: `exchangeId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` The branded confirmation PDF, rendered on first request and cached (same behaviour as `GET /payments/{paymentId}/confirmation`). **API key scope:** `statements:read`. ## POST /exchanges/quotes operationId: `createQuote` Summary: Create FX quote API key scopes: `exchanges:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `QuoteCreate` Responses: 201 `Quote`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `exchanges:write`. ## GET /exchanges/quotes/{quoteId} operationId: `getQuote` Summary: Get quote API key scopes: `exchanges:read` Parameters: `quoteId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `Quote`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `exchanges:read`. ## POST /exchanges/quotes/{quoteId}/refresh operationId: `refreshQuote` Summary: Refresh an expired quote (new quote, same terms) API key scopes: `exchanges:write` Parameters: `quoteId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Responses: 201 `Quote`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `exchanges:write`. ## GET /forward-contracts operationId: `listForwardContracts` Summary: List forward contracts and held rates API key scopes: `exchanges:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query), `q` (query) Responses: 200 `ExchangePage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` Forwards and held rates (`filter[kind]=held_rate`): there is no separate `/held-rates` resource; held rates are booked, listed and drawn down here. **Feature flag:** `fx.forwards`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `exchanges:read`. ## POST /forward-contracts operationId: `createForwardContract` Summary: Book forward contract / held rate API key scopes: `exchanges:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `ForwardContractCreate` Responses: 201 `Exchange`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **Feature flag:** `fx.forwards`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `exchanges:write`. ## GET /forward-contracts/{exchangeId} operationId: `getForwardContract` Summary: Get forward contract API key scopes: `exchanges:read` Parameters: `exchangeId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `Exchange`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **Feature flag:** `fx.forwards`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `exchanges:read`. ## POST /forward-contracts/{exchangeId}/deposit operationId: `topUpForwardDeposit` Summary: Top up a forward deposit (margin call) API key scopes: `exchanges:write` Parameters: `exchangeId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `ForwardDepositTopUp` Responses: 200 `Exchange`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **Feature flag:** `fx.forwards`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `exchanges:write`. ## POST /forward-contracts/{exchangeId}/drawdowns operationId: `drawdownForwardContract` Summary: Draw down a forward contract API key scopes: `exchanges:write` Parameters: `exchangeId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `Drawdown` Responses: 200 `Exchange`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **Feature flag:** `fx.forwards`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `exchanges:write`. ## GET /forward-contracts/deposit-balance operationId: `getForwardDepositBalance` Summary: Deposit balance held for forwards API key scopes: `exchanges:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `DepositBalance`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **Feature flag:** `fx.forwards`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `exchanges:read`. ## GET /funds-requests operationId: `listFundsRequests` Summary: List funds requests API key scopes: `payments:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query) Responses: 200 `FundsRequestPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:read`. ## POST /funds-requests operationId: `createFundsRequest` Summary: Request funds API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `FundsRequestCreate` Responses: 201 `FundsRequest`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` `409 funds-requests-disabled` when the customer setting is off. **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **API key scope:** `payments:write`. ## GET /funds-requests/{fundsRequestId} operationId: `getFundsRequest` Summary: Get funds request API key scopes: `payments:read` Parameters: `fundsRequestId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `FundsRequest`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:read`. ## POST /funds-requests/{fundsRequestId}/cancel operationId: `cancelFundsRequest` Summary: Cancel funds request API key scopes: `payments:write` Parameters: `fundsRequestId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `FundsRequest`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:write`. ## GET /mandates operationId: `listMandates` Summary: List direct debit mandates API key scopes: `payments:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query) Responses: 200 `MandatePage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **Feature flag:** `payments.direct_debits`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `payments:read`. ## POST /mandates operationId: `createMandate` Summary: Create mandate API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `MandateCreate` Responses: 201 `Mandate`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **Feature flag:** `payments.direct_debits`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `payments:write`. ## GET /mandates/{mandateId} operationId: `getMandate` Summary: Get mandate API key scopes: `payments:read` Parameters: `mandateId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `Mandate`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **Feature flag:** `payments.direct_debits`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `payments:read`. ## POST /mandates/{mandateId}/cancel operationId: `cancelMandate` Summary: Cancel mandate API key scopes: `payments:write` Parameters: `mandateId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Request body: `CancelRequest` Responses: 200 `Mandate`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Feature flag:** `payments.direct_debits`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `payments:write`. ## GET /mandates/{mandateId}/schedules operationId: `listMandateSchedules` Summary: Collection schedules of a mandate API key scopes: `payments:read` Parameters: `mandateId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` Activate, deactivate or end them through `/scheduled-payments/{scheduledPaymentId}`. **Feature flag:** `payments.direct_debits`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `payments:read`. ## POST /mandates/{mandateId}/schedules operationId: `createMandateSchedule` Summary: Create a collection schedule API key scopes: `payments:write` Parameters: `mandateId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `CollectionScheduleCreate` Responses: 201 `ScheduledPayment`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **Feature flag:** `payments.direct_debits`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `payments:write`. ## POST /mandates/{mandateId}/sign operationId: `signMandate` Summary: Record the mandate signature API key scopes: `payments:write` Parameters: `mandateId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Request body: `MandateSign` Responses: 200 `Mandate`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Feature flag:** `payments.direct_debits`; returns `404` with problem type `feature-disabled` when the flag is off for the operator. **API key scope:** `payments:write`. ## GET /payments operationId: `listPayments` Summary: List payments API key scopes: `payments:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query), `q` (query) Responses: 200 `PaymentPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:read`. **Metadata filter:** `filter[metadata.]=` (exact match; at most 5; AND). ## POST /payments operationId: `createPayment` Summary: Create payment (single-payment wizard) API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `PaymentCreate` Responses: 201 `Payment`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Validates limits and balance, computes fees, places a hold, applies approval tiers (→ `pending_approval`) or routes and submits. **Conditional step-up:** `x-step-up` names `payment.first_to_recipient`, but the requirement is evaluated after validation: the first payment to a recipient needs `payment.first_to_recipient`, a payment at or above the threshold (customer `settings.payments.stepUpThreshold`, else operator `payments.stepUpThreshold`) needs `payment.above_threshold`; drafts (`submit: false`) need none. `POST /payments/dry-run` returns `requiresStepUp` / `stepUpAction`. API-key requests skip step-up but are always subject to approval tiers (D-24). Recipient must be `approved`. Cross-currency payments (`amount.currency` ≠ source currency) need a live spot `quoteId` selling the source currency for exactly `amount` (`422 quote_required` / `quote_mismatch`, `409 quote-expired`); the conversion executes before submission. **Step-up:** requires a verified step-up challenge for action `payment.first_to_recipient` within the last 5 minutes (session/bearer only). Otherwise `403` with problem type `step-up-required`; the step-up check runs before body validation. **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **API key scope:** `payments:write`. ## GET /payments/{paymentId} operationId: `getPayment` Summary: Get payment API key scopes: `payments:read` Parameters: `paymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-None-Match` (header) Responses: 200 `Payment`, 304, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:read`. **Processing with ETA (D-50):** while an approved payment waits in the submission queue behind other payments of the operator, `processing` gives its queue position and an estimated hand-off time; it is absent once the payment is submitted (and for scheduled payments before their execution date). ## PATCH /payments/{paymentId} operationId: `updatePaymentDraft` Summary: Edit draft payment API key scopes: `payments:write` Parameters: `paymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-Match` (header, required) Request body: `PaymentUpdate` Responses: 200 `Payment`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 412 `Problem`, 422 `Problem`, 428 `Problem`, 429 `Problem`, default `Problem` **Concurrency:** `If-Match` with the current `ETag` is required (`428` if absent, `412` if stale). **API key scope:** `payments:write`. ## POST /payments/{paymentId}/cancel operationId: `cancelPayment` Summary: Cancel payment (before submission) API key scopes: `payments:write` Parameters: `paymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `CancelRequest` Responses: 200 `Payment`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Allowed in `draft`, `pending_approval`, `approved` and scheduled; releases the hold. After submission use a recall via operator support (`409 not-cancellable`). **API key scope:** `payments:write`. ## GET /payments/{paymentId}/confirmation operationId: `getPaymentConfirmation` Summary: Payment order confirmation (PDF) API key scopes: `statements:read` Parameters: `paymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `statements:read`. ## GET /payments/{paymentId}/events operationId: `listPaymentEvents` Summary: Payment status history API key scopes: `payments:read` Parameters: `paymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` Payment timeline. **Operator staff** (`x-tenant: any`): with `X-Tenant-Id` set to a customer they read that customer; with their operator id they read every customer of the operator (RLS operator scope); `404` when the payment is outside the tenant. **API key scope:** `payments:read`. ## POST /payments/{paymentId}/submit operationId: `submitPayment` Summary: Submit draft payment API key scopes: `payments:write` Parameters: `paymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Responses: 200 `Payment`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` Same checks as a direct submit; step-up is conditional as on `createPayment` (`payment.first_to_recipient` or `payment.above_threshold`). **Step-up:** requires a verified step-up challenge for action `payment.first_to_recipient` within the last 5 minutes (session/bearer only). Otherwise `403` with problem type `step-up-required`; the step-up check runs before body validation. **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **API key scope:** `payments:write`. ## POST /payments/dry-run operationId: `dryRunPayment` Summary: Preview fees, rate, route and checks API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header) Request body: `PaymentCreate` Responses: 200 `PaymentDryRun`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:write`. ## GET /payments/purpose-codes operationId: `listPurposeCodes` Summary: Purpose codes for a payment API key scopes: `payments:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `currency` (query, required), `country` (query), `rail` (query) Responses: 200 `PurposeCodeCatalogue`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` Purpose / transfer-reason catalogue (P10-T03, FR-PAY-13, parity P-50) for a currency, destination and rail: the accepted codes and whether one is required. Payment creation applies the same rule. **API key scope:** `payments:read`. ## GET /scheduled-payments operationId: `listScheduledPayments` Summary: List scheduled payments (Active / Inactive) API key scopes: `payments:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query), `withTotal` (query) Responses: 200 `ScheduledPaymentPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:read`. ## POST /scheduled-payments operationId: `createScheduledPayment` Summary: Create scheduled payment / standing order API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `ScheduledPaymentWrite` Responses: 201 `ScheduledPayment`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Step-up:** requires a verified step-up challenge for action `payment.first_to_recipient` within the last 5 minutes (session/bearer only). Otherwise `403` with problem type `step-up-required`; the step-up check runs before body validation. **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **API key scope:** `payments:write`. ## GET /scheduled-payments/{scheduledPaymentId} operationId: `getScheduledPayment` Summary: Get scheduled payment API key scopes: `payments:read` Parameters: `scheduledPaymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-None-Match` (header) Responses: 200 `ScheduledPayment`, 304, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:read`. ## PATCH /scheduled-payments/{scheduledPaymentId} operationId: `updateScheduledPayment` Summary: Edit scheduled payment API key scopes: `payments:write` Parameters: `scheduledPaymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-Match` (header, required) Request body: `ScheduledPaymentWrite` Responses: 200 `ScheduledPayment`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 412 `Problem`, 422 `Problem`, 428 `Problem`, 429 `Problem`, default `Problem` **Concurrency:** `If-Match` with the current `ETag` is required (`428` if absent, `412` if stale). **API key scope:** `payments:write`. ## DELETE /scheduled-payments/{scheduledPaymentId} operationId: `deleteScheduledPayment` Summary: End scheduled payment API key scopes: `payments:write` Parameters: `scheduledPaymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-Match` (header, required) Responses: 204, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 412 `Problem`, 428 `Problem`, 429 `Problem`, default `Problem` **Concurrency:** `If-Match` with the current `ETag` is required (`428` if absent, `412` if stale). **API key scope:** `payments:write`. ## POST /scheduled-payments/{scheduledPaymentId}/activate operationId: `activateScheduledPayment` Summary: Reactivate API key scopes: `payments:write` Parameters: `scheduledPaymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `ScheduledPayment`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:write`. ## POST /scheduled-payments/{scheduledPaymentId}/deactivate operationId: `deactivateScheduledPayment` Summary: Deactivate API key scopes: `payments:write` Parameters: `scheduledPaymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `ScheduledPayment`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:write`. ## GET /scheduled-payments/{scheduledPaymentId}/occurrences operationId: `listScheduledPaymentOccurrences` Summary: Upcoming and past runs API key scopes: `payments:read` Parameters: `scheduledPaymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `limit` (query) Responses: 200 `ScheduledOccurrences`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:read`. ## POST /scheduled-payments/preview operationId: `previewScheduledPayment` Summary: Preview the runs of a schedule API key scopes: `payments:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header) Request body: `SchedulePreviewRequest` Responses: 200 `SchedulePreview`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Canonical RRULE and the next runs (nominal and execution dates, run instant). No writes. **API key scope:** `payments:read`. ## GET /transfers operationId: `listTransfers` Summary: List internal transfers API key scopes: `payments:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query) Responses: 200 `TransferPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:read`. ## POST /transfers operationId: `createTransfer` Summary: Internal transfer between own balances API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header, required) Request body: `TransferCreate` Responses: 201 `Transfer`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Idempotency:** `Idempotency-Key` is required. Replays within 24 h return the original response with `Idempotency-Replayed: true`. **API key scope:** `payments:write`. ## GET /transfers/{transferId} operationId: `getTransfer` Summary: Get transfer API key scopes: `payments:read` Parameters: `transferId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `Transfer`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `payments:read`. # activity Unified transactions (ledger view) with filters, search and exports; transaction detail with updates, annotations, attachments and confirmations; documents and asynchronous jobs. ## GET /attachments operationId: `listAttachments` Summary: List attachments across transactions API key scopes: `transactions:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query) Responses: 200 `AttachmentPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `transactions:read`. ## GET /attachments/{attachmentId} operationId: `getAttachment` Summary: Get attachment API key scopes: `transactions:read` Parameters: `attachmentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `Attachment`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `transactions:read`. ## GET /attachments/{attachmentId}/download operationId: `downloadAttachment` Summary: Pre-signed download link API key scopes: `transactions:read` Parameters: `attachmentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `DownloadLink`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `transactions:read`. ## GET /jobs/{jobId} operationId: `getJob` Summary: Get asynchronous job (exports, reports) API key scopes: `transactions:read` Parameters: `jobId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `Job`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `transactions:read`. ## GET /transactions operationId: `listTransactions` Summary: List transactions (unified activity) API key scopes: `transactions:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query), `q` (query), `withTotal` (query) Responses: 200 `TransactionPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` Server-side filtering and search (description, counterparty, reference, order id). Filters: date, status, type, balance, attachments, annotations (Equals Transactions screen). **API key scope:** `transactions:read`. ## GET /transactions/{transactionId} operationId: `getTransaction` Summary: Transaction details (drawer: Details / Updates) API key scopes: `transactions:read` Parameters: `transactionId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-None-Match` (header) Responses: 200 `TransactionDetail`, 304, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `transactions:read`. ## GET /transactions/{transactionId}/annotations operationId: `listAnnotations` Summary: List annotations API key scopes: `transactions:read` Parameters: `transactionId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `transactions:read`. ## GET /transactions/{transactionId}/attachments operationId: `listTransactionAttachments` Summary: List attachments API key scopes: `transactions:read` Parameters: `transactionId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `transactions:read`. ## GET /transactions/{transactionId}/confirmation operationId: `getTransactionConfirmation` Summary: Order confirmation (PDF) API key scopes: `statements:read` Parameters: `transactionId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` Account id, company address, order id, booking/settlement dates, from/to amounts, method, rate, total incl. fee, status, regulatory footer (Equals order confirmation). **API key scope:** `statements:read`. ## GET /transactions/{transactionId}/updates operationId: `listTransactionUpdates` Summary: Status updates (Updates tab) API key scopes: `transactions:read` Parameters: `transactionId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `transactions:read`. ## POST /transactions/exports operationId: `exportTransactions` Summary: Export statement or transaction activity (async) API key scopes: `transactions:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header) Request body: `ExportRequest` Responses: 202 `Job`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Small exports may complete immediately (`Job.status=succeeded`). Full statement exports require step-up `statement.full_export` for interactive users. **API key scope:** `transactions:read`. # providers Operator banking-partner configuration: provider connections (write-only credentials), routing rules, KYC connections, inbound provider webhooks and the provider event log. ## GET /provider-connections operationId: `listProviderConnections` Summary: List provider connections API key scopes: `integration:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query) Responses: 200 `ProviderConnectionPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `integration:read` (operator-level keys only, `X-Tenant-Id` = the operator id). ## GET /provider-connections/{connectionId} operationId: `getProviderConnection` Summary: Get provider connection (no secrets) API key scopes: `integration:read` Parameters: `connectionId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-None-Match` (header) Responses: 200 `ProviderConnection`, 304, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `integration:read` (operator-level keys only, `X-Tenant-Id` = the operator id). # public-api Integrator access: API keys with scopes and integrator webhooks (HMAC-signed callbacks). All other resources are shared with the web/mobile API and documented per scope. ## GET /integration operationId: `getIntegrationHome` Summary: Integration home: connections, webhooks, API keys, usage and portal links API key scopes: `integration:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `IntegrationHome`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` Operator console "Integration" page (P9-T11, FR-PLAT-06): every non-retired banking connection with lifecycle, health, breaker, webhook registration and last event, event backlog, last reconciliation run and open exceptions, activation checklist progress; API key counts; 7-day usage; integrator webhook health; developer-portal links. **API key scope:** `integration:read` (operator-level keys only, `X-Tenant-Id` = the operator id). ## GET /integration/api-keys/{apiKeyId}/requests operationId: `listOperatorApiKeyRequests` Summary: Recent requests of an API key (7 days, no bodies) API key scopes: `integration:read` Parameters: `apiKeyId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `limit` (query), `outcome` (query) Responses: 200 `ApiKeyRequestLog`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` The last requests of any key of the operator (newest first) and the status breakdown over 7 days. Method, route template, status, duration and request id only. **API key scope:** `integration:read` (operator-level keys only, `X-Tenant-Id` = the operator id). ## GET /integration/api-usage operationId: `getOperatorApiUsage` Summary: API usage per key across the operator (daily) API key scopes: `integration:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `from` (query), `to` (query), `apiKeyId` (query), `environment` (query), `customerId` (query) Responses: 200 `ApiUsageReport`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` Per-key totals and daily series (P9-T11). Days before yesterday come from the daily rollup; yesterday and today are live from the request log (a call appears within `API_REQUEST_LOG_FLUSH_MS`, 5 s by default). Covers every key of the operator: customer keys and operator-level keys. **API key scope:** `integration:read` (operator-level keys only, `X-Tenant-Id` = the operator id). ## GET /integrator-webhooks operationId: `listIntegratorWebhooks` Summary: List integrator webhook endpoints API key scopes: `webhooks:manage` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query) Responses: 200 `IntegratorWebhookPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `webhooks:manage`. **Level (P9-T08b):** `X-Tenant-Id` = a customer → that customer's endpoints; `X-Tenant-Id` = the operator → the operator-level endpoints (sessions of operator owners with `webhooks.manage`, operator-level keys with `webhooks:manage`). ## POST /integrator-webhooks operationId: `createIntegratorWebhook` Summary: Register webhook endpoint API key scopes: `webhooks:manage` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header) Request body: `IntegratorWebhookWrite` Responses: 201 `IntegratorWebhookCreated`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Not idempotent: the response carries the signing secret shown once. SSRF guard on the URL (`422`); at most 10 endpoints per customer (`409`). **API key scope:** `webhooks:manage`. **Level (P9-T08b):** `X-Tenant-Id` = a customer → that customer's endpoints; `X-Tenant-Id` = the operator → the operator-level endpoints (sessions of operator owners with `webhooks.manage`, operator-level keys with `webhooks:manage`). **Step-up:** requires a verified step-up challenge for action `integrator_webhook.change` within the last 5 minutes (session/bearer only). Otherwise `403` with problem type `step-up-required`; the step-up check runs before body validation. API keys holding `webhooks:manage` may call it without a step-up; every create, update, secret rotation and deletion notifies the endpoint owners (`webhook_endpoint_changed`, staff or customer members with `webhooks.manage`), whoever made it (review W3-03, P4 S-14). ## GET /integrator-webhooks/{webhookId} operationId: `getIntegratorWebhook` Summary: Get webhook endpoint API key scopes: `webhooks:manage` Parameters: `webhookId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-None-Match` (header) Responses: 200 `IntegratorWebhook`, 304, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `webhooks:manage`. **Level (P9-T08b):** `X-Tenant-Id` = a customer → that customer's endpoints; `X-Tenant-Id` = the operator → the operator-level endpoints (sessions of operator owners with `webhooks.manage`, operator-level keys with `webhooks:manage`). ## PATCH /integrator-webhooks/{webhookId} operationId: `updateIntegratorWebhook` Summary: Update webhook endpoint API key scopes: `webhooks:manage` Parameters: `webhookId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `If-Match` (header, required) Request body: `IntegratorWebhookWrite` Responses: 200 `IntegratorWebhook`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 412 `Problem`, 422 `Problem`, 428 `Problem`, 429 `Problem`, default `Problem` **Concurrency:** `If-Match` with the current `ETag` is required (`428` if absent, `412` if stale). **API key scope:** `webhooks:manage`. **Level (P9-T08b):** `X-Tenant-Id` = a customer → that customer's endpoints; `X-Tenant-Id` = the operator → the operator-level endpoints (sessions of operator owners with `webhooks.manage`, operator-level keys with `webhooks:manage`). **Step-up:** requires a verified step-up challenge for action `integrator_webhook.change` within the last 5 minutes (session/bearer only). Otherwise `403` with problem type `step-up-required`; the step-up check runs before body validation. API keys holding `webhooks:manage` may call it without a step-up; every create, update, secret rotation and deletion notifies the endpoint owners (`webhook_endpoint_changed`, staff or customer members with `webhooks.manage`), whoever made it (review W3-03, P4 S-14). ## DELETE /integrator-webhooks/{webhookId} operationId: `deleteIntegratorWebhook` Summary: Delete webhook endpoint API key scopes: `webhooks:manage` Parameters: `webhookId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 204, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `webhooks:manage`. **Level (P9-T08b):** `X-Tenant-Id` = a customer → that customer's endpoints; `X-Tenant-Id` = the operator → the operator-level endpoints (sessions of operator owners with `webhooks.manage`, operator-level keys with `webhooks:manage`). **Step-up:** requires a verified step-up challenge for action `integrator_webhook.change` within the last 5 minutes (session/bearer only). Otherwise `403` with problem type `step-up-required`; the step-up check runs before body validation. API keys holding `webhooks:manage` may call it without a step-up; every create, update, secret rotation and deletion notifies the endpoint owners (`webhook_endpoint_changed`, staff or customer members with `webhooks.manage`), whoever made it (review W3-03, P4 S-14). ## GET /integrator-webhooks/{webhookId}/deliveries operationId: `listWebhookDeliveries` Summary: Delivery attempts API key scopes: `webhooks:manage` Parameters: `webhookId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `cursor` (query), `limit` (query), `sort` (query), `filter` (query) Responses: 200 `WebhookDeliveryPage`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `webhooks:manage`. **Level (P9-T08b):** `X-Tenant-Id` = a customer → that customer's endpoints; `X-Tenant-Id` = the operator → the operator-level endpoints (sessions of operator owners with `webhooks.manage`, operator-level keys with `webhooks:manage`). ## POST /integrator-webhooks/{webhookId}/deliveries/{deliveryId}/redeliver operationId: `redeliverWebhook` Summary: Redeliver an event API key scopes: `webhooks:manage` Parameters: `webhookId` (path, required), `deliveryId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 202 `WebhookDelivery`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `webhooks:manage`. **Level (P9-T08b):** `X-Tenant-Id` = a customer → that customer's endpoints; `X-Tenant-Id` = the operator → the operator-level endpoints (sessions of operator owners with `webhooks.manage`, operator-level keys with `webhooks:manage`). ## POST /integrator-webhooks/{webhookId}/rotate-secret operationId: `rotateIntegratorWebhookSecret` Summary: Rotate signing secret (old valid 24 h) API key scopes: `webhooks:manage` Parameters: `webhookId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `IntegratorWebhookCreated`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `webhooks:manage`. **Level (P9-T08b):** `X-Tenant-Id` = a customer → that customer's endpoints; `X-Tenant-Id` = the operator → the operator-level endpoints (sessions of operator owners with `webhooks.manage`, operator-level keys with `webhooks:manage`). **Step-up:** requires a verified step-up challenge for action `integrator_webhook.change` within the last 5 minutes (session/bearer only). Otherwise `403` with problem type `step-up-required`; the step-up check runs before body validation. API keys holding `webhooks:manage` may call it without a step-up; every create, update, secret rotation and deletion notifies the endpoint owners (`webhook_endpoint_changed`, staff or customer members with `webhooks.manage`), whoever made it (review W3-03, P4 S-14). ## POST /integrator-webhooks/{webhookId}/test operationId: `testIntegratorWebhook` Summary: Send a test event API key scopes: `webhooks:manage` Parameters: `webhookId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `WebhookDelivery`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` **API key scope:** `webhooks:manage`. **Level (P9-T08b):** `X-Tenant-Id` = a customer → that customer's endpoints; `X-Tenant-Id` = the operator → the operator-level endpoints (sessions of operator owners with `webhooks.manage`, operator-level keys with `webhooks:manage`). ## POST /sandbox/incoming-payments operationId: `simulateSandboxIncomingPayment` Summary: Simulate an incoming payment API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SandboxIncomingPayment` Responses: 202 `SandboxSimulation`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Sandbox only (`wb_test_` keys and sandbox environments); `404 feature-disabled` elsewhere. **API key scope:** `payments:write`. ## POST /sandbox/payments/{paymentId}/status operationId: `simulateSandboxPaymentStatus` Summary: Simulate a provider status for a payment API key scopes: `payments:write` Parameters: `paymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SandboxPaymentStatus` Responses: 202 `SandboxSimulation`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Sandbox only (`wb_test_` keys and sandbox environments); `404 feature-disabled` elsewhere. **API key scope:** `payments:write`. ## POST /sandbox/reset operationId: `resetSandbox` Summary: Reset sandbox deliveries and paused endpoints API key scopes: `webhooks:manage` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `SandboxReset`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` Sandbox only (`wb_test_` keys and sandbox environments); `404 feature-disabled` elsewhere. **API key scope:** `webhooks:manage`. ## POST /sandbox/webhook-events operationId: `simulateSandboxWebhookEvent` Summary: Emit an integrator event API key scopes: `webhooks:manage` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SandboxWebhookEvent` Responses: 202 `SandboxSimulation`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Sandbox only (`wb_test_` keys and sandbox environments); `404 feature-disabled` elsewhere. Events carry `data.sandbox: true`. **API key scope:** `webhooks:manage`. # developer Self-serve developer sandboxes (D-42, FR-PLAT-07): registration (open mode) with email verification and abuse limits, the developer workspace, and the operator / platform controls (invite or open per operator, capped per environment). ## GET /developer/workspace operationId: `getDeveloperWorkspace` Summary: My developer sandbox workspace API key scopes: `balances:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `DeveloperWorkspace`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` `404` when the customer is not a developer workspace. Records activity (the 90-day idle clock restarts). **API key scope:** `balances:read`. # simulations Sandbox simulations (SC-38, P-89): every asynchronous outcome on demand — incoming payments and debits, payment status, returns and recalls, inbound recalls, exchange settlement, FX rate moves, direct-debit outcomes, card events, KYC outcomes, integrator events, statements, scheduled runs and provider outages. Backed by the sandbox bank, and by the Banking Circle and Integrated Finance sandboxes where they offer a simulation. ## GET /simulations operationId: `listSimulations` Summary: Available simulations and sandbox rules API key scopes: `balances:read` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header) Responses: 200 `SimulationCatalogue`, 401 `Problem`, 403 `Problem`, 429 `Problem`, default `Problem` **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `balances:read`. ## POST /simulations/cards/{cardId}/transactions operationId: `simulateCardTransaction` Summary: Simulate a card transaction event API key scopes: `payments:write` Parameters: `cardId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimCardTransaction` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` A neutral card event (`cards/transaction`) as the card processor would send it: authorisation (hold), settlement, reversal, refund or decline. **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `payments:write`. ## POST /simulations/direct-debits/collections/{collectionId}/status operationId: `simulateCollectionStatus` Summary: Simulate a direct-debit collection outcome API key scopes: `payments:write` Parameters: `collectionId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimCollectionStatus` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `payments:write`. ## POST /simulations/exchanges/{exchangeId}/status operationId: `simulateExchangeStatus` Summary: Simulate the settlement of an exchange API key scopes: `exchanges:write` Parameters: `exchangeId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimExchangeStatus` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Applies to an exchange still `processing` at the sandbox bank. **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `exchanges:write`. ## POST /simulations/fx-rates operationId: `simulateFxRateMove` Summary: Move a sandbox FX rate API key scopes: `exchanges:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimFxRate` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Stored on the operator's sandbox connection (`rateShiftsBps`): every quote of the pair moves (the inverse pair the other way). `applied`; `details.rate` is the new rate. **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `exchanges:write`. ## POST /simulations/incoming-debits operationId: `simulateIncomingDebit` Summary: Simulate a direct debit taken from a balance API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimIncomingDebit` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Banking Circle sandbox only (`POST /api/v1/payments/simulations/debits`); `409` on other connections. **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `payments:write`. ## POST /simulations/incoming-payments operationId: `simulateIncomingPayment` Summary: Simulate an incoming payment API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimIncomingPayment` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Sandbox bank: an `account.credited` event through the webhook pipeline (`queued`). Banking Circle sandbox: `POST /api/v1/payments/simulations/credits` on the balance account (`submitted`; the `IncomingPaymentProcessed` notification follows). Integrated Finance sandbox: a provisioned account credit on the balance IBAN (inferred until IF-33). **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `payments:write`. ## POST /simulations/incoming-payments/{incomingPaymentId}/recall operationId: `simulateIncomingRecall` Summary: Simulate an inbound recall request API key scopes: `payments:write` Parameters: `incomingPaymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimIncomingRecall` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Opens a partner compliance case (`inbound_recall`, provider `sandbox`) in the operator's queue (`applied`). **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `payments:write`. ## POST /simulations/kyc operationId: `simulateKycOutcome` Summary: Simulate a KYC review outcome API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimKycOutcome` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Applied as the KYC provider's `applicantReviewed` webhook: `approved` (GREEN), `rejected` (RED FINAL), `retry` (RED RETRY). **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `payments:write`. ## POST /simulations/payments/{paymentId}/recall operationId: `simulatePaymentRecall` Summary: Simulate the outcome of a payment recall API key scopes: `payments:write` Parameters: `paymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimPaymentRecall` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` `accepted`: the funds come back (a return with the recall reason); `rejected`: recorded on the provider event log, nothing moves. **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `payments:write`. ## POST /simulations/payments/{paymentId}/return operationId: `simulatePaymentReturn` Summary: Simulate the return of a payment API key scopes: `payments:write` Parameters: `paymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimPaymentReturn` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `payments:write`. ## POST /simulations/payments/{paymentId}/status operationId: `simulatePaymentStatus` Summary: Simulate the provider status of a payment API key scopes: `payments:write` Parameters: `paymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimPaymentStatus` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` A payment sent through the sandbox bank (`409` before it is sent or on a partner connection). The worker applies the stated status without asking the sandbox bank. **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `payments:write`. ## POST /simulations/provider-outage operationId: `simulateProviderOutage` Summary: Simulate a sandbox bank outage API key scopes: `payments:write` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimOutage` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Bank calls fail with a retryable provider error until the time given (`applied`): rehearse retries and backoff. **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `payments:write`. ## POST /simulations/scheduled-payments/{scheduledPaymentId}/run-now operationId: `simulateScheduledRun` Summary: Run a scheduled payment now API key scopes: `payments:write` Parameters: `scheduledPaymentId` (path, required), `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Responses: 202 `SimulationResult`, 401 `Problem`, 403 `Problem`, 404 `Problem`, 409 `Problem`, 429 `Problem`, default `Problem` An active schedule with a next occurrence is made due now and handed to the worker (`409` otherwise). **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `payments:write`. ## POST /simulations/statements operationId: `simulateStatement` Summary: Simulate a statement becoming available API key scopes: `webhooks:manage` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimStatement` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` A `statement.available` integrator event for the balance. **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `webhooks:manage`. ## POST /simulations/webhook-events operationId: `simulateWebhookEvent` Summary: Send an integrator event to your endpoints API key scopes: `webhooks:manage` Parameters: `X-Tenant-Id` (header, required), `X-Request-Id` (header), `Idempotency-Key` (header) Request body: `SimWebhookEvent` Responses: 202 `SimulationResult`, 400 `Problem`, 401 `Problem`, 403 `Problem`, 409 `Problem`, 422 `Problem`, 429 `Problem`, default `Problem` Delivered to the customer's active endpoints subscribed to the type, with `data.sandbox: true`. **Sandbox only:** `404 feature-disabled` wherever the sandbox is off (always in production). Callers: a `wb_test_` API key of the customer, or a member session of a developer sandbox workspace (`403` for live keys and other sessions). Every simulation answers `202 SimulationResult`; `queued` outcomes arrive through the normal pipeline (ledger, notifications, integrator webhooks) within seconds. Rules: docs/SANDBOX.md. **API key scope:** `webhooks:manage`.