Fictional reference API for learning, not a real bank API. There is no real provider behind these examples and no real base URL. Do not copy these payloads into a real integration: every real bank documents its own provider, version, authentication, idempotency and error rules, and they differ by provider.
Try payment API requests and responses against that fictional reference API. Pick an endpoint, read the request, then send a valid request and an invalid one to see how validation and errors work. Every example follows the same payment: USD 25,000 from Northgate Trading Ltd, London, to Tanaka Trading Pte Ltd, Singapore, end to end reference INV-8841. Correspondent charges are USD 20 + USD 22 = USD 42, so the beneficiary is expected to receive USD 24,958.00, matching the accounting simulator for the same payment.
Assumptions of this fictional reference API
Base URL (fictional).https://api.example-bank.test/v1 (.test is a reserved domain that never resolves, so no request here can reach a real system).
Versioning. The version is in the path (/v1). A new version is a new path (/v2); the provider publishes a changelog and a sunset date, and old versions keep working until that date. This is a teaching assumption, not a rule every provider follows.
Authentication and authorisation. OAuth 2.0 bearer tokens, shown as Authorization: Bearer <access_token>, with a scope per operation (payments:read, payments:write). Mutual TLS and certificate-based client authentication, as used by real open-banking APIs, are assumed but not shown. No request in this playground is unauthenticated.
Asynchronous processing. Creating, cancelling or enquiring about a payment returns 202 Accepted: the request was accepted for processing. Settlement, beneficiary credit and the final outcome always arrive later, by status enquiry or webhook. An HTTP success never means the payment settled or the beneficiary was credited.
Idempotency. Every POST that changes something requires an Idempotency-Key header. Same key with the same body replays the original response; same key with a different body is 409 Conflict. Retries use the same key.
Correlation. Every request carries X-Request-ID and every response echoes it, so a request can be traced in the bank’s logs.
Timeouts and retries. If a request times out, do not simply resend a POST. Retry with the same Idempotency-Key so the bank returns the original response instead of executing twice. How long to wait before retrying is set by the provider.
Rate limits. The provider limits how many requests a caller may make. Exceeding the limit returns 429 Too Many Requests with a Retry-After value; the caller must wait that long before retrying.
Errors. Every error response carries an HTTP status, a business error code and a human-readable message. See the error catalogue below: schema errors, business errors and authentication errors are different things and carry different statuses.
POST POST /v1/payments
Creates a new customer credit transfer instruction. The caller supplies debtor, creditor, amount, currency and value date, and the API returns a payment identification that can be used to track the payment. The response only confirms the instruction was accepted for processing: it does not mean the payment has settled or the beneficiary has been credited.
{
"debtor_name": "Northgate Trading Ltd",
"debtor_account": "GB29NWBK60161331926819",
"debtor_agent_bic": "NWBKGB2L",
"creditor_name": "Tanaka Trading Pte Ltd",
"creditor_address": {
"street": "1 Harbourfront Avenue",
"town": "Singapore",
"postcode": "098633",
"country": "SG"
},
"creditor_account": {
"bank_name": "DBS Bank Ltd, Singapore",
"bank_code": "7171",
"branch_code": "001",
"account_number": "1234567",
"note": "Singapore has no IBAN. Singapore accounts are identified by bank code + branch code + account number, not by IBAN."
},
"creditor_agent_bic": "DBSSSGSG",
"currency": "USD",
"amount": 25000.0,
"value_date": "2026-11-05",
"remittance_information": "INV-8841",
"end_to_end_id": "INV-8841",
"charge_bearer": "SHA",
"fx_quote_id": "FXQ-20261105-5510"
}
Validation rules
An Idempotency-Key header is required on this POST. The same key with the same body replays the original 202 response; the same key with a different body is rejected with 409 Conflict, so a retry can never double-pay.
Authorization: Bearer access token (OAuth 2.0) with scope payments:write. A missing or expired token is 401 Unauthorized; a token without the scope is 403 Forbidden.
X-Request-ID is required on every request and is echoed on every response, so a request can be traced in the bank's logs.
Debtor name, debtor account and debtor agent BIC are required, and the creditor address is required for screening.
Creditor account: Singapore has no IBAN, so the account is given as bank code 7171 + branch code 001 + account number. A Singapore account written as an IBAN (for example SG71...) is invalid and fails validation.
Amount must be greater than zero and use no more than two decimal places for USD.
Currency must be a valid three letter ISO 4217 code, such as USD, EUR or GBP.
Value date must be a business day for the payment currency and not in the past.
end_to_end_id INV-8841 is the debtor's reference for this payment and stays the same through the whole chain; the UETR is assigned by the bank and returned in the response.
fx_quote_id is optional. When supplied, the rate from that quote is the rate applied.
Success response: HTTP 202 Accepted. 202 Accepted means the bank accepted the instruction for processing. It does NOT mean the payment settled or the beneficiary was credited. The payment then moves ACCP, ACSP, ACSC, ACCC, and only ACCC confirms the creditor's account was credited.
Error response: HTTP 422 Unprocessable Content. 422 means the request was well formed but failed a business validation rule (here the creditor account). No payment was created and nothing was debited.
{
"payment_id": "PAY-20261105-8841",
"status": "ACCP",
"status_description": "Accepted for processing: technical validation and customer profile checks passed. Settlement has not started.",
"uetr": "4f8a2c1e-7b3d-4e9a-8c2f-1a5d6e7f8b90",
"end_to_end_id": "INV-8841",
"value_date": "2026-11-05",
"created_at": "2026-11-05T09:12:00Z",
"request_id": "REQ-20261105-0001",
"estimated_charges": {
"amount": 42.0,
"currency": "USD",
"breakdown": [
{
"description": "First correspondent charge",
"amount": 20.0
},
{
"description": "Second correspondent charge",
"amount": 22.0
}
],
"note": "Estimate only. Correspondent banks deduct their charges in transit (charge bearer SHA), so the beneficiary is expected to receive USD 24,958.00. This matches the accounting simulator for the same payment (USD 20 + USD 22 = USD 42)."
},
"next_step": "Poll GET /v1/payments/PAY-20261105-8841/status or wait for the webhook. This response does not mean the payment has settled or the beneficiary has been credited."
}
{
"error_code": "AC01",
"error_message": "Creditor account number is invalid",
"payment_id": null,
"status": "RJCT",
"request_id": "REQ-20261105-0001"
}
POST POST /v1/payments/validate
Validates a payment instruction without creating it: same checks as initiation (account format, currency, amount, value date, screening pre-check), but no payment is created, nothing is debited and no funds move. Use it to catch errors before sending the real initiation.
{
"debtor_name": "Northgate Trading Ltd",
"debtor_account": "GB29NWBK60161331926819",
"debtor_agent_bic": "NWBKGB2L",
"creditor_name": "Tanaka Trading Pte Ltd",
"creditor_address": {
"street": "1 Harbourfront Avenue",
"town": "Singapore",
"postcode": "098633",
"country": "SG"
},
"creditor_account": {
"bank_name": "DBS Bank Ltd, Singapore",
"bank_code": "7171",
"branch_code": "001",
"account_number": "1234567",
"note": "Singapore has no IBAN. Singapore accounts are identified by bank code + branch code + account number, not by IBAN."
},
"creditor_agent_bic": "DBSSSGSG",
"currency": "USD",
"amount": 25000.0,
"value_date": "2026-11-05",
"remittance_information": "INV-8841",
"end_to_end_id": "INV-8841",
"charge_bearer": "SHA",
"fx_quote_id": "FXQ-20261105-5510"
}
Validation rules
Validation is read-only: it creates no payment and needs no Idempotency-Key, because there is nothing to duplicate.
The same validation rules apply as for POST /v1/payments, so a payment that passes validation here should pass initiation, unless something changed in between.
A pass here does not guarantee execution: full sanctions screening, funds checks and cut-off rules still run at initiation.
Success response: HTTP 200 OK. 200 OK with valid true means the instruction passes the checks. It is not a payment: there is no payment_id and nothing to track yet.
Error response: HTTP 422 Unprocessable Content. 422 lists every failed check with the field, the business code and a message, so the caller can fix the instruction before initiating.
{
"valid": true,
"payment_id": null,
"checks": [
{
"check": "creditor_account_format",
"result": "pass"
},
{
"check": "currency_and_amount",
"result": "pass"
},
{
"check": "value_date",
"result": "pass"
},
{
"check": "sanctions_screening_precheck",
"result": "pass",
"note": "Pre-check only. Full screening runs when the payment is executed."
}
],
"request_id": "REQ-20261105-0002",
"note": "Validation passed. No payment was created, nothing was debited, no funds moved."
}
Retrieves the full record of one payment: debtor, creditor, amount, charges, value date and current status. This is the payment record; for the status history alone, use GET /v1/payments/{payment_id}/status.
(No request body. GET requests do not carry a body. The payment identification is in the URL path only: GET /v1/payments/PAY-20261105-8841)
Validation rules
Payment identification must be supplied in the path and must match an existing payment. A GET request has no body.
Caller must be authorised to view the payment, normally the debtor or an operations user. A caller without access gets 403 Forbidden, not 404.
The response always echoes X-Request-ID so the lookup can be traced.
Success response: HTTP 200 OK. 200 OK returns the stored payment record. ACSC means settlement completed on the debtor's account; it does not confirm the beneficiary was credited (that is ACCC). Charges here are the actual USD 42 deducted in transit, matching the accounting simulator.
Error response: HTTP 404 Not Found. 404 Not Found means no payment with that identification exists. The invalid request in this example asks for PAY-20261105-9999, which was never created.
{
"error_code": "NOT_FOUND",
"error_message": "No payment exists with identification PAY-20261105-9999",
"payment_id": "PAY-20261105-9999",
"request_id": "REQ-20261105-0003"
}
GET GET /v1/payments/{payment_id}/status
Returns the current status and tracking history for one payment. It shows each stage the payment has passed, in time order with the most recent stage last, and the latest status code. Use this to find out where a payment is after initiation.
(No request body. GET requests do not carry a body. The payment identification is in the URL path only: GET /v1/payments/PAY-20261105-8841/status)
Validation rules
Payment identification must be supplied in the path and must match an existing payment. A GET request has no body.
Status codes follow ISO 20022 values: ACTC, ACCP, ACSP, ACSC, ACCC, RJCT, PDNG. ACCP is the earlier acceptance and ACSP the later one; do not swap them.
Tracking history is returned in time order, with the most recent stage last.
Poll no faster than the interval the provider documents. Polling is how the outcome of an asynchronous initiation is learned; the initiation response itself never reports settlement.
Success response: HTTP 200 OK. 200 OK with the history array in time order, most recent stage last. ACSC confirms settlement on the debtor's account only; ACCC is the code that confirms the creditor's account was credited.
Error response: HTTP 404 Not Found. 404 Not Found: no payment with that identification exists. The invalid request in this example asks for PAY-20261105-9999.
{
"payment_id": "PAY-20261105-8841",
"status": "ACSC",
"status_description": "Settlement completed on the debtor's account. This does NOT confirm the beneficiary has been credited; that is ACCC.",
"uetr": "4f8a2c1e-7b3d-4e9a-8c2f-1a5d6e7f8b90",
"end_to_end_id": "INV-8841",
"amount": 25000.0,
"currency": "USD",
"value_date": "2026-11-05",
"history": [
{
"status": "ACCP",
"time": "2026-11-05T09:12:00Z",
"description": "Accepted for processing"
},
{
"status": "ACSP",
"time": "2026-11-05T09:14:00Z",
"description": "Accepted, settlement in process"
},
{
"status": "ACSC",
"time": "2026-11-05T09:20:00Z",
"description": "Settlement completed on the debtor's account"
}
],
"request_id": "REQ-20261105-0004"
}
{
"error_code": "NOT_FOUND",
"error_message": "No payment exists with identification PAY-20261105-9999",
"payment_id": "PAY-20261105-9999",
"request_id": "REQ-20261105-0004"
}
POST POST /v1/payments/{payment_id}/cancel
Requests cancellation of a payment that has not yet settled. Cancellation is asynchronous: the bank sends a camt.056 cancellation request and the outcome arrives later by status enquiry or webhook, so the immediate response can only confirm the request was accepted, never that the payment is cancelled. Cancellation is not guaranteed once the payment has been executed or settled, and after the beneficiary has been credited it needs the beneficiary's consent.
{
"reason_code": "CUST",
"reason_text": "Customer asked to stop the payment, supplier changed bank details"
}
Validation rules
An Idempotency-Key header is required: a retried cancellation with the same key replays the original response instead of sending a second camt.056.
Cancellation reason code: CUST is the customer-requested route (the customer asks their bank to stop the payment). A bank-initiated recall uses DUPL, TECH or FRAD under the EPC SCT recall procedure; those codes are a different procedure with scheme deadlines, not interchangeable labels.
Payment must be in a cancellable state, such as accepted or pending, not already settled.
Caller must be the debtor or an authorised operations user for the payment.
A cancellation request after settlement must be rejected with guidance to request a return or a recall, not silently accepted.
Success response: HTTP 202 Accepted. 202 Accepted with cancellation_status REQUESTED means the request was accepted, not that the payment is cancelled. The outcome follows later; a positive answer returns the funds via pacs.004 with reason FOCR, a negative answer arrives as camt.029.
Error response: HTTP 409 Conflict. 409 Conflict means the payment is in a state that cannot be cancelled. A settled payment is not cancelled by this endpoint; it needs a recall, and after credit that needs the beneficiary's consent.
{
"payment_id": "PAY-20261105-8841",
"cancellation_id": "CXL-20261105-1201",
"status": "PDNG",
"cancellation_status": "REQUESTED",
"message": "Cancellation requested. The bank has sent a camt.056 cancellation request; the outcome arrives later by status enquiry or webhook. The payment is not yet cancelled.",
"request_id": "REQ-20261105-0005"
}
{
"error_code": "ALREADY_SETTLED",
"error_message": "Payment has already settled and cannot be cancelled. A settled payment can only be recovered by a recall (camt.056) with the beneficiary's consent, answered by a pacs.004 return with reason FOCR or by a camt.029 refusal.",
"payment_id": "PAY-20261105-8841",
"request_id": "REQ-20261105-0005"
}
POST POST /v1/payments/{payment_id}/enquiries
Submits a payment status enquiry for a payment that was already sent but has had no confirmation. This is the API equivalent of a pacs.028 status request: it asks the bank to check what happened to the payment. The enquiry itself does not change the payment, and the answer arrives later by status update or webhook.
{
"reason": "NO_CONFIRMATION",
"detail": "No status update received within the expected window; confirm whether the payment settled."
}
Validation rules
Send an enquiry only after the payment was sent and no confirmation arrived within the expected window. It is the investigation step, not a way to hurry a payment that is still in flight.
An Idempotency-Key header is required so a retried enquiry does not open a second investigation for the same payment.
The answer to an enquiry is a status update (the equivalent of a pacs.002) or a resolution message (camt.029 for a recall investigation), delivered by status enquiry or webhook.
Success response: HTTP 202 Accepted. 202 Accepted means the enquiry was accepted for investigation. The answer comes later; an enquiry never settles, cancels or returns a payment by itself.
Error response: HTTP 404 Not Found. 404 Not Found: no payment with that identification exists, so there is nothing to enquire about. The invalid request in this example asks for PAY-20261105-9999.
{
"enquiry_id": "ENQ-20261105-3301",
"payment_id": "PAY-20261105-8841",
"status": "PDNG",
"message": "Enquiry accepted. The bank will investigate and reply by status update or webhook; the enquiry itself does not change the payment.",
"request_id": "REQ-20261105-0006"
}
{
"error_code": "NOT_FOUND",
"error_message": "No payment exists with identification PAY-20261105-9999",
"payment_id": "PAY-20261105-9999",
"request_id": "REQ-20261105-0006"
}
GET GET /v1/fx/quotes?from=GBP&to=USD&amount=19685.04
Returns a foreign exchange quote for converting one currency to another. It gives the rate, the converted amount, the fee and how long the quote stays valid. The quote in this example prices the GBP funding leg of the USD 25,000 payment: GBP 19,685.04 at 1.27 converts to USD 25,000.00, and the initiate example references this quote by its quote_id.
(No request body. GET requests do not carry a body. Parameters are in the URL query string: from=GBP, to=USD, amount=19685.04)
Validation rules
From and to currencies must be different and both must be supported for the account.
Amount must be greater than zero and within the daily foreign exchange limit.
Quote is valid only until the valid until time, after which a fresh quote is required.
Rate shown is the rate that will be applied if the payment uses the quote identification.
Success response: HTTP 200 OK. 200 OK returns the quote. The converted amount USD 25,000.00 is the amount the initiate example's payment is funded from, and the initiate request references this quote as FXQ-20261105-5510.
Error response: HTTP 422 Unprocessable Content. 422: the error matches the request that produced it. The invalid request in this example asks for GBP 50,000, above the daily FX limit of GBP 25,000.00, so no quote is issued.
{
"error_code": "AMOUNT_LIMIT_EXCEEDED",
"error_message": "Requested amount GBP 50,000.00 exceeds the daily FX limit of GBP 25,000.00 for this account",
"quote_id": null,
"request_id": "REQ-20261105-0007"
}
GET GET /v1/payments?status=ACSC&limit=20&offset=0
Lists payments, newest first, with pagination. Use it to reconcile what the bank holds against your own ledger, instead of polling one payment at a time. Responses are pages: always follow the page fields, never assume one call returns everything.
(No request body. GET requests do not carry a body. Parameters are in the URL query string: status=ACSC, limit=20, offset=0)
Validation rules
limit and offset control paging. limit has a provider maximum; asking for more returns the maximum, not an error, in this reference API.
Filter parameters such as status narrow the list. Unknown filters are ignored in this reference API, not rejected; real providers differ, so check the provider's documentation.
A 429 response must be honoured: back off for the Retry-After period. Hammering the API after a 429 is how a temporary limit becomes a longer block.
If a request times out, do not simply resend a POST. Retry with the same Idempotency-Key and X-Request-ID so the bank can return the original response instead of executing twice.
Success response: HTTP 200 OK. 200 OK returns one page and the page fields. total says how many payments match, has_more says whether another page exists; keep paging with offset until has_more is false.
Error response: HTTP 429 Too Many Requests. 429 Too Many Requests means the caller exceeded the provider's rate limit. The response carries a Retry-After of 60 seconds: wait that long, then retry. A POST must only be retried with the same Idempotency-Key, so the retry replays the original response instead of creating a second payment.
{
"error_code": "RATE_LIMITED",
"error_message": "Too many requests. Retry after 60 seconds.",
"retry_after_seconds": 60,
"request_id": "REQ-20261105-0008"
}
POST Webhook (inbound, from the bank to you)
A webhook is inbound to YOUR system: the bank POSTs a status event to the URL you registered, and your system returns 200 OK. It is not an endpoint you call on the bank's API, so there is no Send button here: the two buttons below show a sample delivery from the bank and a delivery with a bad signature. Your system must verify the signature before acting on the event.
Request headers
X-Bank-Signature: sha256=<HMAC-SHA256 of the payload, using the shared secret>
Content-Type: application/json
The event carries an event_id. Duplicate deliveries with the same event_id must be ignored by the receiver, because the bank may deliver the same event more than once.
Verify the signature before acting: recompute HMAC-SHA256 over the payload with the shared secret and compare it with the X-Bank-Signature header. Never trust an unsigned event.
Event must include payment identification, status code, UETR and event time in ISO format.
Retry timing is set by each provider; there is no universal five-second rule. This reference API assumes the bank retries failed deliveries with backoff over several hours, but a real provider's schedule is in its own documentation, so check it before relying on retries.
Return 200 promptly once the event is stored. Slow processing belongs in a queue behind the endpoint, not in the webhook handler.
Success response: HTTP 200 OK (returned by your system). Your system returns 200 OK after verifying the HMAC-SHA256 signature and storing the event. ACSC here means settlement completed on the debtor's account; it does not confirm beneficiary credit.
Error response: HTTP 401 Unauthorized (returned by your system). Your system returns 401 Unauthorized when the signature does not match, and must not act on the event. A rejected delivery may be retried by the bank.
{
"error_code": "INVALID_SIGNATURE",
"error_message": "Webhook signature does not match, update rejected",
"received": false,
"event_id": "EVT-20261105-7788"
}
Error catalogue: HTTP status is not the payment status
An HTTP status says what happened to the request. The payment’s own status (ACCP, ACSP, ACSC, ACCC, RJCT, PDNG) says what happened to the payment, and it is always reported separately, in the response body and in the status history. A 202 Accepted response never means the payment settled or the beneficiary was credited.
HTTP status
Meaning
Business code (example)
When it appears here
200 OK
Request succeeded; the response carries the result.
—
Validation, retrieve payment, retrieve status, FX quote, list. Also what your system returns for a good webhook delivery.
201 Created
A new resource was created synchronously and its location is returned.
—
Not used by this reference API: payment creation is asynchronous, so it returns 202, not 201.
202 Accepted
The request was accepted for processing. The outcome arrives later.
—
Initiate payment, request cancellation, submit an enquiry.
400 Bad Request
The request is malformed: bad JSON or a wrong field type.
INVALID_REQUEST
Schema errors. Different from a business rule failure, which is 422.
401 Unauthorized
Missing, expired or invalid credentials.
INVALID_TOKEN, INVALID_SIGNATURE (webhook)
Bad access token; a webhook whose signature does not match.
403 Forbidden
Authenticated, but not allowed to do this.
INSUFFICIENT_SCOPE
A token without the required scope, or a caller asking for someone else’s payment.
404 Not Found
No resource with that identification exists.
NOT_FOUND
Retrieve, status or cancel for an unknown payment_id.
409 Conflict
The request conflicts with the current state.
ALREADY_SETTLED, IDEMPOTENCY_KEY_REUSED
Cancelling a settled payment; reusing an idempotency key with a different body.
422 Unprocessable Content
Well formed, but a business rule failed.
AC01 (invalid account), AMOUNT_LIMIT_EXCEEDED
Invalid creditor account, an FX amount over the daily limit.
429 Too Many Requests
The caller exceeded the rate limit.
RATE_LIMITED
Too many calls. Honour the Retry-After value before retrying.
Payment status codes (ISO 20022) are separate from HTTP statuses. ACTC: technical validation passed. ACCP: accepted for processing (earlier acceptance). ACSP: accepted, settlement in process (later acceptance). ACSC: settlement completed on the debtor’s account. ACCC: settlement completed on the creditor’s account, the only code that confirms the beneficiary was credited. RJCT: rejected. PDNG: pending. “ACCEPTED” and “CANCELLED” are not ISO 20022 status codes and are never used here.