Errors
For engineers writing error handling against the Quant Flow Platform. The error envelope, what each HTTP status means, the documented errors per area with the correct client action, and how a failed operation is reported after acceptance.
Who this is for
Integration engineers building error handling and operational alerting, and reviewers checking that every failure has a defined client response.
There are two kinds of failure:
- A refused request. The HTTP call fails with a
4xxor5xxand the error envelope below. Nothing was accepted. - A failed operation. The HTTP call succeeded (
202), and the operation later ended inFAILEDorCANCELLED. The failure is reported on the operation and by webhook. No HTTP error is returned. See Failed operations.
The error envelope
Every refused request returns a JSON body with four fields:
| Field | Type | Meaning |
|---|---|---|
timestamp | string | When the error was produced, in UTC. |
status | integer | The HTTP status code, repeated in the body. |
error | string | The HTTP reason phrase, for example Bad Request. |
message | string | A description for a human reader. |
{
"timestamp": "2026-08-03T09:24:11.482Z",
"status": 400,
"error": "Bad Request",
"message": "The 'Idempotency-Key' header is required."
}Branch on the HTTP status and, where present, on code or documented extra fields. Never parse message. Its wording is for people and can change.
Variations to handle
| Variation | Where | What to do |
|---|---|---|
timestamp format | Most endpoints use ISO 8601 with a trailing Z. Webhook, delivery history and some identity errors use yyyy-MM-dd HH:mm:ss.SSS in UTC, with no T and no offset. | PayScript uses ISO 8601 UTC with fixed milliseconds. Treat timestamp as a string. Parse it only if you need it, and accept both documented formats. |
code and details | Policy, template and approval endpoints. code is VALIDATION_FAILED on some 400 responses and IDEMPOTENCY_CONFLICT on a 409. details is an array of field-level messages on some VALIDATION_FAILED responses. | Use code when present. Show details to the operator. |
| Conflict body | POST /api/customers and POST /api/accounts. A 409 for an existing reference adds customerRef or accountRef, and existingStatus. | Use existingStatus to reconcile. See Idempotency and retries. |
| No token on policy endpoints | A request that carries a token that cannot be parsed, or that lacks sub or tenant_id, returns a default error body with a path and no useful message. | Branch on the 401 status only. |
500 | Unexpected service failures commonly use System error occurred. Handle the actual returned status/body; gateways and individual endpoints can return a different envelope. | Retry with backoff. Quote the timestamp to support. |
There is no platform-wide machine-readable error code. Branch on HTTP status and the endpoint's documented structured fields. Preserve unfamiliar fields for diagnostics; do not require one universal error-code field.
Record the request time, endpoint, HTTP status and any returned correlation headers. Also retain transaction or request identifiers from successful submissions; the contract does not guarantee a correlation field on every error.
Status codes and client action
| Status | Meaning | Client action |
|---|---|---|
400 Bad Request | The request is invalid: a missing or malformed field, a missing Idempotency-Key, a disallowed state change on some endpoints, or a filter or paging value outside its range. The message names the field. | Fix the request. Do not retry unchanged. |
401 Unauthorized | No token, or a token that failed verification. On sign-in, wrong credentials. | Use the user refresh or machine-client token-acquisition flow once, then escalate persistent rejection. See Authentication. |
403 Forbidden | The caller is authenticated but lacks the permission, a path names another organisation, or separation of duties blocked the action. | Do not retry. Check the caller's role. |
404 Not Found | The resource does not exist in the caller's organisation. A resource in another organisation also returns 404. | Do not retry, except for the lock case on POST /api/transactions. |
409 Conflict | Either the same Idempotency-Key is still being processed, or the resource's current state does not allow the request. | In-flight key: wait and retry with the same key. State conflict: read the resource and reconcile. |
415 Unsupported Media Type | GET /api/users/{id} without Content-Type: application/json. | Send the header. |
422 Unprocessable Content | The request is well-formed but cannot be applied: an idempotency key reused with a different payload, a state transition that is not allowed, a parameter that does not fit its declaration, or an account with no wallet yet. | Do not retry unchanged. |
429 Too Many Requests | The organisation's webhook replay budget is exhausted. | Retry later. See Rate limits. |
500 Internal Server Error | An unexpected failure. | Use bounded backoff for reads or supported idempotent writes, preserving the original key/body. Reconcile other writes before retrying; a 500 does not prove rollback. |
503 Service Unavailable | The LEI registry could not be reached to verify an institution. | Retry with backoff. |
Error catalogue by area
Each table lists the errors the API Reference documents for that area, beyond common 401 and 500 responses. Bootstrap authority and service enforcement vary as described in Authentication; use the endpoint-specific reference.
Sign-in
| Endpoint | Status | Cause | Client action |
|---|---|---|---|
POST /api/auth/login, new-password, activate | 400 | Body failed validation (Invalid Request parameter. {field} or Malformed JSON request). | Fix the request. |
| Same | 401 | Wrong credentials, no organisation, or user not ACTIVE. Always Invalid credentials. | Check credentials and user status. Do not loop. |
POST /api/auth/refresh | 401 | Refresh token expired or revoked, or account disabled. | Sign in again. |
Organisation access: team members, roles and approval groups
| Status | Cause | Client action |
|---|---|---|
400 | A path id is not a UUID, an unknown filter value, a name that is blank or too long, an unknown permission name (Unknown permissions: {names}.), editing a platform role, removing or changing yourself or the organisation owner. | Fix the request. |
401 | No usable token, or the caller's own status is not ACTIVE (Your access has been revoked.). | Sign in again, or contact your administrator. |
403 | The path organisation is not the caller's own (Invalid tenant id), the caller lacks the permission (for example member:remove, role:create, group:manage), or the change would grant a permission the caller does not hold. | Do not retry. Use a user with the permission. |
404 | No such user, role, group or organisation. | Do not retry. |
409 | Email already registered, role name reserved or in use, group key in use, or the approval group is deactivated. | Reconcile with the existing record. |
500 | A sort parameter named a property that does not exist. | Use only the documented sort properties. See Pagination and filtering. |
Customers and accounts
| Endpoint | Status | Cause | Client action |
|---|---|---|---|
POST /api/customers | 400 | Missing or invalid field, or Idempotency-Key missing, blank or over 255 characters. | Fix the request. |
409 | customerRef already registered (body has existingStatus), or the same key is still running (has Retry-After). | Reconcile, or wait and retry with the same key. | |
422 | Key reused with a different payload. | Use a new key for a new intent. | |
PATCH /api/customers/{customerRef} | 400 | status missing or unknown, or key invalid. | Fix the request. |
404 | No such customer. | Do not retry. | |
409 | Same key still running (Retry-After). | Wait and retry with the same key. | |
422 | Key reused with a different payload. | New key. | |
POST /api/accounts | 400 | Missing or invalid field, or key invalid. | Fix the request. |
404 | Customer does not exist, or the asset has no active contract. | Register the customer or contract first. | |
409 | accountRef already registered and not FAILED (body has existingStatus), or the same key is still running (Retry-After). | Reconcile, or wait and retry. | |
422 | Customer is INACTIVE, or key reused with a different payload. | Reactivate the customer, or use a new key. | |
PATCH /api/accounts/{accountRef} | 400 | status missing or not ACTIVE/INACTIVE (reported against settableStatus), or key invalid. | Fix the request. |
404 | No such account. | Do not retry. | |
409 | Same key still running (Retry-After). | Wait and retry. | |
422 | Transition not allowed from the current status (including reactivating an account whose customer is INACTIVE), or key reused. | Read the account and correct. | |
GET lists | 400 | Invalid paging or unknown status. | Fix the request. |
GET by reference | 404 | No such customer or account. | Do not retry. |
Token contracts
| Endpoint | Status | Cause | Client action |
|---|---|---|---|
POST /api/contracts | 400 | Missing or invalid field. | Fix the request. |
PATCH /api/contracts/{assetId} | 400 | Missing or invalid field. | Fix the request. |
404 | No contract for that assetId. | Register it first. | |
GET /api/contracts | 400 | Invalid paging or unknown status. | Fix the request. |
Token operations and balances
| Endpoint | Status | Cause | Client action |
|---|---|---|---|
POST /api/transactions | 400 | Key missing, blank or over 255 characters; counterpartyRef on a type other than TRANSFER or LOCK, or missing on a transfer; assetId not a recognised ISO 4217 code; amount not positive or finer than the currency's minor unit; no ACTIVE contract for the asset; a named account missing, holding another asset or not ACTIVE; a non-scalar params value; params.lockId on a LOCK; a RELEASE or CANCEL with no valid lockId, a lock that is not ACTIVE, a completion already in flight, or with assetId, accountRef or amount supplied. | Fix the request. |
404 | No lock yet for the params.lockId on a RELEASE or CANCEL. | Retry. The lock becomes visible shortly after its LOCK is submitted. | |
GET /api/transactions | 400 | Negative page, size outside 1 to 100, or unknown state or txType. | Fix the request. |
GET /api/transactions/breaks | 400 | from not before to, negative page, or size outside 1 to 100. | Fix the request. |
GET /api/transactions/{transactionId} | 400 | Path id is not a UUID. | Fix the request. |
404 | No such operation. | Do not retry. | |
GET /api/accounts/{accountRef}/balances | 404 | No such account, or its asset has no ACTIVE contract. | Do not retry. |
422 | The account has not finished provisioning (PENDING or FAILED) and has no wallet address. | Wait for account.provisioned, or re-register a FAILED account. | |
500 | The balance could not be read. A failed read always returns an error. It is never reported as a zero balance. | Retry with backoff. |
Balances are not checked at submission. An operation for more than the account holds is accepted with 202 and fails during settlement. See Failed operations. The ledger transaction reverts. The example in the breaks queue reference shows such a case as CHAIN_FAILED, with the revert reason balance below amount in its detail. Read the terminal operation and its break record for the actual codes. Do not derive a stable reasonCode from the human-readable revert message.
Policies, templates and approvals
| Endpoint | Status | Cause | Client action |
|---|---|---|---|
| Policy and template writes | 400 | Body or parameters failed validation. | Fix the request. Read code and details. |
POST /api/policies | 404 | No template with this key is visible to the organisation. | Use a visible template key. |
POST /api/policies/{policyId}/activate | 409 | Another policy already holds this rule type, action and template key. | Archive or change the other policy first. |
PUT /api/policies/{policyId}/current-version | 404 | No such policy or version number. | Do not retry. |
POST /api/templates | 409 | The key already exists. | Choose another key. |
| Template updates and deprecation | 404 | No template with this key in scope. | Do not retry. |
GET /api/approvals/tasks | 400 | Unknown task state. | Fix the filter. |
GET /api/approvals/requests | 400 | Unknown status, unparseable instant, or inverted resolvedAt range. | Fix the filter. |
POST /api/approvals/requests | 400 | Body failed validation, an approver expression could not be resolved, or an approver is the caller. code: VALIDATION_FAILED. | Fix the request. |
POST /api/approvals/requests/{requestId}/cancel | 409 | The request is not PENDING. | Read the request. |
POST /api/approvals/tasks/{taskId}/approve, reject | 403 | The caller is not an eligible approver, or is the initiator of the request. | Route the task to another approver. This is separation of duties working as designed. |
409 | The task was already acted on, or an earlier step is still pending. | Read the request and wait for the earlier step. | |
POST /api/approvals/tasks/{taskId}/reassign | 400 | The expression could not be resolved, or resolved only to the initiator. | Choose a different approver. |
409 | The request is not PENDING, the task was acted on, or the approver set is unchanged. | Read the request. | |
| Any by id | 404 | No such resource in your organisation. | Do not retry. |
PayScript scripts and automations
| Endpoint | Status | Cause | Client action |
|---|---|---|---|
POST /api/scripts | 400 | Validation or compilation failure, duplicate name, a missing dependency, or key missing. | Fix the script. |
409 | Same key still being processed. | Wait and retry with the same key. | |
PATCH /api/scripts/{scriptId} | 400 | No field provided, invalid name or description, duplicate name, unsupported status, or key missing. | Fix the request. |
409 | Unpublishing a script another published script depends on; code sent for an OUTDATED or DEPRECATED script; other state conflicts; or key in flight. | Resolve the dependency, or wait and retry. | |
DELETE /api/scripts/{scriptId} | 409 | The script is PUBLISHED, has ACTIVE automations, or other scripts depend on it. | Unpublish, deactivate automations or remove dependants first. |
GET /api/scripts/{scriptId}/download | 409 | The script is deleted. | Do not retry. |
POST /api/scripts/{scriptId}/clone | 400 | Override failed validation, duplicate name, or the source failed compilation. | Fix the request. |
409 | Source deleted, or key in flight. | Do not retry, or wait and retry. | |
POST /api/automations | 400 | A field failed validation, or key missing. | Fix the request. |
404 | No such script. | Do not retry. | |
409 | The script is not PUBLISHED, or key in flight. | Publish the script, or wait and retry. | |
PATCH /api/automations/{automationId}/status | 400 | Status missing, transition not allowed (including to the current status, or away from DELETED), or key missing. | Read the automation. |
409 | Key in flight. | Wait and retry. | |
POST /api/automations/{automationId}/executions | 400 | Key missing, an override's key blank or value/type missing, or id not a UUID. | Fix the request. |
409 | The automation is not ACTIVE or not ON_DEMAND, or key in flight. | Activate it, or wait and retry. | |
422 | An override does not fit the script's declared parameters. | Fix the override. | |
500 | The run could not be requested. No partial run is recorded and the key is released. | Retry with the same key. | |
| Any by id | 404 | No such script or automation, or it was deleted. | Do not retry. |
Webhooks and delivery history
| Endpoint | Status | Cause | Client action |
|---|---|---|---|
POST /api/webhooks | 400 | URL not https, host does not resolve to a public address, unknown signatureScheme, no eventTypes, unknown event type, or key missing. | Fix the request. |
409 | The organisation already has this URL registered, or key in flight. | Use the existing registration, or wait and retry. | |
PATCH /api/webhooks/{id}, secret/rotate, test | 400 | Invalid body or key missing. | Fix the request. |
404 | No such registration. On rotate, also a JWS_ES256 registration, which has no secret. | Do not retry. | |
409 | Key in flight. On test, also an INACTIVE registration. | Wait and retry, or activate it first. | |
GET /api/webhooks, GET /api/webhook-events | 400 | limit outside 1 to 100, a cursor this endpoint did not issue, a cursor replayed with different filters, or from not before to. | Restart paging from the first page. |
GET /api/webhook-events/{id} | 404 | No such event. | Do not retry. |
POST /api/webhook-events/{id}/redeliver | 404 | No such event, no delivery of it to the named destination, or the destination was deleted. | Do not retry. |
409 | The latest delivery is still queued, the destination is INACTIVE, the same delivery is already being replayed, or key in flight. | Wait for the delivery to settle, or activate the destination. | |
429 | Replay budget exhausted. Nothing was queued. | Retry later. | |
POST /api/webhook-events/redeliver | 400 | No filter supplied. | Supply at least one filter. |
409 | Key in flight. | Wait and retry. | |
429 | Replay budget exhausted. Nothing was queued. | Retry later. |
Audit events
| Endpoint | Status | Cause | Client action |
|---|---|---|---|
GET /api/audit/events, export | 400 | Inverted occurredAt range, a sort property outside the allowed list, a query over 128 characters, or a date that does not parse. | Fix the filter. |
GET /api/audit/events/{eventId} | 400 | Id is not a UUID. | Fix the request. |
404 | No such event in your organisation. | Do not retry. |
Failed operations
A token operation that was accepted can still fail. Read it with GET /api/transactions/{transactionId} or subscribe to transaction.failed, transaction.rejected and transaction.cancelled. CONFIRMED, FAILED and CANCELLED are terminal.
An operation that ended badly carries two fields:
| Field | Meaning |
|---|---|
failureCode | Classifies the ending. Absent, not null, while the operation is in flight and on one that confirmed. On a CANCELLED operation it may carry free text instead of a code, so do not validate it against the list below in that case. |
reasonCode | The more specific reason, such as a revert reason, a signing refusal or a submission error, for example EXECUTION_REVERTED. Absent when none was reported. |
failureCode | Meaning | Client action |
|---|---|---|
VALIDATION_REJECTED | Validation or policy refused the operation. | Do not resubmit unchanged. Review the reason with operations. |
APPROVAL_REJECTED | An approver refused it. | Resubmit only after a business decision, as a new operation with a new key. |
SIGNING_REJECTED | The custody provider refused to sign. It applies its own policy before signing, independently of the platform. Nothing reached the ledger. | Check the custody provider's record of the request against its policy. Do not resubmit unchanged. Escalate through the custody support route agreed for your deployment, with the operation and signing-request identifiers. |
CHAIN_FAILED | Settlement failed in a way not worth retrying. | Read reasonCode, correct the cause, then submit a new operation with a new key. |
RETRY_EXHAUSTED | The platform retried a temporary failure of a system the operation depends on, and ran out of attempts. | Reconcile the terminal operation and break evidence before a new business-authorized instruction. If a hash exists, verify the ledger outcome first; retry exhaustion alone does not prove that a broadcast transaction cannot settle. |
UNPROCESSABLE | Something the operation depends on is missing, such as an active contract or account, or a lock expiry that was in the past or invalid. | Fix the dependency, then submit a new operation with a new key. |
POLICY_EXPIRED | The policy decision was not made in time. | Resubmit as a new operation if still required. |
POLICY_TIMEOUT | No policy decision arrived within the time allowed. | Resubmit as a new operation if still required. |
Resubmit with a new key. Sending the same Idempotency-Key again returns the failed operation. A new attempt is a new business intent and needs a new key.
A FAILED operation moved no value. If txHash is absent, it never reached the ledger. If txHash and blockNumber are both present, its ledger transaction was included and reverted. Handle failures sets out the full response for each case.
Treat reasonCode as service-specific evidence, not a closed platform-wide enum. Store unfamiliar values and route them for investigation rather than rejecting the operation record.
A failed operation may also appear in the outstanding breaks list, GET /api/transactions/breaks. See Reconcile against your core banking.
Webhook delivery failures
When the platform cannot deliver a webhook to your endpoint, the delivery history records why:
failureClass | Meaning |
|---|---|
UNREACHABLE | The destination could not be connected to. |
TIMEOUT | The destination accepted the connection but did not answer in time. |
REJECTED | The destination answered with a status that is not a success. |
See Webhooks.
Worked example
A transfer with an amount finer than sterling's minor unit is refused at submission.
Illustrative example built from the schema.
POST /api/transactions HTTP/1.1
Host: api.qfp.quantnetwork.net
Authorization: Bearer eyJraWQiOiJ...
API-Version: 1.0.0
Content-Type: application/json
Idempotency-Key: 1b9e6d52-0c41-4b7a-a3f2-5e8d9c0f7a16
{
"txType": "TRANSFER",
"assetId": "GBP",
"accountRef": "ACC-000184-GBP",
"counterpartyRef": "ACC-000207-GBP",
"amount": "10.505"
}HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"timestamp": "2026-09-02T11:12:03.884Z",
"status": 400,
"error": "Bad Request",
"message": "Amount '10.505' has 3 decimal places, while GBP allows only 2."
}An approver who initiated the request tries to approve it:
POST /api/approvals/tasks/7d1e4c93-2a68-4f51-b0c7-3e95a4d81f26/approve HTTP/1.1
Host: api.qfp.quantnetwork.net
Authorization: Bearer <initiator access token>
API-Version: 1.0.0
Content-Type: application/json
{}HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"timestamp": "2026-08-18T09:00:00.000Z",
"status": 403,
"error": "Forbidden",
"message": "You are not an eligible approver for this task."
}View in API Reference
- Submit a token operation ·
POST- Approve a task ·
POST
Errors and edge cases
- Two outcomes for one condition. An asset with no active contract is a
400onPOST /api/transactionsand a404onPOST /api/accounts. 404for another organisation's resource. A resource in another organisation returns404. This applies to tenant-scoped resources; shared catalogues and the unscoped user-by-ID lookup have distinct rules.sorterrors differ. An unknown sort property is a500on identity lists and a400on audit search.- A zero balance is never an error substitute. A balance read that fails returns
500.
API Reference
Open an endpoint for request fields, response examples and the interactive API console.
Related pages
Updated 2 days ago

