# 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.<key>]=<value>` (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`.
