Error catalog

The exhaustive error-code catalog, grouped by category, each with its error type, HTTP status, and exactly when it fires.

This is the complete catalog of code values the public API (api.acute.network/v1) can return. Each code is paired with the ApiError.type it surfaces under, the HTTP status, and the condition that triggers it. For the envelope and how to handle these, see the Errors guide.

Branch on code; the type groups it

type is the broad category for the failure (e.g. every 422 is unprocessable_error, every 409 is conflict_error). The granular code is the stable thing to branch on; it names the specific failure.

HTTP statuserror.type
400validation_error
401authentication_error
403authorization_error
404not_found_error
409conflict_error
422unprocessable_error
429rate_limit_error
500internal_error
502 / 503 / 504internal_error

Bearer acuinf_{test|live}_…. A bad or missing key never reaches a handler.

codeTypeStatusWhen
API_KEY_MISSINGauthentication_error401No Authorization: Bearer header on a non-public route.
API_KEY_INVALIDauthentication_error401The key doesn't resolve, is revoked, or is malformed.
API_KEY_ENVIRONMENT_MISMATCHauthentication_error401The key's environment doesn't match this host (e.g. a live key sent to the sandbox host, or a test key sent to the live host).
API_KEY_SCOPE_FORBIDDENauthorization_error403The key is valid but lacks the required scope. details.requiredScopes / providedScopes are populated.

The scope→endpoint map: wallet, payment, transfer, payout. See Authentication.

The six money POSTs require an Idempotency-Key. See Idempotency.

codeTypeStatusWhen
IDEMPOTENCY_KEY_MISSINGvalidation_error400A money POST was sent without an Idempotency-Key header.
IDEMPOTENCY_KEY_REUSEDconflict_error409The key was already used with a different request body.
IDEMPOTENCY_IN_PROGRESSconflict_error409A request with this key is still running. Back off and retry the same key.

codeTypeStatusWhen
VALIDATION_FAILEDvalidation_error400The body/query/params failed validation. details.fields[] lists each bad field.
INVALID_CURSORvalidation_error400The ?cursor= token is malformed or tampered. See Pagination.

codeTypeStatusWhen
WALLET_NOT_FOUNDnot_found_error404No wallet matches the :id reference (under your org/environment).
WALLET_EMAIL_TAKENconflict_error409An end-user wallet already exists for that email in this org.
WALLET_KYC_REQUIREDunprocessable_error422The action (fund/withdraw/transfer/balance) needs tier1 KYC, but the wallet is none. Submit KYC.
WALLET_TIER1_LIMIT_EXCEEDEDunprocessable_error422The move would breach the tier-1 held-balance or per-transaction cap.
WALLET_INSUFFICIENT_FUNDSunprocessable_error422The source wallet's balance can't cover amount + fees.

codeTypeStatusWhen
TRANSFER_SELFunprocessable_error422Source and destination wallet are the same.
TRANSFER_NOT_FOUNDnot_found_error404No transfer matches the reference.

(WALLET_INSUFFICIENT_FUNDS also applies to transfers; the sender is debited amount + fee.)

codeTypeStatusWhen
WITHDRAWAL_NOT_FOUNDnot_found_error404No withdrawal matches the reference.
WITHDRAWAL_NAME_VERIFICATION_FAILEDunprocessable_error422The destination account name couldn't be verified at quote time.

A withdrawal is created 201 then resolves asynchronously: a later failure on the rail surfaces via the withdrawal.failed webhook (and failureReason), not a synchronous error. The debited funds are restored. See Withdrawals.

codeTypeStatusWhen
PAYMENT_NOT_FOUNDnot_found_error404No payment matches the reference.
PAYMENT_ORG_NOT_PROVISIONEDunprocessable_error422The org's deposit account isn't provisioned yet (collections can't be created). Common in fresh test orgs before provisioning completes.
PAYMENT_TARGET_WALLET_NOT_FOUNDnot_found_error404The targetWalletId doesn't resolve to a wallet in this org.
PAYMENT_NOT_PAYABLEunprocessable_error422The payment can't be settled in its current status (already settled/expired/failed, or not a virtual_wallet payment).

codeTypeStatusWhen
PAYOUT_NOT_FOUNDnot_found_error404No payout matches the reference.
PAYOUT_EMPTYvalidation_error / unprocessable_error400 / 422The batch has no items.

A payout returns 202 Accepted (async) and processes item-by-item; partial failures surface per item and via payout.partially_completed. See Payouts.

codeTypeStatusWhen
REFUND_NOT_FOUNDnot_found_error404No refund matches the reference.
REFUND_PAYMENT_NOT_SETTLEDunprocessable_error422The payment isn't in a refundable (settled / partially_refunded) state.
REFUND_AMOUNT_EXCEEDSunprocessable_error422The refund amount exceeds the settled, not-yet-refunded balance.

These surface on the webhook-management surface (registering/redelivering), and the inbound-verification code applies when you verify a delivered signature.

codeTypeStatusWhen
WEBHOOK_ENDPOINT_NOT_FOUNDnot_found_error404No active endpoint configured for this org/environment (e.g. on a redelivery request).
WEBHOOK_EVENT_NOT_FOUNDnot_found_error404No webhook event matches the reference (e.g. redelivering an unknown event).
WEBHOOK_SIGNATURE_INVALIDauthentication_error / unprocessable_error401 / 422A signature failed verification.

codeTypeStatusWhen
RATE_LIMIT_EXCEEDEDrate_limit_error429More than 600 requests/minute for this API key (fixed window).

The response includes a Retry-After header and details:

429: rate_limit_error
json
{
  "success": false,
  "statusCode": 429,
  "error": {
    "type": "rate_limit_error",
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded for this API key",
    "details": { "limit": 600, "remaining": 0, "resetAt": "2026-06-24T09:42:00.000Z", "retryAfterSeconds": 37 }
  },
  "meta": { "requestId": "req_1a3c5e7b9d2f4a6c8b0e1d33" }
}

These are platform-level codes. The ledger codes indicate an internal invariant was protected (a money move was refused rather than applied wrongly); the system codes are the generic fallbacks.

codeTypeStatusWhen
LEDGER_TRANSACTION_UNBALANCEDunprocessable_error / internal_error422 / 500A posting's legs didn't sum to zero; refused rather than applied wrongly.
LEDGER_INVALID_ENTRYunprocessable_error / internal_error422 / 500A malformed ledger entry was rejected.
LEDGER_ACCOUNT_NOT_FOUNDinternal_error500A referenced ledger account doesn't exist.
LEDGER_TRANSACTION_NOT_FOUNDnot_found_error404A referenced ledger transaction doesn't exist.
LEDGER_TRANSACTION_ALREADY_REVERSEDconflict_error409A ledger transaction was already reversed.
UPSTREAM_ERRORinternal_error (upstream_unavailable)502 / 503 / 504An upstream provider errored. details.retryable is true.
INTERNAL_ERRORinternal_error (internal)500An unexpected error on Acute's side. The real message stays in our logs; quote the requestId. details.retryable is false.

When you hit a 5xx on a money POST

Retry with the same Idempotency-Key. A 500/503 is ambiguous about whether money moved: the replay returns the original outcome instead of double-applying.

Every error carries meta.requestId (also the X-Request-Id header), formatted req_<24 hex>. It pins the exact request in our logs. Include it in any support request and we can trace the call end-to-end.