openapi: 3.0.0 info: version: "1.7.0" title: Lyriq Connector API description: | ## What is the Lyriq Connector? The Lyriq Connector is the HTTPS API through which a bank takes part in the Lyriq tokenised-money network. It is the only system your bank's IT estate integrates with: your applications call it to register customer and treasury accounts, move funds within your bank and to other banks, issue and redeem assets, manage limits and approvals, read balances and interbank positions, and subscribe to event notifications. Everything behind the API (policy evaluation, approval orchestration, ledger signing and submission, settlement and reconciliation) is run by the platform. Your bank sees one consistent API: requests are authenticated with a platform-issued token, always act for exactly one bank, and never return another bank's resources. ## What banks can do through this API - **Complete onboarding** through `/v1/onboarding-cases`: a bank admin registers staff members, machine-to-machine clients, signing and encryption keys and the webhook endpoint, then submits the configuration for operator review. - **Register accounts** with `POST /v1/accounts`, using your core banking system (CBS) identifier as `external_account_id` (ISO 20022 `Max34Text`: 1 to 34 characters), an `asset_id`, and an account type (`CUSTOMER` or `TREASURY`). Internal holding and issuance accounts are managed by the platform and are not exposed. - **Transfer assets** with `POST /v1/transfers`, choosing a `transfer_kind` (`INTERBANK` to a known account at another bank, `BANK_ROUTED` to another bank identified by bank and currency; see the Transfers section for the kinds currently available). - **Approve work** through the `reviews` resource when policy requires human sign-off (currently every transfer waits in `PENDING_REVIEW` for one checker approval). - **Read redemptions** with `GET /v1/redemptions`; settlement-cycle redemptions are queued, batched into a cycle, locked at cutoff, and finalised once cash settles offline. - **Create redemption policies** with `POST /v1/redemption-policies`; policy is activated and automatically run based on set trigger. - **Follow settlement cycles** of settlement-cycle-backed assets your bank issues (cycles are visible to the issuer only); the platform opens, locks, settles and closes cycles automatically. - **Read balances and positions**: account balances under `/v1/balances`, your holdings of other banks' assets under `/v1/exposures` and, as an issuer, what you owe each holder under `/v1/liabilities`. - **Configure limits**: a *mint limit* caps how much of an asset its issuer can create; an *exposure limit* is a cap a holder bank sets on itself: the most of one issuer's asset it will hold at any one time. - **Receive notifications** by registering HTTPS webhooks with rotating signing secrets, and inspect delivery history under `/v1/webhooks/deliveries`. - **Submit ISO 20022 messages** (`pacs.009`) to `/v1/iso20022/messages` as an alternative entry point for supported flows. ## Making requests **Base URL.** Every path in this reference is relative to your deployment's Lyriq Connector URL, shown as the server URL of this reference. **Authentication.** Every request carries `Authorization: Bearer `, where the token is issued by the platform IAM. See the Authentication section. **Bank scoping.** Each request acts for one bank, taken from the token's `bank_memberships` claim. If the token has several memberships, choose one with the `x-dan-bank-id` header. Resources of other banks are never returned. OAuth scopes grant coarse capabilities (for example `connector:transfers:create`); each operation in this reference states the scope it requires. **Media type (JSON:API).** Request and response bodies use JSON:API 1.1 with the media type `application/vnd.api+json`: - `POST` and `PUT` requests must send `Content-Type: application/vnd.api+json` exactly, without parameters such as `charset`; otherwise the response is `415 Unsupported Media Type`. - If you send an `Accept` header, it must list `application/vnd.api+json`; a header such as `Accept: */*` (the default of many HTTP clients, including curl) gets `406 Not Acceptable`. Either send `Accept: application/vnd.api+json` or omit the header. - A request body is a document with a top-level `data` member holding `type` and `attributes`. A single resource is returned as `data: {type, id, attributes, relationships, links}`; a collection as `data: [...]` plus pagination `links`. Related resources are linked through `relationships..links.related`. - The `include` (compound documents) and `fields[]` (sparse fieldsets) parameters are accepted but not applied yet: responses never contain `included` and always contain every attribute. **Identifiers and formats.** Operation, transfer and most other resource ids are UUIDs. Timestamps are RFC 3339 date-times in UTC. Amounts are strings holding an integer in the asset's smallest unit, with a separate `scale` giving the number of decimal places (`value` `"100000"` with `scale` `2` is 1000.00). ## Asynchronous mutations and operations Requests that change state (creating a transfer, registering an account, updating a limit, changing a webhook, cancelling, and so on) are **workflow mutations**. They are validated, recorded, and answered with `202 Accepted` before the work is done. The response body identifies a new **operation**, the platform's record of your request: ```json { "jsonapi": { "version": "1.1" }, "data": { "type": "operations", "id": "01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10", "attributes": { "resource_family": "transfers", "state": "ACCEPTED" }, "relationships": { "resource": { "data": { "type": "transfers", "id": "01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11" }, "links": { "related": "/v1/transfers/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11" } } }, "links": { "self": "/v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10" } } } ``` `202 Accepted` means *recorded*, not *done*: the request can still be rejected by policy or a reviewer, fail during execution, or be cancelled. To learn the outcome, poll `GET /v1/operations/{operation_id}` until `state` is terminal: | Terminal state | Meaning | | --- | --- | | `SUCCEEDED` | The request took effect. | | `REJECTED` | The request was refused; see `result_code` and `result_message`. | | `FAILED` | Execution failed; see `result_code` and `result_message`. | | `CANCELLED` | The request was cancelled before it took effect. | | `EXPIRED` | A required decision was not made in time. | Non-terminal states are `ACCEPTED`, `PROCESSING`, `PENDING_REVIEW` (waiting for approval), `AWAITING_SCREENING` (waiting for the beneficiary bank's screening decision), `PENDING_COMMITS` (waiting for ledger finality) and `MANUAL_REVIEW` (an operator must reconcile the outcome; do not resubmit). See the `OperationState` schema for the full list. A polling loop, with a growing delay between attempts: ```bash OP=01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 delay=1 while :; do state=$(curl -sS "$CONNECTOR_URL/v1/operations/$OP" \ --header "Authorization: Bearer $TOKEN" \ --header "Accept: application/vnd.api+json" | jq -r '.data.attributes.state') echo "state=$state" case "$state" in SUCCEEDED|REJECTED|FAILED|CANCELLED|EXPIRED) break ;; esac sleep "$delay"; delay=$(( delay < 30 ? delay * 2 : 30 )) done ``` ```text state=PROCESSING state=PROCESSING state=SUCCEEDED ``` Instead of polling, you can subscribe a webhook to `operation.updated` events. An operation can be cancelled while it has not yet reached the ledger with `POST /v1/operations/{operation_id}/cancel`. Some requests are not workflow mutations and answer synchronously (for example reads, `POST /v1/transfers/preflight`, and bank onboarding steps); their descriptions say so. ## Idempotency and retries Every workflow mutation requires an `Idempotency-Key` header: a string of 1 to 128 visible ASCII characters that you generate, ideally a fresh UUID per request. A missing key gives `400 Bad Request`. - **Same key, same body:** once the first request has been accepted, the original `202 Accepted` document is returned again with the same operation id, and nothing is executed twice. This makes it safe to retry after a timeout or a dropped connection. - **Same key, different body:** `409 Conflict` with code `IDEMPOTENCY_CONFLICT`. - **Same key while the first request is still being accepted:** `409 Conflict` with code `IDEMPOTENCY_PENDING`; retry after a short delay. - **Scope:** keys are remembered per bank, per calling client, per HTTP method and per endpoint path template, and only the body is compared. Path parameters are not compared, so use a new key for every distinct request. - **Retention:** a key is remembered for at least 72 hours. - **After an error:** if a request fails after its key was recorded (a business `4xx` or a `5xx`), the key stays reserved; the same key and body get `IDEMPOTENCY_PENDING` for up to 5 minutes and are then processed again. If you change the body, use a new key. ## Pagination Collection endpoints use cursor pagination: - `page[size]` sets the page size: 1 to 200, default 20. A value outside that range gives `400 Bad Request`. - The first page is returned when `page[cursor]` is absent. Each response has `links.self` and, when more results exist, `links.next`, which repeats your query with the next cursor. Follow `links.next` until it is absent. - Cursors are opaque; do not build or parse them. Only forward paging is supported (`links.prev` is not returned). - The sort order is stated per endpoint (for example, operations are listed oldest first). ```json "links": { "self": "/v1/operations?page[size]=50", "next": "/v1/operations?page%5Bsize%5D=50&page%5Bcursor%5D=CURSOR_FROM_LINKS_NEXT" } ``` ## Errors Failed JSON requests return a JSON:API error document with the matching HTTP status. Branch on the status and on `code` when present; `title` and `detail` are for humans and their wording can change. `source` points at the offending body member (`pointer`), query or path parameter (`parameter`) or header (`header`) when it can be identified. ```json { "jsonapi": { "version": "1.1" }, "errors": [ { "status": "400", "title": "Field format invalid", "code": "INVALID_FIELD_FORMAT", "detail": "/data/attributes/reason_code must not be empty", "source": { "pointer": "/data/attributes/reason_code" } } ] } ``` | Status | When | Retry? | | --- | --- | --- | | `400` | Malformed JSON, invalid parameter or field, missing `Idempotency-Key` | No; fix the request | | `401` | Missing, malformed, expired or foreign token (`AUTHENTICATION_REQUIRED`) | After getting a new token | | `403` | Missing scope or bank membership, wrong `x-dan-bank-id`, suspended or terminated bank | No | | `404` | Resource does not exist or is not visible to your bank | No | | `406` | `Accept` header does not list `application/vnd.api+json` | No; fix the header | | `409` | Idempotency conflict or pending request, or a state conflict | Depends on `code` | | `415` | `Content-Type` is not exactly `application/vnd.api+json` | No; fix the header | | `422` | Body does not match the expected shape, or breaks a business rule | No; fix the request | | `500`, `502` | Unexpected platform error | Yes, with the same `Idempotency-Key` and body (it may answer `IDEMPOTENCY_PENDING` for up to 5 minutes first) | | `503` | Network halted or read-only (`OUTBOUND_HALTED`, `READ_ONLY`), state unknown, or a dependency unavailable | Yes, later, with backoff | While the network is halted or read-only, read endpoints keep working and mutations return `503`. No `Retry-After` header is sent; use exponential backoff. ISO 20022 endpoints return errors as RFC 7807 `application/problem+xml` documents instead; see their descriptions. contact: name: M10 Engineering email: ops@m10.io license: name: MIT url: https://opensource.org/licenses/MIT servers: - url: https://drm-uat.fiscloudservices.com/bank-connector description: Deployment-configured Connector API security: - bearerAuth: [] tags: - name: Authentication x-displayName: Authentication description: | ## Overview The Lyriq Connector accepts only access tokens issued by the platform IAM (Lyriq IAM). Any other tokens are explicitly unsupported and will be rejected: - raw FIS IdP tokens; - raw bank IdP tokens; - raw SAML assertions; - upstream IdP claim payloads. ## Which token flow should I use? | Caller | Supported flow | | --- | --- | | FIS IdP human user | IAM authorization-code flow with the FIS IdP broker (see [Human users](#human-users-authorization-code-flow)) | | Bank SSO human user | IAM authorization-code flow with the bank IdP broker (see [Human users](#human-users-authorization-code-flow)) | | Bank backend or system client | FIS workload assertion exchanged at IAM with `client_credentials` and JWT client authentication (see [Bank backends](#bank-backends-workload-assertion-exchange)) | | Raw FIS token, raw bank token, or SAML assertion | Unsupported | The IAM endpoints below are served by the platform IAM host, not by the Lyriq Connector. ## Sending the token Every endpoint requires the token as a bearer credential. Requests sent without it are rejected immediately with `401 Unauthorized`. ```bash export CONNECTOR_URL= export TOKEN= curl "$CONNECTOR_URL/v1/operations" \ --header "Authorization: Bearer $TOKEN" \ --header "Accept: application/vnd.api+json" ``` Mutating requests additionally send `Content-Type: application/vnd.api+json` and an `Idempotency-Key` header. ## What's in the access token The access token is a signed JWT. You never build or edit it: the platform IAM mints it from the configuration approved when your bank was onboarded, and the Lyriq Connector validates every claim below on every request. A token issued to a bank backend (M2M workload) through the `client_credentials` flow looks like this once decoded: ```json { "iss": "https://iam.example.com/realms/keystone-network", "sub": "6f1c2d3e-4b5a-4c6d-8e7f-9a0b1c2d3e4f", "aud": ["bank-connector"], "azp": "bank-m2m-01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e0f", "iat": 1790000000, "exp": 1790000300, "scope": "connector:transfers:create connector:operations:read", "connector_token_version": "v1", "principal_type": "service_account", "principal_kind": "bank_member", "connector_managed_client": true, "bank_memberships": [ { "bank_id": "11111111-1111-7111-8111-111111111111", "roles": ["maker"], "scopes": ["connector:transfers:create", "connector:operations:read"] } ] } ``` | Claim | What it holds | What the Lyriq Connector checks | | --- | --- | --- | | `iss` | The platform IAM issuer URL for your deployment | Must be a trusted platform issuer; a token from any other issuer is rejected | | `sub` | The platform's identifier for your workload | Must be a UUID; recorded as the acting principal in audit | | `aud` | The intended recipients of the token | Must include `bank-connector` | | `azp` | The platform client id generated for your workload at onboarding (shown as **Platform client ID** in the bank portal) | Must be a client the platform recognises for the Lyriq Connector | | `iat`, `exp` | Issue and expiry times, in seconds since the epoch | `exp` must be in the future; tokens live 300 seconds | | `scope` | Space-separated API capabilities granted to the workload | See [How scopes are resolved](#how-scopes-are-resolved) | | `connector_token_version` | The version of this claim contract | Must be `v1` | | `principal_type` | The kind of caller: `service_account` for an M2M workload, a user type for a person signed in through a browser flow | Must be present | | `principal_kind` | `bank_member` for every bank caller | Must be `bank_member` on bank-facing routes | | `connector_managed_client` | `true` when the workload was provisioned through bank onboarding | Together with `principal_type` and `principal_kind`, marks the client as one the platform created for your bank | | `bank_memberships` | The banks this caller may act for, each with its `bank_id`, descriptive `roles` and approved `scopes` | See [Selecting the target bank](#selecting-the-target-bank) | Every value comes from onboarding: the bank, the roles and the scopes are those approved for the workload, so the token for an M2M workload always carries exactly one membership. To change what a workload may do, change its onboarding configuration; requesting a different token does not widen it. To inspect a token, decode its middle segment: ```bash jq -R 'split(".")[1] | gsub("-"; "+") | gsub("_"; "/") | @base64d | fromjson' <<< "$TOKEN" ``` ### How scopes are resolved A request is allowed a scope only when it appears in **both** the token's top-level `scope` and the `scopes` of the selected bank membership. A scope present in only one of the two is not granted. Each endpoint in this specification names the scope it requires. Scopes describe coarse API capabilities, such as `connector:transfers:create`. They never name a bank, an account or an asset; which bank a request acts for comes only from `bank_memberships`. The `roles` in a membership (for example `maker`, `checker` or `readonly`) are descriptive labels for the preset the workload was created from. They grant nothing on their own. ## Selecting the target bank The token's `bank_memberships` claim lists the banks the caller may act on, for example: ```json "bank_memberships": [ { "bank_id": "11111111-1111-7111-8111-111111111111", "roles": ["maker"], "scopes": ["connector:transfers:create", "connector:operations:read"] }, { "bank_id": "22222222-2222-7222-8222-222222222222", "roles": ["readonly"], "scopes": ["connector:operations:read"] } ] ``` With exactly one membership the Lyriq Connector selects it automatically. With more than one, declare the target explicitly with the bank's UUID: ```bash --header "x-dan-bank-id: 11111111-1111-7111-8111-111111111111" ``` Send the header at most once. A value that is empty, not a UUID, or not one of your memberships is rejected with `403 Forbidden` and code `BANK_SCOPE_MISMATCH`. ## Human users: authorization-code flow FIS IdP users and bank SSO users start authentication at the platform IAM realm. The IAM redirects the user to the configured upstream identity provider, accepts the successful upstream authentication response, applies the configured mappers, and issues the normalized Lyriq Connector access token. ```text User -> platform IAM authorization endpoint -> FIS IdP (or bank SSO) login -> platform IAM mapper configuration -> IAM-issued Lyriq Connector access token -> Lyriq Connector API ``` The token sent to the Lyriq Connector is always the IAM-issued `access_token`. Do not send the raw FIS IdP token, raw SAML assertion, or copied upstream FIS claims to the Lyriq Connector. ### Start the brokered login Start an OIDC authorization-code flow against the IAM. Public browser clients should use PKCE. The `kc_idp_hint` parameter is optional; when it is set to the configured identity-provider alias (for example the FIS IdP alias), the IAM routes the user directly to that provider's login instead of showing the identity-provider selector. ```bash export IAM_AUTH_URL="https:///realms/keystone-network/protocol/openid-connect/auth" export CLIENT_ID="bank-portal" export REDIRECT_URI="https:///oauth/callback" export FIS_IDP_ALIAS="fis" # Open this URL in the user's browser. URL-encode redirect_uri before sending it. "$IAM_AUTH_URL?client_id=$CLIENT_ID&response_type=code&redirect_uri=&scope=openid%20profile%20email&state=&code_challenge=&code_challenge_method=S256&kc_idp_hint=$FIS_IDP_ALIAS" ``` After the user authenticates, the IAM redirects back to the registered `redirect_uri` with an authorization `code`. ### Exchange the code at the IAM Exchange the authorization code at the IAM token endpoint. Use the same redirect URI and PKCE verifier that were used to start the login. ```bash export IAM_TOKEN_URL="https:///realms/keystone-network/protocol/openid-connect/token" export CLIENT_ID="bank-portal" export REDIRECT_URI="https:///oauth/callback" export AUTHORIZATION_CODE="" export CODE_VERIFIER="" curl -sS -X POST "$IAM_TOKEN_URL" \ -H 'content-type: application/x-www-form-urlencoded' \ -d grant_type=authorization_code \ -d "client_id=$CLIENT_ID" \ -d "redirect_uri=$REDIRECT_URI" \ -d "code=$AUTHORIZATION_CODE" \ -d "code_verifier=$CODE_VERIFIER" ``` For confidential interactive web applications, include the registered client secret in the token request. This applies only to the web application's OIDC registration, never to a bank M2M workload: ```bash -d "client_secret=$CLIENT_SECRET" ``` Use the `access_token` from the response as the bearer token for the Lyriq Connector. ## Bank backends: workload assertion exchange Bank backend and system integrations authenticate to FIS with a bank-controlled asymmetric credential. FIS returns a short-lived workload assertion whose `sub` is the FIS Client ID registered during bank onboarding. The platform IAM verifies that assertion and resolves it to the secretless federated client provisioned for the workload. This flow is for machine-to-machine clients only; human users use the authorization-code flow above. Obtain the FIS workload assertion from the FIS token endpoint using the bank-controlled credential, then exchange it once at the IAM: ```bash export IAM_TOKEN_URL="https:///realms/keystone-network/protocol/openid-connect/token" export FIS_WORKLOAD_ASSERTION="" curl --fail --show-error --silent --request POST "$IAM_TOKEN_URL" \ -H 'content-type: application/x-www-form-urlencoded' \ -d grant_type=client_credentials \ -d 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \ --data-urlencode "client_assertion=$FIS_WORKLOAD_ASSERTION" ``` The access token is valid for 300 seconds; request a new one before it expires. Its claims, including the bank membership and scopes approved for the workload at onboarding, are described in [What's in the access token](#whats-in-the-access-token). ## Common problems ### Missing bearer token Ensure the `Authorization` header is present exactly once and uses the `Bearer ` syntax. The response is `401` with code `AUTHENTICATION_REQUIRED`. ### Expired token Access tokens expire after 5 minutes (300 seconds). Requests using an expired token are rejected with `401` (title `Expired token`); get a new one via the relevant flow. ### Wrong issuer or audience Occurs when passing a token issued directly by an external entity instead of the platform IAM, or when the requesting client isn't configured with the Lyriq Connector audience. The response is `401` with code `AUTHENTICATION_REQUIRED`. Request a new token from the IAM using the relevant flow. If the problem persists, contact your system administrator to verify the client's audience configuration. ### Missing scope The token is valid but lacks the permission scope the endpoint requires; the response is `403` with code `UNAUTHORIZED_SCOPE`. Check the endpoint's required scope in this specification against both the token's `scope` and the selected membership's `scopes` (see [How scopes are resolved](#how-scopes-are-resolved)). A fresh token carries the same scopes, so if the scope is missing from either, contact your system administrator to update your client's access rights. ### Missing or ambiguous bank membership **Missing**: the token's `bank_memberships` array is empty, so the caller has no bank relationship yet (`403`, title `Missing bank membership`). Contact your system administrator to provision one. **Ambiguous**: the array has more than one entry and the request didn't declare a target bank (`403`, title `Bank selector required`). Resolve it with the `x-dan-bank-id` header described in [Selecting the target bank](#selecting-the-target-bank). ### Suspended or terminated bank Requests for a bank that is suspended or terminated on the network are rejected with `403` and code `BANK_SUSPENDED` or `BANK_TERMINATED`. - name: Bank Onboarding x-displayName: Bank Onboarding description: | The bank's onboarding administrator completes the bank's onboarding case with these operations. At most one case exists per bank. A case is editable in the states `AWAITING_BANK_ADMIN`, `BANK_CONFIGURING` and `NEEDS_CHANGES`; the first edit moves `AWAITING_BANK_ADMIN` to `BANK_CONFIGURING`. 1. `GET /v1/onboarding-cases` to find the `case_id`, then `GET /v1/onboarding-cases/{case_id}/key-requirements` to see which keys are needed. 2. Provide the three signing keys (`BANK_ADMIN`, `CREATE_INTERBANK_TRANSFER_MAKER`, `APPROVE_INTERBANK_TRANSFER_CHECKER`) and the key-encryption key (`ENCRYPT_TRANSFER_SOURCE_MESSAGE`). Either register keys you hold (`POST .../signing-targets` and `POST .../encryption-targets`, then `POST .../{target_id}/verify` to prove the platform can use each key), or let the platform generate them (`POST .../key-generation-jobs`, then poll the job until every item is `READY`). Every key must end `VERIFIED`. 3. Add staff members (`POST .../staff-members`); at least one needs the `bank-admin` label. 4. Configure webhooks (`PUT .../webhook-setup`): enabled, subscribed to `beneficiary.screening.requested`, with a signing secret. 5. Optionally declare machine-to-machine workloads (`PUT .../m2m-clients`). 6. `POST .../submit-configuration` moves the case to `READY_FOR_REVIEW`. The case is then no longer editable. `readiness.ready_for_review` on the case tells you in advance whether submission will succeed. The platform operator then accepts the case, which creates an activation operation (`activation_operation_id`, state `AWAITING_APPROVALS`). After approval the case moves to `PROVISIONING`, and to `ACTIVE` once staff and workloads are provisioned in the platform IAM. `REJECTED` and `FAILED` are end states. - name: Bank Staff x-displayName: Bank Staff description: | People of the bank, declared on the onboarding case, who sign in through the bank's federated identity provider. Role labels (`bank-admin`, `maker`, `checker`, `readonly`) are descriptive; only `scopes` authorise API calls. Members are provisioned when the case becomes `ACTIVE`. Staff members are not ledger accounts. - name: M2M Workloads x-displayName: M2M Workloads description: | Read-only view of the bank's machine-to-machine workloads that were approved during onboarding and provisioned when the case became `ACTIVE`. Configure workloads with `PUT /v1/onboarding-cases/{case_id}/m2m-clients`. The access token such a workload receives is described under **What's in the access token** in the Authentication overview. - name: EncryptionTargets x-displayName: Encryption Targets description: | Bank-held key-encryption key (KEK) registration. The bank creates a symmetric AES-256 KMS key in its own AWS account and grants the platform runtime role `kms:GenerateDataKey`, `kms:Encrypt`, and `kms:Decrypt` on it. The platform records only the key ARN and wraps a fresh per-message data key under it; key material never leaves the bank's account. Revoking the grant or disabling the key immediately removes the platform's access to that bank's stored messages. The first KEK is registered during onboarding; this family covers replacement afterwards. - name: Banks x-displayName: Banks description: | Read the directory of active banks registered on the network. - name: Assets x-displayName: Assets description: | Asset metadata (read-only). An asset is a tokenised settlement unit or instrument issued by a specific bank, with a fixed `scale` (decimal places) and a stable `asset_id`. - name: Accounts x-displayName: Accounts description: | Account registration and lookup. A bank supplies its CBS `external_account_id` when registering an account. It must satisfy the ISO 20022 Max34Text XSD type. The JSON:API resource ID is a versioned value derived from the bank, asset, and external account identifier. - name: Operations x-displayName: Operations description: | Track and cancel asynchronous requests. Every workflow mutation returns `202 Accepted` with an operation id; these endpoints expose the operation's state until it is terminal (`SUCCEEDED`, `REJECTED`, `FAILED`, `CANCELLED` or `EXPIRED`) and let you cancel it while it has not yet reached the ledger. See "Asynchronous mutations and operations" in the API overview for the polling model and an example. - name: Transfers x-displayName: Transfers description: | Customer and treasury money movement. `INTERBANK` moves value to a known account at another bank. `BANK_ROUTED` moves value to another bank by bank and currency, letting the Lyriq Connector select the token and destination account. - name: Reviews x-displayName: Approval Reviews description: | Approval workflow review tasks. Operations that policy routes to human review wait in state `PENDING_REVIEW` until reviewers with the required role have decided. Currently every transfer is routed to review and needs one approval from a checker. A review collects decisions from one or more reviewers before the originating operation can proceed. The principal that submitted an operation can never decide its review (four-eyes): approval needs a second, different credential. - name: Redemptions x-displayName: Redemptions description: | Asset redemption records visible to the holder bank. Settlement-cycle redemptions are batched by issuer and asset for offline cash settlement. - name: RedemptionPolicies x-displayName: Redemption Policies description: | Automated redemption policies. Each policy names an `asset_id` and a trigger. The trigger contains a rule with a condition; whenever that condition is met, the policy runs and creates a redemption automatically. - name: SettlementCycles x-displayName: Settlement Cycles description: | Settlement cycle lifecycle visibility (read-only; visible to the asset's issuer only). Settlement cycles batch interbank redemptions for cash settlement. Main lifecycle: `OPEN` → `LOCKED` → `CASH_SETTLED` → `CLOSED`. `CLOSE_PENDING` and `ABORT_PENDING` are transitional statuses, and `MANUAL_REVIEW` marks a cycle that needs operator attention. `ABORTED` is a terminal failure status, reachable only from `LOCKED` or `MANUAL_REVIEW`. Cycle transitions are driven by the platform settlement scheduler, not by bank-initiated requests. - name: Balances x-displayName: Balances description: | Account balance projections (read-only). Balances are eventually consistent snapshots derived from the underlying ledger. - name: Exposures x-displayName: Exposures description: | Interbank holding exposure views (read-only). An exposure is the holder bank's position in an issuer's `INTERBANK_HOLDING` account, including utilisation against the issuer-set exposure limit. - name: Liabilities x-displayName: Liabilities description: | Issuer-side liability views (read-only). A liability is what an issuer owes to a specific holding bank for a given asset. Visible only to the issuer. - name: MintLimits x-displayName: Mint Limits description: | Mint limit management. A *mint limit* caps how much of an asset its issuer is authorised to create; it is a single static `amount` per asset, changed only by an explicit upsert. Mint limits are issuer-owned and are authorised, changed and audited independently of exposure limits. - name: ExposureLimits x-displayName: Exposure Limits description: | Exposure limit management. An *exposure limit* is a cap a holder bank sets on itself: the maximum amount of one issuer's asset it will hold at any one time. Only the holder bank sets and sees its exposure limits. Exposure limits are authorised, changed and audited independently of mint limits. - name: Webhooks x-displayName: Webhooks description: | Webhook subscription management. Banks register HTTPS endpoints that receive HMAC-signed JSON event notifications. See `POST /v1/webhooks` for the event types, signature verification, retries and the verification handshake. - name: AuthProfiles x-displayName: Webhook Auth Profiles description: | OAuth2 receiver-auth profile management. An *auth profile* holds the client-credentials settings the platform uses to obtain an access token from the bank's token endpoint; the token is then sent as `Authorization: Bearer` on webhook deliveries. Bind a webhook to a profile with `POST /v1/webhooks/{webhook_id}/bind`. Binding, or any update to a bound profile, returns the webhook to `PENDING_VERIFICATION` until the new verification event is acknowledged. - name: ISO20022 x-displayName: ISO 20022 Messages description: | ISO 20022 message ingress and status. An alternative XML-based entry point for banks that already speak ISO 20022 (currently `pacs.009.001.08` FI credit transfers, carried with a `head.001.001.03` business application header). Submissions are correlated to the platform's async operations via the UETR. paths: /v1/operations: get: summary: List operations operationId: listOperations tags: - Operations x-required-scopes: - connector:operations:list description: | List the operations visible to the calling bank: those your bank initiated and those in which your bank is the counterparty (for example the receiving bank of an interbank transfer). An *operation* is the platform's record of one workflow mutation (a transfer, an account registration, a limit change, a webhook change, a cancellation, and so on). Every mutation returns `202 Accepted` with the operation's id; use this endpoint to find operations, and `GET /v1/operations/{operation_id}` to follow a single one. **Required scope:** `connector:operations:list`. **Ordering and paging.** Results are ordered oldest first (by `created_at`, then `id`). Follow `links.next` to page forward until it is absent. **Filtering.** Only `filter[state]` is supported. Other `filter[...]` parameters are ignored. The list returns each operation's summary fields; `ledger_refs` and `bank_id` are only returned by `GET /v1/operations/{operation_id}`. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/includeParam' - $ref: '#/components/parameters/fieldsParam' - name: filter[state] in: query required: false description: | Return only operations currently in this state, for example `PENDING_REVIEW` to find operations waiting for approval. An unknown value returns `400 Bad Request`. schema: $ref: '#/components/schemas/OperationState' responses: '200': description: | One page of operations, oldest first. `data` is an empty array when nothing matches. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/OperationSummary' links: $ref: '#/components/schemas/CursorLinks' examples: firstPageWithMore: summary: First page, more results available value: data: - type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 attributes: resource_family: transfers operation_type: TRANSFER_CREATE state: SUCCEEDED created_at: '2026-09-26T10:15:30.482913+00:00' updated_at: '2026-09-26T10:15:32.107554+00:00' relationships: resource: data: type: transfers id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 links: related: /v1/transfers/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 links: self: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 - type: operations id: 01927c3f-1d2e-7a3b-8c4d-5e6f7a8b9c0d attributes: resource_family: operations operation_type: OPERATION_CANCEL state: REJECTED created_at: '2026-09-26T10:20:05.918230+00:00' updated_at: '2026-09-26T10:20:05.940117+00:00' result_code: CANCEL_NOT_ALLOWED relationships: resource: data: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 links: related: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 links: self: /v1/operations/01927c3f-1d2e-7a3b-8c4d-5e6f7a8b9c0d links: self: /v1/operations?page[size]=2 next: /v1/operations?page%5Bsize%5D=2&page%5Bcursor%5D=CURSOR_FROM_LINKS_NEXT filteredByState: summary: Operations waiting for approval (filter[state]=PENDING_REVIEW), last page value: data: - type: operations id: 01927c41-3b4c-7d5e-8f60-718293a4b5c6 attributes: resource_family: transfers operation_type: TRANSFER_CREATE state: PENDING_REVIEW created_at: '2026-09-26T11:02:44.301876+00:00' updated_at: '2026-09-26T11:02:45.012345+00:00' relationships: resource: data: type: transfers id: 01927c41-3b4c-7d5e-8f60-718293a4b5c7 links: related: /v1/transfers/01927c41-3b4c-7d5e-8f60-718293a4b5c7 links: self: /v1/operations/01927c41-3b4c-7d5e-8f60-718293a4b5c6 links: self: /v1/operations?filter[state]=PENDING_REVIEW empty: summary: No matching operations value: data: [] links: self: /v1/operations?filter[state]=MANUAL_REVIEW '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/operations/{operation_id}: parameters: - $ref: '#/components/parameters/operationIdParam' get: summary: Get an operation operationId: getOperation tags: - Operations x-required-scopes: - connector:operations:read description: | Fetch one operation by `operation_id`. This is how you learn the outcome of any mutation that returned `202 Accepted`. **Required scope:** `connector:operations:read`. Poll until `state` is terminal (`SUCCEEDED`, `REJECTED`, `FAILED`, `CANCELLED` or `EXPIRED`); see "Asynchronous mutations and operations" in the API overview for a polling example. On `REJECTED`, `FAILED` or `EXPIRED`, `result_code` and `result_message` explain why. While the operation is `PENDING_REVIEW`, it waits for review decisions; while it is `MANUAL_REVIEW`, an operator must reconcile it, so do not resubmit the request. The operation's target resource is linked from `relationships.resource`. You can read operations your bank initiated and operations in which your bank is the counterparty; any other `operation_id` returns `404 Not Found`. responses: '200': description: The operation. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/Operation' examples: processing: summary: Transfer operation still in progress value: data: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 attributes: resource_family: transfers operation_type: TRANSFER_CREATE state: PROCESSING created_at: '2026-09-26T10:15:30.482913+00:00' updated_at: '2026-09-26T10:15:30.901442+00:00' bank_id: 11111111-1111-7111-8111-111111111111 ledger_refs: - tx_type: transfer ledger_tx_id: null state: submitting relationships: resource: data: type: transfers id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 links: related: /v1/transfers/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 links: self: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 succeeded: summary: Transfer operation completed successfully value: data: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 attributes: resource_family: transfers operation_type: TRANSFER_CREATE state: SUCCEEDED created_at: '2026-09-26T10:15:30.482913+00:00' updated_at: '2026-09-26T10:15:32.107554+00:00' bank_id: 11111111-1111-7111-8111-111111111111 ledger_refs: - tx_type: transfer ledger_tx_id: '4821907' state: succeeded relationships: resource: data: type: transfers id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 links: related: /v1/transfers/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 links: self: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 cancelRejected: summary: Cancellation refused because the target was already past its cancellable point value: data: type: operations id: 01927c3f-1d2e-7a3b-8c4d-5e6f7a8b9c0d attributes: resource_family: operations operation_type: OPERATION_CANCEL state: REJECTED created_at: '2026-09-26T10:20:05.918230+00:00' updated_at: '2026-09-26T10:20:05.940117+00:00' result_code: CANCEL_NOT_ALLOWED bank_id: 11111111-1111-7111-8111-111111111111 ledger_refs: [] relationships: resource: data: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 links: related: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 links: self: /v1/operations/01927c3f-1d2e-7a3b-8c4d-5e6f7a8b9c0d '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/operations/{operation_id}/cancel: parameters: - $ref: '#/components/parameters/operationIdParam' post: summary: Cancel an operation operationId: cancelOperation tags: - Operations x-required-scopes: - connector:operations:cancel description: | Ask the platform to cancel an operation your bank initiated. **Required scope:** `connector:operations:cancel`. The cancellation is itself an operation. This call returns `202 Accepted` with a new operation (`operation_type` `OPERATION_CANCEL`, `resource_family` `operations`) whose `relationships.resource` points at the target. Poll that new operation to learn the result: | Cancellation operation ends in | Meaning | | --- | --- | | `SUCCEEDED` | The target was cancelled; it is now `CANCELLED` and any open reviews on it are closed. | | `REJECTED` with `result_code` `CANCEL_NOT_ALLOWED` | The target could not be cancelled: it is already in a final state (or `MANUAL_REVIEW`), a ledger transaction for it was already submitted, or another cancellation was already requested. The target is unchanged. | An operation can be cancelled only while it is `ACCEPTED`, `PROCESSING`, `PENDING_REVIEW`, `PENDING_COMMITS` or `AWAITING_SCREENING` **and** none of its ledger transactions has been submitted yet. Once the first ledger transaction is submitted, the operation can no longer be cancelled and completes under normal processing. A transfer reaches `PENDING_REVIEW` only after its first ledger transaction has been submitted, so a transfer waiting for review cannot be cancelled; a reviewer can reject it instead (it then ends `REJECTED` with `result_code` `CHECKER_REJECTED`). These problems are reported directly by this call instead of through a cancellation operation: - `400 Bad Request` if `operation_id` is not a UUID, an attribute is blank, or `expected_target_state` does not match the target's current state; - `404 Not Found` if the target operation does not exist; - `401 Unauthorized` if the target operation belongs to another bank. Send an `Idempotency-Key`. Because the key is compared on the request body only, use a new key for each target operation. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/OperationCancelRequest' examples: withReason: summary: Cancel with a reason code and comment value: data: type: operation-cancellations attributes: reason_code: DUPLICATE_REQUEST comment: Duplicate payment submitted by mistake. guarded: summary: Cancel only if the target is still in PROCESSING value: data: type: operation-cancellations attributes: reason_code: CUSTOMER_REQUEST expected_target_state: PROCESSING minimal: summary: Cancel without a reason value: data: type: operation-cancellations attributes: {} responses: '202': description: | Cancellation accepted for processing. `data.id` is the id of the new cancellation operation, not of the target. Poll it with `GET /v1/operations/{operation_id}`. Replaying the same `Idempotency-Key` with the same body returns this document again. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Cancellation operation created value: jsonapi: version: '1.1' data: type: operations id: 01927c3f-1d2e-7a3b-8c4d-5e6f7a8b9c0d attributes: resource_family: operations state: ACCEPTED '400': description: | The request is malformed, or `expected_target_state` does not match the target operation's current state. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: expectedStateMismatch: summary: Target is not in the expected state value: jsonapi: version: '1.1' errors: - status: '400' title: target operation state SUCCEEDED does not match expected PROCESSING blankAttribute: summary: An attribute is present but blank value: jsonapi: version: '1.1' errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: /data/attributes/reason_code must not be empty source: pointer: /data/attributes/reason_code invalidOperationId: summary: operation_id is not a UUID value: jsonapi: version: '1.1' errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: operation_id must be a valid UUID source: parameter: operation_id '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: The target operation does not exist. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: targetNotFound: summary: Unknown target operation value: jsonapi: version: '1.1' errors: - status: '404' title: resource not found '409': $ref: '#/components/responses/Conflict' '415': description: The `Content-Type` header is not exactly `application/vnd.api+json`. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: unsupportedMediaType: summary: Wrong Content-Type value: jsonapi: version: '1.1' errors: - status: '415' title: Unsupported Media Type '422': $ref: '#/components/responses/UnprocessableEntity' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/redemptions: get: summary: List redemptions operationId: listRedemptions tags: - Redemptions x-required-scopes: - connector:redemptions:list description: | List the redemptions in which the calling bank is the **holder**. A *redemption* (resource type `redemptions`) returns units of an issuer's asset that the holder bank keeps in its interbank holding account to the issuer, in exchange for cash settled outside the platform. The *holder* is the bank that holds the issuer's asset and asks for it to be redeemed; the *issuer* is the bank that created the asset and pays the cash. Redemptions are batched into the issuer's *settlement cycles* (see `GET /v1/settlement-cycles`). **Who sees what.** Only redemptions whose `holder_bank_id` is the calling bank are returned. An issuer does not see the redemptions of its holders through this endpoint; it follows them through its settlement cycles instead. **Lifecycle.** A redemption moves through these states: ``` SUBMITTED ──► ASSIGNED ──► LOCKED ──► REDEEMED (cycle closed) │ │ └───────────┴──────► RELEASED (cycle aborted) ``` | `state` | Meaning | |---------|---------| | `SUBMITTED` | Accepted, but no settlement cycle is open yet for this issuer and asset. It is assigned automatically when the next cycle opens. | | `ASSIGNED` | Assigned to an `OPEN` settlement cycle (`assigned_cycle_id` is set). A redemption created while a cycle is open is assigned immediately, and the platform requests a ledger lock for its amount at once. | | `LOCKED` | The amount is locked on the ledger for the assigned cycle and can no longer be spent. This happens once the ledger confirms the lock, at the latest when the cycle locks at its cutoff. The redemption then waits for cash settlement and cycle close. | | `REDEEMED` | Terminal. The issuer confirmed the cash and the locked units were redeemed (burned) on the ledger when the cycle closed. | | `RELEASED` | Terminal. The cycle was aborted and the lock was released; the units are spendable again on the holding account. | The enumeration also contains `QUEUED`, `REJECTED`, and `EXPIRED`. The current release does not assign these values, but clients should accept them. **Ordering and paging.** Results are returned newest first. Paging is cursor based: follow `links.next` until it is absent. `links.prev` is never returned. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' - name: filter[asset_id] in: query required: false schema: type: string description: | Return only redemptions of this root asset, for example `usd`. Exact match on the redemption's `asset_id`. example: usd responses: '200': description: One page of redemptions held by the calling bank, newest first. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/Redemption' links: $ref: '#/components/schemas/CursorLinks' examples: firstPage: summary: Bank Beta lists its redemptions of Bank Alpha's asset (more pages follow) value: data: - type: redemptions id: 5b8f0c2e-3d4a-5e6f-9a1b-2c3d4e5f6a7b attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 issuer_bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd redemption_kind: SETTLEMENT_CYCLE amount: asset_id: usd value: '250000' scale: 2 state: ASSIGNED assigned_cycle_id: 7c9e6679-7425-5de0-9f1a-3b2c1d0e4f5a client_reference: REDEEM-7788 requested_at: '2026-09-24T09:15:02Z' - type: redemptions id: 3f2a1b0c-9d8e-5f7a-8b6c-5d4e3f2a1b0c attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 issuer_bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd redemption_kind: SETTLEMENT_CYCLE amount: asset_id: usd value: '1000000' scale: 2 state: REDEEMED assigned_cycle_id: 0a1b2c3d-4e5f-5a6b-8c7d-9e0f1a2b3c4d requested_at: '2026-09-23T08:02:44Z' links: self: /v1/redemptions?filter[asset_id]=usd&page[size]=2 next: /v1/redemptions?filter%5Basset_id%5D=usd&page%5Bsize%5D=2&page%5Bcursor%5D=3f2a1b0c-9d8e-5f7a-8b6c-5d4e3f2a1b0c empty: summary: No redemptions match value: data: [] links: self: /v1/redemptions?filter[asset_id]=eur '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/redemptions/{redemption_id}: parameters: - name: redemption_id in: path required: true schema: type: string format: uuid description: | Redemption identifier (a UUID): the `id` of a `redemptions` resource returned by `GET /v1/redemptions`. A value that is not a UUID is rejected with `400`. example: 5b8f0c2e-3d4a-5e6f-9a1b-2c3d4e5f6a7b get: summary: Get a redemption operationId: getRedemption tags: - Redemptions x-required-scopes: - connector:redemptions:read description: | Fetch a single redemption by `redemption_id`. Only the **holder** bank of the redemption can read it. A redemption that does not exist and one that belongs to another holder both return `404`. The response carries the current `state`, the requested amount, the holder and issuer bank ids, and `assigned_cycle_id` once the redemption is assigned to a settlement cycle. See `GET /v1/redemptions` for the meaning of each state. responses: '200': description: The requested redemption. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/Redemption' examples: locked: summary: Redemption locked in an open settlement cycle value: data: type: redemptions id: 5b8f0c2e-3d4a-5e6f-9a1b-2c3d4e5f6a7b attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 issuer_bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd redemption_kind: SETTLEMENT_CYCLE amount: asset_id: usd value: '250000' scale: 2 state: LOCKED assigned_cycle_id: 7c9e6679-7425-5de0-9f1a-3b2c1d0e4f5a client_reference: REDEEM-7788 requested_at: '2026-09-24T09:15:02Z' submitted: summary: Redemption waiting for the next settlement cycle value: data: type: redemptions id: 9d4c2b1a-0f9e-5d8c-a7b6-c5d4e3f2a1b0 attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 issuer_bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd redemption_kind: SETTLEMENT_CYCLE amount: asset_id: usd value: '50000' scale: 2 state: SUBMITTED requested_at: '2026-09-24T18:40:11Z' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: | No redemption with this id exists for the calling holder bank. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: notFound: summary: Unknown redemption, or one held by another bank value: jsonapi: version: '1.1' errors: - status: '404' title: redemption not found '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/transfers: get: summary: List transfers operationId: listTransfers tags: - Transfers x-required-scopes: - connector:transfers:list description: | List transfers visible to the calling bank. Requires scope `connector:transfers:list`. The list contains every transfer the calling bank sent (any kind) plus every `INTERBANK` and `BANK_ROUTED` transfer it receives. Use `filter[direction]` to separate the two. A newly submitted transfer appears in the list shortly after `POST /v1/transfers` returns; the list is a read model that is updated asynchronously. **Filters.** All filters are optional, exact-match (case-sensitive) and combined with AND. Query parameters that are not listed here are ignored, not rejected. **Order and paging.** Results are not sorted by time. The order is stable, so paging with `links.next` returns each transfer once, but it is otherwise arbitrary; sort by `created_at` on your side if you need chronological order. Only forward paging is supported (`links.prev` is never returned). parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/includeParam' - $ref: '#/components/parameters/fieldsParam' - name: filter[status] in: query required: false schema: $ref: '#/components/schemas/OperationState' description: | Return only transfers whose `state` equals this value (for example `SUCCEEDED`). Case-sensitive; a value that is not a valid state returns an empty list. - name: filter[direction] in: query required: false schema: type: string enum: - INBOUND - OUTBOUND description: | Direction relative to the calling bank. `OUTBOUND`: transfers the calling bank sent. `INBOUND`: `INTERBANK` and `BANK_ROUTED` transfers the calling bank receives. Any other value returns `400` (`INVALID_FIELD_FORMAT`, detail `must be INBOUND or OUTBOUND`). - name: filter[transfer_kind] in: query required: false schema: type: string enum: - INTERBANK - BANK_ROUTED description: Return only transfers of this kind. Any other value returns `400`. - name: filter[asset_id] in: query required: false schema: type: string description: | Return only transfers whose `amount.asset_id` equals this value, that is the `asset_id` exactly as submitted (for example `usd`). example: usd - name: filter[source_external_account_id] in: query required: false schema: type: string description: Return only transfers whose `source.external_account_id` equals this value. example: CBS-ACC-2026-000123 - name: filter[destination_external_account_id] in: query required: false schema: type: string description: Return only transfers whose `destination.external_account_id` equals this value. example: CBS-ACC-2026-000987 - name: filter[client_reference] in: query required: false schema: type: string description: | Return only transfers whose `client_reference` equals this value. `client_reference` is currently populated only for transfers created from ISO 20022 messages, where it holds the UETR; transfers created with `POST /v1/transfers` never match. example: 8a562c67-ca16-48ba-b074-65581be6f001 responses: '200': description: | One page of transfers. `data` is empty when nothing matches. `links.next` is present only when another page exists. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/Transfer' links: $ref: '#/components/schemas/CursorLinks' examples: outboundPage: summary: First page of outbound transfers, with a next page value: data: - id: 6f1c2a4e-9b3d-5e7f-8a1b-2c3d4e5f6a7b type: transfers attributes: transfer_kind: INTERBANK state: SUCCEEDED amount: asset_id: usd value: '150000' scale: 2 source: bank_id: 11111111-1111-7111-8111-111111111111 bank_display_name: Bank Alpha external_account_id: CBS-ACC-2026-000123 kind: TREASURY destination: bank_id: 22222222-2222-7222-8222-222222222222 bank_display_name: Bank Beta external_account_id: CBS-ACC-2026-000987 kind: CUSTOMER client_reference: null uetr: null created_at: '2026-09-26T14:03:12.481Z' updated_at: '2026-09-26T14:04:02.915Z' transfer_plan: selector_version: realtime-liquidity-v1 requested_amount: 150000 plan_hash: sha256:4f9a1c2e7b3d8a6f0e5c1b9d2a7f3e8c6b0d4a1f9e2c7b5a3d8f6e0c1b4a9d7e receiver_issued_count: 1 other_foreign_count: 0 own_issued_count: 0 own_issued_fallback_used: false legs: - issuer_relation: RECEIVER_ISSUED holder_bank_id: 11111111-1111-7111-8111-111111111111 issuer_bank_id: 22222222-2222-7222-8222-222222222222 settlement_asset_id: usd amount: 150000 relationships: operation: data: id: 0199862c-5f41-7a3e-9b2d-4c8e1f6a7b90 type: operations links: related: /v1/operations/0199862c-5f41-7a3e-9b2d-4c8e1f6a7b90 links: self: /v1/transfers/6f1c2a4e-9b3d-5e7f-8a1b-2c3d4e5f6a7b - id: 3b8e7d2c-1f4a-5c6d-9e0f-7a1b2c3d4e5f type: transfers attributes: transfer_kind: BANK_ROUTED state: PENDING_REVIEW amount: asset_id: usd value: '2500000' scale: 2 source: bank_id: 11111111-1111-7111-8111-111111111111 bank_display_name: Bank Alpha destination: bank_id: 22222222-2222-7222-8222-222222222222 bank_display_name: Bank Beta client_reference: null uetr: null created_at: '2026-09-26T14:10:40.002Z' updated_at: '2026-09-26T14:10:43.618Z' transfer_plan: selector_version: realtime-liquidity-v1 requested_amount: 2500000 plan_hash: sha256:0c7e3a9f1b5d2e8c4a6f0b3d9e1c7a5f2b8d4e0a6c3f9b1e7d5a2c8f4b0e6a3d receiver_issued_count: 0 other_foreign_count: 0 own_issued_count: 1 own_issued_fallback_used: true legs: - issuer_relation: OWN_ISSUED holder_bank_id: 11111111-1111-7111-8111-111111111111 issuer_bank_id: 11111111-1111-7111-8111-111111111111 settlement_asset_id: usd amount: 2500000 relationships: operation: data: id: 0199862e-02b7-7f18-a6c4-8b3d9e1f2a34 type: operations links: related: /v1/operations/0199862e-02b7-7f18-a6c4-8b3d9e1f2a34 links: self: /v1/transfers/3b8e7d2c-1f4a-5c6d-9e0f-7a1b2c3d4e5f links: self: /v1/transfers?filter[direction]=OUTBOUND&page[size]=2 next: /v1/transfers?filter%5Bdirection%5D=OUTBOUND&page%5Bsize%5D=2&page%5Bcursor%5D=3b8e7d2c-1f4a-5c6d-9e0f-7a1b2c3d4e5f emptyPage: summary: No matching transfers value: data: [] links: self: /v1/transfers?filter[status]=FAILED '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' post: summary: Create a transfer operationId: createTransfer tags: - Transfers x-required-scopes: - connector:transfers:create description: | Submit a transfer of existing value for asynchronous execution. Requires scope `connector:transfers:create`. The sending bank is always the calling bank and cannot be overridden. This endpoint moves existing value only; it does not issue or redeem assets. Redemptions are created by redemption policies (see `GET /v1/redemption-policies`). ## Transfer kinds `data.attributes.transfer_kind` selects the request shape: | `transfer_kind` | Moves value | You send | Not allowed | |---|---|---|---| | `INTERBANK` | To a beneficiary at another bank | `source` (`external_account_id` and/or `originator`), `destination` (with `destination.bank_id`, plus `external_account_id` and/or `beneficiary`) | `receiving_bank_id` | | `BANK_ROUTED` | To another bank, by bank and asset only | `receiving_bank_id` | `source`, `destination` | For `INTERBANK` and `BANK_ROUTED` the platform selects which issuers' tokens settle the transfer: tokens issued by the receiving bank first, then tokens issued by other banks, then tokens issued by the sending bank. For `BANK_ROUTED` it also selects the source and destination holding accounts. The result is reported as `transfer_plan` on the transfer. To check liquidity and limits between your bank and the receiving bank before submitting an `INTERBANK` or `BANK_ROUTED` transfer, use `POST /v1/transfers/preflight`; it moves no value. Preflight does not validate accounts or beneficiary details, so an `INTERBANK` transfer that preflights as `would_succeed: true` can still end `FAILED` if an `external_account_id` is not registered. ## Account identifiers `external_account_id` is the core banking identifier the owning bank registered with `POST /v1/accounts` for the same `asset_id`. It must be 1 to 34 characters and must satisfy the ISO 20022 `Max34Text` type. An account that is not registered does not fail the request synchronously; the transfer ends `FAILED` (see below). Each party of an `INTERBANK` transfer is identified in one of two ways, or both: | Party | Registered account | Party details | |---|---|---| | Sender | `source.external_account_id`: must be an active registration at your bank | `source.originator`: passed to the receiving bank for screening | | Beneficiary | `destination.external_account_id`: must be an active registration at the receiving bank | `destination.beneficiary`: passed to the receiving bank to resolve and screen | A party with neither is rejected with `422 VALIDATION_ERROR`. An unregistered `external_account_id` ends the transfer `FAILED`, whether or not details are also sent; details are never checked against registrations. Either way the value moves between the two banks on the ledger in the same way: the parties only tell the receiving bank who sent the payment and whom to credit. `originator` and `beneficiary` share one shape: the party's `name` and `account` (an `IBAN`, a US routing and account number, or `OTHER` for a plain account number), and optionally a `bic` and a `postal_address`. The platform checks the format only (IBAN check digits, 9-digit routing number, BIC format, field lengths), not that the party exists. The receiving bank reads both from `GET /v1/transfers/{transfer_id}` (including while the transfer is `AWAITING_SCREENING`) and from the `transfer.received` webhook, resolves the beneficiary, and screens both parties if it screens transfers. `alias` and `expected_holder_name` are accepted but ignored; use `originator` and `beneficiary` instead. `client_reference` (1 to 256 characters) and `metadata` (a JSON object of at most 4096 bytes when serialised) are stored and returned on the transfer to the sending and the receiving bank. Use them for information without a dedicated field, such as remittance details. `bank_id` on `source` is optional and, if sent, must be the calling bank. `destination.bank_id` is required for `INTERBANK` and must be a different bank. ## Amounts `amount.value` is a positive integer string in the asset's smallest unit (minor units): digits only, no decimal point, exponent or thousands separator, and not zero. For an asset with 2 decimals, `"150000"` is 1,500.00. `amount.scale` is required by the schema but is not used or validated: `value` is always interpreted in the asset's smallest unit, and the transfer reports `amount.scale` from the asset definition. Send the asset's number of decimals as `scale`. ## Idempotency `Idempotency-Key` is required. Keys are scoped to the calling principal and this endpoint, and are kept for 72 hours. - Same key and same body: returns the original `202` response again; no second transfer is created. - Same key and a different body (including a different `transfer_kind`): `409 IDEMPOTENCY_CONFLICT`. - Same key while the first request is still being processed: `409 IDEMPOTENCY_PENDING`. - Validation errors raised by the Lyriq Connector itself (the `400`, `403` and `422` errors in the table below that carry a `code`) do not consume the key. Errors returned after the key was claimed (the `400` errors without a `code`, from the platform stage) leave the key locked for up to 5 minutes, during which a retry with the same key returns `409 IDEMPOTENCY_PENDING`. Use a new key for a corrected request. ## Response and lifecycle On success the response is `202 Accepted` with an operation document: - `data.id` is the `operation_id`; poll `GET /v1/operations/{operation_id}` or subscribe to webhooks for the outcome. - `data.relationships.resource` identifies the transfer; its `id` is the `transfer_id` (a different UUID from the `operation_id`) for `GET /v1/transfers/{transfer_id}`. The transfer becomes readable shortly after the `202`; until then that endpoint returns `404`. The transfer's `state` is always the same as its operation's `state`: | State | Meaning | Next | |---|---|---| | `ACCEPTED` | Request stored. The platform resolves the accounts, selects liquidity and reserves funds and limits. | `AWAITING_SCREENING` or `PROCESSING`; `FAILED` | | `AWAITING_SCREENING` | Only when beneficiary screening is enabled on the network. The receiving bank has been notified (webhook `beneficiary.screening.requested`) and must decide with `POST /v1/transfers/{transfer_id}/screening-decisions`. Nothing has been written to the ledger. | `PROCESSING`, `REJECTED`, `EXPIRED`, `FAILED` | | `PROCESSING` | The transfer is being initiated on the ledger. | `PENDING_REVIEW`; `FAILED` | | `PENDING_REVIEW` | Initiated on the ledger and waiting for a checker at the sending bank (maker/checker). | `PENDING_COMMITS` | | `PENDING_COMMITS` | The checker decided; the commit (on approval) or the cancellation (on rejection) is being written to the ledger. | `SUCCEEDED`, `REJECTED`; `FAILED` | | `SUCCEEDED` | Terminal. The transfer is committed on the ledger. | | | `REJECTED` | Terminal. Declined by the receiving bank during screening or by the sending bank's checker. | | | `EXPIRED` | Terminal. The screening window closed without a decision. Nothing was written to the ledger. | | | `FAILED` | Terminal. See `result_code` and `result_message` on the operation. | | **Maker/checker review.** Every transfer that is initiated on the ledger enters `PENDING_REVIEW`. The platform creates one review task for the sending bank requiring one approval from a principal with scope `connector:reviews:decide` other than the principal that submitted the transfer (self-approval is refused). The submitter sees the transfer and operation in `PENDING_REVIEW`; the review task is listed on the operation and in `GET /v1/reviews`. A checker decides with `POST /v1/reviews/{review_id}/decisions`: approval leads to `SUCCEEDED`, rejection leads to `REJECTED`. ## Synchronous errors and FAILED outcomes Errors returned synchronously (no transfer is created): | Status | `code` | When | |---|---|---| | `400` | `INVALID_FIELD_FORMAT` | `amount.value` is not a non-negative integer, or is zero (pointer `/data/attributes/amount/value`); `external_account_id` is not valid `Max34Text`. | | `400` | (none) | Malformed JSON; missing `Idempotency-Key`; rejected by the platform stage, with the reason in `title`: a terminated sending or receiving bank. | | `403` | `UNAUTHORIZED_SCOPE` | The token lacks `connector:transfers:create`. | | `403` | `BANK_SCOPE_MISMATCH` | A `bank_id` that must be the calling bank is another bank or not a UUID; `destination.bank_id` (`INTERBANK`) or `receiving_bank_id` (`BANK_ROUTED`) is the calling bank; `destination.bank_id` is not a UUID. | | `409` | `IDEMPOTENCY_CONFLICT`, `IDEMPOTENCY_PENDING` | See Idempotency. | | `422` | (none) | The body does not match the schema: unknown or misplaced field, missing required field, wrong type, unknown `transfer_kind`, wrong `data.type` (title `Invalid request body`). | | `422` | `VALIDATION_ERROR` | `external_account_id` missing or blank, `destination.bank_id` missing (`INTERBANK`), an `INTERBANK` source with neither `external_account_id` nor `originator` or destination with neither `external_account_id` nor `beneficiary`, `receiving_bank_id` not a UUID (detail `transfer party fields are invalid for this transfer_kind`); a `transfer_kind` that is not currently accepted (pointer `/data/attributes/transfer_kind`); a malformed `source.originator`, `destination.beneficiary`, `client_reference` or `metadata` (pointer to the field). | | `503` | `OUTBOUND_HALTED`, `READ_ONLY`, `OPERATIONAL_STATE_UNKNOWN` | The network is not accepting new transfers. | Liquidity, limits, eligibility and account problems are not checked synchronously. They are reported by the operation ending `FAILED` (or `REJECTED`/`EXPIRED`), with these `result_code` values: | `result_code` | Final state | Meaning | |---|---|---| | `INSUFFICIENT_ELIGIBLE_LIQUIDITY` | `FAILED` | The sending bank does not hold enough eligible liquidity. | | `LIMIT_EXCEEDED` | `FAILED` | The sender's mint limit (`result_message` `mint limit exceeded`) or the receiving bank's exposure limit (`exposure limit exceeded`) would be exceeded. | | `ASSET_NOT_ELIGIBLE` | `FAILED` | No eligible liquidity position for this asset (`no eligible liquidity position for this asset`). | | `BANK_NOT_ELIGIBLE` | `FAILED` | A participant bank is not eligible for the transfer. | | `VALIDATION_ERROR` | `FAILED` | The amount is not valid for liquidity selection. | | `SCREENING_REJECTED` | `REJECTED` | The receiving bank rejected the transfer during screening. | | `SCREENING_EXPIRED` | `EXPIRED` | The screening window closed without a decision. | | `SCREENING_UNDELIVERABLE` | `FAILED` | The screening notification could not be delivered to the receiving bank. | | `SCREENING_NOT_STARTED` | `FAILED` | Delivery of the screening notification was never confirmed before the screening ceiling. | | `CHECKER_REJECTED` | `REJECTED` | The sending bank's checker rejected the transfer. | Other failures (for example an unregistered account, with `result_message` `no active mapping found for the destination account`, or a ledger failure with `result_message` `transfer ledger submission failed`) end `FAILED` with a generic `result_code`; rely on `result_message` for the reason. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/CreateTransferRequest' examples: interbank: summary: INTERBANK, Bank Alpha treasury account to a Bank Beta customer account value: data: type: transfers attributes: transfer_kind: INTERBANK asset_id: usd amount: value: '150000' scale: 2 source: external_account_id: CBS-ACC-2026-000123 destination: bank_id: 22222222-2222-7222-8222-222222222222 external_account_id: CBS-ACC-2026-000987 interbankBeneficiary: summary: INTERBANK with sender and beneficiary identified by details, not registered accounts value: data: type: transfers attributes: transfer_kind: INTERBANK asset_id: usd amount: value: '150000' scale: 2 source: originator: name: ACME TREASURY LLC account: scheme: OTHER value: '0012345678' postal_address: town_name: Boston country: US destination: bank_id: 22222222-2222-7222-8222-222222222222 beneficiary: name: JOHN DOE account: scheme: IBAN value: DE89370400440532013000 bic: BETAUS33 postal_address: street_name: Main Street building_number: '1' post_code: '10001' town_name: New York country_sub_division: NY country: US client_reference: PAYMENT-7788 metadata: remittance_information: Invoice 2026-0915 bankRouted: summary: BANK_ROUTED, 25,000.00 USD to Bank Beta; platform selects accounts value: data: type: transfers attributes: transfer_kind: BANK_ROUTED receiving_bank_id: 22222222-2222-7222-8222-222222222222 asset_id: usd amount: value: '2500000' scale: 2 responses: '202': description: | Transfer accepted for asynchronous execution. `data` is the operation that tracks it; `data.relationships.resource` identifies the transfer. The response is the same for every `transfer_kind`. A replay with the same `Idempotency-Key` and body returns this document again. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Transfer accepted (any transfer kind) value: jsonapi: version: '1.1' data: id: 0199862c-5f41-7a3e-9b2d-4c8e1f6a7b90 type: operations attributes: resource_family: transfers state: ACCEPTED relationships: resource: data: type: transfers id: 6f1c2a4e-9b3d-5e7f-8a1b-2c3d4e5f6a7b links: related: /v1/transfers/6f1c2a4e-9b3d-5e7f-8a1b-2c3d4e5f6a7b links: self: /v1/operations/0199862c-5f41-7a3e-9b2d-4c8e1f6a7b90 '400': description: | Malformed request, a field that fails its format rule, a missing `Idempotency-Key`, or a rejection by the platform stage (reason in `title`, no `code`). See the error table in the operation description. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: zeroAmount: summary: Amount is zero value: jsonapi: version: '1.1' errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: amount must be greater than zero source: pointer: /data/attributes/amount/value decimalAmount: summary: Amount sent with a decimal point value: jsonapi: version: '1.1' errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: /data/attributes/amount/value must be a valid non-negative integer source: pointer: /data/attributes/amount/value '401': $ref: '#/components/responses/Unauthorized' '403': description: | The token lacks `connector:transfers:create` (`UNAUTHORIZED_SCOPE`), or a bank identifier in the body conflicts with the calling bank (`BANK_SCOPE_MISMATCH`). content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: sameBankDestination: summary: INTERBANK destination.bank_id is the calling bank value: jsonapi: version: '1.1' errors: - status: '403' title: Bank scope mismatch code: BANK_SCOPE_MISMATCH '409': description: | `Idempotency-Key` reuse: `IDEMPOTENCY_CONFLICT` (different body) or `IDEMPOTENCY_PENDING` (the first request with this key has not completed). content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: idempotencyConflict: summary: Same key, different body value: jsonapi: version: '1.1' errors: - status: '409' title: Idempotency conflict code: IDEMPOTENCY_CONFLICT '422': description: | The body parsed but is not valid for its `transfer_kind`: either it does not match the schema (title `Invalid request body`, no `code`), or a cross-field rule failed (`VALIDATION_ERROR`). content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: missingAccount: summary: INTERBANK destination with neither external_account_id nor beneficiary value: jsonapi: version: '1.1' errors: - status: '422' title: Validation error code: VALIDATION_ERROR detail: transfer party fields are invalid for this transfer_kind invalidIban: summary: Beneficiary IBAN with wrong check digits value: jsonapi: version: '1.1' errors: - status: '422' title: Validation error code: VALIDATION_ERROR detail: IBAN check digits are invalid source: pointer: /data/attributes/destination/beneficiary/account/value '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/transfers/{transfer_id}: parameters: - name: transfer_id in: path required: true schema: type: string description: | Transfer identifier (UUID), as returned in `data.relationships.resource.data.id` of the `202` response to `POST /v1/transfers`. This is not the `operation_id`. A value that is not a UUID is rejected with `400`. example: 6f1c2a4e-9b3d-5e7f-8a1b-2c3d4e5f6a7b get: summary: Get a transfer operationId: getTransfer tags: - Transfers x-required-scopes: - connector:transfers:read description: | Fetch one transfer. Requires scope `connector:transfers:read`. The sending bank can read any of its transfers; the receiving bank can read `INTERBANK` and `BANK_ROUTED` transfers it receives. The transfer's `state` mirrors its operation (`relationships.operation`); fetch the operation for `result_code` and `result_message` once the transfer is terminal, and for the review tasks of a transfer in `PENDING_REVIEW`. Returns `404` with title `transfer not found` when the transfer does not exist, is not visible to the calling bank, or has not been recorded yet: a transfer becomes readable shortly after `POST /v1/transfers` returns `202`, so retry a `404` for a just-created transfer after a short delay. responses: '200': description: The transfer. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/Transfer' examples: interbankSucceeded: summary: INTERBANK transfer that succeeded, as seen by the sending bank value: data: id: 6f1c2a4e-9b3d-5e7f-8a1b-2c3d4e5f6a7b type: transfers attributes: transfer_kind: INTERBANK state: SUCCEEDED amount: asset_id: usd value: '150000' scale: 2 source: bank_id: 11111111-1111-7111-8111-111111111111 bank_display_name: Bank Alpha external_account_id: CBS-ACC-2026-000123 kind: TREASURY destination: bank_id: 22222222-2222-7222-8222-222222222222 bank_display_name: Bank Beta external_account_id: CBS-ACC-2026-000987 kind: CUSTOMER client_reference: null uetr: null created_at: '2026-09-26T14:03:12.481Z' updated_at: '2026-09-26T14:04:02.915Z' transfer_plan: selector_version: realtime-liquidity-v1 requested_amount: 150000 plan_hash: sha256:4f9a1c2e7b3d8a6f0e5c1b9d2a7f3e8c6b0d4a1f9e2c7b5a3d8f6e0c1b4a9d7e receiver_issued_count: 1 other_foreign_count: 0 own_issued_count: 0 own_issued_fallback_used: false legs: - issuer_relation: RECEIVER_ISSUED holder_bank_id: 11111111-1111-7111-8111-111111111111 issuer_bank_id: 22222222-2222-7222-8222-222222222222 settlement_asset_id: usd amount: 150000 relationships: operation: data: id: 0199862c-5f41-7a3e-9b2d-4c8e1f6a7b90 type: operations links: related: /v1/operations/0199862c-5f41-7a3e-9b2d-4c8e1f6a7b90 links: self: /v1/transfers/6f1c2a4e-9b3d-5e7f-8a1b-2c3d4e5f6a7b bankRoutedPendingReview: summary: BANK_ROUTED transfer waiting for the sending bank's checker value: data: id: 3b8e7d2c-1f4a-5c6d-9e0f-7a1b2c3d4e5f type: transfers attributes: transfer_kind: BANK_ROUTED state: PENDING_REVIEW amount: asset_id: usd value: '2500000' scale: 2 source: bank_id: 11111111-1111-7111-8111-111111111111 bank_display_name: Bank Alpha destination: bank_id: 22222222-2222-7222-8222-222222222222 bank_display_name: Bank Beta client_reference: null uetr: null created_at: '2026-09-26T14:10:40.002Z' updated_at: '2026-09-26T14:10:43.618Z' transfer_plan: selector_version: realtime-liquidity-v1 requested_amount: 2500000 plan_hash: sha256:0c7e3a9f1b5d2e8c4a6f0b3d9e1c7a5f2b8d4e0a6c3f9b1e7d5a2c8f4b0e6a3d receiver_issued_count: 0 other_foreign_count: 0 own_issued_count: 1 own_issued_fallback_used: true legs: - issuer_relation: OWN_ISSUED holder_bank_id: 11111111-1111-7111-8111-111111111111 issuer_bank_id: 11111111-1111-7111-8111-111111111111 settlement_asset_id: usd amount: 2500000 relationships: operation: data: id: 0199862e-02b7-7f18-a6c4-8b3d9e1f2a34 type: operations links: related: /v1/operations/0199862e-02b7-7f18-a6c4-8b3d9e1f2a34 links: self: /v1/transfers/3b8e7d2c-1f4a-5c6d-9e0f-7a1b2c3d4e5f interbankAwaitingScreening: summary: INTERBANK transfer awaiting screening, as seen by the receiving bank value: data: id: 9d4c3b2a-8e7f-5a6b-8c9d-0e1f2a3b4c5d type: transfers attributes: transfer_kind: INTERBANK state: AWAITING_SCREENING amount: asset_id: usd value: '98000' scale: 2 source: bank_id: 11111111-1111-7111-8111-111111111111 bank_display_name: Bank Alpha external_account_id: CBS-ACC-2026-000123 kind: TREASURY destination: bank_id: 22222222-2222-7222-8222-222222222222 bank_display_name: Bank Beta external_account_id: CBS-ACC-2026-000987 kind: CUSTOMER client_reference: null uetr: null created_at: '2026-09-26T15:21:07.330Z' updated_at: '2026-09-26T15:21:07.912Z' transfer_plan: selector_version: realtime-liquidity-v1 requested_amount: 98000 plan_hash: sha256:7e2b9d4f0a6c1e8b3d5f9a2c7e0b4d6f1a8c3e5b9d2f7a0c4e6b1d8f3a5c9e2b receiver_issued_count: 1 other_foreign_count: 0 own_issued_count: 0 own_issued_fallback_used: false legs: - issuer_relation: RECEIVER_ISSUED holder_bank_id: 11111111-1111-7111-8111-111111111111 issuer_bank_id: 22222222-2222-7222-8222-222222222222 settlement_asset_id: usd amount: 98000 relationships: operation: data: id: 0199862d-1a02-7c4b-8e3f-5d9a2b7c6e11 type: operations links: related: /v1/operations/0199862d-1a02-7c4b-8e3f-5d9a2b7c6e11 links: self: /v1/transfers/9d4c3b2a-8e7f-5a6b-8c9d-0e1f2a3b4c5d '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: The transfer does not exist, is not visible to the calling bank, or has not been recorded yet. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: notFound: summary: Transfer not found value: jsonapi: version: '1.1' errors: - status: '404' title: transfer not found '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/transfers/preflight: post: summary: Preflight a transfer operationId: preflightTransfer tags: - Transfers x-required-scopes: - connector:transfers:create description: | Check whether the calling bank's liquidity and limits would cover a transfer of the given asset and amount to the receiving bank if it were submitted now, without creating a transfer, reserving funds or moving value. `INTERBANK` and `BANK_ROUTED` are supported. Requires scope `connector:transfers:create`, and the calling principal must hold the `maker` role for the bank (otherwise `403 UNAUTHORIZED_ROLE`). The answer depends only on the two banks, the asset and the amount. The receiving bank is the only counterparty input: | `transfer_kind` | You send | Receiving bank | |---|---|---| | `INTERBANK` | `asset_id`, `amount`, `destination.bank_id`; `source` is optional | `destination.bank_id` | | `BANK_ROUTED` | `receiving_bank_id`, `asset_id`, `amount` (the same body as `POST /v1/transfers`) | `receiving_bank_id` | Preflight does not validate accounts. For `INTERBANK`, the account fields (`external_account_id`, `alias`, `expected_holder_name`) may be sent, so you can reuse the body you will submit, but they are ignored and do not change the result. An unregistered account is only detected on submit, where the transfer ends `FAILED`. For the same banks, asset and amount, both kinds return the same result. The check covers: - **Sender capacity:** the sending bank's eligible liquidity, including headroom to issue its own tokens under its mint limit. - **Receiver acceptance:** the receiving bank's exposure limit toward the sender's tokens. A blocking constraint is reported as `200` with `would_succeed: false`, not as an error. Problems that would make the request itself invalid (ineligible bank or asset, malformed input) are returned as errors: | Status | `code` | When | |---|---|---| | `400` | (none) | `amount.value` is not a positive integer, a bank is terminated, or there is no eligible liquidity route between the two banks for the asset (title `no eligible liquidity positions found for the source bank, destination holder, and settlement asset`). The reason is in `title`. | | `403` | `UNAUTHORIZED_SCOPE`, `UNAUTHORIZED_ROLE`, `BANK_SCOPE_MISMATCH` | Missing scope or `maker` role; `source.bank_id` is not the calling bank; the receiving bank is the calling bank. | | `422` | (none) | Body does not match the schema, including an unknown `transfer_kind`, missing `receiving_bank_id`, or account fields on a `BANK_ROUTED` request (title `Invalid request body`). | | `422` | `VALIDATION_ERROR` | `destination.bank_id` is missing on an `INTERBANK` request, or the supplied receiving bank id (`destination.bank_id` or `receiving_bank_id`) is not a UUID. | | `422` | `BANK_NOT_ELIGIBLE`, `ASSET_NOT_ELIGIBLE` | A bank or the asset is not eligible for the transfer. | This endpoint has no side effects and takes no `Idempotency-Key`; repeating a call is always safe. It remains available while the network refuses new transfers (`OUTBOUND_HALTED` or `READ_ONLY`). The result is a point-in-time estimate; see `TransferPreflight` for its limits. requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/TransferPreflightRequest' examples: bankRouted: summary: Could Bank Alpha send 100.00 USD to Bank Beta? value: data: type: transfer-preflight attributes: transfer_kind: BANK_ROUTED receiving_bank_id: 22222222-2222-7222-8222-222222222222 asset_id: usd amount: value: '10000' scale: 2 interbank: summary: Could Bank Alpha pay 1,500.00 USD to a beneficiary at Bank Beta? value: data: type: transfer-preflight attributes: transfer_kind: INTERBANK asset_id: usd amount: value: '150000' scale: 2 destination: bank_id: 22222222-2222-7222-8222-222222222222 responses: '200': description: | Preflight result. `blocking_constraint`, `remediation` and `available_headroom` are always present and are `null` when `would_succeed` is `true`. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: $ref: '#/components/schemas/TransferPreflight' examples: wouldSucceed: summary: The transfer would be accepted value: data: type: transfer-preflight-result attributes: would_succeed: true blocking_constraint: null remediation: null available_headroom: null receiverExposureLimit: summary: Blocked by the receiving bank's exposure limit (headroom not disclosed) value: data: type: transfer-preflight-result attributes: would_succeed: false blocking_constraint: RECEIVER_EXPOSURE_LIMIT_EXCEEDED remediation: REQUEST_COUNTERPARTY_EXPOSURE_LIMIT_INCREASE available_headroom: null mintLimit: summary: Blocked by the sender's mint limit, with remaining issuance headroom value: data: type: transfer-preflight-result attributes: would_succeed: false blocking_constraint: MINT_LIMIT_EXCEEDED remediation: REQUEST_ISSUANCE_LIMIT_INCREASE available_headroom: '40000' insufficientLiquidity: summary: Blocked by insufficient eligible liquidity value: data: type: transfer-preflight-result attributes: would_succeed: false blocking_constraint: INSUFFICIENT_LIQUIDITY remediation: ADJUST_AMOUNT_OR_RETRY_LATER available_headroom: null '400': description: | Invalid input, a terminated bank, or no eligible liquidity route between the two banks. The reason is in `title`; these errors carry no `code`. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: invalidAmount: summary: amount.value is zero value: jsonapi: version: '1.1' errors: - status: '400' title: amount must be greater than zero noLiquidityRoute: summary: The banks have no eligible liquidity route for the asset value: jsonapi: version: '1.1' errors: - status: '400' title: no eligible liquidity positions found for the source bank, destination holder, and settlement asset '401': $ref: '#/components/responses/Unauthorized' '403': description: | Missing scope (`UNAUTHORIZED_SCOPE`), missing `maker` role (`UNAUTHORIZED_ROLE`), or a bank identifier that conflicts with the calling bank (`BANK_SCOPE_MISMATCH`). content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: notMaker: summary: Calling principal does not hold the maker role value: jsonapi: version: '1.1' errors: - status: '403' title: Unauthorized role code: UNAUTHORIZED_ROLE '422': description: | Body does not match the schema, including a missing `receiving_bank_id` on a `BANK_ROUTED` request (title `Invalid request body`); `destination.bank_id` is missing on an `INTERBANK` request or the supplied receiving bank id is malformed (`VALIDATION_ERROR`); or a bank or asset is ineligible (`BANK_NOT_ELIGIBLE`, `ASSET_NOT_ELIGIBLE`). content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: assetNotEligible: summary: No eligible liquidity position for the asset value: jsonapi: version: '1.1' errors: - status: '422' title: no eligible liquidity position for this asset code: ASSET_NOT_ELIGIBLE '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/transfers/{transfer_id}/screening-decisions: parameters: - name: transfer_id in: path required: true schema: type: string description: | Identifier (UUID) of the transfer being screened, as delivered in the `beneficiary.screening.requested` webhook. A value that is not a UUID is rejected with `400`. example: 9d4c3b2a-8e7f-5a6b-8c9d-0e1f2a3b4c5d post: summary: Submit a beneficiary screening decision operationId: submitScreeningDecision tags: - Transfers x-required-scopes: - connector:transfers:screening-decide description: | Approve, reject or extend a transfer that is waiting for beneficiary screening. Requires scope `connector:transfers:screening-decide`. **Who calls it, and when.** Beneficiary screening applies to `INTERBANK` and `BANK_ROUTED` transfers when it is enabled on the network. After the sending bank's transfer passes liquidity selection, the transfer enters `AWAITING_SCREENING` and the receiving (beneficiary) bank is sent a `beneficiary.screening.requested` webhook. Only that receiving bank can decide; for any other caller the transfer is reported as not found (`404`). Nothing is written to the ledger until the transfer is approved. **Decisions.** - `APPROVE`: the transfer moves to `PROCESSING` and continues to the ledger (and then to the sending bank's checker review). - `REJECT`: the transfer ends `REJECTED`; its operation gets `result_code` `SCREENING_REJECTED`. Nothing is written to the ledger. - `EXTEND`: the transfer stays `AWAITING_SCREENING` and its deadline moves out by `extend_ttl_seconds` (default: one screening window). `EXTEND` is refused until the webhook delivery has been confirmed. **Screening window.** The window (5 seconds by default; the length is network configuration) starts when the webhook is delivered to the receiving bank, not when the transfer was accepted. A decision is on time if it reaches the Lyriq Connector before the deadline; the time the platform then takes to apply it does not count. If no decision arrives in time the transfer ends `EXPIRED` (`result_code` `SCREENING_EXPIRED`). Extensions can never push the deadline beyond the screening ceiling (2 hours by default) measured from the moment the transfer entered screening. If the webhook cannot be delivered the transfer ends `FAILED` (`SCREENING_UNDELIVERABLE`, or `SCREENING_NOT_STARTED` when delivery is never confirmed). **Response.** `202 Accepted` returns a new operation for the decision itself (`operation_type` `SCREENING_DECISION_CREATE`, `resource_family` `transfers`); it has no `relationships`. The decision is applied asynchronously: that operation ends `SUCCEEDED` once the transfer has moved, or `FAILED` (for example `result_code` `STATE_CONFLICT`) if it could not be applied. Follow the transfer itself with `GET /v1/transfers/{transfer_id}`. **Idempotency.** `Idempotency-Key` is required. The same key and body return the original `202`; the same key with a different body returns `409 IDEMPOTENCY_CONFLICT`. **Errors.** | Status | `code` | When | |---|---|---| | `400` | (none) | `extend_ttl_seconds` sent with `APPROVE` or `REJECT`, or not positive; missing `Idempotency-Key`; the transfer is no longer `AWAITING_SCREENING` (title `Transfer is no longer awaiting screening, current operation state is `, where a decision after the deadline reports `EXPIRED`); `EXTEND` before the window opened (title `screening window has not opened yet, so it cannot be extended`). | | `403` | `UNAUTHORIZED_SCOPE` | The token lacks `connector:transfers:screening-decide`. | | `404` | (none) | The transfer does not exist, or the calling bank is not its receiving bank (title `resource not found`). | | `409` | `IDEMPOTENCY_CONFLICT`, `IDEMPOTENCY_PENDING` | `Idempotency-Key` reuse. | | `422` | (none) | The body does not match the schema, for example an unknown `decision` (title `Invalid request body`). | | `503` | `OUTBOUND_HALTED`, `READ_ONLY`, `OPERATIONAL_STATE_UNKNOWN` | The network is not fully operational; decisions are refused. | A duplicate or conflicting decision on a transfer that has already left `AWAITING_SCREENING` is rejected and leaves the transfer unchanged. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/CreateScreeningDecisionRequest' examples: approve: summary: Approve the transfer value: data: type: screening-decisions attributes: decision: APPROVE reject: summary: Reject the transfer value: data: type: screening-decisions attributes: decision: REJECT extend: summary: Ask for 30 more seconds value: data: type: screening-decisions attributes: decision: EXTEND extend_ttl_seconds: 30 responses: '202': description: | Decision accepted. `data` is the operation that applies the decision; it has no `relationships` or `links`. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Screening decision accepted value: jsonapi: version: '1.1' data: id: 0199862f-7c55-7d20-b1e8-3f6a9c2d4e87 type: operations attributes: resource_family: transfers state: ACCEPTED '400': description: | Invalid input, or the transfer is no longer awaiting screening. The reason is in `title`; these errors carry no `code`. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: alreadyDecided: summary: Transfer already left AWAITING_SCREENING value: jsonapi: version: '1.1' errors: - status: '400' title: Transfer is no longer awaiting screening, current operation state is PROCESSING tooLate: summary: Decision arrived after the screening deadline value: jsonapi: version: '1.1' errors: - status: '400' title: Transfer is no longer awaiting screening, current operation state is EXPIRED ttlWithoutExtend: summary: extend_ttl_seconds sent with APPROVE value: jsonapi: version: '1.1' errors: - status: '400' title: extend_ttl_seconds is only valid with decision EXTEND '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: The transfer does not exist, or the calling bank is not its receiving bank. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: notFound: summary: Unknown transfer or not the beneficiary bank value: jsonapi: version: '1.1' errors: - status: '404' title: resource not found '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/UnprocessableEntity' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/accounts: get: summary: List accounts operationId: listAccounts tags: - Accounts x-required-scopes: - connector:accounts:read description: | List the accounts your bank has registered. Only accounts owned by the calling bank are returned. An *account* is registered for one root settlement asset (`asset_id`, for example `usd`) and identified by the core banking system (CBS) identifier your bank supplied (`external_account_id`). An account appears here only after its registration operation has completed (see `POST /v1/accounts`). **Required scope:** `connector:accounts:read`. **Filters.** All filters are exact, case-sensitive matches and are combined with AND. A filter value that matches nothing (including a value outside the documented enum) returns an empty `data` array, not an error. **Order.** Results are returned in a stable order defined by the platform's internal account record identifier (descending). Do not rely on any other ordering. **Pagination.** Cursor-based; follow `links.next` until it is absent. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' - name: filter[asset_id] in: query required: false schema: type: string description: | Return only accounts registered for this asset. Matches the account's `asset_id` exactly, so use the root settlement asset identifier (for example `usd`); an issued asset identifier such as `usd.bank-alpha` matches nothing. example: usd - name: filter[kind] in: query required: false schema: type: string enum: - CUSTOMER - TREASURY description: Return only accounts of this kind. example: CUSTOMER - name: filter[status] in: query required: false schema: type: string enum: - ACTIVE description: Return only accounts with this status. `ACTIVE` is the only status. example: ACTIVE responses: '200': description: One page of accounts owned by the calling bank. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/Account' links: $ref: '#/components/schemas/CursorLinks' examples: firstPage: summary: First page with a next page available value: data: - type: accounts id: v1:11111111-1111-7111-8111-111111111111:usd:CBS-ACC-2026-000123 attributes: external_account_id: CBS-ACC-2026-000123 bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd kind: CUSTOMER status: ACTIVE - type: accounts id: v1:11111111-1111-7111-8111-111111111111:usd:TRSY-USD-01 attributes: external_account_id: TRSY-USD-01 bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd kind: TREASURY status: ACTIVE links: self: /v1/accounts?page[size]=2 next: /v1/accounts?page%5Bsize%5D=2&page%5Bcursor%5D=0192a3f0-7c1e-7b2a-9d44-5e6f7a8b9c0d filteredByKind: summary: Filtered to customer accounts, last page value: data: - type: accounts id: v1:11111111-1111-7111-8111-111111111111:usd:CBS-ACC-2026-000123 attributes: external_account_id: CBS-ACC-2026-000123 bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd kind: CUSTOMER status: ACTIVE links: self: /v1/accounts?filter[kind]=CUSTOMER&filter[asset_id]=usd '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' post: summary: Register an account operationId: createAccount tags: - Accounts x-required-scopes: - connector:accounts:create description: | Register one of your bank's core banking system (CBS) accounts with the Lyriq Connector for a root settlement asset, so that it can be used in transfers and position reads. **Required scope:** `connector:accounts:create`. **What happens.** The request is validated and, if accepted, the Lyriq Connector creates an asynchronous *operation* and returns `202 Accepted` with that operation. The operation first waits in `PENDING_REVIEW` for one approval by a user with the approver role in your bank (see Reviews). After approval the platform opens the ledger account and maps it to your `external_account_id`. **When the account is usable.** Poll `GET /v1/operations/{operation_id}` (the URL in `data.links.self`; this response carries no `Location` header) until the state is terminal. Once it is `SUCCEEDED` the account can be read at the URL in `data.relationships.resource.links.related`, which is `/v1/accounts/{external_account_id}?asset_id={asset_id}` with `external_account_id` percent-encoded. Before that, `GET` on that URL returns `404 Not Found`. **Identity.** An account is identified by your bank, `asset_id` and `external_account_id`. The JSON:API `id` of the future account is returned in `data.relationships.resource.data.id` and is derived deterministically as `v1:{bank_id}:{asset_id}:{external_account_id}` with each component percent-encoded (see the `Account` schema for a worked example). **Duplicates and retries.** The `Idempotency-Key` header is required. Repeating the request with the same key and the same body returns the original `202` response without registering again; the same key with a different body returns `409 Conflict` (`IDEMPOTENCY_CONFLICT`), and a retry while the first request is still being processed returns `409 Conflict` (`IDEMPOTENCY_PENDING`). Registering an account that already exists (same `asset_id` and `external_account_id`) under a new key returns `409 Conflict` with title `account already exists`. **Validation.** Errors are returned before anything is created: - `external_account_id` empty: `400`, code `MISSING_FIELD`. - `external_account_id` longer than 34 characters or containing a character not allowed in XML 1.0: `400`, code `INVALID_FIELD_FORMAT`. - `asset_id` empty or whitespace: `400`, code `MISSING_FIELD`. - `display_name` present but blank: `400`. - Unknown attribute, wrong type or missing required attribute in the body: `422 Unprocessable Entity`. - Your bank is not `ACTIVE`: `400` with title `Invalid bank: the owning bank does not exist or is not active.` - `asset_id` is not enabled for your bank: `400` with title `the requested asset is not in bank {bank_id}'s enabled set`. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/CreateAccountRequest' examples: customerAccount: summary: Register a customer account value: data: type: accounts attributes: external_account_id: CBS-ACC-2026-000123 asset_id: usd kind: CUSTOMER treasuryAccount: summary: Register the bank's treasury account value: data: type: accounts attributes: external_account_id: TRSY-USD-01 asset_id: usd kind: TREASURY reservedCharacters: summary: Identifier containing characters that are percent-encoded in URLs and ids value: data: type: accounts attributes: external_account_id: CBS/ACC 2026:01 asset_id: usd kind: CUSTOMER responses: '202': description: | Registration accepted. The body is the operation that tracks it; its `relationships.resource` identifies the account that will exist once the operation succeeds. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: customerAccount: summary: Accepted registration of CBS-ACC-2026-000123 value: jsonapi: version: '1.1' data: type: operations id: 0192a3f1-2b3c-7d4e-8f50-6a7b8c9d0e1f attributes: resource_family: accounts state: ACCEPTED relationships: resource: data: type: accounts id: v1:11111111-1111-7111-8111-111111111111:usd:CBS-ACC-2026-000123 links: related: /v1/accounts/CBS-ACC-2026-000123?asset_id=usd links: self: /v1/operations/0192a3f1-2b3c-7d4e-8f50-6a7b8c9d0e1f reservedCharacters: summary: Accepted registration of "CBS/ACC 2026:01" (encoded id and URL) value: jsonapi: version: '1.1' data: type: operations id: 0192a3f1-4d5e-7f60-8a71-8b9c0d1e2f30 attributes: resource_family: accounts state: ACCEPTED relationships: resource: data: type: accounts id: v1:11111111-1111-7111-8111-111111111111:usd:CBS%2FACC%202026%3A01 links: related: /v1/accounts/CBS%2FACC%202026%3A01?asset_id=usd links: self: /v1/operations/0192a3f1-4d5e-7f60-8a71-8b9c0d1e2f30 '400': description: | The request failed validation, the `Idempotency-Key` header is missing, your bank is not active, or the asset is not enabled for your bank. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: invalidExternalAccountId: summary: external_account_id longer than 34 characters value: jsonapi: version: '1.1' errors: - status: '400' code: INVALID_FIELD_FORMAT title: Field format invalid detail: /data/attributes/external_account_id must satisfy the ISO 20022 Max34Text XSD type source: pointer: /data/attributes/external_account_id missingAssetId: summary: asset_id empty value: jsonapi: version: '1.1' errors: - status: '400' code: MISSING_FIELD title: Required field missing detail: /data/attributes/asset_id is required source: pointer: /data/attributes/asset_id '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': description: | The account is already registered, or the `Idempotency-Key` was already used with a different body or is still being processed. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: accountAlreadyExists: summary: Same asset_id and external_account_id already registered value: jsonapi: version: '1.1' errors: - status: '409' title: account already exists idempotencyConflict: summary: Idempotency-Key reused with a different body value: jsonapi: version: '1.1' errors: - status: '409' code: IDEMPOTENCY_CONFLICT title: Idempotency conflict '422': $ref: '#/components/responses/UnprocessableEntity' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/accounts/{external_account_id}: parameters: - $ref: '#/components/parameters/externalAccountIdParam' get: summary: Get an account operationId: getAccount tags: - Accounts x-required-scopes: - connector:accounts:read description: | Fetch one of your bank's accounts by its `external_account_id` and `asset_id`. Because the same `external_account_id` can be registered for more than one asset, `asset_id` is required to select the account. Returns the account's identity, kind and status only. For amounts use [`GET /v1/accounts/{external_account_id}/balances`](./get-account-balances). **Required scope:** `connector:accounts:read`. Returns `404 Not Found` (title `account not found`) if your bank has no such account, including while its registration operation has not yet succeeded, and for accounts owned by other banks. parameters: - name: asset_id in: query required: true schema: type: string description: | Root settlement asset the account was registered for (exact match), for example `usd`. Omitting it returns `400 Bad Request`. example: usd responses: '200': description: The requested account. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/Account' examples: customerAccount: summary: A registered customer account value: data: type: accounts id: v1:11111111-1111-7111-8111-111111111111:usd:CBS-ACC-2026-000123 attributes: external_account_id: CBS-ACC-2026-000123 bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd kind: CUSTOMER status: ACTIVE '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: No account with this `external_account_id` and `asset_id` exists for your bank. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: accountNotFound: summary: Unknown account, or registration not yet complete value: jsonapi: version: '1.1' errors: - status: '404' title: account not found '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/accounts/{external_account_id}/balances: parameters: - $ref: '#/components/parameters/externalAccountIdParam' get: summary: Get account balances operationId: getAccountBalances tags: - Balances x-required-scopes: - connector:balances:list description: | Get the current balances of one of your bank's accounts: one entry for each asset the `external_account_id` is registered for. There is no `asset_id` parameter; to narrow to one asset use `GET /v1/balances?filter[external_account_id]=...&filter[asset_id]=...`. Each entry reports `total`, `locked`, `reserved` and `available`; see the `Balance` schema for exact definitions (normally `total = available + locked + reserved`). Response-level `meta` gives the ledger cut all entries were read at and whether the read is current (see `PositionReadMetadata`). **Required scope:** `connector:balances:list`. **Unknown accounts.** If your bank has no account with this `external_account_id`, the response is `200 OK` with an empty `data` array and no `meta`, not `404`. parameters: - $ref: '#/components/parameters/fieldsParam' responses: '200': description: Balances of the account, one per registered asset (possibly empty). content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: type: array items: $ref: '#/components/schemas/Balance' meta: $ref: '#/components/schemas/PositionReadMetadata' examples: customerAccount: summary: Customer account with a reservation and a settlement lock value: data: - type: balances id: v1:11111111-1111-7111-8111-111111111111:usd.bank-alpha:CBS-ACC-2026-000123 attributes: external_account_id: CBS-ACC-2026-000123 bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd.bank-alpha root_asset_id: usd issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_asset_code: usd.bank-alpha issuer_relation: OWN_ISSUED total: asset_id: usd value: '1000000' scale: 2 locked: asset_id: usd value: '150000' scale: 2 reserved: asset_id: usd value: '25000' scale: 2 available: asset_id: usd value: '825000' scale: 2 as_of: '2026-09-25T14:03:11.482Z' relationships: account: data: type: accounts id: v1:11111111-1111-7111-8111-111111111111:usd.bank-alpha:CBS-ACC-2026-000123 asset: data: type: assets id: usd.bank-alpha links: related: /v1/assets/usd.bank-alpha meta: cut: block_height: '184502' block_hash: 9f2c4e1a7b3d5f60812a4c6e8b0d2f4618a3c5e7092b4d6f8a1c3e5b7d9f0a2c reservation_revision: '9317' completeness: COMPLETE observed_head: '184502' unknownAccount: summary: No account with this external_account_id value: data: [] '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: | The account is registered but the platform has no ledger position for it yet. Retry later. (An unknown `external_account_id` returns `200` with empty `data`.) content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases: get: summary: List the calling bank's onboarding cases operationId: listBankOnboardingCases x-required-scopes: - connector:onboarding:read tags: - Bank Onboarding description: | Returns the onboarding case of the calling bank. An *onboarding case* is the record the platform operator opens to bring a bank onto the network; the bank's onboarding administrator then completes it through the `/v1/onboarding-cases/{case_id}/...` endpoints. Use this operation to discover the `case_id` you work with. **Required scope:** `connector:onboarding:read`. The bank is taken from the `bank_memberships` claim of your access token; you never pass a `bank_id`. The platform allows at most one onboarding case per bank, so the list contains zero or one entry. **Order:** newest first (by `created_at`, then `id`). The list is not paginated. Each entry includes `readiness` (a checklist of what is still missing before you can submit). The list omits `bank_portal_route_code`; read the single case with `GET /v1/onboarding-cases/{case_id}` to get it. Reading the single case also refreshes the case `state` from its activation operation, so poll the single case, not this list, while the case is being approved and provisioned. **Onboarding administrator workflow (summary).** The case states that allow bank edits are `AWAITING_BANK_ADMIN`, `BANK_CONFIGURING` and `NEEDS_CHANGES` (the case is *editable*). Your first successful edit moves an `AWAITING_BANK_ADMIN` case to `BANK_CONFIGURING`. 1. Read the key catalogue: `GET .../key-requirements`. 2. Provide the three signing keys (`BANK_ADMIN`, `CREATE_INTERBANK_TRANSFER_MAKER`, `APPROVE_INTERBANK_TRANSFER_CHECKER`) and the key-encryption key (`ENCRYPT_TRANSFER_SOURCE_MESSAGE`), either by registering your own (`POST .../signing-targets`, `POST .../encryption-targets`, then `POST .../verify` on each) or by letting the platform generate them in your cloud account (`POST .../key-generation-jobs`). Every key must end in `verification_state` `VERIFIED`. 3. Add staff: `POST .../staff-members`. At least one member must carry the `bank-admin` role label. 4. Configure webhook delivery: `PUT .../webhook-setup`. It must subscribe to `beneficiary.screening.requested` and have a signing secret. 5. Optionally declare machine-to-machine workloads: `PUT .../m2m-clients`. 6. Submit: `POST .../submit-configuration`. The case moves to `READY_FOR_REVIEW` and is no longer editable. After you submit, the platform operator accepts the case, which starts an activation operation (`activation_operation_id`) and moves the case to `AWAITING_APPROVALS`. Once the required approvals are given the case moves to `PROVISIONING` and then `ACTIVE`, when your staff and workloads are provisioned in the platform IAM. A case whose activation is rejected, cancelled or expires moves to `REJECTED`; one whose activation fails moves to `FAILED`. `NEEDS_CHANGES` makes the case editable again. responses: '200': description: | The calling bank's onboarding cases, newest first. An empty `data` array means no case has been opened for your bank. content: application/vnd.api+json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/BankOnboardingCase' examples: configuring: summary: One case, being configured by the bank value: data: - type: bank-onboarding-cases id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 attributes: bank_id: 11111111-1111-7111-8111-111111111111 version: 9 state: BANK_CONFIGURING display_name: Bank Alpha legal_name: Bank Alpha N.A. bic: BALPUS33 environment: sandbox fis_idp_entity_id: https://idp.fis.example/saml2/bank-alpha admin_contact: email: onboarding.admin@bankalpha.example required_distinct_approvals: 2 configuration: identity_provider: single_sign_on_service_url: https://idp.fis.example/saml2/bank-alpha/sso single_logout_service_url: https://idp.fis.example/saml2/bank-alpha/slo signing_certificate: MIIDdzCCAl+gAwIBAgIEbXq1ZTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3du assets: - asset_id: usd.bank-alpha root_asset_id: usd currency: USD scale: 2 display_code: USD.A client_reference: null mint_limits: - asset_id: usd.bank-alpha amount: '100000000000' exposure_limits: [] runtime_accounts: [] m2m_clients: [] workload_identity_provider: null activation_operation_id: null readiness: signing_targets_total: 3 signing_targets_verified: 1 encryption_targets_total: 1 encryption_targets_verified: 0 staff_total: 1 bank_admin_present: true maker_present: false checker_present: false readonly_present: false webhook_ready: false ready_for_review: false created_at: '2026-09-21T09:12:44.518Z' updated_at: '2026-09-24T14:03:10.227Z' none: summary: No onboarding case exists for the calling bank value: data: [] '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' /v1/onboarding-cases/{case_id}: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID), as returned by `GET /v1/onboarding-cases`. schema: type: string format: uuid get: summary: Get one of the calling bank's onboarding cases operationId: getBankOnboardingCase x-required-scopes: - connector:onboarding:read tags: - Bank Onboarding description: | Returns the onboarding case, its saved configuration and its `readiness` checklist. **Required scope:** `connector:onboarding:read`. The bank is taken from the `bank_memberships` claim of your access token. A case that does not exist or belongs to another bank returns `404`. **State refresh.** When the case has an `activation_operation_id`, this call first reads that operation and brings the case `state` up to date (`AWAITING_APPROVALS`, `PROVISIONING`, `ACTIVE`, `REJECTED` or `FAILED`). Poll this endpoint to follow the case after submission. The case becomes `ACTIVE` only after the activation operation succeeds and every identity provisioning job (staff memberships, M2M workloads) has completed; until then it stays `PROVISIONING`. **Readiness.** `readiness.ready_for_review` is `true` when all three signing targets and the encryption target are `VERIFIED`, at least one staff member has the `bank-admin` label, and `readiness.webhook_ready` is `true`. These are exactly the checks `POST .../submit-configuration` enforces. `maker_present`, `checker_present` and `readonly_present` are informational only. This response also includes `bank_portal_route_code`, the code used to build your bank's private Bank Portal login URL. responses: '200': description: Onboarding case, configuration and readiness status. content: application/vnd.api+json: schema: type: object properties: data: $ref: '#/components/schemas/BankOnboardingCase' examples: readyToSubmit: summary: Case with every artifact in place, ready to submit value: data: type: bank-onboarding-cases id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 attributes: bank_id: 11111111-1111-7111-8111-111111111111 version: 18 state: BANK_CONFIGURING display_name: Bank Alpha legal_name: Bank Alpha N.A. bic: BALPUS33 environment: sandbox fis_idp_entity_id: https://idp.fis.example/saml2/bank-alpha admin_contact: email: onboarding.admin@bankalpha.example required_distinct_approvals: 2 configuration: identity_provider: single_sign_on_service_url: https://idp.fis.example/saml2/bank-alpha/sso single_logout_service_url: https://idp.fis.example/saml2/bank-alpha/slo signing_certificate: MIIDdzCCAl+gAwIBAgIEbXq1ZTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3du assets: - asset_id: usd.bank-alpha root_asset_id: usd currency: USD scale: 2 display_code: USD.A client_reference: null mint_limits: - asset_id: usd.bank-alpha amount: '100000000000' exposure_limits: [] runtime_accounts: [] m2m_clients: - display_name: Payments hub business_purpose: Submits interbank transfers from the payments hub keycloak_client_id: bank-m2m-01928f5c-7a10-7e3b-9c21-4d5e6f708192 fis_client_id: BANKALPHA-PAYHUB-01 roles: - maker scopes: - connector:transfers:create - connector:transfers:read - connector:operations:read workload_identity_provider: issuer: https://idp.fis.example/saml2/bank-alpha jwks_url: https://idp.fis.example/workload/bank-alpha/jwks webhook_setup: enabled: true callback_url: https://hooks.bankalpha.example/lyriq/events event_types: - beneficiary.screening.requested - operation.updated - transfer.received delivery_format: jsonapi signing_secret_version: v1 signing_secret_configured: true activation_operation_id: null bank_portal_route_code: 3f9c2a7be1d04c8a9b6e5d2f1a0c7e4b readiness: signing_targets_total: 3 signing_targets_verified: 3 encryption_targets_total: 1 encryption_targets_verified: 1 staff_total: 3 bank_admin_present: true maker_present: true checker_present: true readonly_present: false webhook_ready: true ready_for_review: true created_at: '2026-09-21T09:12:44.518Z' updated_at: '2026-09-25T10:41:02.904Z' awaitingApprovals: summary: Case accepted by the operator, waiting for approvals value: data: type: bank-onboarding-cases id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 attributes: bank_id: 11111111-1111-7111-8111-111111111111 version: 20 state: AWAITING_APPROVALS display_name: Bank Alpha legal_name: Bank Alpha N.A. bic: BALPUS33 environment: sandbox fis_idp_entity_id: https://idp.fis.example/saml2/bank-alpha admin_contact: email: onboarding.admin@bankalpha.example required_distinct_approvals: 2 configuration: identity_provider: single_sign_on_service_url: https://idp.fis.example/saml2/bank-alpha/sso single_logout_service_url: https://idp.fis.example/saml2/bank-alpha/slo signing_certificate: MIIDdzCCAl+gAwIBAgIEbXq1ZTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3du assets: - asset_id: usd.bank-alpha root_asset_id: usd currency: USD scale: 2 display_code: USD.A client_reference: null mint_limits: [] exposure_limits: [] runtime_accounts: [] m2m_clients: [] workload_identity_provider: null webhook_setup: enabled: true callback_url: https://hooks.bankalpha.example/lyriq/events event_types: - beneficiary.screening.requested delivery_format: jsonapi signing_secret_version: v1 signing_secret_configured: true activation_operation_id: 01929a10-5c3d-7e4f-8a1b-2c3d4e5f6a7b bank_portal_route_code: 3f9c2a7be1d04c8a9b6e5d2f1a0c7e4b readiness: signing_targets_total: 3 signing_targets_verified: 3 encryption_targets_total: 1 encryption_targets_verified: 1 staff_total: 1 bank_admin_present: true maker_present: false checker_present: false readonly_present: false webhook_ready: true ready_for_review: true created_at: '2026-09-21T09:12:44.518Z' updated_at: '2026-09-25T16:20:37.115Z' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases/{case_id}/m2m-clients: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid put: summary: Replace the M2M workloads of an onboarding case operationId: replaceBankOnboardingM2mClients x-required-scopes: - connector:onboarding:artifacts:write tags: - Bank Onboarding description: | Declares the bank's machine-to-machine (M2M) workloads: back-end systems that call the Lyriq Connector without a human user. A workload authenticates with a short-lived assertion issued by the bank's FIS identity provider; the platform IAM verifies it against `workload_identity_provider` and exchanges it for a Lyriq Connector access token. Workloads are *secretless*: no client secret or private key is sent to or held by the platform. **Required scope:** `connector:onboarding:artifacts:write`. **Allowed states:** `AWAITING_BANK_ADMIN`, `BANK_CONFIGURING`, `NEEDS_CHANGES` (otherwise `409`). The first edit moves `AWAITING_BANK_ADMIN` to `BANK_CONFIGURING`. **Full replacement.** The request replaces the complete list. Send every workload you want to keep; send `m2m_clients: []` (and `workload_identity_provider: null`) to remove them all. No `Idempotency-Key` is used; repeating the same request is safe and has the same effect (the case `version` still increases). **`workload_identity_provider`** (required key; may be `null` only when `m2m_clients` is empty, otherwise `422`): - `jwks_url` (required): the JWKS endpoint the platform IAM uses to verify your workload assertions. Absolute `https` URL, at most 2048 characters (plain `http` is accepted only for `localhost` in a sandbox case). - `issuer` (optional): the exact `iss` value of your workload assertions. Omitted or blank means the case's `fis_idp_entity_id`. At most 2048 characters. When `m2m_clients` is empty the provider is not stored. **Each `m2m_clients[]` entry** (at most 20 entries; surrounding whitespace is trimmed from all strings): - `display_name` (required, 1 to 128 characters): a name for the workload. - `business_purpose` (required, 1 to 512 characters): what the workload does. - `fis_client_id` (required, 1 to 256 characters): the workload's client identifier at FIS. The `sub` claim of the workload's assertion must equal this value exactly (case-sensitive). Must be unique within the request and within your bank. - `roles` (required, at least one, unique): preset labels `maker`, `checker`, `readonly`. Labels describe the workload; they do not grant permissions. Administrative roles (`bank-admin`) cannot be given to a workload. - `scopes` (required, at least one, unique): the permissions the workload's access tokens carry. This is the only thing that authorises its API calls. Any assignable scope may be combined with any role. Assignable scopes: `connector:accounts:create`, `connector:accounts:list`, `connector:accounts:read`, `connector:accounts:holding-registrations:create`, `connector:accounts:holding-registrations:list`, `connector:accounts:holding-registrations:read`, `connector:assets:list`, `connector:assets:read`, `connector:auth-profiles:bind`, `connector:auth-profiles:create`, `connector:auth-profiles:list`, `connector:auth-profiles:read`, `connector:auth-profiles:update`, `connector:balances:list`, `connector:banks:list`, `connector:banks:read`, `connector:exposures:list`, `connector:liabilities:list`, `connector:limits:list`, `connector:limits:read`, `connector:limits:update`, `connector:operations:cancel`, `connector:operations:list`, `connector:operations:read`, `connector:redemptions:create`, `connector:redemptions:list`, `connector:redemptions:read`, `connector:redemption-policies:list`, `connector:redemption-policies:read`, `connector:reviews:decide`, `connector:reviews:list`, `connector:reviews:read`, `connector:settlement_cycle:list`, `connector:settlement_cycle:read`, `connector:transfers:create`, `connector:transfers:list`, `connector:transfers:read`, `connector:transfers:screening-decide`, `connector:webhook_deliveries:list`, `connector:webhook_deliveries:read`, `connector:webhooks:create`, `connector:webhooks:list`, `connector:webhooks:read`, `connector:webhooks:rotate-secret`, `connector:webhooks:test`, `connector:webhooks:update`. Any other scope (operator, onboarding, staff, `connector:m2m-clients:read`, unknown) returns `422`. Do not send a platform client id. The platform generates one per workload (`keycloak_client_id` in the response, shown as **Platform client ID** in the Bank Portal) of the form `bank-m2m-{uuid}`. It keeps the same value for a workload across later replacements as long as its `fis_client_id` is unchanged. **Provisioning outcome.** Saving workloads provisions nothing. When the case is accepted and its activation operation succeeds, the platform IAM creates one client per workload with the generated platform client id. Each client is created disabled, receives its approved scopes and bank membership, and is then enabled. The case reaches `ACTIVE` only after every workload (and every staff member) has been provisioned; from then on the workload can obtain access tokens (valid 300 seconds) and appears in `GET /v1/m2m-clients`. The response is the updated case; the saved workloads are in `data.attributes.configuration.m2m_clients`. requestBody: required: true content: application/vnd.api+json: schema: type: object required: - data properties: data: type: object additionalProperties: false required: - type - attributes properties: type: type: string enum: - m2m-clients attributes: type: object additionalProperties: false required: - workload_identity_provider - m2m_clients properties: workload_identity_provider: type: object additionalProperties: false nullable: true required: - jwks_url description: | Trust settings for your workload assertions. Required (non-null) when `m2m_clients` is non-empty; send `null` when removing every workload. properties: issuer: type: string minLength: 1 maxLength: 2048 description: | Exact `iss` claim of your workload assertions. Optional; defaults to the case's `fis_idp_entity_id`. jwks_url: type: string format: uri maxLength: 2048 description: | JWKS endpoint used by the platform IAM to verify your workload assertions. Absolute `https` URL (`http` only for `localhost` in a sandbox case). m2m_clients: type: array maxItems: 20 description: Complete list of workloads. An empty array removes all workloads. items: $ref: '#/components/schemas/OnboardingM2mClientInput' examples: oneWorkload: summary: One payments-hub workload value: data: type: m2m-clients attributes: workload_identity_provider: jwks_url: https://idp.fis.example/workload/bank-alpha/jwks m2m_clients: - display_name: Payments hub business_purpose: Submits interbank transfers from the payments hub fis_client_id: BANKALPHA-PAYHUB-01 roles: - maker scopes: - connector:transfers:create - connector:transfers:read - connector:operations:read removeAll: summary: Remove every workload value: data: type: m2m-clients attributes: workload_identity_provider: null m2m_clients: [] responses: '200': description: | The workload list was replaced. Returns the updated onboarding case, including `readiness`. content: application/vnd.api+json: schema: type: object properties: data: $ref: '#/components/schemas/BankOnboardingCase' examples: saved: summary: Case with one saved workload value: data: type: bank-onboarding-cases id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 attributes: bank_id: 11111111-1111-7111-8111-111111111111 version: 12 state: BANK_CONFIGURING display_name: Bank Alpha legal_name: Bank Alpha N.A. bic: BALPUS33 environment: sandbox fis_idp_entity_id: https://idp.fis.example/saml2/bank-alpha admin_contact: email: onboarding.admin@bankalpha.example required_distinct_approvals: 2 configuration: identity_provider: single_sign_on_service_url: https://idp.fis.example/saml2/bank-alpha/sso single_logout_service_url: https://idp.fis.example/saml2/bank-alpha/slo signing_certificate: MIIDdzCCAl+gAwIBAgIEbXq1ZTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3du assets: - asset_id: usd.bank-alpha root_asset_id: usd currency: USD scale: 2 display_code: USD.A client_reference: null mint_limits: [] exposure_limits: [] runtime_accounts: [] m2m_clients: - display_name: Payments hub business_purpose: Submits interbank transfers from the payments hub keycloak_client_id: bank-m2m-01928f5c-7a10-7e3b-9c21-4d5e6f708192 fis_client_id: BANKALPHA-PAYHUB-01 roles: - maker scopes: - connector:transfers:create - connector:transfers:read - connector:operations:read workload_identity_provider: issuer: https://idp.fis.example/saml2/bank-alpha jwks_url: https://idp.fis.example/workload/bank-alpha/jwks activation_operation_id: null readiness: signing_targets_total: 3 signing_targets_verified: 3 encryption_targets_total: 1 encryption_targets_verified: 1 staff_total: 2 bank_admin_present: true maker_present: true checker_present: false readonly_present: false webhook_ready: false ready_for_review: false created_at: '2026-09-21T09:12:44.518Z' updated_at: '2026-09-23T15:27:40.083Z' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '422': description: | The body does not match the schema (for example an unknown field such as `keycloak_client_id`, or a role outside `maker`, `checker`, `readonly`), or a workload violates the rules above. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: scopeNotAllowed: summary: A scope that cannot be given to a workload value: errors: - status: '422' title: Onboarding request failed code: VALIDATION_ERROR detail: 'configuration.m2m_clients[] violates the M2M access policy: scope "connector:m2m-clients:read" is not an assignable bank API scope' providerMissing: summary: Workloads sent without a workload identity provider value: errors: - status: '422' title: Onboarding request failed code: VALIDATION_ERROR detail: configuration.workload_identity_provider is required when M2M workloads are configured '503': $ref: '#/components/responses/ServiceUnavailable' /v1/m2m-clients: get: summary: List the calling bank's activated M2M workloads operationId: listBankM2mClients x-required-scopes: - connector:m2m-clients:read tags: - M2M Workloads description: | Returns the machine-to-machine workloads approved for the calling bank. Only workloads from an onboarding case in state `ACTIVE` are listed; workloads on a case that is still being configured, reviewed or provisioned, or that was rejected or failed, are not. This is the approved configuration recorded at onboarding, not a live health check of the clients in the platform IAM. No private keys, client secrets or tokens are returned. `data.id` and `attributes.keycloak_client_id` both hold the generated platform client id (shown as **Platform client ID** in the Bank Portal). It is the `azp` claim of the workload's access tokens. `fis_client_id` is the `sub` your FIS assertions must carry. **Required scope:** `connector:m2m-clients:read`. This scope can be given to staff members only, never to a workload. The list is not paginated. responses: '200': description: Activated M2M workloads. An empty list means no activated workloads are configured. content: application/vnd.api+json: schema: type: object required: - data properties: data: type: array items: $ref: '#/components/schemas/M2mClient' examples: oneWorkload: summary: One activated workload value: data: - type: m2m-clients id: bank-m2m-01928f5c-7a10-7e3b-9c21-4d5e6f708192 attributes: bank_id: 11111111-1111-7111-8111-111111111111 keycloak_client_id: bank-m2m-01928f5c-7a10-7e3b-9c21-4d5e6f708192 display_name: Payments hub business_purpose: Submits interbank transfers from the payments hub fis_client_id: BANKALPHA-PAYHUB-01 roles: - maker scopes: - connector:transfers:create - connector:transfers:read - connector:operations:read workload_identity_provider: issuer: https://idp.fis.example/saml2/bank-alpha jwks_url: https://idp.fis.example/workload/bank-alpha/jwks none: summary: No activated workloads value: data: [] '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases/{case_id}/webhook-setup: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid put: summary: Configure webhook delivery for an onboarding case operationId: upsertBankOnboardingWebhookSetup x-required-scopes: - connector:onboarding:artifacts:write tags: - Bank Onboarding description: | Saves the webhook subscription the platform creates for your bank when onboarding is activated. Webhook delivery is **required**: the platform must be able to send you `beneficiary.screening.requested` events, so a case cannot be submitted without a complete webhook setup (`readiness.webhook_ready`). **Required scope:** `connector:onboarding:artifacts:write`. **Allowed states:** `AWAITING_BANK_ADMIN`, `BANK_CONFIGURING`, `NEEDS_CHANGES` (otherwise `409`). The first edit moves `AWAITING_BANK_ADMIN` to `BANK_CONFIGURING`. Each call replaces the whole setup. No `Idempotency-Key` is used; repeating a request has the same effect. **Fields (all in `data.attributes`):** - `enabled` (required): must be `true`; `false` returns `400`. - `url` (required): absolute `https` URL with a host that receives the callbacks. Returned as `callback_url`. - `event_types` (required, at least one, unique): must include `beneficiary.screening.requested`. - `delivery_format` (optional, default `jsonapi`): only `jsonapi` is accepted. - `signing_secret` (write-only): the shared secret used to sign each delivery so you can verify it. Must not be blank. The setup is incomplete until a secret has been supplied once; when omitted on a later call, the secret saved earlier is kept. - `signing_secret_version` (optional, default `v1`): label for the signing secret; 1 to 64 printable ASCII characters without spaces. - `receiver_auth` (optional): set it if your endpoint requires an OAuth 2.0 client-credentials access token. `token_endpoint_url` (absolute `https` URL), `client_id` and `requested_scope` are required and must not be blank; `client_secret` (write-only) must be supplied once for the setup to be complete and is kept when omitted later. Omitting `receiver_auth` removes receiver authentication and discards any saved client secret. **Secrets.** Secrets are stored encrypted by the platform and are never returned. The case shows only `signing_secret_configured` and `receiver_auth.client_secret_configured`. **Validation order.** Field format rules (`enabled`, `url`, `event_types`, `delivery_format`, blank secrets) are checked before authorisation and return `400` with a `source.pointer`. The secret-version format is checked afterwards and returns `422`. The response is the updated case; the saved setup is in `data.attributes.configuration.webhook_setup`. requestBody: required: true content: application/vnd.api+json: schema: type: object required: - data properties: data: type: object additionalProperties: false required: - type - attributes properties: type: type: string enum: - onboarding-webhook-setups attributes: type: object additionalProperties: false required: - enabled properties: enabled: type: boolean description: Must be `true`. Webhook delivery is required for beneficiary screening. url: type: string format: uri description: Absolute `https` callback URL. Required. event_types: type: array minItems: 1 uniqueItems: true description: | Business event types to deliver. Required; must include `beneficiary.screening.requested`. Synthetic `webhook.test` and `webhook.verification` events cannot be selected for onboarding. items: $ref: '#/components/schemas/WebhookEventType' delivery_format: type: string enum: - jsonapi default: jsonapi description: Payload format. Only `jsonapi` is supported. signing_secret_version: type: string minLength: 1 maxLength: 64 default: v1 description: Label of the signing secret; printable ASCII without spaces. signing_secret: type: string minLength: 1 writeOnly: true description: | Shared secret used to sign deliveries. Required on the first call; omit it later to keep the saved secret. Never returned. receiver_auth: type: object additionalProperties: false required: - token_endpoint_url - client_id - requested_scope description: | OAuth 2.0 client-credentials settings the platform uses to obtain an access token from your authorisation server for deliveries. Omit to disable receiver authentication. properties: token_endpoint_url: type: string format: uri description: Absolute `https` URL of your token endpoint. client_id: type: string minLength: 1 description: Client id the platform presents to your token endpoint. client_secret: type: string minLength: 1 writeOnly: true description: | Client secret for `client_id`. Required on the first call; omit it later to keep the saved secret. Never returned. requested_scope: type: string minLength: 1 description: Scope the platform requests from your token endpoint. examples: signedOnly: summary: Signed deliveries, no receiver authentication value: data: type: onboarding-webhook-setups attributes: enabled: true url: https://hooks.bankalpha.example/lyriq/events event_types: - beneficiary.screening.requested - operation.updated - transfer.received signing_secret: 8vN2kq7RzX4mT1bW9pL6cY3hF0dS5gJa withReceiverAuth: summary: Signed deliveries with OAuth 2.0 receiver authentication value: data: type: onboarding-webhook-setups attributes: enabled: true url: https://hooks.bankalpha.example/lyriq/events event_types: - beneficiary.screening.requested delivery_format: jsonapi signing_secret_version: v1 signing_secret: 8vN2kq7RzX4mT1bW9pL6cY3hF0dS5gJa receiver_auth: token_endpoint_url: https://auth.bankalpha.example/oauth2/token client_id: lyriq-webhooks client_secret: Qm4tZ8xR2vN6kP1wY7cL3hB9dF5sJ0gA requested_scope: webhooks.receive responses: '200': description: The webhook setup was saved. Returns the updated case, including `readiness`. content: application/vnd.api+json: schema: type: object properties: data: $ref: '#/components/schemas/BankOnboardingCase' examples: saved: summary: Case after saving a webhook setup with receiver authentication value: data: type: bank-onboarding-cases id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 attributes: bank_id: 11111111-1111-7111-8111-111111111111 version: 15 state: BANK_CONFIGURING display_name: Bank Alpha legal_name: Bank Alpha N.A. bic: BALPUS33 environment: sandbox fis_idp_entity_id: https://idp.fis.example/saml2/bank-alpha admin_contact: email: onboarding.admin@bankalpha.example required_distinct_approvals: 2 configuration: identity_provider: single_sign_on_service_url: https://idp.fis.example/saml2/bank-alpha/sso single_logout_service_url: https://idp.fis.example/saml2/bank-alpha/slo signing_certificate: MIIDdzCCAl+gAwIBAgIEbXq1ZTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3du assets: - asset_id: usd.bank-alpha root_asset_id: usd currency: USD scale: 2 display_code: USD.A client_reference: null mint_limits: [] exposure_limits: [] runtime_accounts: [] m2m_clients: [] workload_identity_provider: null webhook_setup: enabled: true callback_url: https://hooks.bankalpha.example/lyriq/events event_types: - beneficiary.screening.requested delivery_format: jsonapi signing_secret_version: v1 signing_secret_configured: true receiver_auth: token_endpoint_url: https://auth.bankalpha.example/oauth2/token client_id: lyriq-webhooks requested_scope: webhooks.receive client_secret_configured: true activation_operation_id: null readiness: signing_targets_total: 3 signing_targets_verified: 3 encryption_targets_total: 1 encryption_targets_verified: 1 staff_total: 1 bank_admin_present: true maker_present: false checker_present: false readonly_present: false webhook_ready: true ready_for_review: true created_at: '2026-09-21T09:12:44.518Z' updated_at: '2026-09-24T11:08:52.661Z' '400': description: A field failed a format rule. `source.pointer` names the field. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: screeningEventMissing: summary: beneficiary.screening.requested not selected value: errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: 'required event type is missing: beneficiary.screening.requested' source: pointer: /data/attributes/event_types disabled: summary: enabled set to false value: errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: webhook delivery is required for beneficiary screening source: pointer: /data/attributes/enabled '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/UnprocessableEntity' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases/{case_id}/key-requirements: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid get: summary: Get the mandatory onboarding key requirements operationId: getOnboardingKeyRequirements x-required-scopes: - connector:onboarding:read tags: - Bank Onboarding description: | Returns the catalogue of keys the bank must provide before it can submit its onboarding configuration. Each requirement `id` is the key *purpose* you use when registering a target or requesting key generation: | `id` | `category` | Register with | | --- | --- | --- | | `BANK_ADMIN` | `signing` | `POST .../signing-targets` | | `CREATE_INTERBANK_TRANSFER_MAKER` | `signing` | `POST .../signing-targets` | | `APPROVE_INTERBANK_TRANSFER_CHECKER` | `signing` | `POST .../signing-targets` | | `ENCRYPT_TRANSFER_SOURCE_MESSAGE` | `encryption` | `POST .../encryption-targets` | All four are currently `required: true`. `data.id` is the `case_id`. `attributes.version` identifies this version of the catalogue. Send it as `requirements_version` when you create a key-generation job; if the catalogue has changed since you read it, the job is rejected with `422` and you must read the catalogue again. **Required scope:** `connector:onboarding:read`. The case must belong to the calling bank (otherwise `404`). The catalogue is the same for every case state. responses: '200': description: Versioned key catalogue for the onboarding case. content: application/vnd.api+json: schema: $ref: '#/components/schemas/OnboardingKeyRequirementsResponse' examples: catalogue: summary: Current key catalogue (version 2) value: data: type: onboarding-key-requirements id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 attributes: version: 2 requirements: - id: BANK_ADMIN category: signing label: Admin description: Keystone administration required: true - id: CREATE_INTERBANK_TRANSFER_MAKER category: signing label: Maker description: Create interbank transfers required: true - id: APPROVE_INTERBANK_TRANSFER_CHECKER category: signing label: Checker description: Approve interbank transfers required: true - id: ENCRYPT_TRANSFER_SOURCE_MESSAGE category: encryption label: Key-encryption description: Protect the data keys used to encrypt transfer source messages required: true '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases/{case_id}/key-generation-jobs: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid get: summary: List key-generation jobs for an onboarding case operationId: listKeyGenerationJobs x-required-scopes: - connector:onboarding:read tags: - Bank Onboarding description: | Lists the most recent key-generation jobs for the case, newest first, with the current progress of each job's keys. At most 30 jobs are returned; the list is not paginated. **Required scope:** `connector:onboarding:read`. A case that does not exist or belongs to another bank returns `404`. If the platform key service is unavailable the call returns `503`. responses: '200': description: Recent jobs and their allocation progress. content: application/vnd.api+json: schema: $ref: '#/components/schemas/KeyGenerationJobCollectionResponse' examples: oneJob: summary: One job, still creating the checker key value: jsonapi: version: '1.1' data: - type: key-generation-jobs id: c703c51e-4aca-4e00-83e7-3ac8e2be09e6 attributes: items: - id: 4a5bb2d7-c498-43b2-84e5-9e4457d1617f purpose: APPROVE_INTERBANK_TRANSFER_CHECKER status: CREATING retryable: false error: null target_id: null key: null encryption_key: null - id: fafd5289-324d-4f1f-bed5-a1baddbef5eb purpose: ENCRYPT_TRANSFER_SOURCE_MESSAGE status: READY retryable: false error: null target_id: 01928f70-1a2b-7c3d-9e4f-5a6b7c8d9e10 key: null encryption_key: backend_class: AWS_KMS backend_ref: arn:aws:kms:us-east-1:590184012001:key/1234abcd-12ab-34cd-56ef-1234567890ab '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' post: summary: Generate missing onboarding keys operationId: createKeyGenerationJob x-required-scopes: - connector:onboarding:artifacts:write tags: - Bank Onboarding description: | Asks the platform to create the requested keys in the AWS KMS account configured for your deployment, then register and verify each one on the case, so you do not have to register signing and encryption targets yourself. Signing keys are created as Ed25519 keys (`ED25519_PH_SHA_512`); the key-encryption key as a symmetric encrypt/decrypt key. The work runs in the background: the call returns `202` with the job, and you poll `GET .../key-generation-jobs/{job_id}`. **Required scope:** `connector:onboarding:artifacts:write`. **Allowed states:** `AWAITING_BANK_ADMIN`, `BANK_CONFIGURING`, `NEEDS_CHANGES` (otherwise `409`). **Fields (all in `data.attributes`):** - `idempotency_key` (required, UUID): identifies this request. Resending the same key with the same body returns the existing job instead of creating a new one; the same key with a different body returns `409`. There is no `Idempotency-Key` header on this operation. - `requirements_version` (required): the `version` from `GET .../key-requirements`. If the catalogue has changed, the job is rejected with `422` (`Key requirements changed. Refresh the page.`). - `purposes` (required, 1 to 4 unique values): the key purposes to generate, from `BANK_ADMIN`, `CREATE_INTERBANK_TRANSFER_MAKER`, `APPROVE_INTERBANK_TRANSFER_CHECKER`, `ENCRYPT_TRANSFER_SOURCE_MESSAGE`. **Existing keys are preserved.** A purpose that already has a target you registered yourself is skipped and does not appear in the job's `items`. The job never rotates or replaces a key. **Allocation lifecycle.** Each requested purpose gets an *allocation* (one entry in `items`) whose `status` moves through `PENDING`, `CREATING` (key created at the provider), `CONFIGURING` (key prepared and its public material read), `REGISTERING` (target registered on the case; `target_id` is set), `VERIFYING` (target verified) and `READY`. A failed step is retried automatically with increasing delays; after repeated failures the allocation becomes `FAILED` with `retryable: true` and a reason in `error`. To retry, create a new job (new `idempotency_key`) for that purpose: the existing allocation resumes where it stopped, and a second key is never created. `CONFLICT` means a different key was registered for the purpose while the job ran; that key is kept. **Limits:** at most 30 new jobs per bank per hour (`429` beyond that). If the platform key service is unavailable the call returns `503`. requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/CreateKeyGenerationJobRequest' examples: allKeys: summary: Generate all four onboarding keys value: data: type: key-generation-jobs attributes: idempotency_key: 570c980d-e49d-4aff-8fb8-013f66aba460 requirements_version: 2 purposes: - BANK_ADMIN - CREATE_INTERBANK_TRANSFER_MAKER - APPROVE_INTERBANK_TRANSFER_CHECKER - ENCRYPT_TRANSFER_SOURCE_MESSAGE encryptionOnly: summary: Generate only the key-encryption key value: data: type: key-generation-jobs attributes: idempotency_key: 2f0b6c1d-8e7a-4b3c-9d5e-1a2b3c4d5e6f requirements_version: 2 purposes: - ENCRYPT_TRANSFER_SOURCE_MESSAGE responses: '202': description: | Job accepted. Poll `GET .../key-generation-jobs/{job_id}` until every item is `READY`, `FAILED` or `CONFLICT`. content: application/vnd.api+json: schema: $ref: '#/components/schemas/KeyGenerationJobResponse' examples: accepted: summary: New job; allocations not started yet value: jsonapi: version: '1.1' data: type: key-generation-jobs id: c703c51e-4aca-4e00-83e7-3ac8e2be09e6 attributes: items: - id: 4a5bb2d7-c498-43b2-84e5-9e4457d1617f purpose: APPROVE_INTERBANK_TRANSFER_CHECKER status: PENDING retryable: false error: null target_id: null key: null encryption_key: null - id: fafd5289-324d-4f1f-bed5-a1baddbef5eb purpose: ENCRYPT_TRANSFER_SOURCE_MESSAGE status: PENDING retryable: false error: null target_id: null key: null encryption_key: null '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': description: The case is not editable, or `idempotency_key` was already used with a different body. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: idempotencyReuse: summary: idempotency_key reused with a different body value: errors: - status: '409' title: State conflict code: STATE_CONFLICT detail: Idempotency key was already used for another request '422': description: The request is invalid, for example `requirements_version` is out of date. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: staleCatalogue: summary: The key catalogue changed since it was read value: errors: - status: '422' title: Validation error code: VALIDATION_ERROR detail: Key requirements changed. Refresh the page. '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases/{case_id}/key-generation-jobs/{job_id}: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid - name: job_id in: path required: true description: Key-generation job identifier (UUID), as returned by `POST .../key-generation-jobs`. schema: type: string format: uuid get: summary: Get the progress of a key-generation job operationId: getKeyGenerationJob x-required-scopes: - connector:onboarding:read tags: - Bank Onboarding description: | Returns the job with the current state of each key allocation. Poll this endpoint after `POST .../key-generation-jobs` until every item has reached `READY`, `FAILED` or `CONFLICT` (see the create operation for the full allocation lifecycle). Per item: - `key` is set for signing purposes once the public material is available: provider reference, algorithm, base64 SPKI DER public key and its SHA-256 fingerprint (hexadecimal). - `encryption_key` is set for `ENCRYPT_TRANSFER_SOURCE_MESSAGE` once the key exists: the immutable KMS key ARN. A key-encryption key has no public key. - `target_id` is the signing or encryption target registered on the case for this key, set from the `REGISTERING` step onwards. - `retryable` is `true` only for `FAILED` items; `error` explains the failure. **Required scope:** `connector:onboarding:read`. A case or job that does not exist or belongs to another bank returns `404`. If the platform key service is unavailable the call returns `503`. responses: '200': description: Job with current allocation progress and any available key material. content: application/vnd.api+json: schema: $ref: '#/components/schemas/KeyGenerationJobResponse' examples: completed: summary: All requested keys generated, registered and verified value: jsonapi: version: '1.1' data: type: key-generation-jobs id: c703c51e-4aca-4e00-83e7-3ac8e2be09e6 attributes: items: - id: 4a5bb2d7-c498-43b2-84e5-9e4457d1617f purpose: APPROVE_INTERBANK_TRANSFER_CHECKER status: READY retryable: false error: null target_id: 01928f65-9e0f-7a1b-8c2d-3e4f5a6b7c03 key: backend_class: AWS_KMS backend_ref: kms://us-east-1/590184012001/bank-onboarding/prod/11111111-1111-7111-8111-111111111111/01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6/APPROVE_INTERBANK_TRANSFER_CHECKER/4a5bb2d7-c498-43b2-84e5-9e4457d1617f algorithm: ED25519_PH_SHA_512 public_key: MCowBQYDK2VwAyEA8/E795OImKTb4RGBJ4583k3zIwJs7X4fMfY8eqCO4eU= fingerprint: f8cb0f9231326d54c910911715ec3ab3748a8e976cd0650d72e0c4ea5d2df0eb encryption_key: null - id: fafd5289-324d-4f1f-bed5-a1baddbef5eb purpose: ENCRYPT_TRANSFER_SOURCE_MESSAGE status: READY retryable: false error: null target_id: 01928f70-1a2b-7c3d-9e4f-5a6b7c8d9e10 key: null encryption_key: backend_class: AWS_KMS backend_ref: arn:aws:kms:us-east-1:590184012001:key/1234abcd-12ab-34cd-56ef-1234567890ab failed: summary: One key failed after repeated attempts and can be retried value: jsonapi: version: '1.1' data: type: key-generation-jobs id: c703c51e-4aca-4e00-83e7-3ac8e2be09e6 attributes: items: - id: 4a5bb2d7-c498-43b2-84e5-9e4457d1617f purpose: APPROVE_INTERBANK_TRANSFER_CHECKER status: FAILED retryable: true error: Key setup could not finish. Your progress is saved. Retry setup or contact your administrator. target_id: null key: null encryption_key: null '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases/{case_id}/signing-targets: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid get: summary: List the bank's signing targets for an onboarding case operationId: listOnboardingSigningTargets x-required-scopes: - connector:onboarding:read tags: - Bank Onboarding description: | Lists the signing targets registered for the case, oldest first (by `created_at`, then `id`). A *signing target* is a reference to a signing key the bank holds in its own key-management backend, together with its public key; the platform never receives private key material. There is at most one target per purpose. Targets created by a key-generation job appear here too, once the job reaches its `REGISTERING` step. **Required scope:** `connector:onboarding:read`. The list is not paginated. A case that does not exist or belongs to another bank returns an empty list, not `404`. responses: '200': description: Signing targets with their verification status. content: application/vnd.api+json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/OnboardingSigningTargetResource' examples: twoTargets: summary: One verified target and one awaiting verification value: data: - type: onboarding-signing-targets id: 01928f61-0a2b-7c3d-8e4f-5a6b7c8d9e01 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 purpose: BANK_ADMIN key: backend_class: AWS_KMS backend_ref: kms://us-east-1/590184012001/bank-alpha/lyriq/admin algorithm: ED25519_PH_SHA_512 public_key: encoding: spki_der_base64 value: MCowBQYDK2VwAyEAXEOTSg/oDdupkW7D23rnFaf6PEI1dFpuyVCIt/pa1Xw= public_key_fingerprint: algorithm: sha256 value: 2318f293848c8114fd2aa6ae1149fe88853c479b6751544057c945ee068f0132 verification_state: VERIFIED verification_error: null verified_at: '2026-09-22T08:31:05.412Z' created_at: '2026-09-22T08:29:47.006Z' updated_at: '2026-09-22T08:31:05.412Z' - type: onboarding-signing-targets id: 01928f63-4d5e-7f60-9a1b-2c3d4e5f6a02 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 purpose: CREATE_INTERBANK_TRANSFER_MAKER key: backend_class: AWS_KMS backend_ref: kms://us-east-1/590184012001/bank-alpha/lyriq/maker algorithm: ED25519_PH_SHA_512 public_key: encoding: spki_der_base64 value: MCowBQYDK2VwAyEAKQZscvZKS1zqHdwrtNNOVz5VLB/Xt6C9cAmQNj2ceOU= public_key_fingerprint: algorithm: sha256 value: 932ab9613d8eb8dea8b162cbca61e7eac080f67c737d84e6e95bfe2f484ddc71 verification_state: PENDING verification_error: null verified_at: null created_at: '2026-09-22T08:40:12.771Z' updated_at: '2026-09-22T08:40:12.771Z' empty: summary: No signing targets registered yet value: data: [] '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' post: summary: Register a bank signing key for an onboarding case operationId: createOnboardingSigningTarget x-required-scopes: - connector:onboarding:artifacts:write tags: - Bank Onboarding description: | Registers a reference to a signing key the bank holds, for one purpose. The key stays in the bank's backend; only the provider reference and the public key are recorded. The target is created with `verification_state: PENDING`; call `POST .../signing-targets/{target_id}/verify` to prove the reference before you submit. As an alternative to registering keys yourself, `POST .../key-generation-jobs` creates, registers and verifies the keys for you. **Required scope:** `connector:onboarding:artifacts:write`. **Allowed states:** `AWAITING_BANK_ADMIN`, `BANK_CONFIGURING`, `NEEDS_CHANGES` (otherwise `409`). The first edit moves `AWAITING_BANK_ADMIN` to `BANK_CONFIGURING`. **Fields (all in `data.attributes`):** - `purpose` (required): `BANK_ADMIN`, `CREATE_INTERBANK_TRANSFER_MAKER` or `APPROVE_INTERBANK_TRANSFER_CHECKER`. Only one target per purpose is allowed; a second one returns `409`. - `backend_class` (required): the key backend. `AWS_KMS` references take the form `kms://{region}/{account_id}/{alias_path}`; `AWS_CLOUDHSM` references `cloudhsm://{locator}`; `GCP_CLOUD_KMS_HSM` references `gcp-kms://{location}/{project_id}/{key_ring}/{key_id}/{key_version}`. A malformed reference returns `422`. - `backend_ref` (required, not blank): the provider reference. Must be unique within the case. - `algorithm` (required): `P256_SHA256_ASN1` or `ED25519_PH_SHA_512`. - `public_key` (required): `encoding` must be `spki_der_base64`; `value` is the public key as base64-encoded SubjectPublicKeyInfo DER. - `public_key_fingerprint` (required): `algorithm` must be `sha256`; `value` is the SHA-256 digest of the decoded DER bytes, in hexadecimal (a `sha256:` prefix and upper case are accepted and normalised to lower-case hex without prefix). It must match `public_key.value` (otherwise `422`) and be unique within the case. - `backend` (optional): provider-specific metadata, stored as given. **References to platform-generated keys.** If `backend_ref` points at a key the platform generated, it must have been generated for this bank and case; otherwise the call returns `403 BANK_SCOPE_MISMATCH`. No `Idempotency-Key` is used. Repeating a successful request returns `409`, because the purpose (and reference) are already registered. requestBody: required: true content: application/vnd.api+json: schema: type: object required: - data properties: data: type: object required: - type - attributes properties: type: type: string enum: - onboarding-signing-targets attributes: x-deny-unknown-fields: true allOf: - $ref: '#/components/schemas/OnboardingSigningKey' - type: object required: - purpose properties: purpose: type: string description: Key purpose this target serves. One target per purpose per case. enum: - BANK_ADMIN - CREATE_INTERBANK_TRANSFER_MAKER - APPROVE_INTERBANK_TRANSFER_CHECKER examples: awsKmsEd25519: summary: Bank-held AWS KMS Ed25519 key for the maker purpose value: data: type: onboarding-signing-targets attributes: purpose: CREATE_INTERBANK_TRANSFER_MAKER backend_class: AWS_KMS backend_ref: kms://us-east-1/590184012001/bank-alpha/lyriq/maker algorithm: ED25519_PH_SHA_512 public_key: encoding: spki_der_base64 value: MCowBQYDK2VwAyEAKQZscvZKS1zqHdwrtNNOVz5VLB/Xt6C9cAmQNj2ceOU= public_key_fingerprint: algorithm: sha256 value: 932ab9613d8eb8dea8b162cbca61e7eac080f67c737d84e6e95bfe2f484ddc71 responses: '201': description: Signing target recorded with `verification_state` `PENDING`. content: application/vnd.api+json: schema: type: object properties: data: $ref: '#/components/schemas/OnboardingSigningTargetResource' examples: created: summary: Target created, awaiting verification value: data: type: onboarding-signing-targets id: 01928f63-4d5e-7f60-9a1b-2c3d4e5f6a02 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 purpose: CREATE_INTERBANK_TRANSFER_MAKER key: backend_class: AWS_KMS backend_ref: kms://us-east-1/590184012001/bank-alpha/lyriq/maker algorithm: ED25519_PH_SHA_512 public_key: encoding: spki_der_base64 value: MCowBQYDK2VwAyEAKQZscvZKS1zqHdwrtNNOVz5VLB/Xt6C9cAmQNj2ceOU= public_key_fingerprint: algorithm: sha256 value: 932ab9613d8eb8dea8b162cbca61e7eac080f67c737d84e6e95bfe2f484ddc71 verification_state: PENDING verification_error: null verified_at: null created_at: '2026-09-22T08:40:12.771Z' updated_at: '2026-09-22T08:40:12.771Z' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': description: | The case is not editable, or a target already exists for this purpose, reference or fingerprint. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: duplicatePurpose: summary: A target is already registered for this purpose value: errors: - status: '409' title: Onboarding request failed code: STATE_CONFLICT detail: onboarding state conflict '422': description: The request body is malformed for this resource or the key material is invalid. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: fingerprintMismatch: summary: Fingerprint does not match the public key value: errors: - status: '422' title: Onboarding request failed code: VALIDATION_ERROR detail: public_key_fingerprint.value does not match public_key.value '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases/{case_id}/signing-targets/{target_id}: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid - name: target_id in: path required: true description: Signing target identifier (UUID), as returned when the target was created or listed. schema: type: string format: uuid get: summary: Get a bank signing target operationId: getOnboardingSigningTarget x-required-scopes: - connector:onboarding:read tags: - Bank Onboarding description: | Returns one signing target with its key reference and verification status. After a failed verification, `verification_state` is `FAILED` and `verification_error` holds the reason. **Required scope:** `connector:onboarding:read`. A target that does not exist, belongs to another case, or belongs to another bank returns `404`. responses: '200': description: Signing target. content: application/vnd.api+json: schema: type: object properties: data: $ref: '#/components/schemas/OnboardingSigningTargetResource' examples: failed: summary: Target whose last verification failed value: data: type: onboarding-signing-targets id: 01928f65-9e0f-7a1b-8c2d-3e4f5a6b7c03 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 purpose: APPROVE_INTERBANK_TRANSFER_CHECKER key: backend_class: AWS_KMS backend_ref: kms://us-east-1/590184012001/bank-alpha/lyriq/checker algorithm: ED25519_PH_SHA_512 public_key: encoding: spki_der_base64 value: MCowBQYDK2VwAyEA8/E795OImKTb4RGBJ4583k3zIwJs7X4fMfY8eqCO4eU= public_key_fingerprint: algorithm: sha256 value: f8cb0f9231326d54c910911715ec3ab3748a8e976cd0650d72e0c4ea5d2df0eb verification_state: FAILED verification_error: key reference verification was not successful verified_at: null created_at: '2026-09-22T08:44:51.230Z' updated_at: '2026-09-22T08:45:30.018Z' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases/{case_id}/signing-targets/{target_id}/verify: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid - name: target_id in: path required: true description: Signing target identifier (UUID). schema: type: string format: uuid post: summary: Verify a bank signing target operationId: verifyOnboardingSigningTarget x-required-scopes: - connector:onboarding:artifacts:write tags: - Bank Onboarding description: | Proves that the registered reference points at the key you described. The platform resolves `backend_ref` at the key provider, retrieves the public key, and checks that the algorithm, the public key and its SHA-256 fingerprint match what you registered. Verification does not sign anything on the network and does not activate the key; keys become active only when onboarding is activated. No request body is sent. **Required scope:** `connector:onboarding:artifacts:write`. **Allowed states:** `AWAITING_BANK_ADMIN`, `BANK_CONFIGURING`, `NEEDS_CHANGES`. In any other state the verification result cannot be recorded and the call returns `404`. **Outcomes:** - Match: the target becomes `VERIFIED`, `verified_at` is set, `verification_error` is cleared, and the call returns `200`. - Mismatch, unknown key or access denied: the target is recorded as `FAILED` with the reason in `verification_error`, and the call returns `422` with the same reason in `detail`. - The key provider or the platform is temporarily unreachable: the target is left unchanged and the call returns `503`. Retry later. You can call verify again at any time while the case is editable, for example after fixing the key policy; a later success replaces a `FAILED` state. responses: '200': description: The target is now `VERIFIED`. content: application/vnd.api+json: schema: type: object properties: data: $ref: '#/components/schemas/OnboardingSigningTargetResource' examples: verified: summary: Verification succeeded value: data: type: onboarding-signing-targets id: 01928f63-4d5e-7f60-9a1b-2c3d4e5f6a02 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 purpose: CREATE_INTERBANK_TRANSFER_MAKER key: backend_class: AWS_KMS backend_ref: kms://us-east-1/590184012001/bank-alpha/lyriq/maker algorithm: ED25519_PH_SHA_512 public_key: encoding: spki_der_base64 value: MCowBQYDK2VwAyEAKQZscvZKS1zqHdwrtNNOVz5VLB/Xt6C9cAmQNj2ceOU= public_key_fingerprint: algorithm: sha256 value: 932ab9613d8eb8dea8b162cbca61e7eac080f67c737d84e6e95bfe2f484ddc71 verification_state: VERIFIED verification_error: null verified_at: '2026-09-22T08:41:03.598Z' created_at: '2026-09-22T08:40:12.771Z' updated_at: '2026-09-22T08:41:03.598Z' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': description: | Verification failed. The target is now `FAILED`; `detail` repeats `verification_error`. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: notVerified: summary: The key at the provider does not match the registration value: errors: - status: '422' title: Onboarding request failed code: VALIDATION_ERROR detail: key reference verification was not successful '503': description: | The key provider or a platform dependency is temporarily unavailable. The target was not changed; retry later. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' /v1/onboarding-cases/{case_id}/encryption-targets: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid get: summary: List the bank's encryption targets for an onboarding case operationId: listOnboardingEncryptionTargets x-required-scopes: - connector:onboarding:read tags: - Bank Onboarding description: | Lists the encryption targets registered for the case, oldest first (by `created_at`, then `id`). An *encryption target* is the key-encryption key (KEK): a symmetric AWS KMS key in the bank's own AWS account under which the platform wraps the per-message data keys that encrypt stored transfer source messages. There is at most one target per purpose; today the only purpose is `ENCRYPT_TRANSFER_SOURCE_MESSAGE`. Targets created by a key-generation job appear here too, once the job reaches its `REGISTERING` step. **Required scope:** `connector:onboarding:read`. The list is not paginated. A case that does not exist or belongs to another bank returns an empty list, not `404`. responses: '200': description: Encryption targets with their verification status. content: application/vnd.api+json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/OnboardingEncryptionTargetResource' examples: verified: summary: The key-encryption key is registered and verified value: data: - type: onboarding-encryption-targets id: 01928f70-1a2b-7c3d-9e4f-5a6b7c8d9e10 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 purpose: ENCRYPT_TRANSFER_SOURCE_MESSAGE key: backend_class: AWS_KMS backend_ref: arn:aws:kms:us-east-1:590184012001:key/1234abcd-12ab-34cd-56ef-1234567890ab verification_state: VERIFIED verification_error: null verified_at: '2026-09-22T09:02:18.640Z' created_at: '2026-09-22T09:01:55.117Z' updated_at: '2026-09-22T09:02:18.640Z' empty: summary: No encryption target registered yet value: data: [] '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' post: summary: Register the bank's key-encryption key for an onboarding case operationId: createOnboardingEncryptionTarget x-required-scopes: - connector:onboarding:artifacts:write tags: - Bank Onboarding description: | Registers the bank-held key-encryption key. The key stays in the bank's AWS account; only its key ARN is recorded and no key material is accepted. The target is created with `verification_state: PENDING`; call `POST .../encryption-targets/{target_id}/verify` to prove the platform can use the key before you submit. Before verifying, grant the platform's runtime role `kms:GenerateDataKey`, `kms:Encrypt` and `kms:Decrypt` on the key (see the Encryption Targets tag). As an alternative, `POST .../key-generation-jobs` with `ENCRYPT_TRANSFER_SOURCE_MESSAGE` creates, registers and verifies the key for you. **Required scope:** `connector:onboarding:artifacts:write`. **Allowed states:** `AWAITING_BANK_ADMIN`, `BANK_CONFIGURING`, `NEEDS_CHANGES` (otherwise `409`). The first edit moves `AWAITING_BANK_ADMIN` to `BANK_CONFIGURING`. **Fields (all in `data.attributes`):** - `purpose` (required): `ENCRYPT_TRANSFER_SOURCE_MESSAGE`. One target per purpose; a second one returns `409`. - `backend_class` (required): `AWS_KMS`. - `backend_ref` (required): a KMS *key* ARN, `arn:aws:kms:{region}:{account_id}:key/{key_id}`, with a 12-digit account id. Alias ARNs are rejected, because a wrapped data key can only be unwrapped by the exact key that wrapped it. Surrounding whitespace is trimmed. A blank value returns `400 MISSING_FIELD`; a malformed ARN returns `400 INVALID_FIELD_FORMAT`. **Ownership check.** The platform checks the ARN against the keys it has generated. A key the platform generated for another bank or case is rejected with `403 BANK_SCOPE_MISMATCH`. This check needs the platform key service; if it is unavailable the call returns `503`. No `Idempotency-Key` is used. Repeating a successful request returns `409`. requestBody: required: true content: application/vnd.api+json: schema: type: object required: - data properties: data: type: object required: - type - attributes properties: type: type: string enum: - onboarding-encryption-targets attributes: allOf: - $ref: '#/components/schemas/OnboardingEncryptionKey' - type: object required: - purpose properties: purpose: type: string description: Encryption purpose this key serves. One target per purpose per case. enum: - ENCRYPT_TRANSFER_SOURCE_MESSAGE examples: awsKmsKey: summary: Bank-held AWS KMS key-encryption key value: data: type: onboarding-encryption-targets attributes: purpose: ENCRYPT_TRANSFER_SOURCE_MESSAGE backend_class: AWS_KMS backend_ref: arn:aws:kms:us-east-1:590184012001:key/1234abcd-12ab-34cd-56ef-1234567890ab responses: '201': description: Encryption target recorded with `verification_state` `PENDING`. content: application/vnd.api+json: schema: type: object properties: data: $ref: '#/components/schemas/OnboardingEncryptionTargetResource' examples: created: summary: Target created, awaiting verification value: data: type: onboarding-encryption-targets id: 01928f70-1a2b-7c3d-9e4f-5a6b7c8d9e10 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 purpose: ENCRYPT_TRANSFER_SOURCE_MESSAGE key: backend_class: AWS_KMS backend_ref: arn:aws:kms:us-east-1:590184012001:key/1234abcd-12ab-34cd-56ef-1234567890ab verification_state: PENDING verification_error: null verified_at: null created_at: '2026-09-22T09:01:55.117Z' updated_at: '2026-09-22T09:01:55.117Z' '400': description: '`backend_ref` is blank or is not a KMS key ARN.' content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: aliasArn: summary: An alias ARN was sent instead of a key ARN value: errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: 'invalid backend_ref "arn:aws:kms:us-east-1:590184012001:alias/bank-alpha/lyriq-kek": expected arn:aws:kms:{region}:{account_id}:key/{key_id}' source: pointer: /data/attributes/backend_ref '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': description: The case is not editable, or a target already exists for this purpose or key ARN. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: notEditable: summary: Case already submitted value: errors: - status: '409' title: Onboarding request failed code: STATE_CONFLICT detail: onboarding case is not editable in state ReadyForReview '422': $ref: '#/components/responses/UnprocessableEntity' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases/{case_id}/encryption-targets/{target_id}: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid - name: target_id in: path required: true description: Encryption target identifier (UUID), as returned when the target was created or listed. schema: type: string format: uuid get: summary: Get a bank encryption target operationId: getOnboardingEncryptionTarget x-required-scopes: - connector:onboarding:read tags: - Bank Onboarding description: | Returns one encryption target with its key ARN and verification status. After a failed verification, `verification_state` is `FAILED` and `verification_error` holds the reason. **Required scope:** `connector:onboarding:read`. A target that does not exist, belongs to another case, or belongs to another bank returns `404`. responses: '200': description: Encryption target. content: application/vnd.api+json: schema: type: object properties: data: $ref: '#/components/schemas/OnboardingEncryptionTargetResource' examples: pending: summary: Target registered, not yet verified value: data: type: onboarding-encryption-targets id: 01928f70-1a2b-7c3d-9e4f-5a6b7c8d9e10 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 purpose: ENCRYPT_TRANSFER_SOURCE_MESSAGE key: backend_class: AWS_KMS backend_ref: arn:aws:kms:us-east-1:590184012001:key/1234abcd-12ab-34cd-56ef-1234567890ab verification_state: PENDING verification_error: null verified_at: null created_at: '2026-09-22T09:01:55.117Z' updated_at: '2026-09-22T09:01:55.117Z' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases/{case_id}/encryption-targets/{target_id}/verify: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid - name: target_id in: path required: true description: Encryption target identifier (UUID). schema: type: string format: uuid post: summary: Verify a bank encryption target operationId: verifyOnboardingEncryptionTarget x-required-scopes: - connector:onboarding:artifacts:write tags: - Bank Onboarding description: | Proves that the platform can use the registered key-encryption key. The platform generates a data key under the referenced KMS key, wraps it and unwraps it again, which exercises `kms:GenerateDataKey`, `kms:Encrypt` and `kms:Decrypt`. The purpose name is used as additional authenticated data. Nothing is stored under the key and the key is not activated; it becomes active only when onboarding is activated. No request body is sent. **Required scope:** `connector:onboarding:artifacts:write`. **Allowed states:** `AWAITING_BANK_ADMIN`, `BANK_CONFIGURING`, `NEEDS_CHANGES`. In any other state the verification result cannot be recorded and the call returns `404`. **Outcomes:** - All three KMS actions succeed: the target becomes `VERIFIED`, `verified_at` is set, `verification_error` is cleared, and the call returns `200`. - The key cannot be used (unknown key, disabled key, or a key policy that does not grant all three actions): the target is recorded as `FAILED` with the reason in `verification_error`, and the call returns `422` with the same reason in `detail`. - KMS or the platform is temporarily unreachable: the target is left unchanged and the call returns `503`. Retry later. You can call verify again at any time while the case is editable, for example after fixing the key policy; a later success replaces a `FAILED` state. responses: '200': description: The target is now `VERIFIED`. content: application/vnd.api+json: schema: type: object properties: data: $ref: '#/components/schemas/OnboardingEncryptionTargetResource' examples: verified: summary: Verification succeeded value: data: type: onboarding-encryption-targets id: 01928f70-1a2b-7c3d-9e4f-5a6b7c8d9e10 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 purpose: ENCRYPT_TRANSFER_SOURCE_MESSAGE key: backend_class: AWS_KMS backend_ref: arn:aws:kms:us-east-1:590184012001:key/1234abcd-12ab-34cd-56ef-1234567890ab verification_state: VERIFIED verification_error: null verified_at: '2026-09-22T09:02:18.640Z' created_at: '2026-09-22T09:01:55.117Z' updated_at: '2026-09-22T09:02:18.640Z' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': description: | The platform cannot use the key. The target is now `FAILED`; `detail` repeats `verification_error`. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases/{case_id}/submit-configuration: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid post: summary: Submit the bank's onboarding configuration for operator review operationId: submitBankOnboardingConfiguration x-required-scopes: - connector:onboarding:submit-configuration tags: - Bank Onboarding description: | Declares the bank's part of onboarding complete and moves the case to `READY_FOR_REVIEW`. No request body is sent. **Required scope:** `connector:onboarding:submit-configuration`. **Allowed states:** `AWAITING_BANK_ADMIN`, `BANK_CONFIGURING` or `NEEDS_CHANGES`. In any other state the call returns `409 STATE_CONFLICT`. **Checks (in this order); the first that fails returns `422 VALIDATION_ERROR`:** 1. A signing target is `VERIFIED` for each of `BANK_ADMIN`, `CREATE_INTERBANK_TRANSFER_MAKER` and `APPROVE_INTERBANK_TRANSFER_CHECKER` (detail: `verified bank-admin, maker, and checker signing targets are required`). 2. The `ENCRYPT_TRANSFER_SOURCE_MESSAGE` encryption target is `VERIFIED` (detail: `transfer source message encryption target is required`). 3. At least one staff member has the `bank-admin` role label (detail: `an initial bank-admin staff member is required`). Staff with `maker`, `checker` or `readonly` labels are optional. 4. Webhook setup is enabled, includes the `beneficiary.screening.requested` event type, has a signing secret and, when receiver authentication is configured, a client secret (detail: `webhook setup is incomplete`). M2M workloads are optional and are not checked here. `readiness.ready_for_review` on the case tells you in advance whether these checks will pass. **Effect:** the case moves to `READY_FOR_REVIEW` and its `version` increases. From now on all bank edits (targets, staff, webhook setup, M2M workloads, key generation) are rejected with `409`. Submitting does not accept the package, activate the bank or provision any identity: the platform operator reviews and accepts the case, which starts the activation operation (see `activation_operation_id` on the case). **Retries:** calling submit again on a case already in `READY_FOR_REVIEW` returns `409`; the first submission stands. The response contains the updated case without the `readiness` and `bank_portal_route_code` attributes; read the case to get them. responses: '200': description: The case is now `READY_FOR_REVIEW`. content: application/vnd.api+json: schema: type: object properties: data: $ref: '#/components/schemas/BankOnboardingCase' examples: submitted: summary: Case moved to READY_FOR_REVIEW value: data: type: bank-onboarding-cases id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 attributes: bank_id: 11111111-1111-7111-8111-111111111111 version: 19 state: READY_FOR_REVIEW display_name: Bank Alpha legal_name: Bank Alpha N.A. bic: BALPUS33 environment: sandbox fis_idp_entity_id: https://idp.fis.example/saml2/bank-alpha admin_contact: email: onboarding.admin@bankalpha.example required_distinct_approvals: 2 configuration: identity_provider: single_sign_on_service_url: https://idp.fis.example/saml2/bank-alpha/sso single_logout_service_url: https://idp.fis.example/saml2/bank-alpha/slo signing_certificate: MIIDdzCCAl+gAwIBAgIEbXq1ZTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3du assets: - asset_id: usd.bank-alpha root_asset_id: usd currency: USD scale: 2 display_code: USD.A client_reference: null mint_limits: [] exposure_limits: [] runtime_accounts: [] m2m_clients: [] workload_identity_provider: null webhook_setup: enabled: true callback_url: https://hooks.bankalpha.example/lyriq/events event_types: - beneficiary.screening.requested delivery_format: jsonapi signing_secret_version: v1 signing_secret_configured: true activation_operation_id: null created_at: '2026-09-21T09:12:44.518Z' updated_at: '2026-09-25T10:45:19.332Z' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': description: The case is not in an editable state (for example it was already submitted). content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: alreadySubmitted: summary: Case already submitted value: errors: - status: '409' title: Onboarding request failed code: STATE_CONFLICT detail: onboarding case is not editable in state ReadyForReview '422': description: A readiness check failed. `detail` names the missing artifact. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: signingTargetsMissing: summary: Not every signing target is verified value: errors: - status: '422' title: Onboarding request failed code: VALIDATION_ERROR detail: verified bank-admin, maker, and checker signing targets are required webhookIncomplete: summary: Webhook setup is missing or incomplete value: errors: - status: '422' title: Onboarding request failed code: VALIDATION_ERROR detail: webhook setup is incomplete '503': $ref: '#/components/responses/ServiceUnavailable' /v1/onboarding-cases/{case_id}/staff-members: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid get: summary: List staff members of an onboarding case operationId: listBankStaffMembers x-required-scopes: - connector:staff:read tags: - Bank Staff description: | Lists the bank staff members declared on the case, oldest first (by `created_at`, then `id`). Staff members are people who will sign in to the Lyriq Connector and Bank Portal through your federated identity provider; they are identity principals, not ledger accounts. **Required scope:** `connector:staff:read`. The list is not paginated. A case that does not exist or belongs to another bank returns an empty list, not `404`. responses: '200': description: Staff members of the case. content: application/vnd.api+json: schema: $ref: '#/components/schemas/StaffMemberCollectionResponse' examples: twoMembers: summary: A bank administrator and a combined maker/checker value: data: - type: staff-members id: 01928f80-2b3c-7d4e-8f50-6a7b8c9d0e11 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 bank_id: 11111111-1111-7111-8111-111111111111 email: jane.doe@bankalpha.example roles: - bank-admin scopes: - connector:m2m-clients:read - connector:operations:list - connector:operations:read - connector:reviews:decide - connector:reviews:list - connector:reviews:read state: READY created_at: '2026-09-22T10:15:03.441Z' updated_at: '2026-09-22T10:15:03.441Z' - type: staff-members id: 01928f82-7c8d-7e9f-a0b1-2c3d4e5f6a12 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 bank_id: 11111111-1111-7111-8111-111111111111 email: sam.lee@bankalpha.example roles: - checker - maker scopes: - connector:reviews:decide - connector:transfers:create - connector:transfers:read state: READY created_at: '2026-09-22T10:18:27.905Z' updated_at: '2026-09-22T10:18:27.905Z' post: summary: Add a staff member to an onboarding case operationId: createBankStaffMember x-required-scopes: - connector:staff:write tags: - Bank Staff description: | Declares a person to be provisioned in the platform IAM when onboarding is activated. The member is created in state `READY`; nothing is provisioned until activation. **Required scope:** `connector:staff:write`. **Allowed states:** `AWAITING_BANK_ADMIN`, `BANK_CONFIGURING`, `NEEDS_CHANGES` (otherwise `409`). The first edit moves `AWAITING_BANK_ADMIN` to `BANK_CONFIGURING`. **Fields (all in `data.attributes`):** - `email` (required): the member's email address. It is trimmed and stored in lower case, must contain `@` and a domain with a dot (otherwise `422`), and must be unique within the case (otherwise `409`). The same value is used as the member's sign-in subject at the federated identity provider. - `roles` (required, at least one, unique): preset labels `bank-admin`, `maker`, `checker`, `readonly`. Labels describe the member's function and may be combined (for example `maker` and `checker`); they do not grant any API permission. At least one member of the case must have `bank-admin` before you can submit. - `scopes` (required, at least one, unique): the member's API permissions. They are the only source of authorisation for the member's calls. Allowed values are the scopes assignable to M2M workloads (listed on `PUT /v1/onboarding-cases/{case_id}/m2m-clients`) plus `connector:m2m-clients:read`. Operator, onboarding (`connector:onboarding:*`), staff administration (`connector:staff:*`) and unknown scopes return `422`. The order of `roles` and `scopes` in responses is not significant. Staff members do not hold signing keys in the platform. **Onboarding administrator.** The onboarding administrator (`admin_contact` on the case) is not a staff member, and their onboarding access is removed when the case is activated. Add them here as well if they need access after activation. **Provisioning outcome.** When the case is activated, every staff member receives a bank membership with their roles and scopes in the platform IAM. Members change to `ACTIVE` when the case becomes `ACTIVE`. No `Idempotency-Key` is used. Repeating a successful request returns `409` because the email is already on the case. requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/CreateBankStaffMemberRequest' examples: bankAdmin: summary: Initial bank administrator value: data: type: staff-members attributes: email: Jane.Doe@BankAlpha.example roles: - bank-admin scopes: - connector:operations:read - connector:operations:list - connector:reviews:read - connector:reviews:list - connector:reviews:decide - connector:m2m-clients:read makerChecker: summary: Member with combined maker and checker labels value: data: type: staff-members attributes: email: sam.lee@bankalpha.example roles: - maker - checker scopes: - connector:transfers:create - connector:transfers:read - connector:reviews:decide responses: '201': description: Staff member created in state `READY`. content: application/vnd.api+json: schema: $ref: '#/components/schemas/StaffMemberResponse' examples: created: summary: Bank administrator created (email normalised to lower case) value: data: type: staff-members id: 01928f80-2b3c-7d4e-8f50-6a7b8c9d0e11 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 bank_id: 11111111-1111-7111-8111-111111111111 email: jane.doe@bankalpha.example roles: - bank-admin scopes: - connector:m2m-clients:read - connector:operations:list - connector:operations:read - connector:reviews:decide - connector:reviews:list - connector:reviews:read state: READY created_at: '2026-09-22T10:15:03.441Z' updated_at: '2026-09-22T10:15:03.441Z' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/UnprocessableEntity' /v1/onboarding-cases/{case_id}/staff-members/{staff_id}: parameters: - name: case_id in: path required: true description: Onboarding case identifier (UUID). schema: type: string format: uuid - name: staff_id in: path required: true description: Staff member identifier (UUID), as returned when the member was created or listed. schema: type: string format: uuid get: summary: Get a staff member of an onboarding case operationId: getBankStaffMember x-required-scopes: - connector:staff:read tags: - Bank Staff description: | Returns one staff member with their role labels, scopes and provisioning `state`: `READY` (declared, not yet provisioned; still editable while the case is editable), `ACTIVE` (provisioned when the case became `ACTIVE`), `SUSPENDED` or `DEACTIVATED`. **Required scope:** `connector:staff:read`. A member that does not exist, belongs to another case, or belongs to another bank returns `404`. responses: '200': description: Staff member. content: application/vnd.api+json: schema: $ref: '#/components/schemas/StaffMemberResponse' examples: active: summary: Member provisioned after activation value: data: type: staff-members id: 01928f80-2b3c-7d4e-8f50-6a7b8c9d0e11 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 bank_id: 11111111-1111-7111-8111-111111111111 email: jane.doe@bankalpha.example roles: - bank-admin scopes: - connector:m2m-clients:read - connector:operations:read state: ACTIVE created_at: '2026-09-22T10:15:03.441Z' updated_at: '2026-09-26T07:02:44.120Z' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' put: summary: Update a pending staff member operationId: updateBankStaffMember x-required-scopes: - connector:staff:write tags: - Bank Staff description: | Replaces the email, role labels and scopes of a staff member. All three are required; the same rules apply as on create (see `POST .../staff-members`). **Required scope:** `connector:staff:write`. **Preconditions:** the member is in state `READY`, the case is in `AWAITING_BANK_ADMIN`, `BANK_CONFIGURING` or `NEEDS_CHANGES`, and the case has no activation operation yet. Otherwise the call returns `409` (detail: `staff member or onboarding case is no longer editable`). `data.id` must equal the `staff_id` path parameter (otherwise `400 RESOURCE_ID_MISMATCH`). No `Idempotency-Key` is used; repeating the request has the same effect. requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/UpdateBankStaffMemberRequest' examples: addChecker: summary: Give an existing member the checker label and review scopes value: data: type: staff-members id: 01928f82-7c8d-7e9f-a0b1-2c3d4e5f6a12 attributes: email: sam.lee@bankalpha.example roles: - maker - checker scopes: - connector:transfers:create - connector:transfers:read - connector:reviews:read - connector:reviews:decide responses: '200': description: Staff member updated. content: application/vnd.api+json: schema: $ref: '#/components/schemas/StaffMemberResponse' examples: updated: summary: Updated member value: data: type: staff-members id: 01928f82-7c8d-7e9f-a0b1-2c3d4e5f6a12 attributes: onboarding_case_id: 01928f4a-3b2c-7d1e-9f00-a1b2c3d4e5f6 bank_id: 11111111-1111-7111-8111-111111111111 email: sam.lee@bankalpha.example roles: - checker - maker scopes: - connector:reviews:decide - connector:reviews:read - connector:transfers:create - connector:transfers:read state: READY created_at: '2026-09-22T10:18:27.905Z' updated_at: '2026-09-23T13:51:09.376Z' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': description: The member or the case is no longer editable, or the new email is already used on the case. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: notEditable: summary: Case accepted or member already provisioned value: errors: - status: '409' title: Onboarding request failed code: STATE_CONFLICT detail: staff member or onboarding case is no longer editable '422': $ref: '#/components/responses/UnprocessableEntity' '503': $ref: '#/components/responses/ServiceUnavailable' delete: summary: Remove a pending staff member operationId: deleteBankStaffMember x-required-scopes: - connector:staff:write tags: - Bank Staff description: | Removes a staff member from the case. Nothing is removed from the platform IAM, because the member has not been provisioned yet. **Required scope:** `connector:staff:write`. **Preconditions:** the same as for update: member in state `READY`, case editable and without an activation operation; otherwise `409`. Removing the last `bank-admin` member is allowed, but the case then cannot be submitted until you add another. Deleting a member that no longer exists returns `404`. responses: '204': description: Staff member removed. No response body. '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/bank-staff: get: summary: List the bank's staff members operationId: listStaffMembers tags: - Bank Staff description: Returns every staff member of the calling bank, regardless of lifecycle state. Staff are IAM principals federated through the identity provider. The collection is scoped to the caller's `bank_id`. Requires the `connector:staff:read` scope. responses: '200': description: Bank-scoped staff members. content: application/vnd.api+json: schema: $ref: '#/components/schemas/StaffMemberCollectionResponse' '403': $ref: '#/components/responses/Forbidden' '503': $ref: '#/components/responses/ServiceUnavailable' post: summary: Add a staff member to the bank operationId: createStaffMember tags: - Bank Staff description: |- Creates a staff member for the calling bank and enqueues identity provisioning. The member is returned in the `READY` state and transitions to `ACTIVE` once the identity provider accepts it (see the state machine in the resource `state` field). Scope rules enforced on the submitted `scopes`: * The caller may only grant scopes it already holds itself. * `connector:staff:read` / `connector:staff:write` (staff-administration scopes) may be granted only to a member that also holds the `bank-admin` role. * `connector:staff:write` requires `connector:staff:read`. Requires the `connector:staff:write` scope. requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/CreateBankStaffMemberRequest' responses: '201': description: Staff member created and queued for identity provisioning. content: application/vnd.api+json: schema: $ref: '#/components/schemas/StaffMemberResponse' '403': $ref: '#/components/responses/Forbidden' '422': $ref: '#/components/responses/UnprocessableEntity' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/bank-staff/{staff_id}: parameters: - name: staff_id in: path required: true schema: type: string format: uuid get: summary: Get one of the bank's staff members operationId: getStaffMember tags: - Bank Staff description: Reads a single staff member of the calling bank. Requires the `connector:staff:read` scope. responses: '200': description: Bank staff member. content: application/vnd.api+json: schema: $ref: '#/components/schemas/StaffMemberResponse' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' patch: summary: Update one of the bank's staff members operationId: updateStaffMember tags: - Bank Staff description: |- Replaces the role labels and API scopes of an existing staff member (the email is immutable). The new values are staged and only applied once identity provisioning succeeds: the member moves to `UPDATING` (while remaining live on its previous grants) and back to `ACTIVE` on success, or to `UPDATE_FAILED` if provisioning is exhausted. `data.id` must match `staff_id` in the path. Only members in the `ACTIVE` or `UPDATE_FAILED` state can be updated; any other state is rejected. The same scope rules as create apply (no granting scopes the caller lacks, staff-administration scopes only for `bank-admin`, write implies read). The request is also rejected if it would remove the last active holder of `connector:staff:read` or `connector:staff:write` in the bank, so the bank can never be locked out of staff administration. Requires the `connector:staff:write` scope. requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/UpdateStaffMemberRequest' responses: '200': description: Staff member update accepted; re-provisioning queued. content: application/vnd.api+json: schema: $ref: '#/components/schemas/StaffMemberResponse' '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/UnprocessableEntity' '503': $ref: '#/components/responses/ServiceUnavailable' delete: summary: Suspend one of the bank's staff members operationId: deleteStaffMember tags: - Bank Staff description: |- Soft-deletes a staff member: it is moved to `SUSPENDING` and de-provisioned from the identity provider, reaching `DEACTIVATED` on success or `DEACTIVATION_FAILED` if de-provisioning is exhausted. Only members in the `ACTIVE`, `UPDATE_FAILED`, or `DEACTIVATION_FAILED` state can be suspended; any other state is rejected. The request is also rejected if it would suspend the last active holder of `connector:staff:read` or `connector:staff:write` in the bank. Requires the `connector:staff:write` scope. responses: '204': description: Staff member suspension accepted; de-provisioning queued. '400': $ref: '#/components/responses/BadRequest' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/encryption-targets/{purpose}: parameters: - name: purpose in: path required: true description: | Encryption purpose the key-encryption key is registered for. Currently only `ENCRYPT_TRANSFER_SOURCE_MESSAGE`; any other value returns `400`. schema: type: string enum: - ENCRYPT_TRANSFER_SOURCE_MESSAGE put: summary: Replace the key-encryption key operationId: replaceEncryptionTarget x-required-scopes: - connector:encryption-targets:update tags: - EncryptionTargets description: | Registers a different key-encryption key (KEK) for this purpose after onboarding. The first KEK is registered during onboarding (`POST /v1/onboarding-cases/{case_id}/encryption-targets`); use this operation to replace it later. **Required scope:** `connector:encryption-targets:update`. **Request.** `data.type` is `encryption-targets`; `backend_class` is `AWS_KMS`; `backend_ref` is the KMS *key* ARN `arn:aws:kms:{region}:{account_id}:key/{key_id}` (12-digit account id; alias ARNs are rejected). A blank or malformed `backend_ref` returns `400` with `source.pointer` `/data/attributes/backend_ref`. **Proof of use.** Before the new key is accepted, the platform generates a data key under it, wraps it and unwraps it again, exercising `kms:GenerateDataKey`, `kms:Encrypt` and `kms:Decrypt`. A key whose policy does not grant all three to the platform runtime role, or that is unknown or disabled, is rejected: the call returns an error (typically `400`) and the operation is recorded as `FAILED`. If KMS is temporarily unreachable the call returns `503`. **Result.** On success the call returns `202` with the operation that recorded the new key; read it with `GET /v1/operations/{operation_id}`. **Idempotency.** The `Idempotency-Key` header is required (`400` without it). Replaying the same key with the same body returns the original `202` document; the same key with a different body returns `409 IDEMPOTENCY_CONFLICT`, and a replay while the first request is still running returns `409 IDEMPOTENCY_PENDING`. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/ReplaceEncryptionTargetRequest' examples: newKmsKey: summary: Replace the KEK with a new AWS KMS key value: data: type: encryption-targets attributes: backend_class: AWS_KMS backend_ref: arn:aws:kms:us-east-1:590184012001:key/9f8e7d6c-5b4a-3928-1706-fedcba987654 responses: '202': $ref: '#/components/responses/AcceptedOperation' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/UnprocessableEntity' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/assets: get: summary: List assets operationId: listAssets tags: - Assets x-required-scopes: - connector:assets:list description: | List the issued assets configured on the platform, for every issuing bank (not only your own). Use this to resolve an `asset_id` seen in balances, exposures or transfers to its issuer, root settlement asset and `scale`. An *asset* here is a bank-issued asset such as `usd.bank-alpha`, issued by one bank under a root settlement asset such as `usd`. `scale` is the number of decimal places (`scale: 2` means the amount `"100"` is 1.00 units). Assets are configured by the platform and are **read-only** through this API. **Required scope:** `connector:assets:list`. **Order.** Ascending by asset `id`. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' - $ref: '#/components/parameters/filterIssuerBankIdParam' responses: '200': description: One page of assets. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/Asset' links: $ref: '#/components/schemas/CursorLinks' examples: allAssets: summary: Assets of two issuers value: data: - type: assets id: usd.bank-alpha attributes: root_asset_id: usd asset_code: usd.bank-alpha issuer_bank_id: 11111111-1111-7111-8111-111111111111 status: ACTIVE scale: 2 - type: assets id: usd.bank-beta attributes: root_asset_id: usd asset_code: usd.bank-beta issuer_bank_id: 22222222-2222-7222-8222-222222222222 status: ACTIVE scale: 2 links: self: /v1/assets byIssuer: summary: Filtered to one issuer value: data: - type: assets id: usd.bank-alpha attributes: root_asset_id: usd asset_code: usd.bank-alpha issuer_bank_id: 11111111-1111-7111-8111-111111111111 status: ACTIVE scale: 2 links: self: /v1/assets?filter[issuer_bank_id]=11111111-1111-7111-8111-111111111111 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/assets/{asset_id}: parameters: - name: asset_id in: path required: true schema: type: string description: | Identifier of the issued asset (exact, case-sensitive match on the asset `id`), for example `usd.bank-alpha`. This is the `asset_id` of balances and exposures and the `data.id` in their `asset` relationship, not the root settlement asset (`usd`). example: usd.bank-alpha get: summary: Get an asset operationId: getAsset tags: - Assets x-required-scopes: - connector:assets:read description: | Fetch one issued asset by its `asset_id`, whichever bank issues it. Returns its root settlement asset, display code, issuer and `scale`; useful for interpreting amounts in balances, exposures and transfers. **Required scope:** `connector:assets:read`. responses: '200': description: The requested asset. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/Asset' examples: bankAlphaUsd: summary: Bank Alpha's issued USD value: data: type: assets id: usd.bank-alpha attributes: root_asset_id: usd asset_code: usd.bank-alpha issuer_bank_id: 11111111-1111-7111-8111-111111111111 status: ACTIVE scale: 2 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: No asset with this `asset_id` exists. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: assetNotFound: summary: Unknown asset_id value: jsonapi: version: '1.1' errors: - status: '404' code: RESOURCE_NOT_FOUND title: Resource not found '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/banks: get: summary: List banks operationId: listBanks tags: - Banks x-required-scopes: - connector:banks:list description: | List the banks on the network that are currently `ACTIVE`, including your own. Banks in any other status (for example still onboarding, suspended or terminated) are not returned. Use this directory to find a counterparty's `bank_id` and BIC. Only public directory attributes are returned for each bank: `short_name`, `display_name`, `description` (when set), `country_code`, `bic_swift_code` and `status`. Nothing about another bank's accounts, balances or configuration is exposed here. **Required scope:** `connector:banks:list`. **Order.** Ascending by `bank_id`. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' - name: filter[display_name] in: query required: false description: | Return only banks whose `display_name` contains this text, ignoring case (for example `alpha` matches `Bank Alpha`). The characters `%` and `_` act as wildcards (any sequence and any single character respectively). schema: type: string example: alpha responses: '200': description: One page of active banks. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/Bank' links: $ref: '#/components/schemas/CursorLinks' examples: allBanks: summary: All active banks value: data: - type: banks id: 11111111-1111-7111-8111-111111111111 attributes: short_name: bank-alpha display_name: Bank Alpha country_code: US bic_swift_code: ALPHUS33 status: ACTIVE - type: banks id: 22222222-2222-7222-8222-222222222222 attributes: short_name: bank-beta display_name: Bank Beta country_code: US bic_swift_code: BETAUS33 status: ACTIVE links: self: /v1/banks byDisplayName: summary: Filtered by display name value: data: - type: banks id: 11111111-1111-7111-8111-111111111111 attributes: short_name: bank-alpha display_name: Bank Alpha country_code: US bic_swift_code: ALPHUS33 status: ACTIVE links: self: /v1/banks?filter[display_name]=alpha '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/banks/{bank_id}: parameters: - name: bank_id in: path required: true schema: type: string format: uuid description: The bank's `bank_id` (UUID). A value that is not a UUID returns `400 Bad Request`. example: 22222222-2222-7222-8222-222222222222 get: summary: Get a bank operationId: getBank tags: - Banks x-required-scopes: - connector:banks:read description: | Fetch the public directory entry of one bank on the network (any bank, not only your own). The same attributes as in `GET /v1/banks` are returned. **Required scope:** `connector:banks:read`. **Errors.** `404 Not Found` (title `bank not found`) if no bank has this `bank_id`. `403 Forbidden` (title `Forbidden`) if the bank exists but is not currently `ACTIVE` (for example still onboarding, suspended or terminated). responses: '200': description: The requested bank. content: application/vnd.api+json: schema: $ref: '#/components/schemas/GetBank200Response' examples: bankBeta: summary: Bank Beta value: data: type: banks id: 22222222-2222-7222-8222-222222222222 attributes: short_name: bank-beta display_name: Bank Beta country_code: US bic_swift_code: BETAUS33 status: ACTIVE '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': description: | The caller lacks `connector:banks:read`, or the requested bank exists but is not `ACTIVE`. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: bankNotActive: summary: Bank exists but is not ACTIVE value: jsonapi: version: '1.1' errors: - status: '403' title: Forbidden '404': description: No bank has this `bank_id`. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: bankNotFound: summary: Unknown bank_id value: jsonapi: version: '1.1' errors: - status: '404' title: bank not found '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/balances: get: summary: List balances operationId: listBalances tags: - Balances x-required-scopes: - connector:balances:list description: | List the balances of all accounts your bank has registered: one entry per `(account, asset)` pair. Only your bank's own accounts are included; interbank holdings of other banks' assets are reported by [`GET /v1/exposures`](./list-exposures). Each entry reports `total`, `locked`, `reserved` and `available`; see the `Balance` schema for exact definitions (normally `total = available + locked + reserved`). Response-level `meta` gives the ledger cut every entry on this page was read at and whether the read is current (see `PositionReadMetadata`). `meta` is omitted when the page is empty. Different pages may be read at different cuts. **Required scope:** `connector:balances:list`. **Filters** are exact, case-sensitive matches combined with AND; a value that matches nothing returns an empty `data` array. **Order.** Ascending by `external_account_id`, then by the account's root asset. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' - name: filter[asset_id] in: query required: false schema: type: string description: | Return only balances for this asset. Matches either the root settlement asset (`root_asset_id`, for example `usd`) or the issued asset (`asset_id`, for example `usd.bank-alpha`). example: usd - $ref: '#/components/parameters/filterExternalAccountIdParam' responses: '200': description: One page of balances for the calling bank's accounts. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/Balance' meta: $ref: '#/components/schemas/PositionReadMetadata' links: $ref: '#/components/schemas/CursorLinks' examples: allBalances: summary: Two accounts, read while the platform is catching up value: data: - type: balances id: v1:11111111-1111-7111-8111-111111111111:usd.bank-alpha:CBS-ACC-2026-000123 attributes: external_account_id: CBS-ACC-2026-000123 bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd.bank-alpha root_asset_id: usd issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_asset_code: usd.bank-alpha issuer_relation: OWN_ISSUED total: asset_id: usd value: '1000000' scale: 2 locked: asset_id: usd value: '150000' scale: 2 reserved: asset_id: usd value: '25000' scale: 2 available: asset_id: usd value: '825000' scale: 2 as_of: '2026-09-25T14:03:11.482Z' relationships: account: data: type: accounts id: v1:11111111-1111-7111-8111-111111111111:usd.bank-alpha:CBS-ACC-2026-000123 asset: data: type: assets id: usd.bank-alpha links: related: /v1/assets/usd.bank-alpha - type: balances id: v1:11111111-1111-7111-8111-111111111111:usd.bank-alpha:CBS-ACC-2026-000124 attributes: external_account_id: CBS-ACC-2026-000124 bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd.bank-alpha root_asset_id: usd issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_asset_code: usd.bank-alpha issuer_relation: OWN_ISSUED total: asset_id: usd value: '50000' scale: 2 locked: asset_id: usd value: '0' scale: 2 reserved: asset_id: usd value: '0' scale: 2 available: asset_id: usd value: '50000' scale: 2 as_of: '2026-09-18T09:41:57.006Z' relationships: account: data: type: accounts id: v1:11111111-1111-7111-8111-111111111111:usd.bank-alpha:CBS-ACC-2026-000124 asset: data: type: assets id: usd.bank-alpha links: related: /v1/assets/usd.bank-alpha meta: cut: block_height: '184498' block_hash: 3b7e0d2a94c1f58e6a0b3c7d9e1f2a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f reservation_revision: '9317' completeness: CATCHING_UP observed_head: '184502' links: self: /v1/balances?page[size]=2 next: /v1/balances?page%5Bsize%5D=2&page%5Bcursor%5D=MTExMTExMTEtMTExMS03MTExLTgxMTEtMTExMTExMTExMTExfENCUy1BQ0MtMjAyNi0wMDAxMjR8dXNk oneAccount: summary: Filtered to one account and asset value: data: - type: balances id: v1:11111111-1111-7111-8111-111111111111:usd.bank-alpha:CBS-ACC-2026-000123 attributes: external_account_id: CBS-ACC-2026-000123 bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd.bank-alpha root_asset_id: usd issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_asset_code: usd.bank-alpha issuer_relation: OWN_ISSUED total: asset_id: usd value: '1000000' scale: 2 locked: asset_id: usd value: '150000' scale: 2 reserved: asset_id: usd value: '25000' scale: 2 available: asset_id: usd value: '825000' scale: 2 as_of: '2026-09-25T14:03:11.482Z' relationships: account: data: type: accounts id: v1:11111111-1111-7111-8111-111111111111:usd.bank-alpha:CBS-ACC-2026-000123 asset: data: type: assets id: usd.bank-alpha links: related: /v1/assets/usd.bank-alpha meta: cut: block_height: '184502' block_hash: 9f2c4e1a7b3d5f60812a4c6e8b0d2f4618a3c5e7092b4d6f8a1c3e5b7d9f0a2c reservation_revision: '9317' completeness: COMPLETE observed_head: '184502' links: self: /v1/balances?filter[external_account_id]=CBS-ACC-2026-000123&filter[asset_id]=usd '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/capacity/spendable: get: summary: List spendable capacities operationId: listSpendableCapacities tags: - Balances description: | Returns the explicitly typed spendable capacity (total, locked, reserved, utilized, and remaining liquidity) across accounts owned by the calling bank, optionally filtered by asset or account id. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/filterAssetIdParam' - $ref: '#/components/parameters/filterExternalAccountIdParam' responses: '200': description: Paginated list of spendable capacities visible to the caller. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/SpendableCapacity' meta: $ref: '#/components/schemas/PositionReadMetadata' links: allOf: - $ref: '#/components/schemas/CursorLinks' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/exposures: get: summary: List exposures operationId: listExposures tags: - Exposures x-required-scopes: - connector:exposures:list description: | List your bank's interbank holding **exposures**: for each other bank whose issued asset your bank holds, how much you hold, how much is locked for settlement, and how that compares with the exposure limit for that issuer. The caller is always the **holder**: only holdings where your bank is the holder and another bank is the issuer are returned, and only while the holding registration is active. The issuer sees exactly the same holding, with the same amounts, from its side as a liability in [`GET /v1/liabilities`](./list-liabilities). An issuer calling this endpoint does not see its liabilities here; it sees only holdings of other banks' assets that it holds itself, which is often none. A bank with no qualifying holdings gets `200 OK` with an empty `data` array (never `404`). Amounts are defined in the `Exposure` schema. Response-level `meta` gives the ledger cut every entry on this page was read at and whether the read is current (see `PositionReadMetadata`); it is omitted when the page is empty. **Required scope:** `connector:exposures:list`. **Order.** Ascending by `issuer_bank_id`, then by root asset. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' - name: filter[asset_id] in: query required: false schema: type: string description: | Return only holdings of this root settlement asset (exact match on `root_asset_id`, for example `usd`). An issued asset identifier such as `usd.bank-alpha` matches nothing. example: usd responses: '200': description: One page of exposures where the calling bank is the holder (possibly empty). content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/Exposure' meta: $ref: '#/components/schemas/PositionReadMetadata' links: $ref: '#/components/schemas/CursorLinks' examples: holderView: summary: Bank Beta's holding of Bank Alpha USD, 40% of its limit used value: data: - type: exposures id: v1:22222222-2222-7222-8222-222222222222:11111111-1111-7111-8111-111111111111:usd.bank-alpha attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 holder_bank_display_name: Bank Beta issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_bank_display_name: Bank Alpha issuer_asset_code: usd.bank-alpha asset_id: usd.bank-alpha root_asset_id: usd available_balance: asset_id: usd value: '4000000' scale: 2 locked_balance: asset_id: usd value: '1000000' scale: 2 balance: asset_id: usd value: '5000000' scale: 2 limit: asset_id: usd value: '12500000' scale: 2 utilization_ratio: '0.4' as_of: '2026-09-25T14:03:11.482Z' relationships: asset: data: type: assets id: usd.bank-alpha links: related: /v1/assets/usd.bank-alpha meta: cut: block_height: '184502' block_hash: 9f2c4e1a7b3d5f60812a4c6e8b0d2f4618a3c5e7092b4d6f8a1c3e5b7d9f0a2c reservation_revision: '9317' completeness: COMPLETE observed_head: '184502' links: self: /v1/exposures?filter[asset_id]=usd noLimitConfigured: summary: Holding with no exposure limit configured value: data: - type: exposures id: v1:22222222-2222-7222-8222-222222222222:11111111-1111-7111-8111-111111111111:usd.bank-alpha attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 holder_bank_display_name: Bank Beta issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_bank_display_name: Bank Alpha issuer_asset_code: usd.bank-alpha asset_id: usd.bank-alpha root_asset_id: usd available_balance: asset_id: usd value: '4000000' scale: 2 locked_balance: asset_id: usd value: '1000000' scale: 2 balance: asset_id: usd value: '5000000' scale: 2 limit: asset_id: usd value: '' scale: 2 utilization_ratio: '' as_of: '2026-09-25T14:03:11.482Z' relationships: asset: data: type: assets id: usd.bank-alpha links: related: /v1/assets/usd.bank-alpha meta: cut: block_height: '184502' block_hash: 9f2c4e1a7b3d5f60812a4c6e8b0d2f4618a3c5e7092b4d6f8a1c3e5b7d9f0a2c reservation_revision: '9317' completeness: COMPLETE observed_head: '184502' links: self: /v1/exposures noHoldings: summary: The caller holds no other bank's asset value: data: [] links: self: /v1/exposures '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/liabilities: get: summary: List liabilities operationId: listLiabilities tags: - Liabilities x-required-scopes: - connector:liabilities:list description: | List your bank's issuer-side **liabilities**: for each other bank that holds your issued asset, how much it holds and how much of that is locked for settlement. The caller is always the **issuer**: only holdings where your bank is the issuer and another bank is the holder are returned, and only while the holding registration is active. Each liability is the same holding, with the same amounts, that the holder sees as an exposure in [`GET /v1/exposures`](./list-exposures) (the resource `id` has the two bank IDs swapped). A holder calling this endpoint does not see its exposures here; a bank whose asset no other bank holds gets `200 OK` with an empty `data` array (never `404`). Exposure limits are the holder-side view and are not repeated here. Amounts are defined in the `Liability` schema. Response-level `meta` gives the ledger cut every entry on this page was read at and whether the read is current (see `PositionReadMetadata`); it is omitted when the page is empty. **Required scope:** `connector:liabilities:list`. **Order.** Ascending by `holding_bank_id`, then by root asset. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' - name: filter[asset_id] in: query required: false schema: type: string description: | Return only holdings of this root settlement asset (exact match on `root_asset_id`, for example `usd`). An issued asset identifier such as `usd.bank-alpha` matches nothing. example: usd responses: '200': description: One page of liabilities where the calling bank is the issuer (possibly empty). content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/Liability' meta: $ref: '#/components/schemas/PositionReadMetadata' links: $ref: '#/components/schemas/CursorLinks' examples: issuerView: summary: Bank Alpha's liability to Bank Beta (mirror of the exposure example) value: data: - type: liabilities id: v1:11111111-1111-7111-8111-111111111111:22222222-2222-7222-8222-222222222222:usd.bank-alpha attributes: issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_bank_display_name: Bank Alpha issuer_asset_code: usd.bank-alpha holding_bank_id: 22222222-2222-7222-8222-222222222222 holding_bank_display_name: Bank Beta asset_id: usd.bank-alpha root_asset_id: usd available_balance: asset_id: usd value: '4000000' scale: 2 locked_balance: asset_id: usd value: '1000000' scale: 2 balance: asset_id: usd value: '5000000' scale: 2 as_of: '2026-09-25T14:03:11.482Z' relationships: asset: data: type: assets id: usd.bank-alpha links: related: /v1/assets/usd.bank-alpha meta: cut: block_height: '184502' block_hash: 9f2c4e1a7b3d5f60812a4c6e8b0d2f4618a3c5e7092b4d6f8a1c3e5b7d9f0a2c reservation_revision: '9317' completeness: COMPLETE observed_head: '184502' links: self: /v1/liabilities?filter[asset_id]=usd noHolders: summary: No other bank holds the caller's asset value: data: [] links: self: /v1/liabilities '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/mint-limits: get: summary: List mint limits operationId: listMintLimits tags: - MintLimits x-required-scopes: - connector:limits:list description: | List the **mint limits** of the calling bank as **issuer**. A *mint limit* caps how much of its own asset an issuer may have issued to other banks at any one time. There is one limit per issuer and root asset (for example Bank Alpha and `usd`), expressed as a single static `amount`: there is no time window and no automatic reset. The limit changes only through [`PUT /v1/mint-limits/{asset_id}`](./upsert-mint-limit) (or when the platform operator sets it). **How it is enforced.** When the issuer sends an `INTERBANK` or `BANK_ROUTED` transfer that must be funded by issuing new units of its own asset, the platform checks the remaining headroom: the limit minus the issuer's issued balance and any amounts already reserved for transfers in flight. The check runs asynchronously, after the transfer is accepted with `202`: a transfer that would exceed the limit does not succeed (its operation ends `REJECTED` or `FAILED`) and the operation's `result_message` contains `MINT_LIMIT_EXCEEDED`. To check in advance, call [`POST /v1/transfers/preflight`](./preflight-transfer), which reports `blocking_constraint: MINT_LIMIT_EXCEEDED` together with the available headroom. **Who sees what.** Only the calling bank's own limits are returned; another issuer's mint limits are never visible. **Ordering and paging.** Ordered by root asset id, ascending. Follow `links.next` until it is absent. `links.prev` is never returned. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' responses: '200': description: One page of the calling issuer's mint limits. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/MintLimit' links: $ref: '#/components/schemas/CursorLinks' examples: oneLimit: summary: Bank Alpha may have at most 500,000.00 of its `usd` asset issued value: data: - type: mint-limits id: v1:11111111-1111-7111-8111-111111111111:usd.bank-alpha attributes: asset_id: usd.bank-alpha root_asset_id: usd amount: asset_id: usd value: '50000000' scale: 2 links: self: /v1/mint-limits?page[size]=20 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/mint-limits/{asset_id}: parameters: - name: asset_id in: path required: true schema: type: string description: | **Root** asset id the mint limit applies to, for example `usd`. Mint limits are keyed by root asset, not by the issued asset id (`usd.bank-alpha` returns `404`). example: usd get: summary: Get a mint limit operationId: getMintLimit tags: - MintLimits x-required-scopes: - connector:limits:read description: | Fetch the calling issuer's mint limit for one root asset. See [`GET /v1/mint-limits`](./list-mint-limits) for what a mint limit is and how it is enforced. Returns `404` when the calling bank has no mint limit for this root asset. responses: '200': description: The requested mint limit. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/MintLimit' examples: usd: summary: 'Bank Alpha''s `usd` mint limit: 500,000.00' value: data: type: mint-limits id: v1:11111111-1111-7111-8111-111111111111:usd.bank-alpha attributes: asset_id: usd.bank-alpha root_asset_id: usd amount: asset_id: usd value: '50000000' scale: 2 '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: The calling bank has no mint limit for this root asset. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: notFound: summary: No mint limit configured, or an issued asset id was used value: jsonapi: version: '1.1' errors: - status: '404' title: mint limit not found '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' put: summary: Create or replace a mint limit operationId: upsertMintLimit tags: - MintLimits x-required-scopes: - connector:limits:update description: | Create the calling issuer's mint limit for a root asset, or replace it if one exists. **Who may call it.** Only the issuer, for its own asset: the limit is always stored under the authenticated bank. A holder bank cannot set another bank's mint limit. **Upsert semantics.** The request is a full replacement, not a partial update: the new `amount` replaces the previous one, and the first call for a root asset creates the limit. `reason` is recorded with the change but is not returned by `GET`. **Request identity checks** (rejected synchronously with `400`): - `data.id` must be exactly `v1:{your_bank_id}:{asset_id}`, where `asset_id` is the path parameter, for example `v1:11111111-1111-7111-8111-111111111111:usd` (`RESOURCE_ID_MISMATCH`). Note that `GET` returns an `id` built from the issued asset id (`...:usd.bank-alpha`); do not reuse that value here. - `data.attributes.asset_id` must equal the path `asset_id`. **Amount.** `amount.value` is a non-negative integer string in the asset's minor units (`"50000000"` is 500,000.00 for a 2-decimal asset). `amount.scale` must be a non-negative integer; set it to the asset's `decimals`. The value is not rescaled by `scale`. Values above 9223372036854775807 are rejected with `400`. **Asynchronous completion.** Returns `202 Accepted` with an operation. The platform records the new limit on the ledger and the operation reaches `SUCCEEDED` once the ledger confirms it; only then does the new limit apply, and `GET` shows it shortly after. If the ledger rejects the change the operation ends `FAILED` and the previous limit stays in force. Poll `GET /v1/operations/{operation_id}` for the outcome. **In-flight changes.** Only one change per mint limit can be in progress. A new `PUT` for the same root asset while an earlier change has not finished is rejected with `400` and the title `an earlier change to this resource is unresolved`; retry after the earlier operation completes. Transfers are checked against the limit in force when they are processed; lowering the limit does not reverse value already issued. **Retries.** Send an `Idempotency-Key`. Repeating the same key with the same body returns the original `202` document; the same key with a different body returns `409`. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/UpsertMintLimitRequest' examples: setLimit: summary: Set Bank Alpha's `usd` mint limit to 750,000.00 value: data: type: mint-limits id: v1:11111111-1111-7111-8111-111111111111:usd attributes: asset_id: usd amount: value: '75000000' scale: 2 reason: Quarterly treasury update responses: '202': description: | Change accepted for asynchronous processing. `data.id` is the `operation_id`; `data.relationships.resource` identifies the mint limit being changed. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Mint limit change accepted value: jsonapi: version: '1.1' data: type: operations id: 0192b7e4-5c1a-7d3e-9f20-6a7b8c9d0e1f attributes: resource_family: mint-limits state: ACCEPTED relationships: resource: data: type: mint-limits id: v1:11111111-1111-7111-8111-111111111111:usd links: related: /v1/mint-limits/usd links: self: /v1/operations/0192b7e4-5c1a-7d3e-9f20-6a7b8c9d0e1f '400': description: | The request failed a synchronous check: wrong `data.id`, `asset_id` mismatch, malformed amount, blank `reason`, missing `Idempotency-Key`, amount too large, or an earlier change to the same limit still in progress. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: resourceIdMismatch: summary: '`data.id` does not match the caller and path' value: jsonapi: version: '1.1' errors: - status: '400' title: Resource id mismatch code: RESOURCE_ID_MISMATCH detail: data.id must equal "v1:11111111-1111-7111-8111-111111111111:usd" source: pointer: /data/id invalidAmount: summary: '`amount.value` is not an integer string' value: jsonapi: version: '1.1' errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: /data/attributes/amount/value must be a valid non-negative integer source: pointer: /data/attributes/amount/value changeInProgress: summary: An earlier change to this mint limit has not finished value: jsonapi: version: '1.1' errors: - status: '400' title: an earlier change to this resource is unresolved '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/UnprocessableEntity' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/exposure-limits: get: summary: List exposure limits operationId: listExposureLimits tags: - ExposureLimits x-required-scopes: - connector:limits:list description: | List the **exposure limits** set by the calling bank as **holder**. An *exposure limit* is a cap a holder bank sets on itself: the maximum amount of one issuer's asset (for example Bank Alpha's `usd`) that the holder is willing to hold at any one time. There is one limit per holder, issuer, and root asset. Only the holder sets and changes it, through [`PUT /v1/exposure-limits/{issuer_bank_id}/{asset_id}`](./upsert-exposure-limit). **How it is enforced.** When an issuer sends an `INTERBANK` or `BANK_ROUTED` transfer to the holder that would leave the holder with more of that issuer's asset than the limit allows, the transfer does not succeed. The check runs asynchronously after the sender's transfer is accepted with `202`: the sender's operation ends `REJECTED` or `FAILED` and its `result_message` contains `RECEIVER_EXPOSURE_LIMIT_EXCEEDED`. Senders can check in advance with [`POST /v1/transfers/preflight`](./preflight-transfer). The exposure limit is only checked on inbound transfers; lowering it below the current holding does not move or unwind anything already held. **Who sees what.** Only the limits the calling bank has set as holder are returned. Issuers do not see the exposure limits their holders set. **Ordering and paging.** Ordered by issuer bank id, then root asset id, ascending. Follow `links.next` until it is absent. `links.prev` is never returned. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' responses: '200': description: One page of the calling holder's exposure limits. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/ExposureLimit' links: $ref: '#/components/schemas/CursorLinks' examples: oneLimit: summary: Bank Beta holds at most 100,000.00 of Bank Alpha's `usd` value: data: - type: exposure-limits id: v1:22222222-2222-7222-8222-222222222222:11111111-1111-7111-8111-111111111111:usd.bank-alpha attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 issuer_bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd.bank-alpha root_asset_id: usd amount: asset_id: usd value: '10000000' scale: 2 links: self: /v1/exposure-limits?page[size]=20 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/exposure-limits/{issuer_bank_id}/{asset_id}: parameters: - name: issuer_bank_id in: path required: true schema: type: string format: uuid description: | Bank id (a UUID) of the **issuer** whose asset the limit caps; the counterparty as seen by the calling holder. A value that is not a UUID is rejected with `400`. example: 11111111-1111-7111-8111-111111111111 - name: asset_id in: path required: true schema: type: string description: | **Root** asset id the limit applies to, for example `usd`. Exposure limits are keyed by root asset, not by the issuer's issued asset id (`usd.bank-alpha`). example: usd get: summary: Get an exposure limit operationId: getExposureLimit tags: - ExposureLimits x-required-scopes: - connector:limits:read description: | Fetch the exposure limit the calling bank (as holder) has set on one issuer's root asset. See [`GET /v1/exposure-limits`](./list-exposure-limits) for what an exposure limit is and how it is enforced. Returns `404` when the calling bank has set no exposure limit for this issuer and asset. responses: '200': description: The requested exposure limit. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/ExposureLimit' examples: betaOnAlpha: summary: 'Bank Beta''s exposure limit on Bank Alpha''s `usd`: 100,000.00' value: data: type: exposure-limits id: v1:22222222-2222-7222-8222-222222222222:11111111-1111-7111-8111-111111111111:usd.bank-alpha attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 issuer_bank_id: 11111111-1111-7111-8111-111111111111 asset_id: usd.bank-alpha root_asset_id: usd amount: asset_id: usd value: '10000000' scale: 2 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: The calling bank has set no exposure limit for this issuer and root asset. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: notFound: summary: No exposure limit configured value: jsonapi: version: '1.1' errors: - status: '404' title: exposure limit not found '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' put: summary: Create or replace an exposure limit operationId: upsertExposureLimit tags: - ExposureLimits x-required-scopes: - connector:limits:update description: | Create the calling bank's exposure limit on one issuer's root asset, or replace it if one exists. **Who may call it.** Only the holder: the limit is always stored under the authenticated bank as `holder_bank_id`. The issuer named in the path cannot set or see it. **Upsert semantics.** The request is a full replacement, not a partial update: the new `amount` replaces the previous one, and the first call for an issuer and root asset creates the limit. `reason` is recorded with the change but is not returned by `GET`. **Request identity checks** (rejected synchronously with `400`): - `data.attributes.asset_id` must equal the path `asset_id` (`INVALID_FIELD_FORMAT`, pointer `/data/attributes/asset_id`). - `data.id` must be exactly `v1:{your_bank_id}:{issuer_bank_id}:{asset_id}` using the path values, for example `v1:22222222-2222-7222-8222-222222222222:11111111-1111-7111-8111-111111111111:usd` (`RESOURCE_ID_MISMATCH`). Note that `GET` returns an `id` built from the issued asset id (`...:usd.bank-alpha`); do not reuse that value here. **Amount.** `amount.value` is a non-negative integer string in the asset's minor units (`"10000000"` is 100,000.00 for a 2-decimal asset). `amount.scale` must be a non-negative integer; set it to the asset's `decimals`. The value is not rescaled by `scale`. Values above 9223372036854775807 are rejected with `400`. **Asynchronous completion.** Returns `202 Accepted` with an operation. The operation reaches `SUCCEEDED` once the new limit is published to the platform; from then on it applies to new inbound transfers, and `GET` shows it shortly after. Poll `GET /v1/operations/{operation_id}` for the outcome. **In-flight changes.** Only one change per exposure limit can be in progress. A new `PUT` for the same issuer and root asset while an earlier change has not finished is rejected with `400` and the title `an earlier change to this resource is unresolved`. Lowering a limit below the current holding does not unwind anything already held, but blocks further inbound transfers from that issuer until the holding falls below the new cap. **Retries.** Send an `Idempotency-Key`. Repeating the same key with the same body returns the original `202` document; the same key with a different body returns `409`. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/UpsertExposureLimitRequest' examples: setLimit: summary: Bank Beta caps its holding of Bank Alpha's `usd` at 150,000.00 value: data: type: exposure-limits id: v1:22222222-2222-7222-8222-222222222222:11111111-1111-7111-8111-111111111111:usd attributes: asset_id: usd amount: value: '15000000' scale: 2 reason: Counterparty risk review responses: '202': description: | Change accepted for asynchronous processing. `data.id` is the `operation_id`; `data.relationships.resource` identifies the exposure limit being changed. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Exposure limit change accepted value: jsonapi: version: '1.1' data: type: operations id: 0192b7e5-2a3b-7c4d-8e5f-6a7b8c9d0e1f attributes: resource_family: exposure-limits state: ACCEPTED relationships: resource: data: type: exposure-limits id: v1:22222222-2222-7222-8222-222222222222:11111111-1111-7111-8111-111111111111:usd links: related: /v1/exposure-limits/11111111-1111-7111-8111-111111111111/usd links: self: /v1/operations/0192b7e5-2a3b-7c4d-8e5f-6a7b8c9d0e1f '400': description: | The request failed a synchronous check: `asset_id` mismatch, wrong `data.id`, malformed amount, blank `reason`, missing `Idempotency-Key`, amount too large, or an earlier change to the same limit still in progress. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: assetMismatch: summary: Body `asset_id` differs from the path value: jsonapi: version: '1.1' errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: asset_id must match the asset_id path parameter source: pointer: /data/attributes/asset_id resourceIdMismatch: summary: '`data.id` does not match the caller and path' value: jsonapi: version: '1.1' errors: - status: '400' title: Resource id mismatch code: RESOURCE_ID_MISMATCH detail: data.id must equal "v1:22222222-2222-7222-8222-222222222222:11111111-1111-7111-8111-111111111111:usd" source: pointer: /data/id '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/UnprocessableEntity' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/reviews: get: summary: List reviews operationId: listReviews tags: - Reviews x-required-scopes: - connector:reviews:list description: | List approval reviews of a bank. Requires the `connector:reviews:list` scope. Bank callers see the reviews of their own bank; platform operators call the same endpoint with the operator scope and must pass `filter[bank_id]`. ## The four-eyes model A *review* is a human approval gate on one operation. The platform opens it automatically when policy requires sign-off for an operation (for example a high-value transfer). While the review is open the operation waits in state `PENDING_REVIEW`, and webhook subscribers receive `operation.manual_review` and `review.created`. - **Who can decide.** A reviewer submits `APPROVE` or `REJECT` with `POST /v1/reviews/{review_id}/decisions`. A review with `required_role: CHECKER` needs the `connector:reviews:decide` scope; a review with `required_role: APPROVER` accepts that scope or the operator scope `connector:operator:reviews:decide`. - **Maker and checker are different people.** The principal that submitted the operation under review cannot decide on its review (`403`). - **Distinct approvals.** Each principal counts once. The review is `APPROVED` when `recorded_distinct_approvals` reaches `required_distinct_approvals`. A single `REJECT` ends the review as `REJECTED`. - **Effect on the operation.** When approved, the operation continues processing. When rejected, a `PRE_EXECUTION_GATE` review rejects the operation (state `REJECTED`). A review with `required_role: CHECKER` gates the commit of an already initiated ledger transfer (`mode: POST_LEDGER_INITIATION_COMMIT_GATE`); on either decision the operation moves to `PENDING_COMMITS` and the platform completes or unwinds the ledger step. - A review still open when its operation is cancelled becomes `CANCELLED`. Review states: `PENDING` (open), then `APPROVED`, `REJECTED` or `CANCELLED`. `EXPIRED` is reserved and not currently set. ## Filters, ordering and pagination `filter[operation_id]` returns the reviews of one operation. Results are ordered oldest first (by creation time). Use `page[size]` (1 to 200, default 20) and follow `links.next` until it is absent. parameters: - name: filter[bank_id] in: query required: false schema: type: string format: uuid description: | Bank whose reviews to list. Required for platform operator callers; ignored for bank callers, whose own bank is always used. - name: filter[operation_id] in: query required: false schema: type: string format: uuid description: Only reviews of this operation (a UUID). example: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' responses: '200': description: One page of reviews, oldest first. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/Review' links: $ref: '#/components/schemas/CursorLinks' examples: pendingReview: summary: One open review that needs two distinct approvals value: data: - type: reviews id: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f attributes: state: PENDING required_role: APPROVER mode: PRE_EXECUTION_GATE required_distinct_approvals: 2 recorded_distinct_approvals: 1 created_at: '2026-09-26T10:15:03.401886+00:00' updated_at: '2026-09-26T10:19:47.002315+00:00' relationships: operation: data: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 links: related: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 links: self: /v1/reviews/6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f links: self: /v1/reviews?filter[operation_id]=01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/reviews/{review_id}: parameters: - $ref: '#/components/parameters/reviewIdParam' get: summary: Get a review operationId: getReview tags: - Reviews x-required-scopes: - connector:reviews:read description: | Fetch one approval review. Requires the `connector:reviews:read` scope (platform operators use `connector:operator:reviews:read`). A review of another bank is refused with `403 BANK_SCOPE_MISMATCH`. Returns the review `state`, the `required_role` and `mode` that define who may decide and what the decision gates, the number of distinct approvals required and recorded so far, and the operation under review (`relationships.operation`). See `GET /v1/reviews` for the four-eyes rules. Individual decisions are not returned. responses: '200': description: The requested review. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/Review' examples: approved: summary: A review approved by two distinct reviewers value: data: type: reviews id: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f attributes: state: APPROVED required_role: APPROVER mode: PRE_EXECUTION_GATE required_distinct_approvals: 2 recorded_distinct_approvals: 2 created_at: '2026-09-26T10:15:03.401886+00:00' updated_at: '2026-09-26T10:21:09.648201+00:00' relationships: operation: data: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 links: related: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 links: self: /v1/reviews/6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/reviews/{review_id}/decisions: parameters: - $ref: '#/components/parameters/reviewIdParam' post: summary: Submit a review decision operationId: submitReviewDecision tags: - Reviews x-required-scopes: - connector:reviews:decide description: | Approve or reject an open (`PENDING`) review. Requires the `connector:reviews:decide` scope (platform operators use `connector:operator:reviews:decide`, which is accepted only for reviews with `required_role: APPROVER`) and an `Idempotency-Key` header. **Checks made before the decision is accepted:** - the review exists (`404`) and belongs to the caller's bank (`403 BANK_SCOPE_MISMATCH`); - the review is still `PENDING` (`409` otherwise); - the caller holds the scope that the review's `required_role` requires; - the caller is **not** the principal that submitted the operation under review (`403`, "review can't be self-approved"). This enforces the maker/checker rule. **What the decision does.** The response is `202 Accepted` with an operation that records the decision. - `APPROVE` adds one distinct approval. When `recorded_distinct_approvals` reaches `required_distinct_approvals`, the review becomes `APPROVED` and the operation under review continues (`PROCESSING`, or `PENDING_COMMITS` for a `CHECKER` review). Otherwise the review stays `PENDING` and subscribers receive `review.updated`. - `REJECT` immediately ends the review as `REJECTED`. The operation under review becomes `REJECTED`, or for a `CHECKER` review moves to `PENDING_COMMITS` so the platform can unwind the initiated ledger step. Each principal decides once per review: a second, different decision by the same principal is not counted and its tracking operation does not succeed. Retrying the same request with the same `Idempotency-Key` returns the original `202` document. `comment`, when sent, must not be empty or only whitespace. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/CreateReviewDecisionRequest' examples: approve: summary: Approve with a comment value: data: type: review-decisions attributes: decision: APPROVE comment: Beneficiary and amount checked against the payment instruction. reject: summary: Reject value: data: type: review-decisions attributes: decision: REJECT comment: Amount does not match the approved invoice. responses: '202': description: Accepted. The operation (`data.id`) tracks the decision. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Decision accepted value: jsonapi: version: '1.1' data: type: operations id: 0192f1b1-f0e1-7d2c-8b3a-49586a7b8c9d attributes: resource_family: reviews state: ACCEPTED '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': description: | The caller lacks the decide scope, the review belongs to another bank (`BANK_SCOPE_MISMATCH`), or the caller submitted the operation under review. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: selfApproval: summary: The maker tries to approve their own operation value: jsonapi: version: '1.1' errors: - status: '403' title: review can't be self-approved otherBank: summary: The review belongs to another bank value: jsonapi: version: '1.1' errors: - status: '403' title: Bank scope mismatch code: BANK_SCOPE_MISMATCH '404': $ref: '#/components/responses/NotFound' '409': description: | The review is no longer `PENDING`, or the `Idempotency-Key` was reused with a different body or is still being processed. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: reviewClosed: summary: The review was already approved, rejected or cancelled value: jsonapi: version: '1.1' errors: - status: '409' title: cannot submit review for state different than PENDING idempotencyConflict: summary: Same Idempotency-Key, different body value: jsonapi: version: '1.1' errors: - status: '409' title: Idempotency conflict code: IDEMPOTENCY_CONFLICT '422': $ref: '#/components/responses/UnprocessableEntity' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/webhooks: get: summary: List webhook subscriptions operationId: listWebhooks tags: - Webhooks x-required-scopes: - connector:webhooks:list description: | List the webhook subscriptions registered by the calling bank. Requires the `connector:webhooks:list` scope. Only subscriptions owned by the caller's bank are returned. A *webhook subscription* tells the platform to POST signed JSON event notifications to an HTTPS URL that your bank operates. Each subscription has: - `url`: the HTTPS endpoint that receives deliveries; - `event_types`: the event types it receives (exact, case-sensitive matches; see `POST /v1/webhooks` for the full list and the delivery contract); - `status`: `PENDING_VERIFICATION` until the endpoint has acknowledged the `webhook.verification` handshake event, then `ACTIVE`. Only `ACTIVE` subscriptions receive business events; - an optional bound *auth profile* (`relationships.auth_profile`), which makes the platform obtain an OAuth2 access token from your token endpoint and send it as `Authorization: Bearer` on every delivery. Signing secrets are never returned. **Ordering and pagination.** Results are ordered newest first (by `id`, a time-ordered UUID). Use `page[size]` (1 to 200, default 20) and follow `links.next` until it is absent. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' responses: '200': description: One page of the calling bank's webhook subscriptions, newest first. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/Webhook' links: $ref: '#/components/schemas/CursorLinks' examples: twoSubscriptions: summary: One active subscription and one awaiting verification (re-verifying after an auth profile bind) value: data: - type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 attributes: url: https://hooks.bank-alpha.example/lyriq/events event_types: - operation.succeeded - operation.failed - operation.rejected - review.created - transfer.received delivery_format: jsonapi status: ACTIVE relationships: deliveries: links: related: /v1/webhooks/0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 auth_profile: data: type: auth-profiles id: 0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 links: self: /v1/webhooks/0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 - type: webhooks id: 0192f19f-0a1b-7c2d-8e3f-40516273a4b5 attributes: url: https://hooks.bank-alpha.example/lyriq/screening event_types: - beneficiary.screening.requested delivery_format: jsonapi status: PENDING_VERIFICATION relationships: deliveries: links: related: /v1/webhooks/0192f19f-0a1b-7c2d-8e3f-40516273a4b5 auth_profile: data: type: auth-profiles id: 0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 links: self: /v1/webhooks/0192f19f-0a1b-7c2d-8e3f-40516273a4b5 links: self: /v1/webhooks?page[size]=20 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' post: summary: Create a webhook subscription operationId: createWebhook tags: - Webhooks x-required-scopes: - connector:webhooks:create description: | Register an HTTPS endpoint to receive signed event notifications. Requires the `connector:webhooks:create` scope and an `Idempotency-Key` header. ## Request - `url` must be an absolute `https://` URL with a host. Plain `http://` is rejected. - `delivery_format` must be `jsonapi`. `iso20022+xml` is reserved and currently rejected with `400 INVALID_FIELD_FORMAT`. - `security.signature.secret` is a shared secret that **your bank chooses** (the platform does not generate one). It is write-only and is never returned. Use a long random value (for example 32 random bytes, hex or base64 encoded). The platform uses the UTF-8 bytes of this string, exactly as sent, as the HMAC key. - `security.signature.secret_version` is your label for this secret (1 to 64 printable ASCII characters, no spaces, for example `v1`). It is echoed on every delivery in the `X-DAN-Secret-Version` header so you know which secret to verify with. - `event_types` lists the event types to receive. Values are matched exactly. **An empty array receives no business events** (only the verification handshake). Values are not validated: a misspelled type is accepted but never matches. ## What happens next The response is `202 Accepted` with an operation. The subscription is created immediately: its `webhook_id` is the `relationships.resource.data.id` of `GET /v1/operations/{operation_id}`. The new subscription starts in `PENDING_VERIFICATION` and the platform immediately sends one `webhook.verification` event to `url` (the *verification handshake*). When your endpoint answers it with any `2xx` status, the subscription becomes `ACTIVE` and starts receiving the event types it subscribed to. Events that occur while a subscription is not `ACTIVE` are not delivered to it later. Retrying the request with the same `Idempotency-Key` and body returns the same `202` document and does not create a second subscription. ## Event types | Event type | Sent when | `resource.type` | | --- | --- | --- | | `operation.updated` | an operation is accepted, starts processing, or starts waiting for ledger commits | `operations` | | `operation.manual_review` | an operation starts waiting for an approval review (operation state `PENDING_REVIEW`) | `operations` | | `operation.succeeded` | an operation completes successfully | `operations` | | `operation.rejected` | an operation is rejected (for example by a review) | `operations` | | `operation.failed` | an operation fails | `operations` | | `review.created` | a new approval review is opened | `reviews` | | `review.updated` | an approval was recorded but more approvals are still needed | `reviews` | | `review.approved` | a review reached its approval threshold | `reviews` | | `review.rejected` | a review was rejected | `reviews` | | `transfer.received` | a transfer to your bank (as destination holder bank) completed; carries the parties' registered accounts and any sender (`originator`) and beneficiary details the sender submitted, for your bank to screen, resolve and credit | `transfers` | | `beneficiary.screening.requested` | your bank is asked to screen the beneficiary of an incoming transfer | `transfers` | | `webhook.verification` | handshake, sent to a subscription in `PENDING_VERIFICATION`; not subject to `event_types` | `webhooks` | | `webhook.test` | synthetic test event requested through `POST /v1/webhooks/{webhook_id}/test` | `webhooks` | Each business event is delivered to every `ACTIVE` subscription of the bank the event belongs to that lists its type. The exact body of each event is shown in the `webhookDelivery` callback below. ## Delivery request Each delivery is an HTTPS `POST` to `url` with a compact JSON body (`Content-Type: application/json`) and these headers: | Header | Value | | --- | --- | | `X-DAN-Event-Id` | UUID of the event. Part of the signed string. | | `X-DAN-Event-Type` | Event type, for example `operation.succeeded`. | | `X-DAN-Webhook-Id` | UUID of the subscription. | | `X-DAN-Delivery-Id` | UUID of this delivery (one per event and subscription; the same on every retry). | | `X-DAN-Timestamp` | Time the request was signed, RFC 3339 in UTC, for example `2026-09-26T10:15:30.482913+00:00`. New on every attempt. | | `X-DAN-Secret-Version` | `secret_version` of the secret that produced the signature. | | `X-DAN-Signature` | `v1=` followed by the lowercase hex HMAC-SHA256 signature. | | `Authorization` | `Bearer `, only when an auth profile is bound. | The platform only connects over HTTPS (TLS 1.2 or later, certificate verified), does not follow redirects, and refuses URLs that resolve to loopback, link-local, multicast or unspecified addresses. Connect timeout is 10 seconds; the whole request must complete within 30 seconds. ## Verifying the signature The signature is `HMAC-SHA256(key = secret, message = X-DAN-Timestamp + "." + X-DAN-Event-Id + "." + raw request body)`, hex encoded in lowercase and prefixed with `v1=`. Always compute it over the **raw body bytes as received**, before any JSON parsing or re-serialisation. Compare in constant time. Pick the secret by the `X-DAN-Secret-Version` header. The platform does not reject old timestamps for you; we recommend that you reject requests whose `X-DAN-Timestamp` is more than 5 minutes from your clock, to limit replay (retries are re-signed with a fresh timestamp, so this does not break retries). Python: ```python import hmac, hashlib from datetime import datetime, timezone SECRETS = {"v1": b"your-webhook-secret"} # keyed by secret_version def verify(headers, raw_body: bytes) -> bool: secret = SECRETS.get(headers["X-DAN-Secret-Version"]) if secret is None: return False ts = headers["X-DAN-Timestamp"] age = datetime.now(timezone.utc) - datetime.fromisoformat(ts) if abs(age.total_seconds()) > 300: return False message = ts.encode() + b"." + headers["X-DAN-Event-Id"].encode() + b"." + raw_body expected = "v1=" + hmac.new(secret, message, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, headers["X-DAN-Signature"]) ``` Node.js: ```js const crypto = require("crypto"); const SECRETS = { v1: "your-webhook-secret" }; // keyed by secret_version function verify(headers, rawBody /* Buffer */) { const secret = SECRETS[headers["x-dan-secret-version"]]; if (!secret) return false; const ts = headers["x-dan-timestamp"]; if (Math.abs(Date.now() - Date.parse(ts)) > 5 * 60 * 1000) return false; const expected = "v1=" + crypto.createHmac("sha256", secret) .update(`${ts}.${headers["x-dan-event-id"]}.`) .update(rawBody) .digest("hex"); const given = String(headers["x-dan-signature"] || ""); return given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected)); } ``` ## Responding, retries and duplicates - Answer with any `2xx` status to acknowledge. The response body is ignored. Respond quickly and process asynchronously. - `429`, any `5xx`, a timeout or a connection error is retried. The retry schedule is set by the platform deployment; in the standard configuration a delivery is attempted at most 5 times, with increasing delays between attempts (currently 1, 5, 15 and 30 minutes). After the last failed attempt the delivery is marked `FAILED` and is not retried again. - Any other status (`3xx`, `400`, `401`, `403`, `404` and other `4xx`) is a permanent failure: the delivery is marked `FAILED` immediately and not retried. - Delivery is **at least once**: the same event can arrive more than once (for example after a timeout on a request you actually processed). De-duplicate on the body `id` (or `X-DAN-Delivery-Id`), which stays the same across retries. - **Ordering is not guaranteed**, across events or even for the same resource, because retries interleave with newer events. Use `occurred_at` to order events, and fetch the resource (`data.links.self`) when you need its current state. - Inspect delivery history with `GET /v1/webhooks/deliveries?filter[webhook_id]=...`. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/CreateWebhookRequest' examples: operationsAndTransfers: summary: Subscribe to operation outcomes, reviews and incoming transfers value: data: type: webhooks attributes: url: https://hooks.bank-alpha.example/lyriq/events event_types: - operation.succeeded - operation.failed - operation.rejected - review.created - transfer.received delivery_format: jsonapi security: signature: algorithm: HMAC_SHA256 secret: 3f9c1e7a5b2d48e6a0c4f81b9d27e53c6a1f0b8e4d9c27a5 secret_version: v1 screeningOnly: summary: A dedicated endpoint for beneficiary screening requests value: data: type: webhooks attributes: url: https://hooks.bank-alpha.example/lyriq/screening event_types: - beneficiary.screening.requested delivery_format: jsonapi security: signature: algorithm: HMAC_SHA256 secret: 8b27d04e9f1a6c35e7b0d2f49a8c61e5b3d07f2a9c4e18b6 secret_version: 2026-09-screening responses: '202': description: | Accepted. The operation (`data.id`) tracks the creation; poll `GET /v1/operations/{operation_id}` to read the new `webhook_id` from `relationships.resource`. The verification handshake is sent right after. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Webhook creation accepted value: jsonapi: version: '1.1' data: type: operations id: 0192f1a1-2d3e-7f40-8a51-b6c7d8e9f001 attributes: resource_family: webhooks state: ACCEPTED '400': description: | The request is malformed or a field fails its format rule: the URL is not a valid `https://` URL with a host, `delivery_format` is not `jsonapi`, the secret is empty, `secret_version` is empty, too long or contains spaces, or the `Idempotency-Key` header is missing. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: insecureUrl: summary: Callback URL is not HTTPS value: jsonapi: version: '1.1' errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: /data/attributes/url must use https:// source: pointer: /data/attributes/url unsupportedFormat: summary: delivery_format is iso20022+xml value: jsonapi: version: '1.1' errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: delivery_format must be JsonApi source: pointer: /data/attributes/delivery_format '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/UnprocessableEntity' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' callbacks: webhookDelivery: '{$request.body#/data/attributes/url}': post: summary: Event delivery to your webhook endpoint description: | The request the platform sends to your subscription `url` for every event. Verify `X-DAN-Signature` over the raw body before trusting it (see `POST /v1/webhooks` for the algorithm and code samples), then answer `2xx`. Every event shares one envelope: - `id`: event identifier, `evt_` followed by a UUID. Stable across retries of the same delivery; use it to de-duplicate. - `type`: the event type (also sent in `X-DAN-Event-Type`). - `occurred_at`: when the underlying change happened (RFC 3339, UTC). - `bank_id`: the bank the event belongs to (your bank). - `resource`: `type` and `id` of the resource the event is about. - `data`: a JSON:API-style resource object (`type`, `id`, `attributes`, and for most events `links.self`, the API path to fetch the full resource). - `meta.delivery_semantics`: always `AT_LEAST_ONCE`. Keys are serialised in alphabetical order without whitespace. Do not rely on key order; always verify the signature over the bytes you received. parameters: - name: X-DAN-Event-Id in: header required: true description: UUID of the event. Part of the signed string. schema: type: string format: uuid example: 0192f1af-6e7d-7c8b-9a0f-1e2d3c4b5a69 - name: X-DAN-Event-Type in: header required: true description: Event type, identical to the body `type`. schema: type: string example: operation.succeeded - name: X-DAN-Webhook-Id in: header required: true description: UUID of the webhook subscription this delivery is for. schema: type: string format: uuid example: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 - name: X-DAN-Delivery-Id in: header required: true description: | UUID of the delivery (one per event and subscription). The same on every retry; matches `id` in `GET /v1/webhooks/deliveries`. schema: type: string format: uuid example: 0192f1b0-3c4d-7e5f-8a6b-7c8d9e0f1a2b - name: X-DAN-Timestamp in: header required: true description: | Time this attempt was signed, RFC 3339 in UTC with fractional seconds and a `+00:00` offset. New on every attempt. Part of the signed string; use it verbatim. schema: type: string example: '2026-09-26T10:15:31.204518+00:00' - name: X-DAN-Secret-Version in: header required: true description: The `secret_version` of the signing secret used for this attempt. schema: type: string example: v1 - name: X-DAN-Signature in: header required: true description: | `v1=` followed by 64 lowercase hex characters: HMAC-SHA256 of `{X-DAN-Timestamp}.{X-DAN-Event-Id}.{raw body}`. schema: type: string pattern: ^v1=[0-9a-f]{64}$ example: v1=5d0b6f3c2a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a29181716151413121110 - name: Authorization in: header required: false description: | `Bearer `, present only when the subscription is bound to an auth profile. The token comes from your own token endpoint. schema: type: string example: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.example.signature requestBody: required: true content: application/json: schema: oneOf: - $ref: '#/components/schemas/OperationWebhookEvent' - $ref: '#/components/schemas/ReviewWebhookEvent' - $ref: '#/components/schemas/TransferReceivedWebhookEvent' - $ref: '#/components/schemas/BeneficiaryScreeningWebhookEvent' - $ref: '#/components/schemas/WebhookTestEvent' - $ref: '#/components/schemas/WebhookVerificationEvent' examples: operationUpdated: summary: operation.updated (operation started processing) value: id: evt_0192f1ae-1a2b-7c3d-8e4f-5a6b7c8d9e01 type: operation.updated occurred_at: '2026-09-26T10:15:02.118204+00:00' bank_id: 11111111-1111-7111-8111-111111111111 resource: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 data: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 attributes: state: PROCESSING operation_type: null links: self: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 meta: delivery_semantics: AT_LEAST_ONCE operationManualReview: summary: operation.manual_review (operation waits for an approval review) value: id: evt_0192f1ae-2b3c-7d4e-8f50-6b7c8d9e0f12 type: operation.manual_review occurred_at: '2026-09-26T10:15:03.402117+00:00' bank_id: 11111111-1111-7111-8111-111111111111 resource: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 data: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 attributes: state: PENDING_REVIEW operation_type: null links: self: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 meta: delivery_semantics: AT_LEAST_ONCE operationSucceeded: summary: operation.succeeded value: id: evt_0192f1af-6e7d-7c8b-9a0f-1e2d3c4b5a69 type: operation.succeeded occurred_at: '2026-09-26T10:15:30.998071+00:00' bank_id: 11111111-1111-7111-8111-111111111111 resource: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 data: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 attributes: state: SUCCEEDED operation_type: null links: self: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 meta: delivery_semantics: AT_LEAST_ONCE operationRejected: summary: operation.rejected value: id: evt_0192f1af-7f8e-7d9c-8b1a-2f3e4d5c6b7a type: operation.rejected occurred_at: '2026-09-26T10:22:14.530962+00:00' bank_id: 11111111-1111-7111-8111-111111111111 resource: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 data: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 attributes: state: REJECTED operation_type: null links: self: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 meta: delivery_semantics: AT_LEAST_ONCE operationFailed: summary: operation.failed value: id: evt_0192f1af-809f-7eab-8c2b-3a4f5e6d7c8b type: operation.failed occurred_at: '2026-09-26T10:16:05.771430+00:00' bank_id: 11111111-1111-7111-8111-111111111111 resource: type: operations id: 0192f1a5-9e0f-7a1b-8c2d-3e4f5a6b7c8d data: type: operations id: 0192f1a5-9e0f-7a1b-8c2d-3e4f5a6b7c8d attributes: state: FAILED operation_type: null links: self: /v1/operations/0192f1a5-9e0f-7a1b-8c2d-3e4f5a6b7c8d meta: delivery_semantics: AT_LEAST_ONCE reviewCreated: summary: review.created (a new approval review is open) value: id: evt_0192f1ae-3c4d-7e5f-8061-7c8d9e0f1a23 type: review.created occurred_at: '2026-09-26T10:15:03.401886+00:00' bank_id: 11111111-1111-7111-8111-111111111111 resource: type: reviews id: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f data: type: reviews id: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f attributes: state: PENDING target_operation_id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 links: self: /v1/reviews/6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f meta: delivery_semantics: AT_LEAST_ONCE reviewUpdated: summary: review.updated (one approval recorded, more needed) value: id: evt_0192f1b1-0a1b-7c2d-8e3f-4a5b6c7d8e90 type: review.updated occurred_at: '2026-09-26T10:19:47.002315+00:00' bank_id: 11111111-1111-7111-8111-111111111111 resource: type: reviews id: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f data: type: reviews id: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f attributes: state: PENDING target_operation_id: null links: self: /v1/reviews/6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f meta: delivery_semantics: AT_LEAST_ONCE reviewApproved: summary: review.approved value: id: evt_0192f1b2-1b2c-7d3e-8f40-5b6c7d8e9fa1 type: review.approved occurred_at: '2026-09-26T10:21:09.648201+00:00' bank_id: 11111111-1111-7111-8111-111111111111 resource: type: reviews id: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f data: type: reviews id: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f attributes: state: APPROVED target_operation_id: null links: self: /v1/reviews/6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f meta: delivery_semantics: AT_LEAST_ONCE reviewRejected: summary: review.rejected value: id: evt_0192f1b2-2c3d-7e4f-8051-6c7d8e9fa0b2 type: review.rejected occurred_at: '2026-09-26T10:22:14.529877+00:00' bank_id: 11111111-1111-7111-8111-111111111111 resource: type: reviews id: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f data: type: reviews id: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f attributes: state: REJECTED target_operation_id: null links: self: /v1/reviews/6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f meta: delivery_semantics: AT_LEAST_ONCE transferReceived: summary: transfer.received (sent to the destination holder bank) value: id: evt_0192f1b3-3d4e-7f50-8162-7d8e9fa0b1c3 type: transfer.received occurred_at: '2026-09-26T10:15:30.997514+00:00' bank_id: 22222222-2222-7222-8222-222222222222 resource: type: transfers id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 data: type: transfers id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 attributes: external_transfer_reference: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 receiver_bank_id: 22222222-2222-7222-8222-222222222222 currency: usd amount: 125000 decimals: 2 iso20022_ref: null source_external_account_id: CBS-ACC-2026-000123 originator: name: ACME TREASURY LLC account: scheme: OTHER value: '0012345678' destination_external_account_id: null beneficiary: name: JOHN DOE account: scheme: IBAN value: DE89370400440532013000 bic: BETAUS33 completed_at: '2026-09-26T10:15:30.997514+00:00' meta: delivery_semantics: AT_LEAST_ONCE beneficiaryScreeningRequested: summary: beneficiary.screening.requested value: id: evt_0192f1b3-4e5f-7061-8273-8e9fa0b1c2d4 type: beneficiary.screening.requested occurred_at: '2026-09-26T10:15:04.210338+00:00' bank_id: 22222222-2222-7222-8222-222222222222 resource: type: transfers id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 data: type: transfers id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 attributes: uetr: 8a562c67-ca16-48ba-b074-65581be6f001 source_message_sha256: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 screening_window_secs: PT300S links: self: /v1/transfers/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 meta: delivery_semantics: AT_LEAST_ONCE webhookVerification: summary: webhook.verification (handshake for a new or re-verified subscription) value: id: evt_0192f1a1-3e4f-7051-8162-c7d8e9f0a1b2 type: webhook.verification occurred_at: '2026-09-26T09:58:12.664020+00:00' bank_id: 11111111-1111-7111-8111-111111111111 resource: type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 data: type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 attributes: {} meta: delivery_semantics: AT_LEAST_ONCE webhookTest: summary: webhook.test (synthetic test event) value: id: evt_0192f1c0-5f60-7172-8384-9fa0b1c2d3e5 type: webhook.test occurred_at: '2026-09-26T11:02:40.117902+00:00' bank_id: 11111111-1111-7111-8111-111111111111 resource: type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 data: type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 attributes: {} meta: delivery_semantics: AT_LEAST_ONCE responses: '429': description: Retried later, until the attempt limit is reached. 2XX: description: | Any `2xx` status acknowledges the event. The body is ignored. For a `webhook.verification` event this activates the subscription. 5XX: description: Retried later, until the attempt limit is reached. default: description: | Any other status (including `3xx` redirects and `4xx` other than `429`) is a permanent failure: the delivery is marked `FAILED` and not retried. /v1/webhooks/{webhook_id}: parameters: - $ref: '#/components/parameters/webhookIdParam' get: summary: Get a webhook subscription operationId: getWebhook tags: - Webhooks x-required-scopes: - connector:webhooks:read description: | Fetch one webhook subscription by `webhook_id`. Requires the `connector:webhooks:read` scope. Returns the callback `url`, the subscribed `event_types`, the `delivery_format`, the current `status` and the bound auth profile (`relationships.auth_profile`, `data: null` when none is bound). The signing secret is never returned. Check `status` after creating a subscription or binding an auth profile: it moves from `PENDING_VERIFICATION` to `ACTIVE` once your endpoint has answered the `webhook.verification` handshake with a `2xx` status. responses: '200': description: The requested webhook subscription. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/Webhook' examples: active: summary: An active subscription bound to an auth profile value: data: type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 attributes: url: https://hooks.bank-alpha.example/lyriq/events event_types: - operation.succeeded - operation.failed - operation.rejected - review.created - transfer.received delivery_format: jsonapi status: ACTIVE relationships: deliveries: links: related: /v1/webhooks/0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 auth_profile: data: type: auth-profiles id: 0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 links: self: /v1/webhooks/0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' put: summary: Update a webhook subscription operationId: updateWebhook tags: - Webhooks x-required-scopes: - connector:webhooks:update description: | Request a change to a subscription's `url`, `event_types`, `delivery_format` or `status` (`ACTIVE` or `DISABLED`). Requires the `connector:webhooks:update` scope and an `Idempotency-Key` header. `data.id` must be the `webhook_id` of the path. Send only the attributes to change; at least one is required, otherwise the request is rejected with `422 MISSING_ANY_OF`. A new `url` must be an absolute `https://` URL with a host. `delivery_format`, when sent, must be `jsonapi`. `security.transport.mtls_required` is accepted but currently has no effect: the platform does not present a client certificate on deliveries. Returns `202 Accepted` with an operation; follow it with `GET /v1/operations/{operation_id}`. This request does not change the signing secret; use `POST /v1/webhooks/{webhook_id}/rotate-secret` for that. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/UpdateWebhookRequest' examples: changeEventTypes: summary: Replace the event type filter value: data: type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 attributes: event_types: - operation.succeeded - operation.failed - operation.rejected - operation.manual_review - review.created - review.approved - review.rejected - transfer.received disable: summary: Disable the subscription value: data: type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 attributes: status: DISABLED responses: '202': description: Accepted. The operation (`data.id`) tracks the update. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Webhook update accepted value: jsonapi: version: '1.1' data: type: operations id: 0192f1c1-6a7b-7c8d-8e9f-a0b1c2d3e4f5 attributes: resource_family: webhooks state: ACCEPTED '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '422': description: | The body does not match the schema (for example an unknown attribute), or no attribute to update was sent (`MISSING_ANY_OF`). content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: nothingToUpdate: summary: No attribute was sent value: jsonapi: version: '1.1' errors: - status: '422' title: At least one field is required code: MISSING_ANY_OF detail: at least one of /data/attributes/url, /data/attributes/event_types, /data/attributes/delivery_format, /data/attributes/status, /data/attributes/security is required '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/webhooks/{webhook_id}/rotate-secret: parameters: - $ref: '#/components/parameters/webhookIdParam' post: summary: Rotate the webhook signing secret operationId: rotateWebhookSecret tags: - Webhooks x-required-scopes: - connector:webhooks:rotate-secret description: | Register a new signing secret for a subscription and schedule when it takes over. Requires the `connector:webhooks:rotate-secret` scope and an `Idempotency-Key` header. Like the initial secret, the new `secret` is chosen by your bank, is write-only and is never returned. Give it a new `secret_version` label (1 to 64 printable ASCII characters without spaces) so you can tell the secrets apart. **How the switch works.** Every delivery is signed with exactly one secret, the *active* one, and names it in the `X-DAN-Secret-Version` header. - Before `activate_at`, deliveries keep using the current secret. - From `activate_at` (a time in the past or `now` means immediately) the new secret becomes active. The platform checks for due secrets about every 10 seconds and caches the active secret for up to 30 seconds, so deliveries signed with the previous version can still arrive for up to about a minute after `activate_at`. Retries of older events are re-signed with whichever secret is active at the time of the attempt. - The previous secret is kept as *retiring* for `grace_period_seconds` after activation and then expires. Send `0` to use the platform default of 86400 seconds (24 hours). Negative values are rejected. **Zero-downtime rotation.** Deploy the new secret to your verifier first, keyed by its `secret_version`, while still accepting the old one. Then call this endpoint. Choose the secret by the `X-DAN-Secret-Version` header of each request, and remove the old secret once you no longer receive deliveries signed with it. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/RotateWebhookSecretRequest' examples: scheduled: summary: Activate a new secret at a planned time, keep the old one for 24 hours value: data: type: webhook-secret-rotations attributes: secret: c41e9a07b3d25f86e1a4c7092bd53e6f8a0c17d94be25a36 secret_version: v2 activate_at: '2026-10-01T06:00:00Z' grace_period_seconds: 86400 immediate: summary: Activate now with the default grace period value: data: type: webhook-secret-rotations attributes: secret: 5b8f2e61d09c4a73b6e1f8a2d45c90e7b3a61f2d8c07e94a secret_version: v3 activate_at: '2026-09-26T12:00:00Z' grace_period_seconds: 0 responses: '202': description: | Accepted. The new secret version is stored and activates at `activate_at`. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Rotation accepted value: jsonapi: version: '1.1' data: type: operations id: 0192f1c2-7b8c-7d9e-8fa0-b1c2d3e4f5a6 attributes: resource_family: webhooks state: ACCEPTED '400': description: | `secret` is empty, `secret_version` is invalid, `grace_period_seconds` is negative, `webhook_id` is not a UUID, or the `Idempotency-Key` header is missing. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: badVersion: summary: secret_version contains a space value: jsonapi: version: '1.1' errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: secret_version must contain only printable ASCII characters without spaces source: pointer: /data/attributes/secret_version '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/UnprocessableEntity' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/webhooks/{webhook_id}/test: parameters: - $ref: '#/components/parameters/webhookIdParam' post: summary: Send a test event to a webhook operationId: sendWebhookTest tags: - Webhooks x-required-scopes: - connector:webhooks:test description: | Request a synthetic `webhook.test` event for a subscription, to check connectivity, TLS, bearer-token handling and signature verification before relying on real events. Requires the `connector:webhooks:test` scope and an `Idempotency-Key` header. `data.attributes.event_type` is required (the request is rejected with `400 MISSING_FIELD` without it). It must be a supported webhook event type. It is recorded with the request but does not change the delivered body: the test event always has `type: webhook.test` and empty `data.attributes` (see the `webhookDelivery` callback of `POST /v1/webhooks`). A test event is signed, and carries a bearer token when an auth profile is bound, exactly like a real event. It does not change the subscription `status`. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: type: object required: - data properties: data: type: object required: - type - attributes properties: type: type: string enum: - webhook-tests attributes: type: object additionalProperties: false x-deny-unknown-fields: true properties: event_type: $ref: '#/components/schemas/WebhookEventType' examples: test: summary: Request a test delivery value: data: type: webhook-tests attributes: event_type: operation.updated responses: '202': description: Accepted. The operation (`data.id`) tracks the test request. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Test request accepted value: jsonapi: version: '1.1' data: type: operations id: 0192f1c0-4e5f-7061-8273-9fa0b1c2d3e4 attributes: resource_family: webhooks state: ACCEPTED '400': description: | `data.attributes.event_type` is missing, or the `Idempotency-Key` header is missing. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: missingEventType: summary: event_type not sent value: jsonapi: version: '1.1' errors: - status: '400' title: Required field missing code: MISSING_FIELD detail: /data/attributes/event_type is required source: pointer: /data/attributes/event_type '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '422': description: The request has an invalid shape or an unsupported event type. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/webhooks/deliveries: get: summary: List webhook deliveries operationId: listWebhookDeliveries tags: - Webhooks x-required-scopes: - connector:webhook_deliveries:list description: | List the deliveries of one webhook subscription. Requires the `connector:webhook_deliveries:list` scope. `filter[webhook_id]` is required. A *delivery* is the platform's attempt to send one event to one subscription, including all its retries. Its `id` is the `X-DAN-Delivery-Id` header of the requests, and its `event_id` is the UUID in `X-DAN-Event-Id` (the body `id` is `evt_` followed by the same UUID). Use this list to find events your endpoint did not acknowledge. `status` values: - `RETRYING`: not yet acknowledged; waiting for its first attempt, in progress, or scheduled for a retry; - `DELIVERED`: your endpoint answered `2xx`; - `FAILED`: permanently failed (a non-retryable status, or the attempt limit was reached). The platform does not send it again. Verification handshakes (`webhook.verification`) are not listed. **Filters** are combined with AND. `filter[created_after]` and `filter[created_before]` compare against the time the delivery was created and are exclusive. **Ordering and pagination.** Newest first (by `id`, a time-ordered UUID). Use `page[size]` (1 to 200, default 20) and follow `links.next` until it is absent. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' - name: filter[webhook_id] in: query required: true schema: type: string format: uuid description: The webhook subscription whose deliveries to list (a UUID). Required. example: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 - name: filter[event_type] in: query required: false schema: $ref: '#/components/schemas/WebhookEventType' description: Only deliveries of this event type (exact match), for example `operation.succeeded`. example: operation.succeeded - name: filter[status] in: query required: false schema: type: string enum: - DELIVERED - FAILED - RETRYING description: Only deliveries in this status. - $ref: '#/components/parameters/filterCreatedAfterParam' - $ref: '#/components/parameters/filterCreatedBeforeParam' responses: '200': description: One page of deliveries for the subscription, newest first. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/WebhookDelivery' links: $ref: '#/components/schemas/CursorLinks' examples: mixed: summary: One delivered event and one waiting for a retry value: data: - type: webhook-deliveries id: 0192f1b0-3c4d-7e5f-8a6b-7c8d9e0f1a2b attributes: event_id: 0192f1af-6e7d-7c8b-9a0f-1e2d3c4b5a69 event_type: operation.succeeded status: DELIVERED attempt_count: 1 last_attempt_at: '2026-09-26T10:15:31.402117+00:00' relationships: webhook: data: type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 links: self: /v1/webhook/deliveries/0192f1b0-3c4d-7e5f-8a6b-7c8d9e0f1a2b - type: webhook-deliveries id: 0192f1ae-4d5e-7f60-8172-8d9e0f1a2b3c attributes: event_id: 0192f1ae-1a2b-7c3d-8e4f-5a6b7c8d9e01 event_type: operation.updated status: RETRYING attempt_count: 2 last_attempt_at: '2026-09-26T10:16:02.915630+00:00' relationships: webhook: data: type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 links: self: /v1/webhook/deliveries/0192f1ae-4d5e-7f60-8172-8d9e0f1a2b3c links: self: /v1/webhooks/deliveries?filter[webhook_id]=0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6&page[size]=2 next: /v1/webhooks/deliveries?filter%5Bwebhook_id%5D=0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6&page%5Bsize%5D=2&page%5Bcursor%5D=0192f1ae-4d5e-7f60-8172-8d9e0f1a2b3c '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/webhooks/deliveries/{delivery_id}: parameters: - name: delivery_id in: path required: true schema: type: string format: uuid description: | Webhook delivery identifier (a UUID), as returned by `GET /v1/webhooks/deliveries` and sent in the `X-DAN-Delivery-Id` header. example: 0192f1b0-3c4d-7e5f-8a6b-7c8d9e0f1a2b get: summary: Get a webhook delivery operationId: getWebhookDelivery tags: - Webhooks x-required-scopes: - connector:webhook_deliveries:read description: | Fetch one delivery by `delivery_id`. Requires the `connector:webhook_deliveries:read` scope. Returns the event it carries (`event_id`, `event_type`), its `status` (`RETRYING`, `DELIVERED` or `FAILED`; see `GET /v1/webhooks/deliveries`), the number of attempts made so far and the time of the latest change (`last_attempt_at`). The event body and request headers are not returned. responses: '200': description: The requested delivery. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/WebhookDelivery' examples: failed: summary: A delivery that failed permanently after all attempts value: data: type: webhook-deliveries id: 0192f1ae-4d5e-7f60-8172-8d9e0f1a2b3c attributes: event_id: 0192f1ae-1a2b-7c3d-8e4f-5a6b7c8d9e01 event_type: operation.updated status: FAILED attempt_count: 5 last_attempt_at: '2026-09-26T11:06:03.118440+00:00' relationships: webhook: data: type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 links: self: /v1/webhook/deliveries/0192f1ae-4d5e-7f60-8172-8d9e0f1a2b3c '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/webhooks/{webhook_id}/bind: parameters: - $ref: '#/components/parameters/webhookIdParam' post: summary: Bind a webhook to an auth profile operationId: bindWebhookAuthProfile tags: - Webhooks x-required-scopes: - connector:auth-profiles:bind description: | Bind a webhook subscription to an OAuth2 receiver auth profile, so that every delivery to this subscription carries an access token from your own token endpoint. Requires the `connector:auth-profiles:bind` scope and an `Idempotency-Key` header. Send `data.type: webhooks` and the `auth_profile_id` (a UUID) of a profile created with `POST /v1/auth-profiles`. Both the webhook and the auth profile must belong to the calling bank; otherwise the request fails with `400` and nothing changes. Binding again with a different `auth_profile_id` replaces the previous binding. There is currently no unbind request. **Effect.** Once bound, before each delivery the platform obtains an access token from the profile's token endpoint with the OAuth2 client credentials grant (see `POST /v1/auth-profiles`) and sends it as `Authorization: Bearer `, in addition to the HMAC signature. Binding also moves the subscription back to `PENDING_VERIFICATION` and sends a new `webhook.verification` event (with the bearer token). Business events are not delivered until your endpoint answers that event with a `2xx` status and the subscription is `ACTIVE` again. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/BindWebhookAuthProfileRequest' examples: bind: summary: Bind an auth profile value: data: type: webhooks attributes: auth_profile_id: 0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 responses: '202': description: | Accepted. The binding is applied and the subscription is in `PENDING_VERIFICATION` until the new verification handshake succeeds. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Binding accepted value: jsonapi: version: '1.1' data: type: operations id: 0192f1a3-8d9e-7fa0-8b12-c3d4e5f6a7b8 attributes: resource_family: webhooks state: ACCEPTED '400': description: | `auth_profile_id` is not a UUID, the `Idempotency-Key` header is missing, or the webhook or the auth profile does not exist in the calling bank. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: invalidId: summary: auth_profile_id is not a UUID value: jsonapi: version: '1.1' errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: auth_profile_id must be a valid UUID source: pointer: /data/attributes/auth_profile_id notInBank: summary: Webhook or auth profile not found in the calling bank value: jsonapi: version: '1.1' errors: - status: '400' title: failed to bind webhook to auth profile '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/UnprocessableEntity' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/auth-profiles: get: summary: List auth profiles operationId: listAuthProfileConfigs tags: - AuthProfiles x-required-scopes: - connector:auth-profiles:list description: | List the webhook receiver auth profiles of the calling bank. Requires the `connector:auth-profiles:list` scope. An *auth profile* tells the platform how to obtain an OAuth2 access token from your bank's token endpoint (client credentials grant) so that webhook deliveries carry `Authorization: Bearer `. The client secret is write-only and never returned. **Ordering and pagination.** Newest first (by `id`, a time-ordered UUID). Use `page[size]` (1 to 200, default 20) and follow `links.next` until it is absent. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' responses: '200': description: One page of the calling bank's auth profiles, newest first. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/AuthProfileConfig' links: $ref: '#/components/schemas/CursorLinks' examples: oneProfile: summary: One OAuth2 client credentials profile value: data: - type: auth-profiles id: 0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 attributes: bank_id: 11111111-1111-7111-8111-111111111111 client_id: lyriq-webhook-sender token_endpoint_url: https://idp.bank-alpha.example/oauth2/token requested_scope: webhooks.receive profile_type: OAuth2 created_at: '2026-09-26T09:40:11.305812+00:00' updated_at: '2026-09-26T09:40:11.305812+00:00' links: self: /v1/auth-profiles/0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 links: self: /v1/auth-profiles?page[size]=20 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' post: summary: Create an auth profile operationId: createAuthProfileConfig tags: - AuthProfiles x-required-scopes: - connector:auth-profiles:create description: | Register an OAuth2 client credentials profile that the platform uses to authenticate to your webhook endpoint. Requires the `connector:auth-profiles:create` scope and an `Idempotency-Key` header. Creating a profile has no effect on deliveries until you bind it to a webhook with `POST /v1/webhooks/{webhook_id}/bind`. ## How the platform uses the profile Before delivering to a webhook bound to this profile, the platform requests a token from `token_endpoint_url`: ```http POST /oauth2/token HTTP/1.1 Content-Type: application/x-www-form-urlencoded grant_type=client_credentials&client_id=lyriq-webhook-sender&client_secret=...&scope=webhooks.receive ``` The client credentials are sent in the form body (`client_secret_post`). Your token endpoint must answer `2xx` with a JSON body containing `access_token` and, optionally, `expires_in` in seconds (300 is assumed when absent). The platform caches the token per profile and requests a new one 30 seconds before it expires, or immediately after the profile is updated. The token is sent on each delivery as `Authorization: Bearer `; the HMAC signature is sent as well. If the token endpoint times out, cannot be reached, or answers `429` or `5xx`, the delivery is retried later. Any other error status, or a body without `access_token`, fails the delivery permanently. The token endpoint must be an `https://` URL that resolves to a public address. ## Request and result All attributes are required. `client_id`, `client_secret` and `requested_scope` must not be empty; `token_endpoint_url` must be an absolute `https://` URL with a host. `profile_type` must be `OAuth2`. The response is `202 Accepted`; the new `auth_profile_id` is the `relationships.resource.data.id` of `GET /v1/operations/{operation_id}`. Retrying with the same `Idempotency-Key` and body returns the same `202` document without creating a second profile. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/CreateAuthProfileConfigRequest' examples: oauth2: summary: OAuth2 client credentials against the bank's identity provider value: data: type: auth-profiles attributes: client_id: lyriq-webhook-sender client_secret: 9Hq2vX7kLp3sT8wZ1cF6bN4mR0yJ5dGa token_endpoint_url: https://idp.bank-alpha.example/oauth2/token requested_scope: webhooks.receive profile_type: OAuth2 responses: '202': description: | Accepted. The operation (`data.id`) tracks the creation; read the new `auth_profile_id` from the operation's `relationships.resource`. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Auth profile creation accepted value: jsonapi: version: '1.1' data: type: operations id: 0192f1a2-6b7c-7d8e-8f90-a1b2c3d4e5f6 attributes: resource_family: auth-profiles state: ACCEPTED '400': description: | A field is empty or invalid (for example `token_endpoint_url` is not an `https://` URL), or the `Idempotency-Key` header is missing. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: insecureTokenEndpoint: summary: Token endpoint is not HTTPS value: jsonapi: version: '1.1' errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: /data/attributes/token_endpoint_url must use https:// source: pointer: /data/attributes/token_endpoint_url missingScope: summary: requested_scope is empty value: jsonapi: version: '1.1' errors: - status: '400' title: Required field missing code: MISSING_FIELD detail: /data/attributes/requested_scope is required source: pointer: /data/attributes/requested_scope '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/UnprocessableEntity' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/auth-profiles/{auth_profile_id}: parameters: - $ref: '#/components/parameters/authProfileIdParam' get: summary: Get an auth profile operationId: getAuthProfileConfig tags: - AuthProfiles x-required-scopes: - connector:auth-profiles:read description: | Fetch one auth profile of the calling bank by `auth_profile_id`. Requires the `connector:auth-profiles:read` scope. Returns the client identity, token endpoint, requested scope and profile type. The client secret is never returned. responses: '200': description: The requested auth profile. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/AuthProfileConfig' examples: rotated: summary: A profile whose client secret was rotated value: data: type: auth-profiles id: 0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 attributes: bank_id: 11111111-1111-7111-8111-111111111111 client_id: lyriq-webhook-sender token_endpoint_url: https://idp.bank-alpha.example/oauth2/token requested_scope: webhooks.receive profile_type: OAuth2 created_at: '2026-09-26T09:40:11.305812+00:00' updated_at: '2026-10-15T08:02:47.918330+00:00' links: self: /v1/auth-profiles/0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' put: summary: Update an auth profile operationId: updateAuthProfileConfig tags: - AuthProfiles x-required-scopes: - connector:auth-profiles:update description: | Change an auth profile's `client_id`, `client_secret`, `token_endpoint_url`, `requested_scope` or `profile_type`. Requires the `connector:auth-profiles:update` scope and an `Idempotency-Key` header. `data.id` must equal the `auth_profile_id` of the path. Send only the attributes to change; at least one is required (otherwise `422 MISSING_ANY_OF`). Sent values must not be empty, and a new `token_endpoint_url` must be an `https://` URL. Sending `client_secret` rotates the credential. **Effect on webhooks.** After a successful update, every webhook bound to this profile goes back to `PENDING_VERIFICATION` and receives a new `webhook.verification` event, authenticated with a token obtained with the new settings. Business events are not delivered to those webhooks until the handshake is acknowledged with `2xx`. The cached access token of the previous version is not reused. To rotate a client secret without losing events, make your token endpoint accept both the old and the new secret during the change. parameters: - $ref: '#/components/parameters/idempotencyKeyParam' requestBody: required: true content: application/vnd.api+json: schema: $ref: '#/components/schemas/UpdateAuthProfileConfigRequest' examples: rotateSecret: summary: Rotate the client secret value: data: type: auth-profiles id: 0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 attributes: client_secret: Zt5wQ8nB2xK7hV0pL4cM9sD1fR6gY3jA changeEndpoint: summary: Move to a new token endpoint and scope value: data: type: auth-profiles id: 0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 attributes: token_endpoint_url: https://login.bank-alpha.example/oauth2/v2/token requested_scope: lyriq.webhooks responses: '202': description: | Accepted. The update is applied and bound webhooks are in `PENDING_VERIFICATION` until their new handshake succeeds. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: accepted: summary: Auth profile update accepted value: jsonapi: version: '1.1' data: type: operations id: 0192f1c3-8c9d-7eaf-80b1-c2d3e4f5a6b7 attributes: resource_family: auth-profiles state: ACCEPTED '400': description: | `data.id` does not match the path, `auth_profile_id` is not a UUID, a sent value is empty or invalid, the update changes nothing, or the `Idempotency-Key` header is missing. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: idMismatch: summary: data.id differs from the path value: jsonapi: version: '1.1' errors: - status: '400' title: Resource id mismatch code: RESOURCE_ID_MISMATCH detail: data.id must equal "0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7" source: pointer: /data/id '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/Conflict' '422': $ref: '#/components/responses/UnprocessableEntity' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/settlement-cycles: get: summary: List settlement cycles operationId: listSettlementCycles tags: - SettlementCycles x-required-scopes: - connector:settlement_cycle:list description: | List the **settlement cycles** of the calling bank as **issuer**. A *settlement cycle* batches the interbank redemptions of one issuer and one root asset (for example Bank Alpha and `usd`) for periodic cash settlement outside the platform. Cycles are opened, locked, cash-settled, and closed automatically by the platform settlement scheduler; banks cannot create or change them through this API. **Who sees what.** Only cycles whose issuer is the calling bank are returned. A holder bank follows its own redemptions through `GET /v1/redemptions` (each redemption carries its `assigned_cycle_id`), not through this endpoint. **Lifecycle.** ``` OPEN ──► LOCKED ──► CASH_SETTLED ──► CLOSE_PENDING ──► CLOSED │ ├──► MANUAL_REVIEW ──┐ │ ▼ └──────────────► ABORT_PENDING ──► ABORTED ``` | `status` | Meaning | |----------|---------| | `OPEN` | Accepts new redemptions. `cutoff_at` is the planned lock time. | | `LOCKED` | The cutoff has passed. No new redemptions are assigned; the assigned redemptions are locked on the ledger and the cycle waits for cash settlement. | | `CASH_SETTLED` | Receipt of the cash has been confirmed (see `cash_settlement`); the redemptions are about to be burned on the ledger. | | `CLOSE_PENDING` | The ledger redemptions have been submitted and the platform is waiting for ledger finality. | | `CLOSED` | Terminal. All locked redemptions were redeemed (`REDEEMED`). | | `MANUAL_REVIEW` | Automatic processing stopped (for example a lock could not be placed, or the cash evidence did not match the netting report); the platform operator must repair or abort the cycle. | | `ABORT_PENDING` | Lock releases have been submitted and the platform is waiting for ledger finality. | | `ABORTED` | Terminal. All locks were released and the redemptions are `RELEASED`. A cycle can be aborted only from `LOCKED` or `MANUAL_REVIEW`. | **Ordering and paging.** Results are ordered by cycle `id`, descending. Cycle ids are derived deterministically and are not time ordered, so do not rely on the list order for chronology; use `filter[cutoff_after]` and `filter[cutoff_before]` to select a time range. Follow `links.next` until it is absent. `links.prev` is never returned. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/fieldsParam' - name: filter[status] in: query required: false schema: type: string enum: - OPEN - LOCKED - CASH_SETTLED - CLOSE_PENDING - CLOSED - ABORT_PENDING - ABORTED - MANUAL_REVIEW description: | Return only cycles in this status. Any other value is rejected with `400`. example: OPEN - name: filter[mode] in: query required: false schema: type: string enum: - MANDATORY description: Return only cycles with this settlement mode. `MANDATORY` is the only mode. - name: filter[asset_id] in: query required: false schema: type: string description: | Return only cycles for this asset. Matches either the root asset id (for example `usd`) or the issuer's issued asset id (for example `usd.bank-alpha`). example: usd - name: filter[cutoff_after] in: query required: false schema: type: string format: date-time description: | Return cycles whose `cutoff_at` is at or after this instant (RFC 3339, for example `2026-09-24T00:00:00Z`; inclusive). An unparsable value is rejected with `400`. example: '2026-09-24T00:00:00Z' - name: filter[cutoff_before] in: query required: false schema: type: string format: date-time description: | Return cycles whose `cutoff_at` is at or before this instant (RFC 3339; inclusive). An unparsable value is rejected with `400`. example: '2026-09-25T00:00:00Z' responses: '200': description: One page of the calling issuer's settlement cycles. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/SettlementCycle' links: $ref: '#/components/schemas/CursorLinks' examples: openAndClosed: summary: Bank Alpha's open cycle and a closed cycle for `usd` value: data: - type: settlement-cycles id: 7c9e6679-7425-5de0-9f1a-3b2c1d0e4f5a attributes: status: OPEN mode: MANDATORY cutoff_at: '2026-09-24T16:00:00Z' asset_id: usd.bank-alpha root_asset_id: usd issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_asset_code: USD.ALPHA asset_summaries: - asset_id: usd.bank-alpha root_asset_id: usd issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_asset_code: USD.ALPHA lock_count: 2 - type: settlement-cycles id: 0a1b2c3d-4e5f-5a6b-8c7d-9e0f1a2b3c4d attributes: status: CLOSED mode: MANDATORY cutoff_at: '2026-09-23T16:00:00Z' asset_id: usd.bank-alpha root_asset_id: usd issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_asset_code: USD.ALPHA settled_at: '2026-09-23T17:05:12Z' closed_at: '2026-09-23T17:06:40Z' cash_settlement: state: CONFIRMED reference: FED-20260923-000451 mode: MANUAL evidence_source: manual-operator-confirmation netting_digest: 9f2c4e1a7b3d5f6e8a0c2d4f6b8e0a1c3e5f7a9b1d3f5e7c9a0b2d4f6e8a0c2d confirmed_by_principal_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b confirmed_at: '2026-09-23T17:05:12Z' net_amount: '1000000' links: self: /v1/settlement-cycles?filter[asset_id]=usd&page[size]=2 next: /v1/settlement-cycles?filter%5Basset_id%5D=usd&page%5Bsize%5D=2&page%5Bcursor%5D=0a1b2c3d-4e5f-5a6b-8c7d-9e0f1a2b3c4d holderCaller: summary: A bank that issues no settlement-cycle asset gets an empty list value: data: [] links: self: /v1/settlement-cycles '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/settlement-cycles/{settlement_cycle_id}: parameters: - $ref: '#/components/parameters/settlementCycleIdParam' get: summary: Get a settlement cycle operationId: getSettlementCycle tags: - SettlementCycles x-required-scopes: - connector:settlement_cycle:read description: | Fetch a single settlement cycle by `settlement_cycle_id`. Only the **issuer** of the cycle can read it. A cycle that does not exist and one that belongs to another issuer both return `404`. A holder bank finds the cycle id of its redemption in `assigned_cycle_id` on `GET /v1/redemptions/{redemption_id}`, but cannot read the cycle itself. The response carries the cycle's `status` (see `GET /v1/settlement-cycles` for the lifecycle), `cutoff_at`, `settled_at`, `closed_at`, the `cash_settlement` evidence once cash has been confirmed, and per-asset `asset_summaries` while redemptions are assigned or locked. responses: '200': description: The requested settlement cycle. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/SettlementCycle' examples: locked: summary: Locked cycle waiting for cash confirmation value: data: type: settlement-cycles id: 7c9e6679-7425-5de0-9f1a-3b2c1d0e4f5a attributes: status: LOCKED mode: MANDATORY cutoff_at: '2026-09-24T16:00:00Z' asset_id: usd.bank-alpha root_asset_id: usd issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_asset_code: USD.ALPHA asset_summaries: - asset_id: usd.bank-alpha root_asset_id: usd issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_asset_code: USD.ALPHA lock_count: 2 cashSettled: summary: Cash confirmed; ledger redemption about to run value: data: type: settlement-cycles id: 7c9e6679-7425-5de0-9f1a-3b2c1d0e4f5a attributes: status: CASH_SETTLED mode: MANDATORY cutoff_at: '2026-09-24T16:00:00Z' asset_id: usd.bank-alpha root_asset_id: usd issuer_bank_id: 11111111-1111-7111-8111-111111111111 issuer_asset_code: USD.ALPHA settled_at: '2026-09-24T17:02:31Z' cash_settlement: state: CONFIRMED reference: FED-20260924-000512 mode: MANUAL evidence_source: manual-operator-confirmation netting_digest: 4b1d7e2a9c3f5e8d0a6b2c4e6f8a0b1c3d5e7f9a1b3c5d7e9f0a2b4c6d8e0f1a confirmed_by_principal_id: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b confirmed_at: '2026-09-24T17:02:31Z' net_amount: '300000' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: No settlement cycle with this id exists for the calling issuer bank. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: notFound: summary: Unknown cycle, or a cycle of another issuer value: jsonapi: version: '1.1' errors: - status: '404' title: settlement cycle not found '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/redemption-policies: get: summary: List redemption policies operationId: listRedemptionPolicies tags: - RedemptionPolicies x-required-scopes: - connector:redemption-policies:list description: | List the redemption policies of the calling bank as **holder**. A *redemption policy* makes the platform create settlement-cycle redemptions automatically for one interbank holding: the holder's holding of one issuer's asset. Each policy has exactly one `trigger`: | `trigger.type` | What it does | |----------------|--------------| | `above_threshold` | Whenever the available balance of the holding is **greater than** `threshold`, redeem either the excess down to `min_balance` (`redemption_type: excess`) or the whole available balance (`redemption_type: full`). | | `scheduled` | Starting at `start_at` and then every `repeat_after`, redeem either `fixed_amount` (`redemption_strategy: fixed`) or the whole available balance (`redemption_strategy: full`). | Each run creates a normal redemption (see `GET /v1/redemptions`) that is settled through the issuer's next settlement cycle. Amounts are integer strings in the asset's minor units. **Policy status.** `ACTIVE` policies run. A policy whose automated run fails (for example because the holding account cannot be resolved) is moved to `SUSPENDED` and stops running. A policy can also be `REJECTED`. **Who sees what.** Only policies whose `holder_bank_id` is the calling bank are returned. **Ordering and paging.** Newest first. `links.next` is present whenever the page is not empty, even on the last page; keep following it until a page returns an empty `data` array. `links.prev` is never returned. parameters: - $ref: '#/components/parameters/pageCursorParam' - $ref: '#/components/parameters/pageSizeParam' - $ref: '#/components/parameters/includeParam' - $ref: '#/components/parameters/fieldsParam' - name: filter[asset_id] in: query required: false schema: type: string description: Return only policies whose `asset_id` equals this value. example: usd.bank-alpha - name: filter[status] in: query required: false schema: type: string enum: - ACTIVE - SUSPENDED - REJECTED description: | Return only policies in this status. Any other value is rejected with `400`. example: ACTIVE responses: '200': description: One page of the calling holder's redemption policies, newest first. content: application/vnd.api+json: schema: type: object required: - data - links properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/RedemptionPolicy' links: $ref: '#/components/schemas/CursorLinks' examples: bothTriggerTypes: summary: One threshold policy and one scheduled policy value: data: - attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 issuer_bank_id: 11111111-1111-7111-8111-111111111111 root_asset_id: usd asset_id: usd.bank-alpha trigger: type: above_threshold threshold: '11000000' redemption_type: excess min_balance: '10000000' - attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 issuer_bank_id: 11111111-1111-7111-8111-111111111111 root_asset_id: usd asset_id: usd.bank-alpha trigger: type: scheduled start_at: '2026-10-01T16:00:00Z' repeat_after: 1day redemption_strategy: fixed fixed_amount: '500000' links: self: /v1/redemption-policies?page[size]=20 next: /v1/redemption-policies?page%5Bsize%5D=20&page%5Bcursor%5D=0192b0a1-7c2d-7e3f-8a4b-5c6d7e8f9a0b '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/redemption-policies/{redemption_policy_id}: parameters: - name: redemption_policy_id in: path required: true schema: type: string format: uuid description: | Redemption policy identifier (a UUID). A value that is not a UUID is rejected with `400`. example: 0192b0a1-7c2d-7e3f-8a4b-5c6d7e8f9a0b get: summary: Get a redemption policy operationId: getRedemptionPolicy tags: - RedemptionPolicies x-required-scopes: - connector:redemption-policies:read description: | Fetch a single redemption policy by `redemption_policy_id`. Only the **holder** bank of the policy can read it. A policy that does not exist and one that belongs to another holder both return `404`. The response carries `holder_bank_id`, `issuer_bank_id`, `root_asset_id`, `asset_id`, and the `trigger`. See [`GET /v1/redemption-policies`](./list-redemption-policies) for how each trigger type works. responses: '200': description: The requested redemption policy. content: application/vnd.api+json: schema: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/RedemptionPolicy' examples: aboveThresholdExcess: summary: Redeem everything above 100,000.00 whenever the holding exceeds 110,000.00 value: data: attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 issuer_bank_id: 11111111-1111-7111-8111-111111111111 root_asset_id: usd asset_id: usd.bank-alpha trigger: type: above_threshold threshold: '11000000' redemption_type: excess min_balance: '10000000' aboveThresholdFull: summary: Redeem the whole holding whenever it exceeds 50,000.00 value: data: attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 issuer_bank_id: 11111111-1111-7111-8111-111111111111 root_asset_id: usd asset_id: usd.bank-alpha trigger: type: above_threshold threshold: '5000000' redemption_type: full scheduledFixed: summary: Redeem 5,000.00 every day from 1 October 2026, 16:00 UTC value: data: attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 issuer_bank_id: 11111111-1111-7111-8111-111111111111 root_asset_id: usd asset_id: usd.bank-alpha trigger: type: scheduled start_at: '2026-10-01T16:00:00Z' repeat_after: 1day redemption_strategy: fixed fixed_amount: '500000' scheduledFull: summary: Redeem the whole holding every 12 hours value: data: attributes: holder_bank_id: 22222222-2222-7222-8222-222222222222 issuer_bank_id: 11111111-1111-7111-8111-111111111111 root_asset_id: usd asset_id: usd.bank-alpha trigger: type: scheduled start_at: '2026-10-01T04:00:00Z' repeat_after: 12h redemption_strategy: full '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: No redemption policy with this id exists for the calling holder bank. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: notFound: summary: Unknown policy, or a policy of another holder value: jsonapi: version: '1.1' errors: - status: '404' title: redemption policy not found '429': $ref: '#/components/responses/TooManyRequests' '503': $ref: '#/components/responses/ServiceUnavailable' /v1/iso20022/messages: post: summary: Submit an ISO 20022 pacs.009 credit transfer operationId: submitIso20022Message tags: - ISO20022 description: | Submit an ISO 20022 **pacs.009.001.08** (Financial Institution Credit Transfer) message to start an `INTERBANK` transfer from your bank to another bank on the network. This is an XML alternative to [`POST /v1/transfers`](./create-transfer) for banks whose payment systems already produce ISO 20022 messages. The transfer is processed asynchronously: a successful submission returns `202 Accepted` with an empty body and the operation id in the headers. **Required scope:** `connector:transfers:create`. ### Supported messages Only one message type and version is accepted: | Part | Namespace | Notes | |------|-----------|-------| | Business application header | `urn:iso:std:iso:20022:tech:xsd:head.001.001.03` | Required. `MsgDefIdr` must be `pacs.009.001.08`. | | Document | `urn:iso:std:iso:20022:tech:xsd:pacs.009.001.08` | Exactly one `CdtTrfTxInf` (one transaction per message). | Any other message type or version (for example `pacs.009.001.09`, `pacs.008`, or `head.001.001.02`) is rejected with `400`. The request body must be a single XML document whose root element is a wrapper (envelope) element of your choice, for example `BusMsgEnvlp`, containing the `AppHdr` element first and the `Document` element second as its only child elements. A bare `Document` without an application header is rejected with `missing ISO 20022 application header`. ### How the message maps to the transfer | ISO 20022 element | Use | Rules | |-------------------|-----|-------| | `AppHdr/Fr/FIId/FinInstnId/BICFI` | Sending bank | Required. Must equal (case-insensitively) the BIC registered for the bank you are calling as; otherwise `403`. | | `CdtTrfTxInf/CdtrAgt/FinInstnId/BICFI` | Receiving (beneficiary) bank | Required. Must be the BIC of exactly one active bank on the network, and not your own bank; otherwise `400`. | | `CdtTrfTxInf/PmtId/UETR` | Payment identity | Required. Lowercase UUID version 4. Must also be sent as the `Idempotency-Key` header. Used to retrieve the status and the source message. | | `CdtTrfTxInf/IntrBkSttlmAmt` | Amount | Required. Decimal amount in major units, for example `250000.00`. It may have no more fractional digits than the asset's `decimals`, must be greater than zero, and is converted to the asset's minor units. | | `CdtTrfTxInf/IntrBkSttlmAmt/@Ccy` | Asset | Required. Three uppercase letters. Matched case-insensitively to the root id of an active asset (for example `USD` selects asset `usd`); if none matches, `400`. | | `CdtTrfTxInf/DbtrAcct/Id/Othr/Id` or `.../Id/IBAN` | Source account | Required. The account identifier at your bank, treated as the account's `external_account_id`. | | `CdtTrfTxInf/CdtrAcct/Id/Othr/Id` or `.../Id/IBAN` | Destination account | Required. The account identifier at the receiving bank, treated as that account's `external_account_id`. | | `CdtTrfTxInf/PmtId/InstrId`, `EndToEndId`, `TxId` | None | Required to be present, but not used to process the transfer. They are kept in the stored source message. | All other elements, for example `AppHdr/To`, `BizMsgIdr`, `GrpHdr/MsgId`, `SttlmInf`, `IntrBkSttlmDt`, `Dbtr`, `Cdtr`, `InstgAgt`, `InstdAgt` and intermediary agents, only need to be schema-valid; they do not affect processing and are kept in the stored source message. ### Validation Before anything is stored or any workflow is started, the message is checked for: - size: at most 1 MiB (1,048,576 bytes); larger bodies are rejected with `413`; - structure: at most 256 XML nodes (elements, text and whitespace between elements all count) and at most 32 levels of nesting; - safety: document type declarations (DTDs) and external entities are always rejected; - the pinned head.001.001.03 and pacs.009.001.08 XSD schemas; - the mapping rules in the table above. Every failure returns `400` with an XML problem document. Schema errors name the offending element, for example `invalid XML: UETR value ... does not match the required format`; several errors can be returned at once. ### Idempotency and UETR uniqueness The `Idempotency-Key` header is required and must be the message's UETR (compared as a UUID, so letter case does not matter); otherwise the request is rejected with `400`. A UETR identifies one message for good. Resending the **byte-for-byte identical** message is always safe: it returns `202` with the original operation id and creates nothing new. Sending a different message (any byte difference, including whitespace) with a UETR that has already been used is rejected with `409`: - `IDEMPOTENCY_CONFLICT` when the same API client reuses the UETR within 72 hours; - `STATE_CONFLICT` (detail: "Resubmit the original message unchanged or assign a new UETR.") otherwise, including when the UETR was already used by another bank. A duplicate that arrives while the first submission is still being accepted returns `409` with `IDEMPOTENCY_PENDING`. Validation failures (the `400` errors listed under *Validation*, and a wrong `Idempotency-Key`) are detected before the UETR is claimed, so you can correct the message and resend it with the same UETR. Any later failure (for example `400` for an unknown receiver BIC or asset, `403` for a sender BIC mismatch, `424`, or `503`) happens after the UETR is claimed by your API client: - resending the identical message returns `409` `IDEMPOTENCY_PENDING` for up to 5 minutes after the failed attempt, after which it is processed again; - sending a corrected message with the same UETR returns `409` `IDEMPOTENCY_CONFLICT` for 72 hours, so assign a new UETR to a corrected message. ### Source message storage The raw message is stored encrypted, readable only by the sending and the receiving bank, using each bank's registered key-encryption key for source messages. If either key cannot be used, the submission fails with `424` and nothing is started (`KEY_UNAVAILABLE` for your key, `COUNTERPARTY_UNAVAILABLE` for the receiving bank's key). If either bank has no encryption target registered, the submission fails with `400`. ### After acceptance Track the transfer with [`GET /v1/iso20022/messages/{uetr}`](./get-iso-20022-message) (pacs.002 status report) or with [`GET /v1/operations/{operation_id}`](./get-operation). The transfer also appears as an `INTERBANK` transfer in the transfers API. Business outcomes decided after acceptance, such as screening rejection by the receiving bank or insufficient liquidity, are reported through the operation and the pacs.002 status, not through this response. While the network is halted (outbound halted or read-only mode), submissions are refused with `503` and a JSON:API error document. parameters: - name: Idempotency-Key in: header required: true description: | The UETR of the submitted message (`CdtTrfTxInf/PmtId/UETR`), as a UUID. Any other value, or a missing header, is rejected with `400`. schema: type: string format: uuid example: 8a562c67-ca16-48ba-b074-65581be6f011 requestBody: required: true description: | A head.001.001.03 application header and a pacs.009.001.08 document inside one wrapper element. Send it with `Content-Type: application/iso20022+xml` (parameters such as `charset=utf-8` are allowed); any other content type is rejected with `415`. content: application/iso20022+xml: schema: type: string maxLength: 1048576 description: head.001.001.03 application header plus pacs.009.001.08 document, in a wrapper element. examples: proprietaryAccountIds: summary: Bank Alpha pays USD 250,000.00 to Bank Beta (accounts identified by CBS id) value: | ALPHUS33 BETAUS33 ALPHA-BMI-20260926-0001 pacs.009.001.08 2026-09-26T09:30:00Z ALPHA-MSG-20260926-0001 2026-09-26T09:30:00Z 1 CLRG ALPHA-INS-0001 ALPHA-E2E-0001 ALPHA-TX-0001 8a562c67-ca16-48ba-b074-65581be6f011 250000.00 2026-09-26 ALPHUS33 CBS-ACC-2026-000123 BETAUS33 BETAUS33 CBS-ACC-2026-000456 ibanAccountIds: summary: Accounts identified by IBAN value: | ALPHUS33 BETAUS33 ALPHA-BMI-20260926-0002 pacs.009.001.08 2026-09-26T09:30:00Z ALPHA-MSG-20260926-0002 2026-09-26T09:30:00Z 1 CLRG ALPHA-INS-0002 ALPHA-E2E-0002 ALPHA-TX-0002 3b1f2e4d-6a7c-4d8e-9f01-2a3b4c5d6e7f 1500.5 2026-09-26 ALPHUS33 GB29NWBK60161331926819 BETAUS33 BETAUS33 DE89370400440532013000 responses: '202': $ref: '#/components/responses/AcceptedIso20022Operation' '400': description: | The message is malformed, breaks a schema or mapping rule, or cannot be routed. Examples: `missing ISO 20022 application header`, `ISO 20022 document type declarations are not allowed`, `expected exactly one credit-transfer transaction`, `Idempotency-Key must equal the ISO 20022 UETR`, `ISO 20022 receiver BIC is not configured`, `ISO 20022 transfer must target a different bank`, `invalid ISO 20022 amount`, `no active asset is configured for the ISO 20022 settlement currency`. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: missingHeader: summary: Document sent without an application header value: | 400 missing ISO 20022 application header unsupportedVersion: summary: Unsupported message version (for example pacs.009.001.09) value: | 400 invalid XML: Document is not a recognized ISO 20022 message root for this schema idempotencyKeyMismatch: summary: Idempotency-Key header missing or not equal to the UETR value: | 400 Idempotency-Key must equal the ISO 20022 UETR unknownReceiver: summary: Creditor agent BIC is not an active bank on the network value: | 400 ISO 20022 receiver BIC is not configured '401': $ref: '#/components/responses/XmlProblem' '403': description: | The caller may not submit this message: the token lacks `connector:transfers:create` or is not scoped to the bank (`Forbidden` or `ISO 20022 authorization failed`), the bank is suspended or terminated, or the application header's sender BIC is not the calling bank's BIC. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: senderBicMismatch: summary: AppHdr sender BIC does not belong to the calling bank value: | 403 ISO 20022 sender BIC does not match the authenticated bank missingScope: summary: Token lacks the connector:transfers:create scope value: | 403 Forbidden '409': description: | The UETR is already associated with a different message (`IDEMPOTENCY_CONFLICT` or `STATE_CONFLICT`), or an identical submission is still in progress (`IDEMPOTENCY_PENDING`). content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: idempotencyConflict: summary: Same API client reused the UETR for a different message value: | 409 IDEMPOTENCY_CONFLICT Idempotency conflict uetrAlreadyUsed: summary: UETR already stored for a different message value: | 409 STATE_CONFLICT State conflict Resubmit the original message unchanged or assign a new UETR. '413': description: | The request body is larger than 1 MiB. This response is produced before the message is read and has a plain-text body, not an XML problem document. content: text/plain: schema: type: string '415': description: The `Content-Type` header is missing or is not `application/iso20022+xml`. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: unsupportedMediaType: summary: Wrong content type value: | 415 Unsupported Media Type '422': description: The platform rejected the command with a business-rule error code. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' '424': description: | The source message could not be encrypted because a key-encryption key cannot be used. `KEY_UNAVAILABLE`: your bank's key (the `detail` says whether to restore the platform's permission to use it or to re-enable or replace it), then resubmit. `COUNTERPARTY_UNAVAILABLE`: the receiving bank's key. No transfer is started in either case. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: keyUnavailable: summary: The platform may no longer use your key-encryption key value: | 424 KEY_UNAVAILABLE Key unavailable Restore the platform's permission to use your key-encryption key at your key provider, then resubmit. counterpartyUnavailable: summary: The receiving bank's key-encryption key cannot be used value: | 424 COUNTERPARTY_UNAVAILABLE Counterparty unavailable '429': $ref: '#/components/responses/XmlProblem' '503': description: | A platform dependency (key provider, object store or workflow service) is temporarily unavailable, for example `DEPENDENCY_UNAVAILABLE`. Resend the identical message with the same `Idempotency-Key` after 5 minutes (see *Idempotency and UETR uniqueness*). While the network is halted, this status is returned with a JSON:API error document instead of XML. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: dependencyUnavailable: summary: Key provider or object store temporarily unavailable value: | 503 DEPENDENCY_UNAVAILABLE Dependency unavailable application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' /v1/iso20022/messages/preflight: post: summary: Preflight an ISO 20022 pacs.009 credit transfer operationId: preflightIso20022Message tags: - ISO20022 description: | Check, without executing anything, whether a pacs.009.001.08 message would currently pass the platform's liquidity, mint-limit and exposure-limit checks if you submitted it. This is the XML counterpart of [`POST /v1/transfers/preflight`](./preflight-transfer). **Required scope:** `connector:transfers:create` (the same scope as submitting). ### Request Send exactly the message you intend to submit to [`POST /v1/iso20022/messages`](./submit-iso-20022-message), with `Content-Type: application/iso20022+xml`. It goes through the same validation (size, node count and depth limits, DTD and external-entity rejection, head.001.001.03 and pacs.009.001.08 XSD schemas) and the same field mapping (sender BIC, receiver BIC, currency, amount and accounts), so the same `400` and `403` errors apply. See the submit operation for the full mapping table. As with `POST /v1/transfers/preflight`, the result depends only on the two banks, the currency and the amount: the accounts must be present in the message but are not checked against registered accounts, so an unregistered account is only detected on submission. Preflight has no side effects: it does not store the message, does not claim the UETR, and creates no operation or transfer. No `Idempotency-Key` header is needed and repeating a call is always safe. The UETR is not checked for prior use. ### Response `200 OK` with a `transfer-preflight.001.001.01` document in `application/xml`. This is a platform-defined message (namespace `urn:dan:xsd:transfer-preflight.001.001.01`), not an ISO 20022 registered message type. The `X-ISO20022-UETR` header echoes the message UETR. | Element | Meaning | |---------|---------| | `Header/MessageId` | Unique id of this response (`PFR-` followed by a UUID). | | `Header/CreatedAt`, `Assessment/EvaluatedAt` | UTC date and time of the evaluation, without a zone designator. | | `Header/OriginalMessage/MessageType` | Always `pacs.009.001.08`. | | `Header/OriginalMessage/Fingerprint` | Lowercase hex SHA-256 of the exact request body bytes; use it to match the result to the message you sent. | | `Assessment/PreflightId` | Unique id of this evaluation (`PF-` followed by a UUID). | | `Assessment/Decision` | `PASS` or `BLOCKED`. | | `PreflightResult/OriginalUETR` | UETR of the evaluated message. | | `PreflightResult/WouldSucceed` | `true` for `PASS`, `false` for `BLOCKED`. | | `PreflightResult/BlockingReason` | Only when blocked: `INSUFFICIENT_LIQUIDITY`, `MINT_LIMIT_EXCEEDED` or `RECEIVER_EXPOSURE_LIMIT_EXCEEDED`. | | `PreflightResult/Mitigation` | Only when blocked: `ADJUST_AMOUNT_OR_RETRY_LATER`, `REQUEST_ISSUANCE_LIMIT_INCREASE` or `REQUEST_COUNTERPARTY_EXPOSURE_LIMIT_INCREASE` respectively. | | `PreflightResult/AvailableHeadroom` | Only when blocked and the headroom is known: remaining headroom in the asset's minor units (for example cents), with the currency in `@Ccy`. Returned for `MINT_LIMIT_EXCEEDED`; for `RECEIVER_EXPOSURE_LIMIT_EXCEEDED` only if the network is configured to disclose counterparty headroom. | `PASS` reflects the state when the request was evaluated; balances and limits can change before you submit. It also does not predict beneficiary screening or approval workflows, which only run after submission. Responses are sent without indentation; the examples are indented for readability. requestBody: required: true description: The pacs.009.001.08 message you intend to submit, in the same format as for submission. content: application/iso20022+xml: schema: type: string maxLength: 1048576 description: head.001.001.03 application header plus pacs.009.001.08 document, in a wrapper element. examples: interbankTransfer: summary: Bank Alpha checks a USD 250,000.00 payment to Bank Beta value: | ALPHUS33 BETAUS33 ALPHA-BMI-20260926-0001 pacs.009.001.08 2026-09-26T09:30:00Z ALPHA-MSG-20260926-0001 2026-09-26T09:30:00Z 1 CLRG ALPHA-INS-0001 ALPHA-E2E-0001 ALPHA-TX-0001 8a562c67-ca16-48ba-b074-65581be6f011 250000.00 2026-09-26 ALPHUS33 CBS-ACC-2026-000123 BETAUS33 BETAUS33 CBS-ACC-2026-000456 responses: '200': description: The eligibility assessment. headers: X-ISO20022-UETR: $ref: '#/components/headers/XIso20022Uetr' content: application/xml: schema: type: string description: transfer-preflight.001.001.01 document (namespace `urn:dan:xsd:transfer-preflight.001.001.01`). examples: pass: summary: The transfer would pass liquidity and limit checks value: |
PFR-6e653eb1-172b-4b9b-8be9-a0e3ec9598d5 2026-09-26T09:31:12 pacs.009.001.08 e95c09515b4e49106badc533c06b2247879ba762815633997661256d07a16f47
PF-2ac000a7-d5fa-489a-ade5-b6ea44ccf7be PASS 2026-09-26T09:31:12 8a562c67-ca16-48ba-b074-65581be6f011 true
mintLimitExceeded: summary: Blocked by the issuer mint limit, with USD 125,000.00 headroom left value: |
PFR-79564270-e4b6-4edb-a835-04633b8e37e9 2026-09-26T09:31:12 pacs.009.001.08 e95c09515b4e49106badc533c06b2247879ba762815633997661256d07a16f47
PF-3c7f6b7a-0104-486f-919c-621d0ef4d584 BLOCKED 2026-09-26T09:31:12 8a562c67-ca16-48ba-b074-65581be6f011 false MINT_LIMIT_EXCEEDED REQUEST_ISSUANCE_LIMIT_INCREASE 12500000
insufficientLiquidity: summary: Blocked by insufficient liquidity (no headroom reported) value: |
PFR-0b8e1f4c-93d2-4a6e-8c1b-5f7a2d9e3c41 2026-09-26T09:31:12 pacs.009.001.08 e95c09515b4e49106badc533c06b2247879ba762815633997661256d07a16f47
PF-d41c7e2a-6b3f-4f89-a0d5-1e2c3b4a5f67 BLOCKED 2026-09-26T09:31:12 8a562c67-ca16-48ba-b074-65581be6f011 false INSUFFICIENT_LIQUIDITY ADJUST_AMOUNT_OR_RETRY_LATER
'400': description: | The message is malformed, breaks a schema or mapping rule, or cannot be routed, for example `ISO 20022 receiver BIC is not configured` or `invalid ISO 20022 amount`. The same validation errors as for submission apply. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: invalidAmount: summary: More fractional digits than the asset allows value: | 400 invalid ISO 20022 amount '401': $ref: '#/components/responses/XmlProblem' '403': description: | The token lacks `connector:transfers:create` or is not scoped to the bank, the bank is suspended or terminated, or the sender BIC is not the calling bank's BIC (`ISO 20022 sender BIC does not match the authenticated bank`). content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' '404': description: | One of the banks is not eligible for this asset, or a referenced resource was not found. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' '413': description: The request body is larger than 1 MiB. The body is plain text, not an XML problem document. content: text/plain: schema: type: string '415': description: The `Content-Type` header is missing or is not `application/iso20022+xml`. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' '422': description: | The transfer cannot be evaluated in the current configuration, for example `no active asset is configured for the ISO 20022 settlement currency`, `ISO 20022 receiver BIC is configured for multiple active banks`, or `source bank is not active`. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: noAssetForCurrency: summary: No active asset matches the settlement currency value: | 422 no active asset is configured for the ISO 20022 settlement currency '429': $ref: '#/components/responses/XmlProblem' '500': $ref: '#/components/responses/XmlProblem' '503': $ref: '#/components/responses/XmlProblem' /v1/iso20022/messages/{uetr}: parameters: - name: uetr in: path required: true schema: type: string format: uuid example: 8a562c67-ca16-48ba-b074-65581be6f011 description: | UETR (Unique End-to-end Transaction Reference) of a pacs.009 message submitted through `POST /v1/iso20022/messages`: a UUID version 4, in any letter case. Any other value is rejected with `400` (`Invalid ISO 20022 UETR`). get: summary: Get the pacs.002 payment status for a UETR operationId: getIso20022Message tags: - ISO20022 description: | Return the current status of a payment submitted through [`POST /v1/iso20022/messages`](./submit-iso-20022-message), as an ISO 20022 **pacs.002.001.12** (FI to FI Payment Status Report) document in `application/iso20022+xml`. Both the sending bank and the receiving bank can read it. **Required scope:** `connector:transfers:read`. Send `Accept: application/iso20022+xml` (or `application/*`, `*/*`, or no `Accept` header); anything else is rejected with `406`. ### Status codes and group status The HTTP status tells you whether the payment has finished: | Operation state | HTTP status | `GrpSts` | |-----------------|-------------|----------| | `ACCEPTED`, `PROCESSING`, `AWAITING_SCREENING`, `PENDING_REVIEW`, `PENDING_COMMITS`, `MANUAL_REVIEW`, `CANCELLING` | `202 Accepted` | `PDNG` (pending) | | `SUCCEEDED` | `200 OK` | `ACCP` (accepted) | | `REJECTED`, `FAILED`, `CANCELLED`, `EXPIRED`, `UNKNOWN` | `200 OK` | `RJCT` (rejected) | Keep polling while you receive `202`; a `200` is final. For a rejected payment with a recorded failure reason, `TxInfAndSts/StsRsnInf` carries an ISO 20022 `ExternalStatusReason1Code` and, in `AddtlInf`, the platform's failure message: | `Rsn/Cd` | Meaning | |----------|---------| | `MS02` | Rejected by the receiving bank during beneficiary screening. | | `MS03` | Rejected by a checker in an approval review. | | `AB05` | Beneficiary screening was not completed in time. | | `AG07` | Settlement failed after the transfer had been accepted. | | `NARR` | Any other failure; see `AddtlInf`. | ### Report contents | Element | Content | |---------|---------| | `GrpHdr/MsgId` | The platform operation id without hyphens (32 hex characters). | | `GrpHdr/CreDtTm` | UTC time the report was generated, without a zone designator. | | `OrgnlGrpInfAndSts/OrgnlMsgId` | The UETR without hyphens. This is not the `GrpHdr/MsgId` of your original message. | | `OrgnlGrpInfAndSts/OrgnlMsgNmId` | Always `pacs.009.001.08`. | | `OrgnlGrpInfAndSts/GrpSts` | `PDNG`, `ACCP` or `RJCT`, as above. | | `TxInfAndSts/OrgnlUETR` | The UETR. | | `TxInfAndSts/StsRsnInf` | Only for `RJCT` with a recorded failure reason. | Every report is validated against the pacs.002.001.12 XSD before it is returned. The `X-DAN-Operation-Id` header gives the operation id in UUID form, for use with [`GET /v1/operations/{operation_id}`](./get-operation). Responses are sent without indentation; the examples are indented for readability. responses: '200': description: Final pacs.002.001.12 status report (`GrpSts` is `ACCP` or `RJCT`). headers: X-DAN-Operation-Id: $ref: '#/components/headers/XDanOperationId' X-ISO20022-UETR: $ref: '#/components/headers/XIso20022Uetr' content: application/iso20022+xml: schema: type: string description: pacs.002.001.12 payment status report. examples: accepted: summary: Payment settled (ACCP) value: | 01998a2e5b3c7d419f2a3c4b5d6e7f80 2026-09-26T09:35:00 8a562c67ca1648bab07465581be6f011 pacs.009.001.08 ACCP 8a562c67-ca16-48ba-b074-65581be6f011 rejectedByScreening: summary: Payment rejected by the receiving bank's screening (RJCT, MS02) value: | 01998a2e5b3c7d419f2a3c4b5d6e7f80 2026-09-26T09:35:00 8a562c67ca1648bab07465581be6f011 pacs.009.001.08 RJCT 8a562c67-ca16-48ba-b074-65581be6f011 MS02 transfer was rejected by beneficiary during screening '202': description: The payment is still in progress; pacs.002.001.12 report with `GrpSts` `PDNG`. headers: X-DAN-Operation-Id: $ref: '#/components/headers/XDanOperationId' X-ISO20022-UETR: $ref: '#/components/headers/XIso20022Uetr' content: application/iso20022+xml: schema: type: string description: pacs.002.001.12 payment status report. examples: pending: summary: Payment still being processed (PDNG) value: | 01998a2e5b3c7d419f2a3c4b5d6e7f80 2026-09-26T09:35:00 8a562c67ca1648bab07465581be6f011 pacs.009.001.08 PDNG 8a562c67-ca16-48ba-b074-65581be6f011 '400': description: The `uetr` path parameter is not a UUID version 4. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: invalidUetr: summary: Malformed UETR in the path value: | 400 Invalid ISO 20022 UETR '401': $ref: '#/components/responses/XmlProblem' '403': description: | The token lacks `connector:transfers:read` or is not scoped to the bank, or the bank is suspended or terminated. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' '404': description: | No ISO 20022 payment with this UETR exists for which the calling bank is the sender or the receiver. The response is identical whether the UETR does not exist or belongs to other banks. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: notFound: summary: Unknown UETR, or the calling bank is not a party to it value: | 404 Not Found '406': description: | The `Accept` header does not allow `application/iso20022+xml` (accepted values are `application/iso20022+xml`, `application/*` and `*/*`, with a quality above 0). Note that `application/xml` alone is not accepted. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: notAcceptable: summary: Accept header excludes application/iso20022+xml value: | 406 Not Acceptable '429': $ref: '#/components/responses/XmlProblem' '503': description: The workflow service is temporarily unavailable (`Workflow unavailable`); retry later. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' /v1/iso20022/messages/{uetr}/source: parameters: - name: uetr in: path required: true schema: type: string format: uuid example: 8a562c67-ca16-48ba-b074-65581be6f011 description: | UETR (Unique End-to-end Transaction Reference) of a pacs.009 message submitted through `POST /v1/iso20022/messages`: a UUID version 4, in any letter case. Any other value is rejected with `400` (`Invalid ISO 20022 UETR`). get: summary: Get the original ISO 20022 source message operationId: getIso20022MessageSource tags: - ISO20022 description: | Return the exact bytes of the pacs.009 message that was submitted for this UETR, in `application/iso20022+xml`. The sending bank and the receiving bank can both read it, for example so that the receiving bank can see the ordering details (such as `Dbtr` and `Cdtr`) that the platform itself does not use. **Required scope:** `connector:transfers:read`. Send `Accept: application/iso20022+xml` (or `application/*`, `*/*`, or no `Accept` header); anything else is rejected with `406`. The message is available as soon as the submission has been accepted, whatever the state of the transfer. It is stored encrypted, and each bank's copy is decrypted with that bank's own registered key-encryption key for source messages. Before the message is returned, its SHA-256 digest is checked against the digest recorded at submission; the SHA-256 of the returned body therefore equals the `Fingerprint` a preflight of the same message reports. responses: '200': description: The original message, byte for byte as submitted. headers: X-DAN-Operation-Id: $ref: '#/components/headers/XDanOperationId' X-ISO20022-UETR: $ref: '#/components/headers/XIso20022Uetr' content: application/iso20022+xml: schema: type: string description: The submitted ISO 20022 XML message, unchanged. examples: sourceMessage: summary: Source message of a Bank Alpha to Bank Beta payment value: | ALPHUS33 BETAUS33 ALPHA-BMI-20260926-0001 pacs.009.001.08 2026-09-26T09:30:00Z ALPHA-MSG-20260926-0001 2026-09-26T09:30:00Z 1 CLRG ALPHA-INS-0001 ALPHA-E2E-0001 ALPHA-TX-0001 8a562c67-ca16-48ba-b074-65581be6f011 250000.00 2026-09-26 ALPHUS33 CBS-ACC-2026-000123 BETAUS33 BETAUS33 CBS-ACC-2026-000456 '400': description: The `uetr` path parameter is not a UUID version 4. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: invalidUetr: summary: Malformed UETR in the path value: | 400 Invalid ISO 20022 UETR '401': $ref: '#/components/responses/XmlProblem' '403': description: | The token lacks `connector:transfers:read` or is not scoped to the bank, or the bank is suspended or terminated. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' '404': description: | No ISO 20022 payment with this UETR exists for which the calling bank is the sender or the receiver. The response is identical whether the UETR does not exist or belongs to other banks. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: notFound: summary: Unknown UETR, or the calling bank is not a party to it value: | 404 Not Found '406': description: | The `Accept` header does not allow `application/iso20022+xml` (accepted values are `application/iso20022+xml`, `application/*` and `*/*`, with a quality above 0). Note that `application/xml` alone is not accepted. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: notAcceptable: summary: Accept header excludes application/iso20022+xml value: | 406 Not Acceptable '424': description: | Your bank's key-encryption key cannot be used to decrypt the message (`KEY_UNAVAILABLE`). The `detail` says whether to restore the platform's permission to use the key, or to re-enable the key or cancel its deletion; the message can only be decrypted with that key. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: keyUnavailable: summary: The key-encryption key is disabled value: | 424 KEY_UNAVAILABLE Key unavailable Re-enable your key-encryption key or cancel its deletion; this message can only be decrypted with it. '429': $ref: '#/components/responses/XmlProblem' '500': description: | The stored message failed its integrity check (`INTEGRITY_CHECK_FAILED`) or an internal error occurred. Contact the network operator. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' '503': description: | The key provider, object store or workflow service is temporarily unavailable (`DEPENDENCY_UNAVAILABLE` or `Workflow unavailable`); retry later. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: | Access token (a JWT) issued by the platform IAM, sent as `Authorization: Bearer `. Required on every route. Bank-facing tokens carry a `bank_memberships` claim whose `bank_id` entries determine which bank a request acts for; each operation states the OAuth scope it requires. See the Authentication section. Operator-only routes require operator scopes and reject bank principals. parameters: pageCursorParam: name: page[cursor] in: query required: false description: | Opaque cursor that selects the page to return. Omit it (or send an empty value) to get the first page. To get the next page, follow `links.next` from the previous response rather than building the URL yourself; it carries the cursor and repeats your other query parameters. When `links.next` is absent there are no more results. Only forward paging is supported: responses do not include `links.prev`. Do not parse or construct cursor values; their format may change. schema: type: string example: CURSOR_FROM_LINKS_NEXT pageSizeParam: name: page[size] in: query required: false description: | Maximum number of items to return in one page: an integer from 1 to 200, default 20. A value outside that range returns `400 Bad Request` with title `Invalid pagination parameters`. Use `links.next`, not the number of items returned, to detect whether more results exist. schema: type: integer format: int32 default: 20 minimum: 1 maximum: 200 example: 50 includeParam: name: include in: query required: false description: | JSON:API compound-document request: a comma-separated list of relationship paths to side-load in a top-level `included` array. **Currently accepted but not applied.** The Lyriq Connector parses this parameter (a malformed value returns `400 Bad Request`) but does not yet return an `included` array. Follow the `relationships.*.links.related` URLs to fetch related resources. schema: type: string example: resource fieldsParam: name: fields in: query required: false description: | JSON:API sparse fieldset, given per resource type as `fields[]=,` (for example `fields[transfers]=transfer_kind,amount,state`). **Currently accepted but not applied.** The Lyriq Connector parses this parameter (a malformed value returns `400 Bad Request`) but always returns every attribute. style: deepObject explode: true schema: type: object additionalProperties: type: string example: transfers: transfer_kind,amount,state operationIdParam: name: operation_id in: path required: true description: | Operation identifier (a UUID) as returned in `data.id` of the `202 Accepted` response that created the operation. schema: type: string example: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 idempotencyKeyParam: name: Idempotency-Key in: header required: true description: | Client-generated key that makes a mutating request safe to retry. Required on every `POST`, `PUT` and `DELETE` that returns `202 Accepted`; a request without it is rejected with `400 Bad Request` before anything is processed. **Format.** Any string of 1 to 128 visible ASCII characters. A fresh UUID per request is recommended. **Scope.** A key is remembered per bank, per calling client (the token `sub`), per HTTP method and per endpoint path template (for example `POST /v1/operations/{operation_id}/cancel`). Path parameter values are not part of the scope, and only the request body attributes are compared, so generate a new key for every distinct request, even when only a path parameter differs. **Retention.** The key is kept for at least 72 hours after the first request that used it. **Replay behaviour.** | You send the same key with | Result | | --- | --- | | the same body, after the first request was accepted | The original `202 Accepted` document is returned again, with the same operation `id`. No new operation is created. | | a different body | `409 Conflict` with code `IDEMPOTENCY_CONFLICT`. | | the same body, while the first request is still being accepted | `409 Conflict` with code `IDEMPOTENCY_PENDING`. Retry after a short delay. | If the first request failed after the key was recorded (for example with a `4xx` from business validation or a `5xx`), the key stays reserved: retrying with the same key and body returns `IDEMPOTENCY_PENDING` for up to 5 minutes, after which the request is processed again. If you correct the body after such a failure, use a new key. schema: type: string minLength: 1 maxLength: 128 example: 5f0c9a2e-7d3b-4c1a-9e8f-2b6d4a1c3e5f externalAccountIdParam: name: external_account_id in: path required: true schema: type: string description: | The core banking system (CBS) identifier your bank supplied as `external_account_id` when it registered the account (1 to 34 characters, ISO 20022 `Max34Text`). Matching is exact and case-sensitive. Percent-encode reserved characters in the path; for example the identifier `CBS/ACC 01` is sent as `CBS%2FACC%2001`. example: CBS-ACC-2026-000123 filterIssuerBankIdParam: name: filter[issuer_bank_id] in: query required: false schema: type: string format: uuid description: | Return only records whose issuer bank has this `bank_id` (exact match). The value must be a UUID; any other value returns `400 Bad Request` with title `Invalid query parameters`. example: 11111111-1111-7111-8111-111111111111 filterExternalAccountIdParam: name: filter[external_account_id] in: query required: false description: | Return only records for this account, identified by the core banking system (CBS) identifier your bank supplied when it registered the account. The value satisfies the ISO 20022 `Max34Text` type (1 to 34 characters). schema: type: string minLength: 1 maxLength: 34 example: CBS-ACC-2026-000123 filterAssetIdParam: name: filter[asset_id] in: query required: false description: | Return only records for this asset. Whether the value is matched against the root asset (for example `usd`), the issued asset (for example `usd.bank-alpha`), or either, depends on the endpoint; see each endpoint's description. schema: type: string example: usd.bank-alpha reviewIdParam: name: review_id in: path required: true schema: type: string format: uuid description: | Review identifier (a UUID), from `GET /v1/reviews` or from a `review.*` webhook event. example: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f webhookIdParam: name: webhook_id in: path required: true schema: type: string format: uuid description: | Webhook subscription identifier (a UUID). Find it in `relationships.resource` of the operation returned by `POST /v1/webhooks`, or in `GET /v1/webhooks`. example: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 filterCreatedAfterParam: name: filter[created_after] in: query required: false description: | Return only records created after this instant (exclusive). An RFC 3339 date-time with a time zone offset, for example `2026-09-01T00:00:00Z`. URL-encode `+` offsets as `%2B`. schema: type: string format: date-time example: '2026-09-01T00:00:00Z' filterCreatedBeforeParam: name: filter[created_before] in: query required: false description: | Return only records created before this instant (exclusive). An RFC 3339 date-time with a time zone offset, for example `2026-10-01T00:00:00Z`. URL-encode `+` offsets as `%2B`. schema: type: string format: date-time example: '2026-10-01T00:00:00Z' authProfileIdParam: name: auth_profile_id in: path required: true schema: type: string format: uuid description: | Auth profile identifier (a UUID). Find it in `relationships.resource` of the operation returned by `POST /v1/auth-profiles`, or in `GET /v1/auth-profiles`. example: 0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 settlementCycleIdParam: name: settlement_cycle_id in: path required: true schema: type: string format: uuid description: | Settlement cycle identifier (a UUID): the `id` of a `settlement-cycles` resource, or the `assigned_cycle_id` of a redemption. A value that is not a UUID is rejected with `400`. example: 7c9e6679-7425-5de0-9f1a-3b2c1d0e4f5a schemas: OperationState: description: | Lifecycle state of an operation. Terminal states never change again; poll until you see one of them. | State | Terminal | Meaning | | --- | --- | --- | | `ACCEPTED` | No | The request was recorded and is queued for processing. | | `PROCESSING` | No | The platform is validating, evaluating policy for, or executing the request. | | `PENDING_REVIEW` | No | Policy requires human approval; the operation waits for decisions on its reviews (see the Reviews endpoints). | | `AWAITING_SCREENING` | No | An interbank transfer waits for the beneficiary bank's screening decision. | | `PENDING_COMMITS` | No | The ledger transactions were submitted and the platform waits for them to be final. | | `CANCELLING` | No | Reserved for a cancellation in progress. | | `MANUAL_REVIEW` | No | The platform could not settle the outcome automatically (for example an ambiguous ledger result) and an operator must reconcile it. Do not resubmit; contact the network operator. | | `UNKNOWN` | No | Reserved; not currently set by the platform. If you see it, do not resubmit; contact the network operator. | | `SUCCEEDED` | Yes | The request completed successfully. | | `REJECTED` | Yes | The request was refused (for example by policy, a review decision, or because a cancellation was not allowed). See `result_code` and `result_message`. | | `FAILED` | Yes | Execution failed. See `result_code` and `result_message`. | | `CANCELLED` | Yes | The operation was cancelled before it took effect. | | `EXPIRED` | Yes | A required decision (for example beneficiary screening) was not made in time. | type: string enum: - ACCEPTED - PROCESSING - PENDING_REVIEW - PENDING_COMMITS - CANCELLING - SUCCEEDED - REJECTED - FAILED - CANCELLED - EXPIRED - MANUAL_REVIEW - UNKNOWN - AWAITING_SCREENING example: PROCESSING JsonApiVersion: description: | JSON:API version object. Always present in `202 Accepted` and error documents; may be absent from other responses. Clients may include it in request bodies. type: object properties: version: type: string enum: - '1.1' example: '1.1' OperationKey: type: object required: - id - type properties: id: type: string description: Operation identifier (a UUID). example: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 type: type: string description: Always `operations`. enum: - operations ResourceIdentifier: description: JSON:API resource identifier object. type: object required: - type - id properties: type: type: string description: Resource type. example: transfers id: type: string description: Resource identifier. example: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 RelationshipLinks: description: JSON:API relationship links object. type: object properties: related: type: string description: Relative URL to fetch the related resource. example: /v1/transfers/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 ResourceIdentifierRelationship: description: JSON:API to-one relationship to a generic resource identifier. type: object properties: data: $ref: '#/components/schemas/ResourceIdentifier' links: $ref: '#/components/schemas/RelationshipLinks' ResourceLinks: description: JSON:API links object for a single resource. type: object properties: self: type: string nullable: true description: Relative URL of this resource. example: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 OperationSummary: description: | Operation as returned in `GET /v1/operations`. Contains the tracking fields only; use `GET /v1/operations/{operation_id}` for the ledger references and owning bank. allOf: - $ref: '#/components/schemas/OperationKey' - type: object required: - attributes properties: attributes: type: object required: - resource_family - operation_type - state - created_at - updated_at properties: resource_family: type: string description: | Resource family the operation acts on, for example `transfers`, `accounts`, `mint-limits`, `redemption-policies`, `webhooks` or `operations` (for a cancellation). example: transfers operation_type: type: string description: | Command that created the operation, for example `TRANSFER_CREATE`, `ACCOUNT_CREATE`, `REDEMPTION_CREATE`, `MINT_LIMIT_UPDATE`, `EXPOSURE_LIMIT_UPSERT`, `WEBHOOK_CREATE` or `OPERATION_CANCEL`. example: TRANSFER_CREATE state: $ref: '#/components/schemas/OperationState' created_at: type: string format: date-time description: When the operation was created (RFC 3339, UTC). example: '2026-09-26T10:15:30.482913+00:00' updated_at: type: string format: date-time description: When the operation last changed state (RFC 3339, UTC). example: '2026-09-26T10:15:32.107554+00:00' result_code: type: string nullable: true description: | Machine-readable reason for an unsuccessful outcome, set when the operation is `REJECTED`, `FAILED` or `EXPIRED`. Omitted while the operation is in progress and on `SUCCEEDED`. Codes you can rely on include: `MINT_LIMIT_EXCEEDED`, `RECEIVER_EXPOSURE_LIMIT_EXCEEDED`, `INSUFFICIENT_LIQUIDITY`, `VALIDATION_ERROR`, `BANK_NOT_ELIGIBLE`, `ASSET_NOT_ELIGIBLE`, `INSUFFICIENT_ELIGIBLE_LIQUIDITY` and `LIMIT_EXCEEDED` (the request could not be funded or authorised); `SCREENING_REJECTED`, `SCREENING_EXPIRED`, `SCREENING_NOT_STARTED` and `SCREENING_UNDELIVERABLE` (beneficiary screening); `CHECKER_REJECTED` (a reviewer rejected it); and `CANCEL_NOT_ALLOWED` (a cancellation was refused). Other failures carry a less specific code; rely on `result_message` for detail. example: CANCEL_NOT_ALLOWED result_message: type: string nullable: true description: | Human-readable explanation accompanying `result_code`, when available. Omitted otherwise. relationships: type: object properties: resource: allOf: - $ref: '#/components/schemas/ResourceIdentifierRelationship' description: | The resource the operation acts on. For a cancellation, this is the target operation. Omitted when the resource family is not yet known. links: $ref: '#/components/schemas/ResourceLinks' CursorLinks: description: | Pagination links of a collection response. Only forward paging is supported: follow `next` until it is absent. `prev` is not returned in the current release. type: object required: - self properties: self: type: string description: Relative URL of the current page, exactly as requested (path and query). example: /v1/operations?page[size]=20 prev: type: string description: Link to the previous page. Not returned in the current release. next: type: string description: | Relative URL of the next page. It repeats your query parameters with an updated `page[cursor]` (percent-encoded). Absent on the last page. example: /v1/operations?page%5Bsize%5D=20&page%5Bcursor%5D=CURSOR_FROM_LINKS_NEXT Errors: description: | JSON:API error document, returned with `Content-Type: application/vnd.api+json` for every JSON endpoint that fails. `errors` always holds at least one error object; today the Lyriq Connector returns exactly one. Branch on the HTTP status and on `code` when present; `title` and `detail` are human-readable and may change wording. type: object required: - errors properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' errors: type: array minItems: 1 description: Errors that occurred while processing the request. items: type: object required: - status - title properties: status: type: string description: HTTP status code of the response, as a string. example: '409' enum: - '400' - '401' - '403' - '404' - '406' - '409' - '413' - '415' - '422' - '424' - '429' - '500' - '502' - '503' code: type: string description: | Machine-readable error code. Present for errors the platform classifies (authentication, authorization, idempotency, validation, state conflicts, network state); absent for some generic errors, such as a malformed JSON body, a missing resource or an unexpected server error. | Code | Typical status | Meaning | | --- | --- | --- | | `AUTHENTICATION_REQUIRED` | 401 | Bearer token missing, malformed, expired, or not issued for the Lyriq Connector | | `UNAUTHORIZED_SCOPE` | 403 | Token lacks the required scope, has no bank membership, or needs `x-dan-bank-id` to choose between memberships | | `BANK_SCOPE_MISMATCH` | 403 | `x-dan-bank-id` is malformed or names a bank the token has no membership for, or the request names another bank where your own bank is required | | `BANK_SUSPENDED`, `BANK_TERMINATED` | 403 | The caller's bank is suspended or terminated on the network | | `IDEMPOTENCY_CONFLICT` | 409 | `Idempotency-Key` reused with a different request body | | `IDEMPOTENCY_PENDING` | 409 | A request with the same `Idempotency-Key` is still being processed | | `MISSING_FIELD`, `INVALID_FIELD_FORMAT`, `UNKNOWN_FIELD`, `TYPE_MISMATCH`, `RESOURCE_ID_MISMATCH` | 400 | A single field or parameter is missing or invalid; see `source` | | `VALIDATION_ERROR`, `MUTUALLY_EXCLUSIVE_FIELDS`, `MISSING_ANY_OF` | 422 | The request is well formed but breaks a business rule or a cross-field rule | | `STATE_CONFLICT` | 409 | The target resource is not in a state that allows the request | | `OUTBOUND_HALTED`, `READ_ONLY` | 503 | The network is halted or read-only; mutations are refused, reads still work | | `OPERATIONAL_STATE_UNKNOWN` | 503 | The network operational state could not be determined; retry later | | `DEPENDENCY_UNAVAILABLE` | 503 | A platform dependency is temporarily unavailable; retry later | example: IDEMPOTENCY_CONFLICT enum: - AUTHENTICATION_REQUIRED - UNAUTHORIZED_SCOPE - UNAUTHORIZED_ROLE - BANK_SCOPE_MISMATCH - BANK_TERMINATED - BANK_SUSPENDED - VALIDATION_ERROR - TYPE_MISMATCH - UNKNOWN_FIELD - MISSING_FIELD - INVALID_FIELD_FORMAT - RESOURCE_ID_MISMATCH - MUTUALLY_EXCLUSIVE_FIELDS - MISSING_ANY_OF - INVALID_REFERENCE - IDEMPOTENCY_CONFLICT - IDEMPOTENCY_PENDING - RESOURCE_NOT_FOUND - STATE_CONFLICT - RATE_LIMITED - DEPENDENCY_UNAVAILABLE - KEY_UNAVAILABLE - COUNTERPARTY_UNAVAILABLE - INTEGRITY_CHECK_FAILED - INSUFFICIENT_AVAILABLE_BALANCE - SETTLEMENT_LOCK_ACTIVE - SETTLEMENT_CYCLE_NOT_OPEN - INVALID_SETTLEMENT_STATE - UNAUTHORIZED_SETTLEMENT_ACTION - REDEMPTION_NOT_FOUND - REDEMPTION_STATE_CONFLICT - OPERATION_NOT_CANCELLABLE - CANCEL_IRREVERSIBLE_STATE - OUTBOUND_HALTED - READ_ONLY - OPERATIONAL_STATE_UNKNOWN - BANK_NOT_ELIGIBLE - ASSET_NOT_ELIGIBLE - INSUFFICIENT_ELIGIBLE_LIQUIDITY - LIMIT_EXCEEDED title: type: string description: Short human-readable summary of the problem. example: Idempotency conflict detail: type: string description: Human-readable explanation specific to this occurrence, when available. example: page[size] must be between 1 and 200, got 500 source: type: object description: What in the request caused the error, when it can be pinpointed. properties: pointer: type: string description: JSON Pointer (RFC 6901) to the request body member that caused the error. example: /data/attributes/reason_code parameter: type: string description: Path or query parameter that caused the error. example: operation_id header: type: string description: Request header that caused the error. example: Idempotency-Key links: type: object description: Links related to this error, when provided. additionalProperties: type: string meta: type: object description: Additional non-standard information about this error, when provided. additionalProperties: type: string DocumentLinks: description: | Top-level JSON:API document links. When present, `self` is the relative URL (path and query) of the request that produced this document. type: object properties: self: type: string description: URL of this document (the request URL). example: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 LedgerRef: description: | One ledger transaction submitted as part of an operation. A multi-step operation (for example a transfer that locks, then commits) has one entry per step. type: object required: - tx_type - state properties: tx_type: type: string description: | Kind of ledger step, for example `transfer`, `initiate_transfer`, `commit_transfer`, `create_lock`, `release_lock` or `set_issuance_limit`. example: transfer ledger_tx_id: type: string nullable: true description: | Ledger transaction identifier (a decimal integer, as a string), or `null` until the step has been submitted to the ledger. example: '4821907' state: type: string description: | Submission state of this step, for example `pending`, `signing`, `submitting`, `submitted`, `observing`, `succeeded`, `failed`, `ambiguous` or `manual_review_required`. example: succeeded ReviewKey: type: object required: - id - type properties: id: type: string description: Review task identifier (a UUID). example: 01927c40-2a3b-7c4d-8e5f-6a7b8c9d0e1f type: type: string enum: - reviews ReviewsRelationship: description: JSON:API to-many relationship to Review resources. type: object properties: data: type: array items: $ref: '#/components/schemas/ReviewKey' links: $ref: '#/components/schemas/RelationshipLinks' Operation: description: | Full operation record returned by `GET /v1/operations/{operation_id}`. An operation is created by every workflow mutation (the `202 Accepted` response carries its id) and tracks that request until it reaches a terminal state. Right after acceptance, and for up to about 5 minutes until the platform's read model catches up, the operation may be returned in state `PROCESSING` with `updated_at` equal to `created_at` and no `ledger_refs`, even if it has already moved on. Keep polling. allOf: - $ref: '#/components/schemas/OperationKey' - type: object required: - attributes properties: attributes: type: object required: - resource_family - operation_type - state - created_at - updated_at properties: resource_family: type: string description: Resource family the operation acts on, for example `transfers`, `accounts` or `operations`. example: transfers operation_type: type: string description: Command that created the operation, for example `TRANSFER_CREATE` or `OPERATION_CANCEL`. example: TRANSFER_CREATE state: $ref: '#/components/schemas/OperationState' created_at: type: string format: date-time description: When the operation was created (RFC 3339, UTC). example: '2026-09-26T10:15:30.482913+00:00' updated_at: type: string format: date-time description: When the operation last changed state (RFC 3339, UTC). example: '2026-09-26T10:15:32.107554+00:00' result_code: type: string nullable: true description: | Machine-readable reason for an unsuccessful outcome, set on `REJECTED`, `FAILED` or `EXPIRED`. Omitted while in progress and on `SUCCEEDED`. Codes you can rely on include: `MINT_LIMIT_EXCEEDED`, `RECEIVER_EXPOSURE_LIMIT_EXCEEDED`, `INSUFFICIENT_LIQUIDITY`, `VALIDATION_ERROR`, `BANK_NOT_ELIGIBLE`, `ASSET_NOT_ELIGIBLE`, `INSUFFICIENT_ELIGIBLE_LIQUIDITY` and `LIMIT_EXCEEDED` (the request could not be funded or authorised); `SCREENING_REJECTED`, `SCREENING_EXPIRED`, `SCREENING_NOT_STARTED` and `SCREENING_UNDELIVERABLE` (beneficiary screening); `CHECKER_REJECTED` (a reviewer rejected it); and `CANCEL_NOT_ALLOWED` (a cancellation was refused). Other failures carry a less specific code; rely on `result_message` for detail. example: CANCEL_NOT_ALLOWED result_message: type: string nullable: true description: Human-readable explanation accompanying `result_code`, when available. bank_id: type: string nullable: true description: | Bank that initiated the operation. A counterparty bank involved in the operation can also read it, in which case this is not the caller's bank. example: 11111111-1111-7111-8111-111111111111 idempotency_key: type: string description: | Reserved for the `Idempotency-Key` submitted with the request. Not currently returned. example: 5f0c9a2e-7d3b-4c1a-9e8f-2b6d4a1c3e5f ledger_tx_id: type: string nullable: true description: | Reserved convenience copy of the ledger transaction id for single-step workflows. Not currently returned; read `ledger_refs` instead. example: '4821907' ledger_refs: type: array description: | Ledger transactions submitted for this operation, one per step. Empty until the first step is submitted, and for operations that do not touch the ledger. items: $ref: '#/components/schemas/LedgerRef' planes: type: object description: Reserved per-plane execution summary. Not currently returned. properties: approval: type: object description: Approval plane state. properties: state: type: string description: Current approval plane state. review_ids: type: array description: Review task identifiers created for this operation. items: type: string ledger: type: object description: Ledger plane state. properties: state: type: string description: Current ledger plane state. ledger_tx_id: type: string nullable: true description: Primary ledger transaction id. ledger_refs: type: array description: Per-step ledger transaction references. items: $ref: '#/components/schemas/LedgerRef' cbs: type: object description: Core banking system plane state. properties: state: type: string description: Current CBS plane state. cash_settlement: type: object description: Cash settlement plane state. properties: state: type: string description: Current cash settlement plane state. reference: type: string nullable: true description: External settlement reference. integrity: type: object description: Integrity plane state. properties: state: type: string description: Current integrity plane state. relationships: type: object properties: resource: allOf: - $ref: '#/components/schemas/ResourceIdentifierRelationship' description: | The resource the operation acts on. For a cancellation, this is the target operation. Omitted when the resource family is not yet known. reviews: allOf: - $ref: '#/components/schemas/ReviewsRelationship' description: Reserved link to the operation's review tasks. Not currently returned. links: $ref: '#/components/schemas/ResourceLinks' OperationCancelRequest: description: Request body for `POST /v1/operations/{operation_id}/cancel`. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - attributes properties: type: type: string description: Must be `operation-cancellations`. enum: - operation-cancellations example: operation-cancellations attributes: type: object additionalProperties: false x-deny-unknown-fields: true description: | All attributes are optional; send `{}` to cancel without a reason. An attribute that is present must not be empty or blank. Unknown attributes are rejected with `422 Unprocessable Entity`. properties: reason_code: type: string minLength: 1 description: Your machine-readable reason for the cancellation, recorded for audit. example: DUPLICATE_REQUEST comment: type: string nullable: true minLength: 1 description: Free-text explanation of the cancellation. example: Duplicate payment submitted by mistake. expected_target_state: type: string nullable: true description: | Optimistic concurrency guard: the state (an `OperationState` value such as `PROCESSING`) you expect the target operation to be in. If the target is in any other state, the request fails with `400 Bad Request` and nothing is cancelled. Omit it to cancel regardless of the current state. example: PROCESSING AcceptedOperation: description: | Body of every `202 Accepted` response to a workflow mutation. It identifies the operation created to track the request; it does not describe the final outcome. Poll `GET /v1/operations/{operation_id}` (or follow `data.links.self` when present) until the operation reaches a terminal state. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - id - attributes properties: type: type: string description: Always `operations`. enum: - operations example: operations id: type: string description: | Operation identifier. Use it as `operation_id` in `GET /v1/operations/{operation_id}` and `POST /v1/operations/{operation_id}/cancel`. example: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 attributes: type: object required: - resource_family - state properties: resource_family: type: string description: | Resource family the operation acts on, for example `transfers`, `accounts`, `mint-limits`, `exposure-limits`, `redemption-policies`, `webhooks`, `auth-profiles`, `encryption-targets`, or `operations` (for a cancellation). example: transfers state: type: string description: Always `ACCEPTED` in this response; later states are read from the operation. enum: - ACCEPTED example: ACCEPTED relationships: type: object description: | Present only for mutations whose target resource has a stable identifier at acceptance time (for example transfers, accounts and mint limits). Omitted otherwise. properties: resource: $ref: '#/components/schemas/ResourceIdentifierRelationship' links: type: object description: Present together with `relationships`. properties: self: type: string description: Relative URL of the operation, `/v1/operations/{operation_id}`. example: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 example: jsonapi: version: '1.1' data: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 attributes: resource_family: transfers state: ACCEPTED relationships: resource: data: type: transfers id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 links: related: /v1/transfers/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 links: self: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 RedemptionKey: type: object required: - id - type properties: id: type: string description: Redemption identifier (a UUID). Use it as `redemption_id`. example: 5b8f0c2e-3d4a-5e6f-9a1b-2c3d4e5f6a7b type: type: string description: Always `redemptions`. enum: - redemptions OperationRelationship: description: JSON:API to-one relationship to an Operation. type: object properties: data: $ref: '#/components/schemas/OperationKey' links: $ref: '#/components/schemas/RelationshipLinks' SettlementCycleKey: type: object required: - id - type properties: id: type: string description: Settlement cycle identifier (a UUID). Use it as `settlement_cycle_id`. example: 7c9e6679-7425-5de0-9f1a-3b2c1d0e4f5a type: type: string description: Always `settlement-cycles`. enum: - settlement-cycles SettlementCycleRelationship: description: JSON:API to-one relationship to the settlement cycle a redemption is assigned to. type: object properties: data: $ref: '#/components/schemas/SettlementCycleKey' links: $ref: '#/components/schemas/RelationshipLinks' Redemption: description: | A redemption: the holder bank returns units of an issuer's asset from its interbank holding account to the issuer, settled in cash through the issuer's settlement cycle. Visible only to the holder bank. allOf: - $ref: '#/components/schemas/RedemptionKey' - type: object required: - attributes properties: attributes: type: object required: - holder_bank_id - issuer_bank_id - asset_id - redemption_kind - amount - state - requested_at properties: holder_bank_id: type: string description: Bank that holds the asset and asked for the redemption; always the calling bank. example: 22222222-2222-7222-8222-222222222222 issuer_bank_id: type: string description: Bank that issued the asset and settles the cash. example: 11111111-1111-7111-8111-111111111111 asset_id: type: string description: Root asset being redeemed, for example `usd`. example: usd redemption_kind: type: string description: | Redemption workflow. `SETTLEMENT_CYCLE`: settled through the issuer's settlement cycle. `IMMEDIATE`: burned without waiting for a cycle. Redemptions returned by the current release are always `SETTLEMENT_CYCLE`. enum: - IMMEDIATE - SETTLEMENT_CYCLE example: SETTLEMENT_CYCLE amount: type: object description: | Amount requested for redemption. `value` is an integer string in minor units; divide by 10^`scale` for the decimal amount (`"250000"` with `scale` 2 is 2500.00). required: - asset_id - value - scale properties: asset_id: type: string description: Asset the amount is denominated in (the root asset). example: usd value: type: string description: Amount in the asset's smallest unit (non-negative integer string). example: '250000' scale: type: integer description: Number of decimal places of the asset. example: 2 state: type: string description: | Current lifecycle state. `SUBMITTED`: waiting for a cycle. `ASSIGNED`: in an open cycle. `LOCKED`: locked on the ledger for the cycle. `REDEEMED` (terminal): burned after cash settlement. `RELEASED` (terminal): the cycle was aborted and the units are spendable again. `QUEUED`, `REJECTED`, and `EXPIRED` are not assigned by the current release but clients should accept them. See `GET /v1/redemptions` for details. enum: - SUBMITTED - QUEUED - ASSIGNED - LOCKED - REDEEMED - RELEASED - REJECTED - EXPIRED example: ASSIGNED assigned_cycle_id: type: string nullable: true description: | Settlement cycle the redemption is assigned to. Omitted while the redemption is `SUBMITTED`. example: 7c9e6679-7425-5de0-9f1a-3b2c1d0e4f5a client_reference: type: string nullable: true description: Caller-supplied opaque reference. Omitted when none was given. example: REDEEM-7788 requested_at: type: string format: date-time description: When the redemption was submitted (UTC). example: '2026-09-24T09:15:02Z' relationships: type: object properties: operation: $ref: '#/components/schemas/OperationRelationship' cycle: $ref: '#/components/schemas/SettlementCycleRelationship' links: $ref: '#/components/schemas/ResourceLinks' TransferKey: type: object required: - id - type properties: id: type: string description: | Transfer identifier (UUID). This is not the same value as the `operation_id` returned by `POST /v1/transfers`; the transfer's operation is linked through `relationships.operation`. example: 6f1c2a4e-9b3d-5e7f-8a1b-2c3d4e5f6a7b type: type: string description: JSON:API resource type; always `transfers`. enum: - transfers TransferPartyAccount: description: | Account of a transfer party. `scheme` selects the fields: | `scheme` | Fields | |---|---| | `IBAN` | `value`: the IBAN, without spaces; the check digits must be valid | | `US_ROUTING_ACCOUNT` | `routing_number` (9 digits) and `account_number_token` (1 to 34 characters) | | `OTHER` | `value`: an account number (ISO 20022 `Max34Text`, 1 to 34 characters) | Fields that do not belong to the scheme are rejected. type: object additionalProperties: false x-deny-unknown-fields: true required: - scheme properties: scheme: type: string description: Account scheme. enum: - IBAN - US_ROUTING_ACCOUNT - OTHER example: IBAN value: type: string nullable: true description: The IBAN (`IBAN`) or the account number (`OTHER`). example: DE89370400440532013000 routing_number: type: string nullable: true description: ABA routing number (`US_ROUTING_ACCOUNT`), 9 digits. example: '110000000' account_number_token: type: string nullable: true description: Account number or its token (`US_ROUTING_ACCOUNT`), 1 to 34 characters. example: acct_tok_01J9Z6V4X6R4P0M7F3N4S8T9Q2 TransferPartyPostalAddress: description: | Postal address, a subset of ISO 20022 `PostalAddress24`. All fields are optional. type: object additionalProperties: false x-deny-unknown-fields: true properties: street_name: type: string nullable: true description: Street name (`Max70Text`, 1 to 70 characters). example: Main Street building_number: type: string nullable: true description: Building number (`Max16Text`, 1 to 16 characters). example: '1' post_code: type: string nullable: true description: Postal code (`Max16Text`, 1 to 16 characters). example: '10001' town_name: type: string nullable: true description: Town or city (`Max35Text`, 1 to 35 characters). example: New York country_sub_division: type: string nullable: true description: State, province or other subdivision (`Max35Text`, 1 to 35 characters). example: NY country: type: string nullable: true description: Country, ISO 3166-1 alpha-2 (two uppercase letters). example: US TransferPartyDetails: description: | Details of a transfer party: the sender (`source.originator`) or the beneficiary (`destination.beneficiary`), modelled on the ISO 20022 debtor and creditor with their accounts (pacs.008 `Dbtr`/`DbtrAcct`, `Cdtr`/`CdtrAcct`). The platform validates the format only. The receiving bank resolves the beneficiary and screens both parties if it screens transfers. `name` and `account` are required; a request without them is rejected with `422 VALIDATION_ERROR` and a pointer to the missing field. type: object additionalProperties: false x-deny-unknown-fields: true properties: name: type: string minLength: 1 maxLength: 140 description: Required. Party name (ISO 20022 `Max140Text`, 1 to 140 characters). example: JOHN DOE account: allOf: - $ref: '#/components/schemas/TransferPartyAccount' description: Required. The party's account. bic: type: string nullable: true description: | BIC of the party's bank or branch (ISO 9362: 8 or 11 characters, uppercase letters and digits). example: BETAUS33 postal_address: type: object allOf: - $ref: '#/components/schemas/TransferPartyPostalAddress' nullable: true description: Party postal address. AccountAlias: description: Payment alias for a customer account. type: object required: - scheme properties: scheme: type: string description: Alias scheme. enum: - IBAN - US_ROUTING_ACCOUNT - BANK_BENEFICIARY_REF example: IBAN value: type: string nullable: true description: Alias value for single-value schemes such as IBAN. example: GB82WEST12345698765432 routing_number: type: string nullable: true description: Routing number for US routing/account aliases. example: '110000000' account_number_token: type: string nullable: true description: Tokenized account number for US routing/account aliases. example: acct_tok_01J9Z6V4X6R4P0M7F3N4S8T9Q2 TransferParty: description: Account party on a money-movement response. type: object properties: bank_id: type: string description: Bank (a UUID) that holds the account. example: 22222222-2222-7222-8222-222222222222 bank_display_name: type: string nullable: true description: Display name of the party's bank. Always present; `null` when it cannot be resolved. example: Bank Beta external_account_id: type: string description: | Resolved account identifier within the bank. Omitted when the sending bank identified the party only by its details (`originator` or `beneficiary`). example: CBS-ACC-2026-000123 originator: type: object allOf: - $ref: '#/components/schemas/TransferPartyDetails' nullable: true description: | Source only: the sender details the sending bank submitted for an `INTERBANK` transfer, for the receiving bank to screen. Omitted when none were submitted. beneficiary: type: object allOf: - $ref: '#/components/schemas/TransferPartyDetails' nullable: true description: | Destination only: the beneficiary details the sending bank submitted for an `INTERBANK` transfer. Omitted when none were submitted. The receiving bank resolves the beneficiary from these details. submitted_destination_ref: allOf: - $ref: '#/components/schemas/AccountAlias' description: Reserved; not currently returned. resolved_external_account_id: type: string description: Reserved; not currently returned. example: CBS-ACC-2026-000123 kind: type: string description: Account classification. May be omitted. enum: - CUSTOMER - TREASURY example: CUSTOMER TransferLiquidityLeg: description: | One settlement leg of a transfer plan: the part of the requested amount settled with tokens of one issuer, held by one bank. type: object required: - issuer_relation - holder_bank_id - issuer_bank_id - settlement_asset_id - amount properties: issuer_relation: type: string description: | How the issuer of the tokens used in this leg relates to the transfer: - `RECEIVER_ISSUED`: issued by the receiving bank. - `OTHER_FOREIGN`: issued by a third bank (neither sender nor receiver). - `OWN_ISSUED`: issued by the sending bank itself. enum: - OWN_ISSUED - RECEIVER_ISSUED - OTHER_FOREIGN example: RECEIVER_ISSUED holder_bank_id: type: string description: '`bank_id` (UUID) of the bank holding the tokens used in this leg (the sending bank).' example: 11111111-1111-7111-8111-111111111111 issuer_bank_id: type: string description: '`bank_id` (UUID) of the bank that issued the tokens used in this leg.' example: 22222222-2222-7222-8222-222222222222 settlement_asset_id: type: string description: Asset settled by this leg; the root `asset_id` of the transfer. example: usd amount: type: integer format: int64 description: Part of the requested amount settled by this leg, in the asset's smallest unit. example: 150000 TransferLiquidityPlan: description: | Liquidity plan chosen by the platform for an `INTERBANK` or `BANK_ROUTED` transfer: which issuers' tokens settle the requested amount. The platform prefers tokens issued by the receiving bank, then tokens issued by third banks, and falls back to tokens issued by the sending bank itself. type: object required: - selector_version - requested_amount - plan_hash - receiver_issued_count - other_foreign_count - own_issued_count - own_issued_fallback_used - legs properties: selector_version: type: string description: Version of the liquidity-selection algorithm that built the plan. example: realtime-liquidity-v1 requested_amount: type: integer format: int64 description: Total amount of the transfer, in the asset's smallest unit. example: 150000 plan_hash: type: string description: | Deterministic hash of the plan: `sha256:` followed by 64 lowercase hexadecimal characters. Useful as a stable fingerprint of the plan when reconciling. example: sha256:4f9a1c2e7b3d8a6f0e5c1b9d2a7f3e8c6b0d4a1f9e2c7b5a3d8f6e0c1b4a9d7e receiver_issued_count: type: integer format: int32 description: Number of legs (across the whole plan) that use tokens issued by the receiving bank. example: 1 other_foreign_count: type: integer format: int32 description: Number of legs (across the whole plan) that use tokens issued by a third bank. example: 0 own_issued_count: type: integer format: int32 description: Number of legs (across the whole plan) that use tokens issued by the sending bank. example: 0 own_issued_fallback_used: type: boolean description: '`true` when at least one leg uses tokens issued by the sending bank.' example: false legs: type: array description: | Legs of the plan, in selection order. Only legs in which the calling bank is the holder or the issuer are listed, so the receiving bank may see fewer legs than the counts above. items: $ref: '#/components/schemas/TransferLiquidityLeg' Transfer: description: | A transfer resource: one movement of existing value between accounts, of kind `INTERBANK` or `BANK_ROUTED`. The transfer is a read model of the transfer's operation. It appears shortly after `POST /v1/transfers` returns `202 Accepted` (until then, `GET /v1/transfers/{transfer_id}` returns `404`), and its `state` follows the operation's state. Both the sending bank and, for `INTERBANK` and `BANK_ROUTED` transfers, the receiving bank can read the transfer. `transfer_plan` (how the platform sourced liquidity) is present only for `INTERBANK` and `BANK_ROUTED` transfers, and only once liquidity has been selected; it is omitted before that. Its `legs` contain only the legs in which the calling bank is the holder or the issuer. allOf: - $ref: '#/components/schemas/TransferKey' - type: object required: - attributes properties: attributes: type: object required: - transfer_kind - amount - state - created_at - updated_at properties: transfer_kind: type: string description: | Kind of transfer, as submitted: - `INTERBANK`: to a named account at another bank. - `BANK_ROUTED`: to another bank, identified only by bank and asset. enum: - INTERBANK - BANK_ROUTED example: INTERBANK state: allOf: - $ref: '#/components/schemas/OperationState' description: | Current lifecycle state. Always the same value as the `state` of the transfer's operation (`relationships.operation`); see the lifecycle table under `POST /v1/transfers`. The value can lag the operation by a short moment, because the transfer is updated from the operation's state changes. amount: type: object description: Transferred amount. required: - asset_id - value - scale properties: asset_id: type: string description: The `asset_id` exactly as submitted in the create request. example: usd value: type: string description: | Amount in the asset's smallest unit (minor units), as a base-10 integer string. For example `"150000"` with `scale` 2 is 1,500.00. example: '150000' scale: type: integer description: | Number of decimal places of the asset (its minor-unit exponent), taken from the asset definition. Divide `value` by 10^`scale` to get the amount in major units. example: 2 source: type: object nullable: true description: | Sending party. `bank_id` is always the sending bank. `external_account_id` is the source account submitted by the sender; it is absent for `BANK_ROUTED` transfers, whose source holding account is chosen by the platform and is not exposed, and for `INTERBANK` transfers whose sender was identified only by `originator`. `originator` carries the sender details, when submitted. allOf: - $ref: '#/components/schemas/TransferParty' destination: type: object nullable: true description: | Receiving party. `bank_id` is the receiving bank. `external_account_id` is the destination account submitted by the sender; it is absent for `BANK_ROUTED` transfers, whose destination holding account is chosen by the platform and is not exposed. allOf: - $ref: '#/components/schemas/TransferParty' destination_reference_state: type: string nullable: true description: Reserved. Not currently returned. client_reference: type: string nullable: true description: | The `client_reference` sent in the create request, or `null` if none was sent. For transfers created from an ISO 20022 message, the message's UETR (the same value as `uetr`). Visible to the sending and the receiving bank. example: PAYMENT-7788 uetr: type: string nullable: true description: | ISO 20022 Unique End-to-end Transaction Reference, for transfers created from an ISO 20022 message. `null` for transfers created with `POST /v1/transfers`. example: null metadata: type: object nullable: true additionalProperties: true description: | The `metadata` object sent in the create request, or `null` if none was sent. Visible to the sending and the receiving bank. example: memo: Invoice 2026-0915 ledger: type: object nullable: true description: | Reserved. Not currently returned. Ledger references are available on the operation (`GET /v1/operations/{operation_id}`). properties: state: type: string description: Ledger commit state. ledger_tx_id: type: string nullable: true description: Ledger transaction identifier. created_at: type: string format: date-time description: When the transfer was accepted (RFC 3339 timestamp, UTC). example: '2026-09-26T14:03:12.481Z' updated_at: type: string format: date-time description: When the transfer's `state` last changed (RFC 3339 timestamp, UTC). example: '2026-09-26T14:03:14.027Z' transfer_plan: $ref: '#/components/schemas/TransferLiquidityPlan' relationships: type: object description: | `operation` links the operation that tracks this transfer; poll `GET /v1/operations/{operation_id}` for `result_code` and `result_message` once the transfer reaches a terminal state. `reviews` is reserved and not currently returned on the transfer; review tasks for the transfer are listed on its operation. properties: operation: $ref: '#/components/schemas/OperationRelationship' reviews: $ref: '#/components/schemas/ReviewsRelationship' links: $ref: '#/components/schemas/ResourceLinks' TransferAmount: description: Amount expressed as an integer value in the asset's smallest unit with an explicit decimal scale. type: object required: - value - scale properties: value: type: string description: | Amount in the asset's smallest unit (minor units), as a base-10 integer string with no sign, decimal point or exponent. Transfers reject `0`; limit updates accept `0`. example: '100000' scale: type: integer description: | Number of decimal places of the asset. Send the asset's decimals (see the asset's `scale`); the value is currently not validated or used by the platform. example: 2 InterbankTransferSource: description: | Sending party of an `INTERBANK` transfer, at the calling bank. Identify the sender with `external_account_id` (an account the calling bank registered), with `originator` (details passed to the receiving bank for screening), or with both. type: object properties: bank_id: type: string description: | Optional. If sent, it must equal the calling bank's `bank_id` (otherwise `403 BANK_SCOPE_MISMATCH`). example: 11111111-1111-7111-8111-111111111111 external_account_id: type: string nullable: true minLength: 1 maxLength: 34 description: | Account the calling bank registered for `asset_id` (ISO 20022 `Max34Text`, 1 to 34 characters). Required unless `originator` is sent. If sent, it must be an active registration at the calling bank, with or without `originator`; otherwise the transfer ends `FAILED`. example: CBS-ACC-2026-000123 originator: allOf: - $ref: '#/components/schemas/TransferPartyDetails' description: | Sender details, passed to the receiving bank so it can screen the transfer. Required unless `external_account_id` is sent. Not checked against account registrations. alias: allOf: - $ref: '#/components/schemas/AccountAlias' description: Reserved. Accepted but ignored; use `originator` to send sender details. expected_holder_name: type: string description: Reserved. Accepted but ignored; use `originator.name`. example: ACME TREASURY InterbankTransferDestination: description: | Receiving party of an `INTERBANK` transfer. `bank_id` is required. Identify the beneficiary with `external_account_id` (an account the receiving bank registered), with `beneficiary` (details the receiving bank resolves), or with both. type: object properties: bank_id: type: string description: Receiving bank (a UUID), different from the calling bank. example: 22222222-2222-7222-8222-222222222222 external_account_id: type: string nullable: true minLength: 1 maxLength: 34 description: | Account the receiving bank registered for `asset_id` (ISO 20022 `Max34Text`, 1 to 34 characters). Required unless `beneficiary` is sent. If sent, it must be an active registration at the receiving bank, with or without `beneficiary`; otherwise the transfer ends `FAILED`. example: CBS-ACC-2026-000987 beneficiary: allOf: - $ref: '#/components/schemas/TransferPartyDetails' description: | Beneficiary details for the receiving bank to resolve and credit on its core banking system. Required unless `external_account_id` is sent. Not checked against account registrations. alias: allOf: - $ref: '#/components/schemas/AccountAlias' description: | Reserved. Accepted but ignored; use `beneficiary` to send an IBAN or a US routing and account number. expected_holder_name: type: string description: | Reserved for payee verification. Accepted but ignored; use `beneficiary.name` to send the beneficiary's name. example: JOHN DOE TransferAttributesInterbank: description: | Attributes of an `INTERBANK` transfer: a movement from the calling bank to a beneficiary at another bank. Each party is identified by a registered account, by party details (`source.originator`, `destination.beneficiary`), or both. type: object additionalProperties: false x-deny-unknown-fields: true required: - transfer_kind - asset_id - amount - source - destination properties: transfer_kind: type: string description: Discriminator; always `INTERBANK` for this shape. x-rust-discriminator-owned-by-parent: true enum: - INTERBANK example: INTERBANK asset_id: type: string description: | Asset to transfer. Use the root asset id (for example `usd`), not an issuer-scoped position id such as `usd.bank-alpha`. The platform chooses which issuers' tokens settle the transfer (see `transfer_plan` on the transfer). example: usd amount: description: | Amount to transfer. `value` is a positive integer string in the asset's smallest unit; see the amount rules under `POST /v1/transfers`. allOf: - $ref: '#/components/schemas/TransferAmount' source: type: object description: | Sending party at the calling bank: `external_account_id` (an account registered at the calling bank for `asset_id`), `originator`, or both. `bank_id` is optional; if sent it must equal the calling bank's `bank_id` (otherwise `403 BANK_SCOPE_MISMATCH`). allOf: - $ref: '#/components/schemas/InterbankTransferSource' destination: type: object description: | Receiving party. `bank_id` (the receiving bank's UUID, different from the calling bank) is required, plus `external_account_id` (an account the receiving bank registered for `asset_id`), `beneficiary`, or both. allOf: - $ref: '#/components/schemas/InterbankTransferDestination' client_reference: type: string nullable: true minLength: 1 maxLength: 256 description: | Optional caller reference, 1 to 256 characters. Stored and returned on the transfer to the sending and the receiving bank. example: PAYMENT-7788 metadata: type: object nullable: true additionalProperties: true description: | Optional free-form JSON object for information that has no dedicated field, such as remittance details. At most 4096 bytes when serialised as JSON. Stored and returned on the transfer to the sending and the receiving bank. example: memo: Invoice 2026-0915 TransferAttributesBankRouted: description: | Attributes of a `BANK_ROUTED` transfer: a movement to another bank identified only by its `bank_id` and the asset. The platform selects the source liquidity and the receiving bank's holding account; no account identifiers are sent or accepted. type: object additionalProperties: false x-deny-unknown-fields: true required: - transfer_kind - receiving_bank_id - asset_id - amount properties: transfer_kind: type: string description: Discriminator; always `BANK_ROUTED` for this shape. x-rust-discriminator-owned-by-parent: true enum: - BANK_ROUTED example: BANK_ROUTED receiving_bank_id: type: string description: | `bank_id` (UUID) of the receiving bank. Must be a valid UUID different from the calling bank (a non-UUID value returns `422 VALIDATION_ERROR`; the calling bank's own id returns `403 BANK_SCOPE_MISMATCH`). example: 22222222-2222-7222-8222-222222222222 asset_id: type: string description: Root asset to transfer (for example `usd`), not an issuer-scoped position id. example: usd amount: description: | Amount to transfer. `value` is a positive integer string in the asset's smallest unit; see the amount rules under `POST /v1/transfers`. allOf: - $ref: '#/components/schemas/TransferAmount' client_reference: type: string nullable: true minLength: 1 maxLength: 256 description: | Optional caller reference, 1 to 256 characters. Stored and returned on the transfer. example: PAYMENT-7788 metadata: type: object nullable: true additionalProperties: true description: | Optional free-form JSON object, at most 4096 bytes when serialised as JSON. Stored and returned on the transfer. example: memo: Invoice 2026-0915 CreateTransferRequest: description: | Request body for `POST /v1/transfers`. `data.attributes` takes one shape per `transfer_kind`. Unknown attributes, and fields that belong to another shape (for example `receiving_bank_id` on an `INTERBANK` request, or `source` on a `BANK_ROUTED` request), are rejected with `422 Unprocessable Entity`. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - attributes properties: type: type: string description: JSON:API resource type; must be `transfers`. enum: - transfers attributes: description: Transfer attributes, discriminated on `transfer_kind`. oneOf: - $ref: '#/components/schemas/TransferAttributesInterbank' - $ref: '#/components/schemas/TransferAttributesBankRouted' discriminator: propertyName: transfer_kind mapping: INTERBANK: '#/components/schemas/TransferAttributesInterbank' BANK_ROUTED: '#/components/schemas/TransferAttributesBankRouted' TransferPreflightRequestInterbank: description: | Attributes of an `INTERBANK` preflight: a check of the calling bank's liquidity and limits toward the receiving bank named in `destination.bank_id`. Account fields are optional and are not validated, so you can send the body you intend to submit to `POST /v1/transfers`; they do not affect the result. type: object required: - transfer_kind - asset_id - amount - destination properties: transfer_kind: type: string description: Discriminator; always `INTERBANK` for this shape. x-rust-discriminator-owned-by-parent: true enum: - INTERBANK example: INTERBANK asset_id: type: string description: | Root asset to transfer (for example `usd`), not an issuer-scoped position id such as `usd.bank-alpha`. example: usd amount: description: | Amount to evaluate. `value` must be a positive integer string in the asset's smallest unit. allOf: - $ref: '#/components/schemas/TransferAmount' source: type: object description: | Optional. `bank_id`, if sent, must equal the calling bank's `bank_id` (otherwise `403 BANK_SCOPE_MISMATCH`). `external_account_id`, `originator`, `alias` and `expected_holder_name` are accepted and ignored. allOf: - $ref: '#/components/schemas/InterbankTransferSource' destination: type: object description: | The receiving bank. `bank_id` (UUID of a bank other than the caller) is required. `external_account_id`, `beneficiary`, `alias` and `expected_holder_name` are accepted and ignored. allOf: - $ref: '#/components/schemas/InterbankTransferDestination' TransferPreflightRequest: description: | Request body for `POST /v1/transfers/preflight`. `data.attributes` takes one of two shapes, selected by `transfer_kind`. The receiving bank is the only counterparty input: `destination.bank_id` for `INTERBANK`, `receiving_bank_id` for `BANK_ROUTED`. The `BANK_ROUTED` shape is the same as for `POST /v1/transfers`. required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - attributes properties: type: type: string description: JSON:API resource type; must be `transfer-preflight`. enum: - transfer-preflight attributes: description: Preflight attributes, discriminated on `transfer_kind`. oneOf: - $ref: '#/components/schemas/TransferPreflightRequestInterbank' - $ref: '#/components/schemas/TransferAttributesBankRouted' discriminator: propertyName: transfer_kind mapping: INTERBANK: '#/components/schemas/TransferPreflightRequestInterbank' BANK_ROUTED: '#/components/schemas/TransferAttributesBankRouted' TransferPreflight: description: | Result of `POST /v1/transfers/preflight`: whether a transfer of the given asset and amount from the calling bank to the receiving bank would pass liquidity selection if it were submitted now. It checks the sending bank's eligible liquidity (available balances plus own-issuance headroom under its mint limit) and the receiving bank's exposure limit toward the sender. The result is the same for `INTERBANK` and `BANK_ROUTED`. The result is a point-in-time estimate, not a reservation: balances and limits can change before a real transfer is submitted, and a transfer can still fail later for reasons the preflight does not cover (for example an unregistered account, beneficiary screening, checker review, or ledger submission). type: object required: - type - attributes properties: type: type: string description: JSON:API resource type; always `transfer-preflight-result`. enum: - transfer-preflight-result attributes: type: object required: - would_succeed properties: would_succeed: type: boolean description: | `true` when no blocking constraint was found. `false` when one was; see `blocking_constraint`. example: false blocking_constraint: type: string nullable: true description: | The constraint that would block the transfer. Always present; `null` when `would_succeed` is `true`. - `INSUFFICIENT_LIQUIDITY`: the sender does not hold enough eligible liquidity. - `MINT_LIMIT_EXCEEDED`: covering the amount would need more own issuance than the sender's mint limit leaves. - `RECEIVER_EXPOSURE_LIMIT_EXCEEDED`: the receiving bank's exposure limit toward the sender's tokens would be exceeded. The same conditions make a real transfer end `FAILED` with `result_code` `INSUFFICIENT_ELIGIBLE_LIQUIDITY` or `LIMIT_EXCEEDED` respectively. enum: - INSUFFICIENT_LIQUIDITY - MINT_LIMIT_EXCEEDED - RECEIVER_EXPOSURE_LIMIT_EXCEEDED - null example: RECEIVER_EXPOSURE_LIMIT_EXCEEDED remediation: type: string nullable: true description: | Suggested next action, paired one to one with `blocking_constraint`. Always present; `null` when `would_succeed` is `true`. - `ADJUST_AMOUNT_OR_RETRY_LATER` for `INSUFFICIENT_LIQUIDITY`. - `REQUEST_ISSUANCE_LIMIT_INCREASE` for `MINT_LIMIT_EXCEEDED`. - `REQUEST_COUNTERPARTY_EXPOSURE_LIMIT_INCREASE` for `RECEIVER_EXPOSURE_LIMIT_EXCEEDED`. enum: - ADJUST_AMOUNT_OR_RETRY_LATER - REQUEST_ISSUANCE_LIMIT_INCREASE - REQUEST_COUNTERPARTY_EXPOSURE_LIMIT_INCREASE - null example: REQUEST_COUNTERPARTY_EXPOSURE_LIMIT_INCREASE available_headroom: type: string format: uint64 nullable: true description: | Remaining capacity against `blocking_constraint`, as an integer string in the asset's smallest unit. Always present; populated only for: - `MINT_LIMIT_EXCEEDED`: always populated with the sender's remaining issuance headroom under its mint limit. - `RECEIVER_EXPOSURE_LIMIT_EXCEEDED`: the receiving bank's remaining exposure capacity toward the sender, only when the network is configured to disclose it. `null` in every other case, including when `would_succeed` is `true`. example: null CreateScreeningDecisionRequest: description: Request body for `POST /v1/transfers/{transfer_id}/screening-decisions`. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - attributes properties: type: type: string description: JSON:API resource type; must be `screening-decisions`. enum: - screening-decisions attributes: type: object required: - decision properties: decision: type: string enum: - APPROVE - REJECT - EXTEND description: | The beneficiary bank's decision: - `APPROVE`: accept the transfer; it moves to `PROCESSING` and continues to the ledger. - `REJECT`: decline the transfer; it ends `REJECTED` (operation `result_code` `SCREENING_REJECTED`) and nothing is written to the ledger. - `EXTEND`: ask for more time; the transfer stays `AWAITING_SCREENING` and its screening deadline moves out by `extend_ttl_seconds`. example: APPROVE extend_ttl_seconds: type: integer format: int32 minimum: 1 description: | Only allowed with `decision` `EXTEND` (sending it with another decision returns `400`). Number of seconds added to the transfer's current screening deadline; must be positive. When omitted, the deadline moves out by one screening window. However many times `EXTEND` is used, screening always ends no later than the network's screening ceiling (2 hours by default), measured from the moment the transfer entered screening; a longer extension is shortened to that ceiling. example: 30 AccountKey: type: object required: - id - type properties: id: type: string description: | JSON:API resource identifier of the account. It is derived from the account's identity and is not a second account number: do not send it to your core banking system, and do not use it in URL paths (account URLs use `external_account_id`). Format: `v1:{bank_id}:{asset_id}:{external_account_id}`. Each of the three components is percent-encoded before joining (every byte except `A-Z a-z 0-9 - . _ *` becomes `%XX`, and a space becomes `%20`), so a `:` inside a component can never be mistaken for a separator. Worked example: bank `11111111-1111-7111-8111-111111111111`, asset `usd` and external account ID `CBS/ACC 2026:01` give `v1:11111111-1111-7111-8111-111111111111:usd:CBS%2FACC%202026%3A01`. example: v1:11111111-1111-7111-8111-111111111111:usd:CBS-ACC-2026-000123 type: type: string enum: - accounts LinkOnlyRelationship: description: JSON:API relationship object with links only (no data key). type: object properties: links: $ref: '#/components/schemas/RelationshipLinks' Account: description: | An account your bank has registered with the Lyriq Connector for one asset. Its identity is the combination of `bank_id`, `asset_id` and `external_account_id`: the same `external_account_id` may be registered once per `asset_id`. The JSON:API `id` is derived from that combination (see `id`). Only the attributes marked as returned below appear in responses today. `display_name`, `aliases` and `metadata` are accepted on registration but are not currently returned, and account resources do not currently carry `relationships` or `links`. allOf: - $ref: '#/components/schemas/AccountKey' - type: object required: - attributes properties: attributes: type: object required: - external_account_id - bank_id - asset_id - kind - status properties: external_account_id: type: string description: | Returned. The core banking system (CBS) identifier your bank supplied at registration, exactly as supplied. For accounts registered through `POST /v1/accounts` it satisfies the ISO 20022 `Max34Text` type (1 to 34 characters). example: CBS-ACC-2026-000123 bank_id: type: string description: Returned. `bank_id` of the bank that owns the account (always the calling bank). example: 11111111-1111-7111-8111-111111111111 asset_id: type: string description: | Returned. The asset the account was registered for, exactly as supplied at registration. This is the root settlement asset identifier (for example `usd`), not a bank-issued asset identifier such as `usd.bank-alpha`. example: usd display_name: type: string nullable: true description: Not currently returned. Reserved for a bank-facing label. example: Customer Operating Account kind: type: string description: | Returned. Account classification: `CUSTOMER` for an account that holds a customer's funds, `TREASURY` for the bank's own account. enum: - CUSTOMER - TREASURY status: type: string description: | Returned. Account lifecycle status. `ACTIVE` is the only value: an account is returned only after registration has completed. enum: - ACTIVE aliases: type: array nullable: true description: Not currently returned. Reserved for payment aliases of a customer account. items: $ref: '#/components/schemas/AccountAlias' metadata: type: object additionalProperties: true description: Not currently returned. Reserved for bank-supplied metadata. example: customer_segment: CORPORATE relationships: type: object description: Not currently returned. properties: created_by_operation: $ref: '#/components/schemas/OperationRelationship' balances: $ref: '#/components/schemas/LinkOnlyRelationship' links: allOf: - $ref: '#/components/schemas/ResourceLinks' description: Not currently returned. CreateAccountRequest: description: Request body for registering an account. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - attributes properties: type: type: string enum: - accounts attributes: type: object additionalProperties: false x-deny-unknown-fields: true description: | Account attributes. Unknown attributes are rejected with `422 Unprocessable Entity`. required: - external_account_id - asset_id - kind properties: external_account_id: type: string minLength: 1 maxLength: 34 description: | Required. The identifier of this account in your core banking system (CBS). It must satisfy the ISO 20022 `Max34Text` type used by `AccountIdentification4Choice/Othr/Id`: 1 to 34 characters, each a valid XML 1.0 character (tab, line feed, carriage return, or any character from U+0020 upward except the surrogate range and U+FFFE/U+FFFF). Length is counted in characters, not bytes. It is stored exactly as sent (no trimming or case folding) and later used, together with `asset_id`, to address the account in URLs and transfers. example: CBS-ACC-2026-000123 asset_id: type: string description: | Required. The root settlement asset the account holds, for example `usd`. The asset must be enabled for your bank; otherwise the request is rejected with `400 Bad Request`. Must not be empty or whitespace only. example: usd kind: type: string description: | Required. `CUSTOMER` for an account that holds a customer's funds, `TREASURY` for the bank's own account. enum: - CUSTOMER - TREASURY display_name: type: string nullable: true description: | Optional label. If present it must not be empty or whitespace only (`400 Bad Request`). It is currently validated but not stored; account reads do not return it. example: Customer Operating Account aliases: type: array nullable: true description: | Optional payment aliases. Currently accepted but not stored; account reads do not return them. items: $ref: '#/components/schemas/AccountAlias' metadata: type: object nullable: true additionalProperties: true description: | Optional key-value metadata. Currently accepted but not stored; account reads do not return it. example: customer_segment: CORPORATE BalanceKey: type: object required: - id - type properties: id: type: string description: | JSON:API resource identifier of the balance: `v1:{bank_id}:{asset_id}:{external_account_id}`, where `asset_id` is the balance's `asset_id` attribute (the bank-issued asset, for example `usd.bank-alpha`). Each component is percent-encoded before joining, exactly as for account identifiers. Because the account identifier uses the root asset (for example `usd`), a balance `id` is usually different from the `id` of the account it belongs to. example: v1:11111111-1111-7111-8111-111111111111:usd.bank-alpha:CBS-ACC-2026-000123 type: type: string enum: - balances MonetaryAmount: description: Monetary amount with asset denomination. type: object required: - asset_id - value - scale properties: asset_id: type: string description: | Asset identifier. Depending on the resource this is an issued asset (for example `usd.bank-alpha`) or a root settlement asset (for example `usd`); see the endpoint. example: usd value: type: string description: Amount in the asset's smallest unit (integer string). example: '100000' scale: type: integer description: | Number of decimal places. An amount of 100000 with scale=2 represents 1000.00 units of the asset. example: 2 AccountRelationship: description: JSON:API to-one relationship to an account. type: object properties: data: $ref: '#/components/schemas/AccountKey' links: $ref: '#/components/schemas/RelationshipLinks' AssetKey: type: object required: - id - type properties: id: type: string description: Asset identifier. example: usd.bank-alpha type: type: string enum: - assets AssetRelationship: description: JSON:API to-one relationship to an Asset. type: object properties: data: $ref: '#/components/schemas/AssetKey' links: $ref: '#/components/schemas/RelationshipLinks' Balance: description: | The position of one registered account in one asset, read from the finalized ledger and combined with the Lyriq Connector's own in-flight reservations. All four amounts are integer strings in the asset's smallest unit and share the same `asset_id` (the root settlement asset) and `scale`. They are related as follows: - `total` is the finalized ledger balance of the account. - `locked` is the part of `total` that the ledger has locked for open settlement cycles. - `reserved` is the amount the Lyriq Connector has set aside for work it has accepted but that is not yet final on the ledger (for example a transfer awaiting review or submission). - `available` is what can still be committed now: `available = total - locked - reserved`, floored at `0`. So normally `total = available + locked + reserved`. If reservations briefly exceed the unlocked balance, `available` is `0` and the sum is larger than `total`. If the underlying ledger account carries an issuance limit (an issuer's issuance account), `available` and `reserved` describe issuance capacity instead: `available = issuance limit - issued amount - reserved`, floored at `0`, and `reserved` is the issuance capacity held by accepted work. `total` and `locked` keep their meaning. allOf: - $ref: '#/components/schemas/BalanceKey' - type: object required: - attributes properties: attributes: type: object required: - external_account_id - bank_id - asset_id - root_asset_id - issuer_bank_id - issuer_relation - available - locked - reserved - total - as_of properties: external_account_id: type: string description: The CBS identifier your bank supplied when it registered the account. example: CBS-ACC-2026-000123 bank_id: type: string description: '`bank_id` of the bank that owns the account (always the calling bank).' example: 11111111-1111-7111-8111-111111111111 asset_id: type: string description: | The bank-issued asset this balance is denominated in (for example `usd.bank-alpha`). If the platform has no issued asset configured for the account's root asset, this equals `root_asset_id`. example: usd.bank-alpha root_asset_id: type: string description: | The root settlement asset of the account (the `asset_id` the account was registered with, for example `usd`). example: usd issuer_bank_id: type: string description: '`bank_id` of the bank that issued the asset held in this balance.' example: 11111111-1111-7111-8111-111111111111 issuer_asset_code: type: string description: | Display code of the issued asset. Omitted when no issued asset is configured. example: usd.bank-alpha issuer_relation: type: string description: | `OWN_ISSUED` when `issuer_bank_id` equals `bank_id` (the balance is in your bank's own issued asset); `FOREIGN_ISSUED` when another bank issued it. enum: - OWN_ISSUED - FOREIGN_ISSUED example: OWN_ISSUED available: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: | Amount that can be committed now: `total - locked - reserved`, floored at `0` (or remaining issuance capacity for an issuance account; see the schema description). locked: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: Part of `total` locked on the ledger for open settlement cycles. reserved: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: | Amount held by Lyriq Connector work that has been accepted but is not yet final on the ledger. Released or consumed when that work completes. total: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: Finalized ledger balance, including `locked` and before `reserved` is deducted. as_of: type: string format: date-time description: | Finalization time of the most recent ledger block that changed this account. This is not the time of your request: an account with no recent activity has an old `as_of` even when the read is current. Use the response `meta` to judge how current the read is. example: '2026-09-25T14:03:11.482Z' relationships: type: object properties: account: allOf: - $ref: '#/components/schemas/AccountRelationship' description: | The account this balance belongs to. `data.id` currently repeats this balance's own `id` (built with the issued `asset_id`), so it does not necessarily match the account resource `id`. To fetch the account use `GET /v1/accounts/{external_account_id}?asset_id={root_asset_id}`. asset: allOf: - $ref: '#/components/schemas/AssetRelationship' description: The asset in `asset_id`, with a `links.related` URL to `GET /v1/assets/{asset_id}`. links: allOf: - $ref: '#/components/schemas/ResourceLinks' description: Not currently returned. PositionCut: description: | The exact point in time a position read reflects: a finalized ledger block plus a revision of the Lyriq Connector's reservation state. Two reads with the same cut return the same numbers. Every row in one response is read at the same cut; separate requests (including separate pages of one list) may have different cuts. type: object required: - block_height - block_hash - reservation_revision properties: block_height: type: string pattern: ^[0-9]+$ description: | Height of the last finalized ledger block included in the read, as a decimal integer string. All finalized blocks up to and including this height are reflected. example: '184502' block_hash: type: string pattern: ^[0-9a-f]+$ description: Lowercase hex hash of that block (64 characters). example: 9f2c4e1a7b3d5f60812a4c6e8b0d2f4618a3c5e7092b4d6f8a1c3e5b7d9f0a2c reservation_revision: type: string pattern: ^[0-9]+$ description: | Revision of the Lyriq Connector's reservation state used for `reserved` amounts, as a decimal integer string. It increases as reservations are taken and released. example: '9317' PositionReadMetadata: description: | How current a position read is. Returned as the response-level `meta` of position reads when at least one row is returned; omitted when `data` is empty. Reads never block waiting for the ledger to catch up: they return the latest data the platform has processed and describe it here. To judge staleness, compare `cut.block_height` with `observed_head` and check `completeness`. type: object required: - cut - completeness - observed_head properties: cut: $ref: '#/components/schemas/PositionCut' completeness: type: string enum: - COMPLETE - CATCHING_UP - GAPPED - UNAVAILABLE description: | State of the platform's ledger position processing at the time of the read: - `COMPLETE`: every finalized block the platform has seen is reflected (`cut.block_height` equals `observed_head`). The numbers are current. - `CATCHING_UP`: newer finalized blocks exist than are reflected (`cut.block_height` is below `observed_head`). The numbers are correct as of `cut` but may be stale; retry shortly for current values. - `GAPPED`: processing stopped because of a ledger consistency problem (a missing or mismatched block). The numbers are correct as of `cut` but will not advance until the platform operator resolves it. - `UNAVAILABLE`: position processing is currently unavailable. The numbers are the last known values as of `cut`. example: COMPLETE observed_head: type: string pattern: ^[0-9]+$ description: | Highest finalized ledger block height the platform has observed, as a decimal integer string. `observed_head - cut.block_height` is how many blocks behind the read is. example: '184502' OnboardingWorkloadIdentityProvider: description: Trust settings the platform IAM uses to verify the bank's workload assertions. type: object additionalProperties: false x-deny-unknown-fields: true required: - issuer - jwks_url properties: issuer: type: string minLength: 1 maxLength: 2048 description: | Exact issuer expected in this bank's workload assertions; must equal the assertion `iss` claim. Defaults to the onboarding case's `fis_idp_entity_id` when not supplied. jwks_url: type: string format: uri maxLength: 2048 description: | JWKS endpoint, reachable by the platform IAM, used to verify this bank's workload assertions. Absolute `https` URL (`http` only for `localhost` in a sandbox case). WebhookEventType: type: string description: | A supported webhook event name. Business events are selected by subscriptions. `webhook.verification` and `webhook.test` are synthetic events sent by their respective verification and test flows, regardless of subscription filters. enum: - operation.updated - operation.rejected - operation.manual_review - operation.succeeded - operation.failed - review.created - review.approved - review.rejected - review.updated - transfer.received - beneficiary.screening.requested - webhook.verification - webhook.test OnboardingWebhookReceiverAuthConfiguration: type: object additionalProperties: false x-deny-unknown-fields: true required: - token_endpoint_url - client_id - requested_scope - client_secret_configured properties: token_endpoint_url: type: string format: uri client_id: type: string requested_scope: type: string client_secret_configured: type: boolean OnboardingWebhookSetupConfiguration: type: object readOnly: true additionalProperties: false x-deny-unknown-fields: true required: - enabled - callback_url - event_types - delivery_format - signing_secret_version - signing_secret_configured properties: enabled: type: boolean callback_url: type: string format: uri event_types: type: array minItems: 1 uniqueItems: true items: $ref: '#/components/schemas/WebhookEventType' delivery_format: type: string enum: - jsonapi signing_secret_version: type: string signing_secret_configured: type: boolean receiver_auth: $ref: '#/components/schemas/OnboardingWebhookReceiverAuthConfiguration' OnboardingConfiguration: description: | Configuration saved on the onboarding case. The operator supplies the identity provider, assets, limits and runtime accounts when opening the case; the bank supplies M2M workloads and webhook setup through the dedicated endpoints. type: object additionalProperties: false x-deny-unknown-fields: true required: - identity_provider - assets properties: identity_provider: type: object additionalProperties: false x-deny-unknown-fields: true description: FIS SAML identity provider registered for this bank during onboarding. required: - single_sign_on_service_url - single_logout_service_url - signing_certificate properties: single_sign_on_service_url: type: string format: uri description: FIS SAML SingleSignOnService URL from the firm's IdP metadata. single_logout_service_url: type: string format: uri description: FIS SAML HTTP-Redirect SingleLogoutService URL used to terminate the upstream browser session. signing_certificate: type: string minLength: 1 description: X.509 certificate used to validate signed SAML responses, as PEM or base64 DER. assets: type: array minItems: 1 description: Assets the bank will issue or hold, as set by the operator. items: type: object additionalProperties: false x-deny-unknown-fields: true required: - asset_id - currency - scale - display_code properties: asset_id: type: string description: Asset identifier. root_asset_id: type: string nullable: true description: Root asset the asset belongs to (optional). currency: type: string description: Currency code of the asset. scale: type: integer minimum: 0 description: Number of decimal places. display_code: type: string description: Code shown to users. client_reference: type: string nullable: true description: Optional operator reference for the bank. mint_limits: type: array description: Initial mint limits, applied at activation. `amount` is a non-negative integer string. items: type: object additionalProperties: false x-deny-unknown-fields: true required: - asset_id - amount properties: asset_id: type: string amount: type: string pattern: ^[0-9]+$ exposure_limits: type: array description: Initial exposure limits, applied at activation. `amount` is a non-negative integer string. items: type: object additionalProperties: false x-deny-unknown-fields: true required: - asset_id - holder_bank_id - issuer_bank_id - amount properties: asset_id: type: string holder_bank_id: type: string format: uuid issuer_bank_id: type: string format: uuid amount: type: string pattern: ^[0-9]+$ runtime_accounts: type: array description: Runtime accounts requested for the bank at activation. items: type: object additionalProperties: false x-deny-unknown-fields: true required: - kind - asset_id - external_account_id properties: kind: type: string asset_id: type: string external_account_id: type: string m2m_clients: type: array maxItems: 20 readOnly: true description: Bank-supplied FIS workload identities saved by the onboarding administrator through the dedicated M2M endpoint. Platform operators cannot include them when creating a draft. items: type: object additionalProperties: false x-deny-unknown-fields: true required: - display_name - business_purpose - keycloak_client_id - fis_client_id - roles - scopes properties: display_name: type: string minLength: 1 maxLength: 128 description: Human-readable name of the workload. business_purpose: type: string minLength: 1 maxLength: 512 description: What the workload does. keycloak_client_id: type: string minLength: 3 maxLength: 128 pattern: ^[A-Za-z0-9][A-Za-z0-9._-]+$ readOnly: true description: | Platform client id (`bank-m2m-{uuid}`), generated when the workload is saved and kept for as long as its `fis_client_id` is unchanged. The client is created in the platform IAM when onboarding is activated. Banks do not choose or pre-provision this value. fis_client_id: type: string minLength: 1 maxLength: 256 description: FIS client identifier of the workload; the `sub` claim of its FIS assertion must equal this value exactly (case-sensitive). roles: type: array minItems: 1 uniqueItems: true description: Preset labels. They do not grant permissions. items: type: string enum: - maker - checker - readonly scopes: type: array minItems: 1 uniqueItems: true description: Permissions carried by the workload's access tokens. items: type: string pattern: ^connector:(?!operator:)[A-Za-z0-9_:-]+$ description: Must be a registered, bank-facing Connector M2M scope. Presets are suggestions, so any allowed scope may be combined with any non-administrative workload role. The API rejects unknown, operator, onboarding, staff-administration, and all other administrative scopes. public_key_fingerprint: type: string nullable: true deprecated: true description: Deprecated compatibility field. Always returned as null; no longer collected or used for authentication. workload_identity_provider: type: object allOf: - $ref: '#/components/schemas/OnboardingWorkloadIdentityProvider' nullable: true readOnly: true description: Bank-supplied workload assertion trust. Required whenever m2m_clients is non-empty and shared only by workloads belonging to this bank. webhook_setup: $ref: '#/components/schemas/OnboardingWebhookSetupConfiguration' BankOnboardingCase: description: | A bank onboarding case: the record the platform operator opens to bring a bank onto the network, which the bank's onboarding administrator completes. At most one case exists per bank. type: object required: - type - id - attributes properties: type: type: string enum: - bank-onboarding-cases id: type: string format: uuid description: Onboarding case identifier (`case_id`). attributes: type: object required: - bank_id - version - state - display_name - legal_name - bic - environment - fis_idp_entity_id - admin_contact - required_distinct_approvals - configuration properties: bank_id: type: string format: uuid description: The bank being onboarded (always the calling bank on bank-facing routes). version: type: integer format: int64 minimum: 1 description: Revision counter. Increases on every change to the case, including each bank edit. state: type: string description: | Case lifecycle state. - `OPERATOR_DRAFT`: being prepared by the platform operator; not yet editable by the bank. - `AWAITING_BANK_ADMIN`: handed to the bank; editable. The first bank edit moves it to `BANK_CONFIGURING`. - `BANK_CONFIGURING`: the bank is adding keys, staff, webhook and workloads; editable. - `READY_FOR_REVIEW`: submitted by the bank (`submit-configuration`); waiting for the operator. Not editable. - `AWAITING_APPROVALS`: accepted by the operator; the activation operation waits for approvals. - `PROVISIONING`: activation approved and running, or identity provisioning still in progress. - `ACTIVE`: the bank, its staff and its workloads are provisioned. Terminal. - `NEEDS_CHANGES`: returned to the bank for changes; editable and can be resubmitted. - `REJECTED`: activation was rejected, cancelled or expired. Terminal. - `FAILED`: activation or identity provisioning failed; the operator can retry identity provisioning. enum: - OPERATOR_DRAFT - AWAITING_BANK_ADMIN - BANK_CONFIGURING - READY_FOR_REVIEW - AWAITING_APPROVALS - PROVISIONING - ACTIVE - NEEDS_CHANGES - REJECTED - FAILED display_name: type: string description: Short bank name shown in the network. legal_name: type: string description: Registered legal name of the bank. bic: type: string description: ISO 9362 BIC of the bank (8 or 11 characters, upper case). pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$ environment: type: string description: | `sandbox` or `production`. enum: - sandbox - production fis_idp_entity_id: type: string maxLength: 2048 description: | Exact FIS SAML IdP entityID from this bank's metadata, mapped to `bank_id` by the platform IAM. Also the default `issuer` of the bank's workload assertions. admin_contact: type: object additionalProperties: false x-deny-unknown-fields: true description: | The onboarding administrator who completes the case. This person's onboarding access is removed when the case is activated; they are not a staff member unless added separately. required: - email properties: email: type: string format: email required_distinct_approvals: type: integer minimum: 1 description: Number of distinct approvers required on the activation operation. configuration: $ref: '#/components/schemas/OnboardingConfiguration' activation_operation_id: type: string format: uuid nullable: true description: | Operation created when the operator accepts the case. `null` until then. Follow it with `GET /v1/operations/{operation_id}`. bank_portal_route_code: type: string readOnly: true description: | Platform-generated route code used to construct this bank's private Bank Portal login URL. Returned only by `GET /v1/onboarding-cases/{case_id}`. readiness: type: object readOnly: true description: | Submission checklist, computed on read. Returned by the list, get, M2M and webhook-setup operations; omitted from the submit-configuration response. properties: signing_targets_total: type: integer minimum: 0 description: Number of signing purposes required (currently 3). signing_targets_verified: type: integer minimum: 0 description: Number of required signing purposes that have a `VERIFIED` target. encryption_targets_total: type: integer minimum: 0 description: Number of encryption purposes required (currently 1). encryption_targets_verified: type: integer minimum: 0 description: Number of required encryption purposes that have a `VERIFIED` target. staff_total: type: integer minimum: 0 description: Number of staff members on the case. bank_admin_present: type: boolean description: At least one staff member has the `bank-admin` label. Required for submission. maker_present: type: boolean description: At least one staff member has the `maker` label. Informational. checker_present: type: boolean description: At least one staff member has the `checker` label. Informational. readonly_present: type: boolean description: At least one staff member has the `readonly` label. Informational. webhook_ready: type: boolean description: | Webhook setup is enabled, includes `beneficiary.screening.requested`, has a signing secret and, when receiver authentication is set, a client secret. ready_for_review: type: boolean description: | All submission checks pass: every signing and encryption target verified, `bank_admin_present` and `webhook_ready`. created_at: type: string format: date-time updated_at: type: string format: date-time OnboardingM2mClientInput: description: | One machine-to-machine workload declared by the bank. Do not send a platform client id; the platform generates it. Surrounding whitespace is trimmed from every string. type: object additionalProperties: false x-deny-unknown-fields: true required: - display_name - business_purpose - fis_client_id - roles - scopes properties: display_name: type: string minLength: 1 maxLength: 128 description: Human-readable name of the workload. business_purpose: type: string minLength: 1 maxLength: 512 description: What the workload does and why it needs its scopes. fis_client_id: type: string minLength: 1 maxLength: 256 description: | The workload's client identifier at FIS. The `sub` claim of the workload's FIS assertion must equal this value exactly (case-sensitive). Unique per bank. roles: type: array minItems: 1 uniqueItems: true description: | Preset labels describing the workload. They do not grant permissions; `scopes` do. Administrative roles cannot be assigned to a workload. items: type: string enum: - maker - checker - readonly scopes: type: array minItems: 1 uniqueItems: true description: | Permissions carried by the workload's access tokens; the only source of authorisation for its calls. Any assignable scope may be combined with any role. items: type: string pattern: ^connector:(?!operator:)[A-Za-z0-9_:-]+$ description: | A bank-facing Connector scope assignable to workloads (see `PUT /v1/onboarding-cases/{case_id}/m2m-clients` for the list). Unknown, operator, onboarding, staff-administration and `connector:m2m-clients:read` scopes are rejected. public_key_fingerprint: type: string nullable: true deprecated: true description: | Deprecated compatibility field accepted from older clients. Optional in every environment and ignored by current Workflow Core; never used for authentication. M2mClientAttributes: description: Approved configuration of an activated machine-to-machine workload. type: object additionalProperties: false x-deny-unknown-fields: true required: - bank_id - keycloak_client_id - display_name - business_purpose - fis_client_id - roles - scopes - workload_identity_provider properties: bank_id: type: string format: uuid description: Bank that owns the workload (the calling bank). keycloak_client_id: type: string description: | Platform client id generated at onboarding (`bank-m2m-{uuid}`), shown as **Platform client ID** in the Bank Portal. It is the `azp` claim of the workload's access tokens. Same value as the resource `id`. example: bank-m2m-01928f5c-7a10-7e3b-9c21-4d5e6f708192 display_name: type: string description: Human-readable name of the workload. business_purpose: type: string description: What the workload does. fis_client_id: type: string description: FIS client identifier; the `sub` the workload's FIS assertions must carry. roles: type: array uniqueItems: true description: Preset labels. They do not grant permissions. items: type: string enum: - maker - checker - readonly scopes: type: array uniqueItems: true description: Permissions carried by the workload's access tokens. items: type: string public_key_fingerprint: type: string nullable: true deprecated: true description: Deprecated compatibility field. Always returned as null; no longer collected or used for authentication. workload_identity_provider: type: object allOf: - $ref: '#/components/schemas/OnboardingWorkloadIdentityProvider' nullable: true description: Assertion issuer and JWKS endpoint trusted for this bank's workloads. M2mClient: type: object additionalProperties: false x-deny-unknown-fields: true required: - type - id - attributes properties: type: type: string enum: - m2m-clients id: type: string description: Generated platform client ID. attributes: $ref: '#/components/schemas/M2mClientAttributes' OnboardingKeyRequirement: type: object required: - id - category - label - description - required properties: id: type: string description: Key purpose, used as `purpose` when registering a target or generating a key. example: BANK_ADMIN category: type: string description: '`signing` (register with signing targets) or `encryption` (register with encryption targets).' example: signing label: type: string description: Short display label. description: type: string description: What the key is used for. required: type: boolean description: Whether the key must be verified before submission. OnboardingKeyRequirementsResource: type: object required: - type - id - attributes properties: type: type: string enum: - onboarding-key-requirements id: type: string format: uuid attributes: type: object required: - version - requirements properties: version: type: integer format: int64 minimum: 1 requirements: type: array items: $ref: '#/components/schemas/OnboardingKeyRequirement' OnboardingKeyRequirementsResponse: type: object required: - data properties: data: $ref: '#/components/schemas/OnboardingKeyRequirementsResource' GeneratedKeyMaterial: description: Public material of a signing key generated by the platform. type: object required: - backend_class - backend_ref - algorithm - public_key - fingerprint properties: backend_class: type: string description: Key backend holding the key. enum: - AWS_KMS - AWS_CLOUDHSM - GCP_CLOUD_KMS_HSM - local backend_ref: type: string description: Provider reference of the key, for AWS KMS `kms://{region}/{account_id}/{alias_path}`. algorithm: type: string description: Signing algorithm. enum: - P256_SHA256_ASN1 - ED25519_PH_SHA_512 public_key: type: string description: SPKI DER public key encoded as base64. fingerprint: type: string description: SHA-256 of the public key DER bytes in lower-case hexadecimal. OnboardingEncryptionKey: type: object additionalProperties: false x-deny-unknown-fields: true required: - backend_class - backend_ref properties: backend_class: type: string enum: - AWS_KMS backend_ref: type: string description: | AWS KMS key ARN of the key-encryption key, either registered by the bank or generated for this onboarding case. A key ARN, never an alias ARN: a stored wrapped data key can only be unwrapped by the exact key that wrapped it, and an alias can be repointed by the bank. Key material is never accepted. example: arn:aws:kms:us-east-1:590184012001:key/1234abcd-12ab-34cd-56ef-1234567890ab KeyGenerationAllocation: description: | Progress of one generated key (one purpose) within a key-generation job. The same allocation is reused if a later job retries the purpose. type: object required: - id - purpose - status - retryable - error - target_id - key - encryption_key properties: id: type: string format: uuid description: Allocation identifier. purpose: type: string description: | Key purpose: `BANK_ADMIN`, `CREATE_INTERBANK_TRANSFER_MAKER`, `APPROVE_INTERBANK_TRANSFER_CHECKER` or `ENCRYPT_TRANSFER_SOURCE_MESSAGE`. status: type: string description: | - `PENDING`: queued. - `CREATING`: the key is being created at the key provider. - `CONFIGURING`: the key is being prepared and its public material read. - `REGISTERING`: the key is being registered as a target on the case. - `VERIFYING`: the registered target is being verified. - `READY`: the target is registered and `VERIFIED`. Final. - `FAILED`: automatic retries were exhausted; see `error`. Create a new job for the purpose to resume. - `CONFLICT`: a different key was registered for this purpose; that key is kept and no replacement is made. Final. enum: - PENDING - CREATING - CONFIGURING - REGISTERING - VERIFYING - READY - FAILED - CONFLICT retryable: type: boolean description: '`true` only when `status` is `FAILED`.' error: type: string nullable: true description: Reason for the last failure or conflict, in plain language; `null` otherwise. target_id: type: string format: uuid nullable: true description: Signing or encryption target registered on the case for this key; `null` until registration. key: type: object nullable: true description: Public material of a generated signing key; `null` for the encryption purpose or until available. allOf: - $ref: '#/components/schemas/GeneratedKeyMaterial' encryption_key: type: object nullable: true description: Reference to a generated key-encryption key; `null` for signing purposes or until available. allOf: - $ref: '#/components/schemas/OnboardingEncryptionKey' KeyGenerationJobResource: type: object required: - type - id - attributes properties: type: type: string enum: - key-generation-jobs id: type: string format: uuid description: Key-generation job identifier (`job_id`). attributes: type: object required: - items properties: items: type: array description: | One allocation per requested purpose that the job is working on. Purposes skipped because a key was already registered do not appear. items: $ref: '#/components/schemas/KeyGenerationAllocation' KeyGenerationJobCollectionResponse: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: array items: $ref: '#/components/schemas/KeyGenerationJobResource' KeyGenerationPurpose: type: string enum: - BANK_ADMIN - CREATE_INTERBANK_TRANSFER_MAKER - APPROVE_INTERBANK_TRANSFER_CHECKER - ENCRYPT_TRANSFER_SOURCE_MESSAGE CreateKeyGenerationJobAttributes: type: object additionalProperties: false x-deny-unknown-fields: true required: - idempotency_key - requirements_version - purposes properties: idempotency_key: type: string format: uuid description: | Client-chosen UUID identifying this request. Resending it with the same body returns the existing job; with a different body the request is rejected with `409`. requirements_version: type: integer format: int64 minimum: 1 maximum: 4294967295 description: '`version` from `GET .../key-requirements`. A stale value is rejected with `422`.' purposes: type: array minItems: 1 uniqueItems: true description: Key purposes to generate. Purposes that already have a bank-registered target are skipped. items: $ref: '#/components/schemas/KeyGenerationPurpose' CreateKeyGenerationJobRequest: type: object required: - data properties: data: type: object required: - type - attributes properties: type: type: string enum: - key-generation-jobs attributes: $ref: '#/components/schemas/CreateKeyGenerationJobAttributes' KeyGenerationJobResponse: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: $ref: '#/components/schemas/KeyGenerationJobResource' OnboardingSigningKey: description: Reference to a bank-held signing key and its public key. No private key material is accepted. type: object additionalProperties: false x-deny-unknown-fields: true required: - backend_class - backend_ref - algorithm - public_key - public_key_fingerprint properties: backend_class: type: string description: | Key backend. Reference formats: `AWS_KMS` `kms://{region}/{account_id}/{alias_path}`; `AWS_CLOUDHSM` `cloudhsm://{locator}`; `GCP_CLOUD_KMS_HSM` `gcp-kms://{location}/{project_id}/{key_ring}/{key_id}/{key_version}`. enum: - AWS_KMS - AWS_CLOUDHSM - GCP_CLOUD_KMS_HSM - local backend_ref: type: string description: Provider reference in the format of `backend_class`; unique within the case. Key material is never accepted. example: kms://us-east-1/590184012001/bank-alpha/lyriq/maker algorithm: type: string description: Signing algorithm of the key. enum: - P256_SHA256_ASN1 - ED25519_PH_SHA_512 public_key: type: object description: The key's public key. additionalProperties: false x-deny-unknown-fields: true required: - encoding - value properties: encoding: type: string enum: - spki_der_base64 value: type: string description: Base64-encoded SubjectPublicKeyInfo DER. example: MCowBQYDK2VwAyEAKQZscvZKS1zqHdwrtNNOVz5VLB/Xt6C9cAmQNj2ceOU= public_key_fingerprint: type: object description: SHA-256 fingerprint of the public key; must match `public_key.value` and be unique within the case. additionalProperties: false x-deny-unknown-fields: true required: - algorithm - value properties: algorithm: type: string enum: - sha256 value: type: string description: | SHA-256 of the decoded DER bytes in hexadecimal. A `sha256:` prefix and upper case are accepted on input; stored and returned as lower-case hex without prefix. example: 932ab9613d8eb8dea8b162cbca61e7eac080f67c737d84e6e95bfe2f484ddc71 backend: type: object additionalProperties: true description: Typed provider metadata owned by the selected backend adapter. OnboardingSigningTargetResource: type: object required: - type - id - attributes properties: type: type: string enum: - onboarding-signing-targets id: type: string format: uuid attributes: type: object required: - onboarding_case_id - purpose - key - verification_state - created_at - updated_at properties: onboarding_case_id: type: string format: uuid description: Case the target belongs to. purpose: type: string enum: - BANK_ADMIN - CREATE_INTERBANK_TRANSFER_MAKER - APPROVE_INTERBANK_TRANSFER_CHECKER key: $ref: '#/components/schemas/OnboardingSigningKey' verification_state: type: string description: | `PENDING` until verified; `VERIFIED` after a successful verify call; `FAILED` after a failed one (see `verification_error`). Only `VERIFIED` counts for submission. enum: - PENDING - VERIFIED - FAILED verification_error: type: string nullable: true description: Reason of the last failed verification; `null` otherwise. verified_at: type: string format: date-time nullable: true description: Time of the last successful verification; `null` otherwise. created_at: type: string format: date-time updated_at: type: string format: date-time OnboardingEncryptionTargetResource: type: object required: - type - id - attributes properties: type: type: string enum: - onboarding-encryption-targets id: type: string format: uuid attributes: type: object required: - onboarding_case_id - purpose - key - verification_state - created_at - updated_at properties: onboarding_case_id: type: string format: uuid description: Case the target belongs to. purpose: type: string enum: - ENCRYPT_TRANSFER_SOURCE_MESSAGE key: $ref: '#/components/schemas/OnboardingEncryptionKey' verification_state: type: string description: | `PENDING` until verified; `VERIFIED` after a successful verify call; `FAILED` after a failed one (see `verification_error`). Only `VERIFIED` counts for submission. enum: - PENDING - VERIFIED - FAILED verification_error: type: string nullable: true description: Reason of the last failed verification; `null` otherwise. verified_at: type: string format: date-time nullable: true description: Time of the last successful verification; `null` otherwise. created_at: type: string format: date-time updated_at: type: string format: date-time StaffMemberResource: type: object required: - type - id - attributes properties: type: type: string enum: - staff-members id: type: string format: uuid attributes: type: object required: - onboarding_case_id - bank_id - email - roles - scopes - state - created_at - updated_at properties: onboarding_case_id: type: string format: uuid description: Case the member belongs to. bank_id: type: string format: uuid email: type: string format: email description: Email address in lower case; also the member's federated sign-in subject. roles: type: array description: Descriptive preset/ownership labels. They do not grant API permissions. minItems: 1 uniqueItems: true items: type: string enum: - bank-admin - maker - checker - readonly scopes: type: array description: Bank-facing API permissions of this membership; the only source of authorisation for the member. minItems: 1 uniqueItems: true items: type: string state: type: string description: | Provisioning lifecycle of the membership. `READY`: declared, not yet provisioned (editable while the onboarding case is editable). `ACTIVE`: provisioned and live. `UPDATING`: a role/scope change is being provisioned; the member stays live on its previous grants until it succeeds. `SUSPENDING`: the member is being de-provisioned. `DEACTIVATED`: de-provisioned; access has been removed. `ACTIVATION_FAILED`, `UPDATE_FAILED`, `DEACTIVATION_FAILED`: provisioning of the respective transition was exhausted. enum: - READY - ACTIVE - UPDATING - ACTIVATION_FAILED - UPDATE_FAILED - SUSPENDING - DEACTIVATED - DEACTIVATION_FAILED created_at: type: string format: date-time updated_at: type: string format: date-time StaffMemberCollectionResponse: type: object properties: data: type: array items: $ref: '#/components/schemas/StaffMemberResource' CreateBankStaffMemberRequest: type: object required: - data properties: data: type: object required: - type - attributes properties: type: type: string enum: - staff-members attributes: type: object additionalProperties: false x-deny-unknown-fields: true required: - email - roles - scopes properties: email: type: string format: email description: Email address; stored trimmed and in lower case. Unique within the case. roles: type: array description: Descriptive preset/ownership labels. They do not grant API permissions. minItems: 1 uniqueItems: true items: type: string enum: - bank-admin - maker - checker - readonly scopes: type: array description: Sole authorization source for the member's bank-facing API access. minItems: 1 uniqueItems: true items: type: string pattern: ^connector:(?!operator:)[A-Za-z0-9_:-]+$ description: Must be a registered bank-facing Connector API scope or the staff-only `connector:m2m-clients:read`. Unknown, operator, onboarding and staff-administration scopes are rejected. StaffMemberResponse: type: object properties: data: $ref: '#/components/schemas/StaffMemberResource' UpdateBankStaffMemberRequest: description: Request body for replacing the editable attributes of a pending onboarding staff member. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object additionalProperties: false x-deny-unknown-fields: true required: - type - id - attributes properties: type: type: string enum: - staff-members id: type: string format: uuid description: Staff member identifier; must match the path parameter. attributes: type: object additionalProperties: false x-deny-unknown-fields: true required: - email - roles - scopes properties: email: type: string format: email roles: type: array description: Descriptive preset/ownership labels. They do not grant API permissions. minItems: 1 uniqueItems: true items: type: string enum: - bank-admin - maker - checker - readonly scopes: type: array description: Sole authorization source for the member's bank-facing API access. minItems: 1 uniqueItems: true items: type: string pattern: ^connector:(?!operator:)[A-Za-z0-9_:-]+$ description: Must be a registered bank-facing Connector API scope or the staff-only `connector:m2m-clients:read`. Unknown, operator, onboarding and staff-administration scopes are rejected. UpdateStaffMemberRequest: description: Request body for updating an active bank staff member's roles and API scopes. The email is immutable after the member is provisioned and cannot be changed here. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object additionalProperties: false x-deny-unknown-fields: true required: - type - id - attributes properties: type: type: string enum: - staff-members id: type: string format: uuid description: Staff member identifier; must match the path parameter. attributes: type: object additionalProperties: false x-deny-unknown-fields: true required: - roles - scopes properties: roles: type: array description: Descriptive preset/ownership labels. They do not grant API permissions. minItems: 1 uniqueItems: true items: type: string enum: - bank-admin - maker - checker - readonly scopes: type: array description: Sole authorization source for the member's bank-facing API access. minItems: 1 uniqueItems: true items: type: string pattern: ^connector:(?!operator:)[A-Za-z0-9_:-]+$ description: Must be a registered bank-facing Connector API scope or the staff-only `connector:m2m-clients:read`. Unknown, operator, onboarding and staff-administration scopes are rejected. ReplaceEncryptionTargetRequest: description: | Request body for replacing the key-encryption key registered for a purpose. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - attributes properties: type: type: string enum: - encryption-targets attributes: type: object additionalProperties: false x-deny-unknown-fields: true required: - backend_class - backend_ref properties: backend_class: type: string enum: - AWS_KMS backend_ref: type: string description: | AWS KMS key ARN of the replacement key-encryption key. A key ARN, never an alias ARN. The key policy must grant the platform runtime role `kms:GenerateDataKey`, `kms:Encrypt`, and `kms:Decrypt`; the platform proves all three before the replacement is accepted. example: arn:aws:kms:us-east-1:590184012001:key/9f8e7d6c-5b4a-3928-1706-fedcba987654 Asset: description: | A bank-issued asset. Each issuing bank has at most one issued asset per root settlement asset: for example Bank Alpha's `usd.bank-alpha` and Bank Beta's `usd.bank-beta` are both issued under the root asset `usd`. Amounts are integer strings in the smallest unit; `scale` gives the number of decimal places. allOf: - $ref: '#/components/schemas/AssetKey' - type: object required: - attributes properties: attributes: type: object required: - root_asset_id - asset_code - status - scale properties: root_asset_id: type: string description: | The root settlement asset this issued asset belongs to, for example `usd`. Use this value as `asset_id` when you register accounts and create transfers. example: usd asset_code: type: string description: Display code configured for the issued asset (up to 32 characters). example: usd.bank-alpha issuer_bank_id: type: string nullable: true description: '`bank_id` of the bank that issues this asset.' example: 11111111-1111-7111-8111-111111111111 status: type: string description: Asset lifecycle status. enum: - ACTIVE scale: type: integer description: | Number of decimal places. With `scale: 2`, the amount `"100"` represents 1.00 units of the asset. example: 2 links: allOf: - $ref: '#/components/schemas/ResourceLinks' description: Not currently returned. BankKey: type: object required: - id - type properties: id: type: string format: uuid description: The bank's `bank_id` (UUID). example: 11111111-1111-7111-8111-111111111111 type: type: string enum: - banks Bank: description: | Public directory information about a bank on the network. Only these attributes are exposed to other banks; operational configuration is never returned. allOf: - $ref: '#/components/schemas/BankKey' - type: object required: - attributes properties: attributes: type: object required: - short_name - display_name - country_code - bic_swift_code - status properties: short_name: type: string description: Short name of the bank. example: bank-alpha display_name: type: string description: Human-readable name of the bank. example: Bank Alpha description: type: string description: Free-text description. Omitted when not set. example: Issuer of usd.bank-alpha on the network. country_code: type: string description: Country of the bank as configured at onboarding. example: US bic_swift_code: type: string description: The bank's BIC (8 or 11 characters). pattern: ^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$ example: ALPHUS33 status: type: string description: | Bank lifecycle status. Bank-facing reads return only `ACTIVE` banks, so this is always `ACTIVE` in `GET /v1/banks` and `GET /v1/banks/{bank_id}`. Other values appear only on operator routes. enum: - UNSPECIFIED - PENDING - PROVISIONING - ACTIVE - SUSPENDED - TERMINATED example: ACTIVE links: allOf: - $ref: '#/components/schemas/ResourceLinks' description: Not currently returned. GetBank200Response: type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' links: $ref: '#/components/schemas/DocumentLinks' data: $ref: '#/components/schemas/Bank' SpendableCapacity: description: Represents the spendable capacity and liquidity of a specific bank account. type: object required: - type - id - attributes properties: type: type: string enum: - spendable-capacities id: type: string description: Unique identifier in format v1::: attributes: type: object required: - bank_id - external_account_id - asset_id - total - locked - reserved - utilized - remaining - as_of properties: bank_id: type: string format: uuid description: Bank scope that owns this account. external_account_id: type: string description: | Bank-facing account identifier. Accounts registered through POST /v1/accounts use the caller's CBS identifier and require it to satisfy the ISO 20022 Max34Text XSD type. asset_id: type: string description: Asset this account holds. total: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: Exact finalized ledger balance before locks and Connector reservations. locked: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: Funds locked for active settlement cycles. reserved: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: Funds claimed by Connector work but not yet represented by a finalized ledger debit or lock. utilized: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: The total utilized capacity (the sum of locked and reserved amounts). remaining: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: The available spendable liquidity (total capacity minus utilized amount). as_of: type: string format: date-time description: Timestamp at which the ledger balance was read. ExposureKey: type: object required: - id - type properties: id: type: string description: | JSON:API resource identifier: `v1:{holder_bank_id}:{issuer_bank_id}:{asset_id}`, each component percent-encoded before joining (as for account identifiers). The matching liability seen by the issuer has the first two components swapped. example: v1:22222222-2222-7222-8222-222222222222:11111111-1111-7111-8111-111111111111:usd.bank-alpha type: type: string enum: - exposures SettlementSnapshot: description: | Open settlement cycles affecting a position. Reserved for exposures and liabilities; not currently returned (the `settlement` attribute is omitted). Use `GET /v1/settlement-cycles` for settlement state. type: object properties: active_cycle_ids: type: array items: type: string description: Identifiers of open settlement cycles affecting this position. AssetRelationships: description: JSON:API relationships block containing only an asset link. type: object properties: asset: $ref: '#/components/schemas/AssetRelationship' Exposure: description: | The holder bank's view of an interbank holding: how much of another bank's issued asset your bank holds, and how that compares with the exposure limit for that issuer. An exposure exists for each active interbank holding registration where your bank is the holder and another bank is the issuer. The issuer sees the same holding, with the same amounts, as a liability (`GET /v1/liabilities`). All amounts are integer strings in the smallest unit of the root settlement asset (`asset_id` of each amount is `root_asset_id`): `balance = available_balance + locked_balance + (amount reserved by accepted work)`. The reserved part is not returned separately. allOf: - $ref: '#/components/schemas/ExposureKey' - type: object required: - attributes properties: attributes: type: object required: - holder_bank_id - issuer_bank_id - asset_id - root_asset_id - available_balance - locked_balance - balance - limit - utilization_ratio - as_of properties: holder_bank_id: type: string description: '`bank_id` of the holder bank (always the calling bank).' example: 22222222-2222-7222-8222-222222222222 holder_bank_display_name: type: string description: Display name of the holder bank. Omitted when not set. example: Bank Beta issuer_bank_id: type: string description: '`bank_id` of the bank that issued the held asset.' example: 11111111-1111-7111-8111-111111111111 issuer_bank_display_name: type: string description: Display name of the issuer bank. Omitted when not set. example: Bank Alpha issuer_asset_code: type: string description: Display code of the issuer's asset. Omitted when not configured. example: usd.bank-alpha asset_id: type: string description: | The issuer's issued asset (for example `usd.bank-alpha`); equals `root_asset_id` if the issuer has no issued asset configured. example: usd.bank-alpha root_asset_id: type: string description: Root settlement asset of the holding, for example `usd`. example: usd available_balance: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: | Amount the holder can still commit: `balance - locked_balance - reserved`, floored at `0`. locked_balance: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: Part of `balance` locked on the ledger for open settlement cycles. balance: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: Finalized ledger balance of the holding (the full exposure). limit: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: | Exposure limit configured for this holder, issuer and root asset (see `GET /v1/exposure-limits`). When no limit is configured, `limit.value` is an empty string. utilization_ratio: type: string description: | `balance / limit` as a decimal string without trailing zeros (for example `"0.4"`; above `"1"` means the limit is exceeded). Empty string when no limit is configured or the limit is `0`. example: '0.4' settlement: allOf: - $ref: '#/components/schemas/SettlementSnapshot' description: Not currently returned. as_of: type: string format: date-time description: | Finalization time of the most recent ledger block that changed this holding. Not the time of the request; see the response `meta` for read currency. example: '2026-09-25T14:03:11.482Z' relationships: $ref: '#/components/schemas/AssetRelationships' links: allOf: - $ref: '#/components/schemas/ResourceLinks' description: Not currently returned. LiabilityKey: type: object required: - id - type properties: id: type: string description: | JSON:API resource identifier: `v1:{issuer_bank_id}:{holding_bank_id}:{asset_id}`, each component percent-encoded before joining (as for account identifiers). The matching exposure seen by the holder has the first two components swapped. example: v1:11111111-1111-7111-8111-111111111111:22222222-2222-7222-8222-222222222222:usd.bank-alpha type: type: string enum: - liabilities Liability: description: | The issuer bank's view of an interbank holding: how much of your bank's issued asset another bank holds. It is the same holding, with the same amounts, that the holder sees as an exposure (`GET /v1/exposures`). A liability exists for each active interbank holding registration where your bank is the issuer and another bank is the holder. All amounts are integer strings in the smallest unit of the root settlement asset (`asset_id` of each amount is `root_asset_id`): `balance = available_balance + locked_balance + (amount reserved by accepted work)`. The reserved part is not returned separately. allOf: - $ref: '#/components/schemas/LiabilityKey' - type: object required: - attributes properties: attributes: type: object required: - issuer_bank_id - holding_bank_id - asset_id - root_asset_id - available_balance - locked_balance - balance - as_of properties: issuer_bank_id: type: string description: '`bank_id` of the issuer bank (always the calling bank).' example: 11111111-1111-7111-8111-111111111111 issuer_bank_display_name: type: string description: Display name of the issuer bank. Omitted when not set. example: Bank Alpha issuer_asset_code: type: string description: Display code of your issued asset. Omitted when not configured. example: usd.bank-alpha holding_bank_id: type: string description: '`bank_id` of the bank holding your asset.' example: 22222222-2222-7222-8222-222222222222 holding_bank_display_name: type: string description: Display name of the holding bank. Omitted when not set. example: Bank Beta asset_id: type: string description: | Your issued asset (for example `usd.bank-alpha`); equals `root_asset_id` if no issued asset is configured. example: usd.bank-alpha root_asset_id: type: string description: Root settlement asset of the holding, for example `usd`. example: usd available_balance: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: | Amount the holder can still commit: `balance - locked_balance - reserved`, floored at `0`. locked_balance: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: Part of `balance` locked on the ledger for open settlement cycles. balance: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: Finalized ledger balance of the holding (what you owe the holder). settlement: allOf: - $ref: '#/components/schemas/SettlementSnapshot' description: Not currently returned. as_of: type: string format: date-time description: | Finalization time of the most recent ledger block that changed this holding. Not the time of the request; see the response `meta` for read currency. example: '2026-09-25T14:03:11.482Z' relationships: $ref: '#/components/schemas/AssetRelationships' links: allOf: - $ref: '#/components/schemas/ResourceLinks' description: Not currently returned. MintLimitKey: type: object required: - id - type properties: id: type: string description: | Versioned mint-limit identifier `v1:{issuer_bank_id}:{asset_id}`, with each component percent-encoded before joining. In responses the last component is the issued asset id (for example `usd.bank-alpha`); in a `PUT` request `data.id` must use the root asset id from the path instead (see `PUT /v1/mint-limits/{asset_id}`). example: v1:11111111-1111-7111-8111-111111111111:usd.bank-alpha type: type: string description: Always `mint-limits`. enum: - mint-limits MintLimit: description: | The mint limit of one issuer for one root asset: the maximum amount of its own asset the issuer may have issued to other banks at any one time. Visible only to the issuer. allOf: - $ref: '#/components/schemas/MintLimitKey' - type: object required: - attributes properties: attributes: type: object required: - asset_id - root_asset_id - amount properties: asset_id: type: string description: | The issuer's issued asset id under the root asset, for example `usd.bank-alpha`. Falls back to the root asset id when no issued asset is registered. example: usd.bank-alpha root_asset_id: type: string description: | Root asset the limit is keyed by, for example `usd`. Use this value as the `asset_id` path parameter of `GET` and `PUT /v1/mint-limits/{asset_id}`. example: usd amount: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: | The limit. `asset_id` is the root asset, `value` is an integer string in minor units, and `scale` is the asset's number of decimals (`"50000000"` with `scale` 2 is 500,000.00). relationships: $ref: '#/components/schemas/AssetRelationships' links: $ref: '#/components/schemas/ResourceLinks' UpsertMintLimitRequest: description: | Request body of `PUT /v1/mint-limits/{asset_id}`. A full replacement: every call must send the complete new limit. Unknown attributes are rejected. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - id - attributes properties: type: type: string description: Must be `mint-limits`. enum: - mint-limits id: type: string description: | Must be exactly `v1:{issuer_bank_id}:{asset_id}`, where `issuer_bank_id` is the authenticated bank and `asset_id` is the root asset id from the path. Otherwise the request is rejected with `400 RESOURCE_ID_MISMATCH`. example: v1:11111111-1111-7111-8111-111111111111:usd attributes: type: object additionalProperties: false x-deny-unknown-fields: true required: - asset_id - amount properties: asset_id: type: string description: Root asset id; must equal the `asset_id` path parameter. example: usd amount: description: | The new limit. `value` is a non-negative integer string in the asset's minor units and replaces the previous limit; `scale` must be a non-negative integer and should equal the asset's `decimals` (it is not used to rescale `value`). allOf: - $ref: '#/components/schemas/TransferAmount' reason: type: string nullable: true description: Optional free-text reason recorded with the change. Must not be blank if present. example: Quarterly treasury update ExposureLimitKey: type: object required: - id - type properties: id: type: string description: | Versioned exposure-limit identifier `v1:{holder_bank_id}:{issuer_bank_id}:{asset_id}`, with each component percent-encoded before joining. In responses the last component is the issued asset id (for example `usd.bank-alpha`); in a `PUT` request `data.id` must use the root asset id from the path instead (see `PUT /v1/exposure-limits/{issuer_bank_id}/{asset_id}`). example: v1:22222222-2222-7222-8222-222222222222:11111111-1111-7111-8111-111111111111:usd.bank-alpha type: type: string description: Always `exposure-limits`. enum: - exposure-limits ExposureLimit: description: | The exposure limit a holder bank has set on one issuer's root asset: the maximum amount of that issuer's asset the holder is willing to hold at any one time. Visible only to the holder. allOf: - $ref: '#/components/schemas/ExposureLimitKey' - type: object required: - attributes properties: attributes: type: object required: - holder_bank_id - issuer_bank_id - asset_id - root_asset_id - amount properties: holder_bank_id: type: string description: Bank that set the limit and holds the asset; always the calling bank. example: 22222222-2222-7222-8222-222222222222 issuer_bank_id: type: string description: Bank that issues the capped asset. example: 11111111-1111-7111-8111-111111111111 asset_id: type: string description: | The issuer's issued asset id under the root asset, for example `usd.bank-alpha`. Falls back to the root asset id when no issued asset is registered. example: usd.bank-alpha root_asset_id: type: string description: | Root asset the limit is keyed by, for example `usd`. Use this value as the `asset_id` path parameter of `GET` and `PUT /v1/exposure-limits/{issuer_bank_id}/{asset_id}`. example: usd amount: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: | The limit. `asset_id` is the root asset, `value` is an integer string in minor units, and `scale` is the asset's number of decimals (`"10000000"` with `scale` 2 is 100,000.00). relationships: $ref: '#/components/schemas/AssetRelationships' links: $ref: '#/components/schemas/ResourceLinks' UpsertExposureLimitRequest: description: | Request body of `PUT /v1/exposure-limits/{issuer_bank_id}/{asset_id}`. A full replacement: every call must send the complete new limit. Unknown attributes are rejected. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - id - attributes properties: type: type: string description: Must be `exposure-limits`. enum: - exposure-limits id: type: string description: | Must be exactly `v1:{holder_bank_id}:{issuer_bank_id}:{asset_id}`, where `holder_bank_id` is the authenticated bank and `issuer_bank_id` and `asset_id` (the root asset id) are the path values. Otherwise the request is rejected with `400 RESOURCE_ID_MISMATCH`. example: v1:22222222-2222-7222-8222-222222222222:11111111-1111-7111-8111-111111111111:usd attributes: type: object additionalProperties: false x-deny-unknown-fields: true required: - asset_id - amount properties: asset_id: type: string description: Root asset id; must equal the `asset_id` path parameter. example: usd amount: description: | The new maximum holding. `value` is a non-negative integer string in the asset's minor units and replaces the previous limit; `scale` must be a non-negative integer and should equal the asset's `decimals` (it is not used to rescale `value`). allOf: - $ref: '#/components/schemas/TransferAmount' reason: type: string nullable: true description: Optional free-text reason recorded with the change. Must not be blank if present. example: Counterparty risk review Review: description: | An approval review: a human sign-off gate on one operation. See `GET /v1/reviews` for the four-eyes rules. allOf: - $ref: '#/components/schemas/ReviewKey' - type: object required: - attributes properties: attributes: type: object required: - state - required_role - mode - required_distinct_approvals - recorded_distinct_approvals - created_at - updated_at properties: state: type: string description: | - `PENDING`: open; waiting for decisions. - `APPROVED`: enough distinct approvals were recorded. - `REJECTED`: a reviewer rejected. - `CANCELLED`: the operation under review was cancelled while the review was open. - `EXPIRED`: reserved; not currently set. example: PENDING enum: - PENDING - APPROVED - REJECTED - EXPIRED - CANCELLED required_role: type: string description: | Who may decide. `APPROVER`: callers with `connector:reviews:decide` or the operator scope `connector:operator:reviews:decide`. `CHECKER`: only callers with `connector:reviews:decide`. In both cases the principal that submitted the operation cannot decide. example: APPROVER mode: type: string description: | What the review gates. `PRE_EXECUTION_GATE`: the operation does not execute until approved. `POST_LEDGER_INITIATION_COMMIT_GATE`: a ledger transfer was initiated and waits for the decision before it is committed or unwound. example: PRE_EXECUTION_GATE required_distinct_approvals: type: integer description: Number of approvals from distinct principals needed to approve the review. example: 1 recorded_distinct_approvals: type: integer description: Number of approvals from distinct principals recorded so far. example: 1 created_at: type: string format: date-time description: When the review was opened (RFC 3339, UTC). example: '2026-09-26T10:15:03.401886+00:00' updated_at: type: string format: date-time description: When the review last changed, for example when a decision was recorded (RFC 3339, UTC). example: '2026-09-26T10:19:47.002315+00:00' relationships: type: object properties: operation: $ref: '#/components/schemas/OperationRelationship' links: $ref: '#/components/schemas/ResourceLinks' CreateReviewDecisionRequest: description: Request body for `POST /v1/reviews/{review_id}/decisions`. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - attributes properties: type: type: string enum: - review-decisions attributes: type: object additionalProperties: false x-deny-unknown-fields: true required: - decision properties: decision: type: string description: | `APPROVE` adds one distinct approval; `REJECT` ends the review as `REJECTED`. example: APPROVE enum: - APPROVE - REJECT comment: type: string nullable: true description: | Optional free-text reason, stored with the decision. When sent it must not be empty or only whitespace. example: Beneficiary and amount checked against the payment instruction. WebhookKey: type: object required: - id - type properties: id: type: string description: Webhook subscription identifier (a UUID). example: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 type: type: string description: Always `webhooks`. enum: - webhooks AuthProfileConfigKey: type: object required: - id - type properties: id: type: string description: Auth profile identifier (a UUID). example: 0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 type: type: string description: Always `auth-profiles`. enum: - auth-profiles AuthProfileConfigRelationship: description: | JSON:API to-one relationship from a webhook subscription to the auth profile bound to it. `data` is `null` when no auth profile is bound (deliveries then carry no `Authorization` header). type: object properties: data: type: object nullable: true description: The bound auth profile, or `null` when none is bound. allOf: - $ref: '#/components/schemas/AuthProfileConfigKey' links: $ref: '#/components/schemas/RelationshipLinks' Webhook: description: | A webhook subscription: an HTTPS endpoint of your bank that receives signed event notifications. See `POST /v1/webhooks` for the delivery contract. allOf: - $ref: '#/components/schemas/WebhookKey' - type: object required: - attributes properties: attributes: type: object required: - url - event_types - delivery_format - status properties: url: type: string format: uri description: HTTPS endpoint that receives the deliveries. example: https://hooks.bank-alpha.example/lyriq/events event_types: type: array items: type: string description: | Event types this subscription receives, matched exactly. An empty array receives no business events. The `webhook.verification` handshake is sent regardless of this list. example: - operation.succeeded - operation.failed - operation.rejected - review.created - transfer.received delivery_format: type: string description: Body format of deliveries. Always `jsonapi` today; `iso20022+xml` is reserved. enum: - jsonapi - iso20022+xml example: jsonapi status: type: string description: | Subscription status: - `PENDING_VERIFICATION`: waiting for your endpoint to answer the `webhook.verification` event with `2xx`. No business events are delivered. A new subscription starts here, and binding an auth profile or updating a bound auth profile returns it here. - `ACTIVE`: receives the event types in `event_types`. - `DISABLED`: receives no events. enum: - ACTIVE - PENDING_VERIFICATION - DISABLED example: ACTIVE security: type: object description: Reserved for the signature and transport configuration. Not currently returned. properties: signature: type: object description: HMAC signature configuration. properties: algorithm: type: string description: Signing algorithm used for delivery signatures. enum: - HMAC_SHA256 example: HMAC_SHA256 secret_version: type: string description: Version label of the active signing secret. example: v1 transport: type: object description: Transport configuration. properties: mtls_required: type: boolean description: Reserved. Mutual TLS on delivery is not currently supported. example: false relationships: type: object description: | `deliveries` links to this subscription (list its deliveries with `GET /v1/webhooks/deliveries?filter[webhook_id]={webhook_id}`). `auth_profile` identifies the bound auth profile. properties: deliveries: $ref: '#/components/schemas/LinkOnlyRelationship' auth_profile: $ref: '#/components/schemas/AuthProfileConfigRelationship' links: $ref: '#/components/schemas/ResourceLinks' CreateWebhookRequest: description: | Request body for `POST /v1/webhooks`. See that operation for the delivery contract and signature verification. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - attributes properties: type: type: string enum: - webhooks attributes: type: object additionalProperties: false x-deny-unknown-fields: true required: - url - event_types - delivery_format - security properties: url: type: string format: uri description: | HTTPS endpoint that receives the deliveries. Must be an absolute `https://` URL with a host that resolves to a public address. Redirects are not followed. example: https://hooks.bank-alpha.example/lyriq/events event_types: type: array items: $ref: '#/components/schemas/WebhookEventType' description: | Event types to receive, matched exactly (case-sensitive). One or more of `operation.updated`, `operation.manual_review`, `operation.succeeded`, `operation.rejected`, `operation.failed`, `review.created`, `review.updated`, `review.approved`, `review.rejected`, `transfer.received`, `beneficiary.screening.requested`. An empty array receives no business events. Unknown values are rejected. Synthetic event names do not subscribe to business events; verification and test deliveries bypass this filter. example: - operation.succeeded - operation.failed - operation.rejected - review.created - transfer.received delivery_format: type: string description: | Body format of deliveries. Must be `jsonapi`. `iso20022+xml` is reserved for outbound ISO 20022 messages and is currently rejected with `400`. enum: - jsonapi - iso20022+xml security: type: object description: Signing configuration for this webhook. additionalProperties: false x-deny-unknown-fields: true required: - signature properties: signature: type: object description: HMAC signature configuration. All three fields are required. additionalProperties: false x-deny-unknown-fields: true required: - algorithm - secret - secret_version properties: algorithm: type: string description: Signing algorithm. Only `HMAC_SHA256` is supported. enum: - HMAC_SHA256 example: HMAC_SHA256 secret: type: string minLength: 1 writeOnly: true description: | Shared secret chosen by your bank. Its UTF-8 bytes are the HMAC-SHA256 key. Must not be empty or only whitespace; use a long random value (at least 32 random bytes, hex or base64 encoded). Write-only; never returned. example: 3f9c1e7a5b2d48e6a0c4f81b9d27e53c6a1f0b8e4d9c27a5 secret_version: type: string minLength: 1 maxLength: 64 pattern: ^[!-~]+$ description: | Your label for this secret: 1 to 64 printable ASCII characters, no spaces. Sent on every delivery in the `X-DAN-Secret-Version` header. example: v1 WebhookEventEnvelopeBase: description: | Fields shared by every webhook event body. Together with `type`, `resource`, `data` and `meta` they form the event envelope. type: object required: - id - occurred_at - bank_id properties: id: type: string description: | Event identifier: `evt_` followed by a UUID. The same on every retry of a delivery; use it to de-duplicate. For business events the UUID equals the `X-DAN-Event-Id` header. example: evt_0192f1af-6e7d-7c8b-9a0f-1e2d3c4b5a69 occurred_at: type: string description: | When the underlying change happened: RFC 3339 in UTC with fractional seconds and a `+00:00` offset. Use it to order events; deliveries can arrive out of order. example: '2026-09-26T10:15:30.998071+00:00' bank_id: type: string description: The bank the event belongs to (a UUID); always the bank that owns the subscription. example: 11111111-1111-7111-8111-111111111111 WebhookEventMeta: description: Delivery metadata attached to every webhook event. type: object required: - delivery_semantics properties: delivery_semantics: type: string description: | Delivery guarantee. Always `AT_LEAST_ONCE`: an event can be delivered more than once, so de-duplicate on the event `id`. enum: - AT_LEAST_ONCE example: AT_LEAST_ONCE OperationWebhookEvent: description: | Sent when an operation changes state. `type` tells you which change: - `operation.updated`: the operation was accepted, started processing, or started waiting for ledger commits; - `operation.manual_review`: the operation is waiting for an approval review (state `PENDING_REVIEW`); a `review.created` event carries the review; - `operation.succeeded`, `operation.rejected`, `operation.failed`: terminal outcomes. Fetch `data.links.self` for the full operation, including its result and the resource it created or changed. allOf: - $ref: '#/components/schemas/WebhookEventEnvelopeBase' - type: object required: - type - resource - data - meta properties: type: type: string enum: - operation.updated - operation.rejected - operation.manual_review - operation.succeeded - operation.failed resource: allOf: - $ref: '#/components/schemas/ResourceIdentifier' description: The operation the event is about. example: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 data: type: object required: - type - id - attributes - links properties: type: type: string enum: - operations id: type: string description: Operation identifier (a UUID). example: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 attributes: type: object required: - state - operation_type properties: state: type: string nullable: true description: | Operation state when the event was emitted, for example `ACCEPTED`, `PROCESSING`, `PENDING_REVIEW`, `PENDING_COMMITS`, `SUCCEEDED`, `REJECTED` or `FAILED`. `null` when not available. example: SUCCEEDED operation_type: type: string nullable: true description: | Workflow class of the operation (`PRIMARY_OPERATION` or `ACTION_OPERATION`), sent only on the acceptance event; `null` on later state changes. Read `operation_type` from `GET /v1/operations/{operation_id}`. example: null links: $ref: '#/components/schemas/ResourceLinks' meta: $ref: '#/components/schemas/WebhookEventMeta' ReviewWebhookEvent: description: | Sent when an approval review is opened or changes state: - `review.created`: a review was opened (state `PENDING`); `target_operation_id` is set; - `review.updated`: an approval was recorded but more are needed (state `PENDING`); - `review.approved`: the approval threshold was reached; - `review.rejected`: a reviewer rejected. `target_operation_id` is only set on `review.created`; on later events it is `null`. Fetch `data.links.self` for the full review. allOf: - $ref: '#/components/schemas/WebhookEventEnvelopeBase' - type: object required: - type - resource - data - meta properties: type: type: string enum: - review.created - review.approved - review.rejected - review.updated resource: allOf: - $ref: '#/components/schemas/ResourceIdentifier' description: The review the event is about. example: type: reviews id: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f data: type: object required: - type - id - attributes - links properties: type: type: string enum: - reviews id: type: string description: Review identifier (a UUID). example: 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f attributes: type: object required: - state - target_operation_id properties: state: type: string nullable: true description: Review state after the change (`PENDING`, `APPROVED` or `REJECTED`). example: APPROVED target_operation_id: type: string nullable: true description: | The operation waiting for this review. Set on `review.created`, `null` on other review events. example: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 links: $ref: '#/components/schemas/ResourceLinks' meta: $ref: '#/components/schemas/WebhookEventMeta' TransferReceivedWebhookEvent: description: | Sent to the destination holder bank when a transfer to it has completed. `bank_id` and `receiver_bank_id` are the receiving bank. There is no `data.links`; fetch the transfer with `GET /v1/transfers/{transfer_id}` using `data.id`. allOf: - $ref: '#/components/schemas/WebhookEventEnvelopeBase' - type: object required: - type - resource - data - meta properties: type: type: string enum: - transfer.received resource: allOf: - $ref: '#/components/schemas/ResourceIdentifier' description: The transfer that was received. example: type: transfers id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 data: type: object required: - type - id - attributes properties: type: type: string enum: - transfers id: type: string description: Transfer identifier. example: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 attributes: type: object required: - external_transfer_reference - receiver_bank_id - currency - amount - decimals - iso20022_ref - source_external_account_id - originator - destination_external_account_id - beneficiary - completed_at properties: external_transfer_reference: type: string description: | Reference for reconciliation, never empty: the destination `external_account_id` when the sending bank named a registered account, otherwise the transfer id (the same value as `data.id`). Not the sender's `client_reference`; read that from `GET /v1/transfers/{transfer_id}`. example: CBS-ACC-2026-000987 receiver_bank_id: type: string description: Destination holder bank (your bank), a UUID. example: 22222222-2222-7222-8222-222222222222 currency: type: string description: Currency code of the asset, as configured on the network (for example `usd`). example: usd amount: type: integer format: int64 description: | Amount in minor units of the asset: divide by 10 to the power of `decimals`. `125000` with `decimals: 2` is 1250.00. example: 125000 decimals: type: integer description: Number of decimal places of `amount`. example: 2 iso20022_ref: type: string nullable: true description: ISO 20022 reference associated with the transfer, or `null`. example: null source_external_account_id: type: string nullable: true description: | The sending bank's registered account the transfer was sent from, or `null` when the sender was identified only by `originator`. example: CBS-ACC-2026-000123 originator: type: object allOf: - $ref: '#/components/schemas/TransferPartyDetails' nullable: true description: | The sender details the sending bank submitted, or `null`. Use them, with the `beneficiary`, to screen the transfer. destination_external_account_id: type: string nullable: true description: | The registered account the sending bank named as the destination, or `null` when it identified the beneficiary only by `beneficiary`. example: CBS-ACC-2026-000987 beneficiary: type: object allOf: - $ref: '#/components/schemas/TransferPartyDetails' nullable: true description: | The beneficiary details the sending bank submitted, or `null`. Your bank resolves the beneficiary from these details and credits it on your core banking system. completed_at: type: string description: When the transfer completed (RFC 3339, UTC). Equal to `occurred_at`. example: '2026-09-26T10:15:30.997514+00:00' meta: $ref: '#/components/schemas/WebhookEventMeta' BeneficiaryScreeningWebhookEvent: description: | Sent when a beneficiary screening check is requested for a transfer. Respond with a screening decision through `POST /v1/transfers/{transfer_id}/screening-decisions`; `screening_window_secs` gives the length of the screening window. `data.links.self` is the transfer. allOf: - $ref: '#/components/schemas/WebhookEventEnvelopeBase' - type: object required: - type - resource - data - meta properties: type: type: string enum: - beneficiary.screening.requested resource: allOf: - $ref: '#/components/schemas/ResourceIdentifier' description: The transfer to screen. example: type: transfers id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 data: type: object required: - type - id - attributes - links properties: type: type: string enum: - transfers id: type: string description: Transfer identifier. example: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 attributes: type: object required: - uetr - source_message_sha256 - screening_window_secs properties: uetr: type: string nullable: true description: ISO 20022 Unique End-to-end Transaction Reference (UETR) of the transfer, or `null`. example: 8a562c67-ca16-48ba-b074-65581be6f001 source_message_sha256: type: string nullable: true description: | Lowercase hex SHA-256 of the ISO 20022 source message the transfer came from. `null` when the transfer was not initiated by an ISO 20022 message. example: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 screening_window_secs: type: string description: | Length of the screening window as an ISO 8601 duration expressed in seconds, for example `PT300S` (five minutes). Despite the field name this is not a plain number. example: PT300S links: $ref: '#/components/schemas/ResourceLinks' meta: $ref: '#/components/schemas/WebhookEventMeta' WebhookTestEvent: description: | Synthetic event requested with `POST /v1/webhooks/{webhook_id}/test`, to check connectivity and signature verification. Signed and delivered like a real event; `data.attributes` is always empty and there is no `data.links`. Acknowledge it with `2xx` and take no business action. allOf: - $ref: '#/components/schemas/WebhookEventEnvelopeBase' - type: object required: - type - resource - data - meta properties: type: type: string enum: - webhook.test resource: allOf: - $ref: '#/components/schemas/ResourceIdentifier' description: The webhook subscription that was tested. example: type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 data: type: object required: - type - id - attributes properties: type: type: string enum: - webhooks id: type: string description: Webhook subscription identifier (a UUID). example: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 attributes: type: object description: Always empty for test events. meta: $ref: '#/components/schemas/WebhookEventMeta' WebhookVerificationEvent: description: | Handshake event sent when a subscription enters `PENDING_VERIFICATION`: on creation, after an auth profile is bound, and after a bound auth profile is updated. Verify its signature like any event and answer `2xx`; the subscription then becomes `ACTIVE`. No response body or challenge echo is needed. It is retried like other events; if it fails permanently the subscription stays in `PENDING_VERIFICATION`. `data.attributes` is always empty, and `event_types` does not apply to it. allOf: - $ref: '#/components/schemas/WebhookEventEnvelopeBase' - type: object required: - type - resource - data - meta properties: type: type: string enum: - webhook.verification resource: allOf: - $ref: '#/components/schemas/ResourceIdentifier' description: The webhook subscription being verified. example: type: webhooks id: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 data: type: object required: - type - id - attributes properties: type: type: string enum: - webhooks id: type: string description: Webhook subscription identifier (a UUID). example: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 attributes: type: object description: Always empty for verification events. meta: $ref: '#/components/schemas/WebhookEventMeta' UpdateWebhookRequest: description: Request body for `PUT /v1/webhooks/{webhook_id}`. Send only the attributes to change. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - id - attributes properties: type: type: string enum: - webhooks id: type: string description: Webhook subscription identifier; must equal the `webhook_id` path parameter. example: 0192f1a0-4b2c-7d3e-8f40-a1b2c3d4e5f6 attributes: type: object additionalProperties: false x-deny-unknown-fields: true description: Attributes to change. Each is optional, but at least one must be sent. properties: url: type: string format: uri description: New HTTPS endpoint; an absolute `https://` URL with a host. example: https://hooks.bank-alpha.example/lyriq/v2/events event_types: type: array items: $ref: '#/components/schemas/WebhookEventType' description: | Replacement list of event types, matched exactly (see `POST /v1/webhooks`). An empty array receives no business events. example: - operation.updated - review.updated - transfer.received delivery_format: type: string description: Body format of deliveries. Must be `jsonapi` when sent. enum: - jsonapi - iso20022+xml status: type: string description: | `DISABLED` to stop deliveries, `ACTIVE` to resume them. `PENDING_VERIFICATION` is set by the platform and cannot be requested. enum: - ACTIVE - DISABLED security: type: object description: Transport configuration. Accepted but currently has no effect. properties: transport: type: object properties: mtls_required: type: boolean description: Reserved. Mutual TLS on delivery is not currently supported. example: true RotateWebhookSecretRequest: description: Request body for `POST /v1/webhooks/{webhook_id}/rotate-secret`. All attributes are required. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - attributes properties: type: type: string enum: - webhook-secret-rotations attributes: type: object additionalProperties: false x-deny-unknown-fields: true required: - secret - secret_version - activate_at - grace_period_seconds properties: secret: type: string writeOnly: true description: | New shared secret chosen by your bank; its UTF-8 bytes are the HMAC-SHA256 key. Must not be empty or only whitespace. Write-only; never returned. example: c41e9a07b3d25f86e1a4c7092bd53e6f8a0c17d94be25a36 secret_version: type: string minLength: 1 maxLength: 64 pattern: ^[!-~]+$ description: | Label for the new secret: 1 to 64 printable ASCII characters, no spaces. Sent in `X-DAN-Secret-Version` once the secret is active. example: v2 activate_at: type: string format: date-time description: | When the new secret becomes the active signing secret (RFC 3339). Until then the current secret keeps signing. A time in the past activates it immediately. example: '2026-10-01T06:00:00Z' grace_period_seconds: type: integer description: | Seconds the previous secret is kept as retiring after the new one activates. `0` uses the platform default of 86400 (24 hours); negative values are rejected. Deliveries are always signed with the active secret only. minimum: 0 example: 86400 WebhookDeliveryKey: type: object required: - id - type properties: id: type: string description: | Webhook delivery identifier (a UUID). Equal to the `X-DAN-Delivery-Id` header of the delivery requests. example: 0192f1b0-3c4d-7e5f-8a6b-7c8d9e0f1a2b type: type: string description: Always `webhook-deliveries`. enum: - webhook-deliveries WebhookRelationship: description: JSON:API to-one relationship to a Webhook. type: object properties: data: $ref: '#/components/schemas/WebhookKey' links: $ref: '#/components/schemas/RelationshipLinks' WebhookDelivery: description: | One event delivered (or being delivered) to one webhook subscription, covering all of its attempts. allOf: - $ref: '#/components/schemas/WebhookDeliveryKey' - type: object required: - attributes properties: attributes: type: object required: - event_id - event_type - status - attempt_count - last_attempt_at properties: event_id: type: string description: | UUID of the delivered event, as sent in `X-DAN-Event-Id`. The event body `id` is `evt_` followed by this UUID. example: 0192f1af-6e7d-7c8b-9a0f-1e2d3c4b5a69 event_type: type: string description: Event type, for example `operation.succeeded`. example: operation.succeeded status: type: string description: | - `RETRYING`: not yet acknowledged (waiting for the first attempt, in progress, or scheduled for a retry). - `DELIVERED`: acknowledged with a `2xx` status. - `FAILED`: permanently failed; it is not sent again. enum: - DELIVERED - FAILED - RETRYING example: DELIVERED attempt_count: type: integer minimum: 0 description: Number of attempts made so far (0 before the first attempt). example: 1 last_http_status: type: integer nullable: true description: Reserved for the HTTP status of the latest attempt. Not currently returned. example: 200 last_attempt_at: type: string format: date-time description: Time of the latest change to this delivery (RFC 3339, UTC), such as its latest attempt. example: '2026-09-26T10:15:31.402117+00:00' next_attempt_at: type: string format: date-time nullable: true description: Reserved for the time of the next scheduled retry. Not currently returned. relationships: type: object properties: webhook: $ref: '#/components/schemas/WebhookRelationship' links: $ref: '#/components/schemas/ResourceLinks' BindWebhookAuthProfileRequest: description: | Request body for `POST /v1/webhooks/{webhook_id}/bind`. The auth profile must belong to the same bank as the webhook. Binding moves the webhook to `PENDING_VERIFICATION` until the new `webhook.verification` event is acknowledged. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - attributes properties: type: type: string enum: - webhooks attributes: type: object additionalProperties: false x-deny-unknown-fields: true required: - auth_profile_id properties: auth_profile_id: type: string description: Identifier (a UUID) of the auth profile to bind to this webhook. example: 0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 AuthProfileConfig: description: | An OAuth2 client credentials profile of your bank. When a webhook is bound to it, the platform obtains an access token from `token_endpoint_url` and sends it as `Authorization: Bearer` on every delivery. The client secret is never returned. allOf: - $ref: '#/components/schemas/AuthProfileConfigKey' - type: object required: - attributes properties: attributes: type: object required: - bank_id - client_id - token_endpoint_url - requested_scope - profile_type - created_at - updated_at properties: bank_id: type: string description: Bank that owns this auth profile (a UUID). example: 11111111-1111-7111-8111-111111111111 client_id: type: string description: OAuth2 `client_id` the platform presents to your token endpoint. example: lyriq-webhook-sender token_endpoint_url: type: string format: uri description: Your OAuth2 token endpoint (HTTPS). example: https://idp.bank-alpha.example/oauth2/token requested_scope: type: string description: Value sent as `scope` in the token request (space-delimited OAuth2 scopes). example: webhooks.receive profile_type: type: string description: Profile type. `OAuth2` (client credentials grant) is the only type. enum: - OAuth2 example: OAuth2 created_at: type: string format: date-time description: When the profile was created (RFC 3339, UTC). example: '2026-09-26T09:40:11.305812+00:00' updated_at: type: string format: date-time description: When the profile was last updated, including client secret rotations (RFC 3339, UTC). example: '2026-09-26T09:40:11.305812+00:00' links: $ref: '#/components/schemas/ResourceLinks' CreateAuthProfileConfigRequest: description: Request body for `POST /v1/auth-profiles`. All attributes are required. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - attributes properties: type: type: string enum: - auth-profiles attributes: type: object additionalProperties: false x-deny-unknown-fields: true required: - client_id - client_secret - token_endpoint_url - requested_scope - profile_type properties: client_id: type: string description: OAuth2 `client_id` the platform presents to your token endpoint. Must not be empty. example: lyriq-webhook-sender client_secret: type: string writeOnly: true description: | OAuth2 client secret, sent to your token endpoint as `client_secret`. Must not be empty. Write-only; stored encrypted and never returned. example: 9Hq2vX7kLp3sT8wZ1cF6bN4mR0yJ5dGa token_endpoint_url: type: string format: uri description: | Your OAuth2 token endpoint: an absolute `https://` URL with a host that resolves to a public address. example: https://idp.bank-alpha.example/oauth2/token requested_scope: type: string description: Value sent as `scope` in the token request (space-delimited OAuth2 scopes). Must not be empty. example: webhooks.receive profile_type: type: string description: Profile type. Must be `OAuth2` (client credentials grant). enum: - OAuth2 example: OAuth2 UpdateAuthProfileConfigRequest: description: | Request body for `PUT /v1/auth-profiles/{auth_profile_id}`. Any successful update moves every webhook bound to the profile to `PENDING_VERIFICATION` until its new `webhook.verification` event is acknowledged. type: object required: - data properties: jsonapi: $ref: '#/components/schemas/JsonApiVersion' data: type: object required: - type - id - attributes properties: type: type: string enum: - auth-profiles id: type: string description: Auth profile identifier; must equal the `auth_profile_id` path parameter. example: 0192f1a2-7c8d-7e9f-8a01-b2c3d4e5f6a7 attributes: type: object additionalProperties: false x-deny-unknown-fields: true description: Fields to update. All attributes are optional; at least one must be set. properties: client_id: type: string description: OAuth2 `client_id` the platform presents to your token endpoint. Must not be empty. example: lyriq-webhook-sender client_secret: type: string writeOnly: true description: | New OAuth2 client secret; sending it rotates the credential. Must not be empty. Write-only; never returned. example: Zt5wQ8nB2xK7hV0pL4cM9sD1fR6gY3jA token_endpoint_url: type: string format: uri description: | Your OAuth2 token endpoint: an absolute `https://` URL with a host that resolves to a public address. example: https://idp.bank-alpha.example/oauth2/token requested_scope: type: string description: Value sent as `scope` in the token request (space-delimited OAuth2 scopes). Must not be empty. example: webhooks.receive profile_type: type: string description: Profile type. Must be `OAuth2` (client credentials grant). enum: - OAuth2 example: OAuth2 SettlementCycle: description: | A settlement cycle: one batch of interbank redemptions of one issuer and one root asset, settled in cash outside the platform. Visible only to the issuer bank. Optional attributes are omitted (not `null`) when they have no value, unless marked nullable. allOf: - $ref: '#/components/schemas/SettlementCycleKey' - type: object required: - attributes properties: attributes: type: object required: - status - mode - cutoff_at properties: status: type: string description: | Current lifecycle status. See `GET /v1/settlement-cycles` for the full lifecycle. `CLOSED` and `ABORTED` are terminal; `CLOSE_PENDING` and `ABORT_PENDING` mean ledger effects are submitted and awaiting finality; `MANUAL_REVIEW` means the platform operator must repair or abort the cycle. enum: - OPEN - LOCKED - CASH_SETTLED - CLOSED - ABORTED - CLOSE_PENDING - ABORT_PENDING - MANUAL_REVIEW example: OPEN mode: type: string description: | Settlement mode. `MANDATORY` is the only mode: at cutoff every assigned redemption is locked and settled in this cycle. enum: - MANDATORY example: MANDATORY cutoff_at: type: string format: date-time description: | Planned lock time (UTC). While the cycle is `OPEN`, new redemptions are assigned to it; once `cutoff_at` has passed, the scheduler locks the cycle. example: '2026-09-24T16:00:00Z' asset_id: type: string description: | The issuer's issued asset id settled by this cycle, for example `usd.bank-alpha`. Falls back to the root asset id when no issued asset is registered. example: usd.bank-alpha root_asset_id: type: string description: Root settlement asset of the cycle, for example `usd`. example: usd issuer_bank_id: type: string description: Issuer bank of the cycle; always the calling bank. example: 11111111-1111-7111-8111-111111111111 issuer_asset_code: type: string description: Display code of the issued asset. Omitted when not configured. example: USD.ALPHA settled_at: type: string format: date-time nullable: true description: When cash settlement was confirmed. Omitted until then. example: '2026-09-24T17:02:31Z' closed_at: type: string format: date-time nullable: true description: When the cycle reached `CLOSED` or `ABORTED`. Omitted until then. example: '2026-09-24T17:06:40Z' cash_settlement: type: object description: | Evidence of the cash leg. Present once cash settlement has been recorded (status `CASH_SETTLED` or later); omitted before. Every field is optional. properties: state: type: string description: | `PENDING_CONFIRMATION`: the cash leg is recorded but not yet confirmed. `CONFIRMED`: receipt of the cash is confirmed. enum: - PENDING_CONFIRMATION - CONFIRMED example: CONFIRMED reference: type: string nullable: true description: External reference of the cash payment, for example a wire reference. example: FED-20260924-000512 mode: type: string description: | How the cash evidence was produced. `MANUAL`: confirmed by the platform operator with an external reference. `SIMULATED`: generated automatically in test and demo environments only; it does not represent a real cash movement. enum: - SIMULATED - MANUAL example: MANUAL evidence_source: type: string description: | Label of where the evidence came from, for example `manual-operator-confirmation` or `simulated-non-wire-mvp`. example: manual-operator-confirmation netting_digest: type: string description: | Hex-encoded SHA-256 digest (64 characters) of the netting report the cash confirmation is bound to. example: 4b1d7e2a9c3f5e8d0a6b2c4e6f8a0b1c3d5e7f9a1b3c5d7e9f0a2b4c6d8e0f1a confirmed_by_principal_id: type: string description: Principal (person or system identity) that confirmed the cash leg. example: 0192a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b confirmed_at: type: string format: date-time description: When the cash evidence was recorded. example: '2026-09-24T17:02:31Z' net_amount: type: string description: | Total net amount settled in the cycle, as an integer string in the asset's minor units (for example `"300000"` is 3000.00 for a 2-decimal asset). example: '300000' asset_summaries: type: array description: | Per-asset breakdown of the cycle. Present while redemptions are assigned to or locked in the cycle; omitted when there is nothing to summarise. items: type: object properties: asset_id: type: string description: Issued asset id, for example `usd.bank-alpha`. example: usd.bank-alpha root_asset_id: type: string description: Root asset id. Omitted when not known. example: usd issuer_bank_id: type: string description: Issuer bank of the asset. Omitted when not known. example: 11111111-1111-7111-8111-111111111111 issuer_asset_code: type: string description: Display code of the issued asset. Omitted when not configured. example: USD.ALPHA total_locked_amount: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: Total amount locked in this cycle for the asset, when available. lock_count: type: integer description: Number of redemptions assigned to or locked in this cycle for the asset. example: 2 redeemed_amount: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: Amount redeemed for the asset, when available. released_amount: allOf: - $ref: '#/components/schemas/MonetaryAmount' description: Amount released without redemption (after an abort), when available. links: $ref: '#/components/schemas/ResourceLinks' RedemptionPolicyTrigger: description: | The rule that fires the policy. Exactly one of the two shapes, selected by `type`. All amounts are positive integer strings in the asset's minor units (`"10000000"` is 100,000.00 for a 2-decimal asset). oneOf: - type: object title: AboveThreshold description: | Fires whenever the available balance of the holding is strictly greater than `threshold`. required: - type - threshold - redemption_type properties: type: type: string enum: - above_threshold description: Discriminator; always `above_threshold` for this shape. threshold: type: string description: Available balance above which the rule fires. Greater than zero. example: '11000000' redemption_type: type: string enum: - excess - full description: | `excess`: redeem the available balance minus `min_balance`, bringing the holding down to `min_balance`. `full`: redeem the whole available balance. example: excess min_balance: type: string description: | Balance to keep after an `excess` redemption. Present only when `redemption_type` is `excess`, and always lower than `threshold`. example: '10000000' - type: object title: Scheduled description: | Fires at `start_at` and then every `repeat_after`. If runs were missed (for example during an outage), the next run is scheduled for the next future slot; missed runs are not made up. required: - type - start_at - repeat_after - redemption_strategy properties: type: type: string enum: - scheduled description: Discriminator; always `scheduled` for this shape. start_at: type: string format: date-time description: First run (UTC, whole seconds). example: '2026-10-01T16:00:00Z' repeat_after: type: string description: | Interval between runs, as a human-readable duration made of number and unit pairs, for example `30m`, `12h`, `1day`, or `10m 30s`. Always positive. example: 1day redemption_strategy: type: string enum: - fixed - full description: | `fixed`: redeem `fixed_amount` on each run. `full`: redeem the whole available balance on each run. example: fixed fixed_amount: type: string description: | Amount to redeem on each run. Present only when `redemption_strategy` is `fixed`; greater than zero. example: '500000' RedemptionPolicy: description: | A redemption policy: automation that creates settlement-cycle redemptions for one interbank holding (the holder's holding of one issuer's asset) whenever its trigger fires. Visible only to the holder bank. The current release returns only `attributes`; the resource document has no `id` or `type` member. allOf: - type: object required: - attributes properties: attributes: type: object required: - holder_bank_id - issuer_bank_id - root_asset_id - asset_id - trigger properties: holder_bank_id: type: string description: Bank that holds the asset and on whose behalf redemptions are created; always the calling bank. example: 22222222-2222-7222-8222-222222222222 issuer_bank_id: type: string description: Bank that issued the asset being redeemed. example: 11111111-1111-7111-8111-111111111111 root_asset_id: type: string description: Root asset of the holding, for example `usd`. example: usd asset_id: type: string description: Asset being redeemed, for example the issuer's issued asset `usd.bank-alpha`. example: usd.bank-alpha trigger: $ref: '#/components/schemas/RedemptionPolicyTrigger' XmlProblem: description: | Problem document in XML, modelled on RFC 7807 (problem details for HTTP APIs). Every ISO 20022 endpoint returns errors in this format instead of the JSON:API `errors` document. | Element | Occurs | Content | |---------|--------|---------| | `problem` | 1 | Root element, in the namespace `urn:ietf:rfc:7807`. | | `problem/status` | 1 | HTTP status code of the response, repeated in the body (integer). | | `problem/errors` | 1 | Container for one or more `error` elements. | | `problem/errors/error` | 1 or more | One error. A request can produce several, for example when the submitted XML breaks more than one schema rule. | | `error/code` | 0 or 1 | Machine-readable code in upper snake case, for example `IDEMPOTENCY_CONFLICT`, `IDEMPOTENCY_PENDING`, `STATE_CONFLICT`, `KEY_UNAVAILABLE`, `COUNTERPARTY_UNAVAILABLE`, `DEPENDENCY_UNAVAILABLE` or `INTEGRITY_CHECK_FAILED`. Omitted for parsing, schema-validation, routing, media-type and authorization errors, which are identified by their `title`. | | `error/title` | 1 | Human-readable summary. Schema-validation errors name the offending element, for example `invalid XML: BICFI value ALPHUS3 does not match the required format`. | | `error/detail` | 0 or 1 | Additional explanation or remediation advice. | Responses are sent without indentation; examples in this reference are indented for readability only. type: string example: | 409 STATE_CONFLICT State conflict Resubmit the original message unchanged or assign a new UETR. responses: BadRequest: description: | The request is malformed: invalid JSON syntax, an invalid path or query parameter, a missing required header such as `Idempotency-Key`, or a single field that fails its own format rule. Fix the request before retrying; retrying it unchanged fails again. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: invalidField: summary: A body field fails its format rule value: jsonapi: version: '1.1' errors: - status: '400' title: Field format invalid code: INVALID_FIELD_FORMAT detail: /data/attributes/reason_code must not be empty source: pointer: /data/attributes/reason_code missingIdempotencyKey: summary: Idempotency-Key header missing on a mutation value: jsonapi: version: '1.1' errors: - status: '400' title: Missing idempotency key invalidPagination: summary: page[size] out of range value: jsonapi: version: '1.1' errors: - status: '400' title: Invalid pagination parameters detail: page[size] must be between 1 and 200, got 500 malformedJson: summary: Request body is not valid JSON value: jsonapi: version: '1.1' errors: - status: '400' title: Invalid request body detail: 'Failed to parse the request body as JSON: key must be a string at line 1 column 2' Unauthorized: description: | The bearer token is missing, malformed, expired, signed by an unknown key, or was not issued by the platform IAM for the Lyriq Connector. Obtain a new token and retry. See the Authentication section. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: missingToken: summary: No Authorization header value: jsonapi: version: '1.1' errors: - status: '401' title: Missing bearer token code: AUTHENTICATION_REQUIRED expiredToken: summary: Token has expired value: jsonapi: version: '1.1' errors: - status: '401' title: Expired token code: AUTHENTICATION_REQUIRED invalidAudience: summary: Token was not issued for the Lyriq Connector value: jsonapi: version: '1.1' errors: - status: '401' title: Invalid audience code: AUTHENTICATION_REQUIRED Forbidden: description: | The token is valid but may not perform this request: it lacks the required scope, has no bank membership, needs an `x-dan-bank-id` header to choose between several memberships, names a bank in `x-dan-bank-id` it has no membership for, or the caller's bank is suspended or terminated. A new token with the same configuration fails the same way. See the Authentication section. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: missingScope: summary: Token lacks the scope the endpoint requires value: jsonapi: version: '1.1' errors: - status: '403' title: Unauthorized scope code: UNAUTHORIZED_SCOPE bankSelectorRequired: summary: Several bank memberships and no x-dan-bank-id header value: jsonapi: version: '1.1' errors: - status: '403' title: Bank selector required code: UNAUTHORIZED_SCOPE bankScopeMismatch: summary: x-dan-bank-id names a bank the token has no membership for value: jsonapi: version: '1.1' errors: - status: '403' title: Bank scope mismatch code: BANK_SCOPE_MISMATCH bankSuspended: summary: The caller's bank is suspended value: jsonapi: version: '1.1' errors: - status: '403' title: Bank membership is suspended code: BANK_SUSPENDED ServiceUnavailable: description: | The request could not be served right now. Either the network is not fully operational (`OUTBOUND_HALTED` or `READ_ONLY`: mutations are refused while read endpoints keep working; `OPERATIONAL_STATE_UNKNOWN`: the state could not be determined), or a platform dependency is temporarily unavailable. No `Retry-After` header is sent; retry later with backoff. When retrying a mutation, reuse the same `Idempotency-Key` and body. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: outboundHalted: summary: Network outbound activity halted; mutations refused value: jsonapi: version: '1.1' errors: - status: '503' title: Outbound halted code: OUTBOUND_HALTED detail: Scheduled network maintenance operationalStateUnknown: summary: Network operational state could not be determined value: jsonapi: version: '1.1' errors: - status: '503' title: Operational state unknown code: OPERATIONAL_STATE_UNKNOWN NotFound: description: | The resource does not exist, or it belongs to another bank. The Lyriq Connector does not distinguish the two cases, so resources of other banks are never disclosed. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: operationNotFound: summary: Unknown operation, or an operation of another bank value: jsonapi: version: '1.1' errors: - status: '404' title: operation not found Conflict: description: | The request conflicts with an earlier request or with the current state of the target: an `Idempotency-Key` reused with a different body (`IDEMPOTENCY_CONFLICT`), a request with the same key still in progress (`IDEMPOTENCY_PENDING`), or a target resource in a state that does not allow the request (`STATE_CONFLICT`). content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: idempotencyConflict: summary: Same Idempotency-Key, different body value: jsonapi: version: '1.1' errors: - status: '409' title: Idempotency conflict code: IDEMPOTENCY_CONFLICT idempotencyPending: summary: Same Idempotency-Key, first request still in progress value: jsonapi: version: '1.1' errors: - status: '409' title: Idempotency pending code: IDEMPOTENCY_PENDING UnprocessableEntity: description: | The request is well-formed JSON but cannot be processed: the body does not match the expected shape (a missing or unknown member, a wrong type, or a wrong `data.type`), or it breaks a business or cross-field rule. Correct the request before retrying. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: bodyShape: summary: Body does not match the expected document shape value: jsonapi: version: '1.1' errors: - status: '422' title: Invalid request body detail: 'Failed to deserialize the JSON body into the target type: data.attributes: unknown field `reason`, expected one of `reason_code`, `comment`, `expected_target_state` at line 1 column 58' missingAnyOf: summary: A cross-field rule is violated (for example an update with nothing to change) value: jsonapi: version: '1.1' errors: - status: '422' title: At least one field is required code: MISSING_ANY_OF detail: at least one of /data/attributes/url, /data/attributes/event_types, /data/attributes/delivery_format, /data/attributes/status, /data/attributes/security is required TooManyRequests: description: | The request was refused because a rate limit was reached (code `RATE_LIMITED`). No `Retry-After` header is sent; retry with exponential backoff. content: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' examples: rateLimited: summary: Rate limit reached value: jsonapi: version: '1.1' errors: - status: '429' title: Rate limited code: RATE_LIMITED AcceptedOperation: description: | Request accepted for asynchronous processing. An operation has been created to track it; `data.id` is its `operation_id`. Acceptance is not success: the request can still be rejected, fail or be cancelled. Poll `GET /v1/operations/{operation_id}` until the operation reaches a terminal state. Replaying the same `Idempotency-Key` with the same body returns this same document again. content: application/vnd.api+json: schema: $ref: '#/components/schemas/AcceptedOperation' examples: withResource: summary: Accepted, with a link to the target resource (for example a transfer) value: jsonapi: version: '1.1' data: type: operations id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 attributes: resource_family: transfers state: ACCEPTED relationships: resource: data: type: transfers id: 01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 links: related: /v1/transfers/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e11 links: self: /v1/operations/01927c3e-8b4a-7d21-9f3e-5a6b7c8d9e10 withoutResource: summary: Accepted, without a resource link (for example an operation cancellation) value: jsonapi: version: '1.1' data: type: operations id: 01927c3f-1d2e-7a3b-8c4d-5e6f7a8b9c0d attributes: resource_family: operations state: ACCEPTED AcceptedIso20022Operation: description: | Message accepted. The platform has stored the source message and created an asynchronous transfer operation for it. The response body is empty; use the headers to track the payment: - `Location`: the operation resource, `/v1/operations/{operation_id}`. - `X-DAN-Operation-Id`: the operation id (a UUID). - `X-ISO20022-UETR`: the UETR of the submitted message. A replay of an identical message returns this same response with the original operation id. headers: Location: $ref: '#/components/headers/Location' X-DAN-Operation-Id: $ref: '#/components/headers/XDanOperationId' X-ISO20022-UETR: $ref: '#/components/headers/XIso20022Uetr' XmlProblem: description: | Error, returned as an XML problem document (`application/problem+xml`). The HTTP status code is repeated in ``; each `` carries a `` and, where one applies, a `<code>` and a `<detail>`. content: application/problem+xml: schema: $ref: '#/components/schemas/XmlProblem' examples: schemaValidation: summary: Submitted XML breaks a schema rule (400) value: | <?xml version="1.0" encoding="UTF-8"?> <problem xmlns="urn:ietf:rfc:7807"> <status>400</status> <errors> <error> <title>invalid XML: BICFI value ALPHUS3 does not match the required format authorization: summary: Missing, invalid or expired bearer token (401) value: | 401 ISO 20022 authorization failed notFound: summary: Unknown UETR, or the calling bank is not a party to it (404) value: | 404 Not Found idempotencyConflict: summary: Same UETR reused with a different message (409) value: | 409 IDEMPOTENCY_CONFLICT Idempotency conflict headers: Location: description: | Sent by the ISO 20022 endpoints only. Relative URL of the operation created for this request, `/v1/operations/{operation_id}`. Poll it to follow the request to a terminal state. schema: type: string example: /v1/operations/01998a2e-5b3c-7d41-9f2a-3c4b5d6e7f80 XDanOperationId: description: | Sent by the ISO 20022 endpoints only. Identifier (a UUID) of the operation that tracks this request. schema: type: string format: uuid example: 01998a2e-5b3c-7d41-9f2a-3c4b5d6e7f80 XIso20022Uetr: description: | UETR (Unique End-to-end Transaction Reference) of the payment this response refers to: a lowercase, hyphenated UUID version 4, taken from `PmtId/UETR` of the submitted pacs.009 message. The platform never assigns a UETR; it always echoes the one the sending bank chose. schema: type: string format: uuid example: 8a562c67-ca16-48ba-b074-65581be6f011