Skip to content
LogoLogo

Error Responses

BLOX APIs use standard HTTP status codes and return JSON error bodies.

Error Format

Every error on /v1 and /checkout has the same three fields:

{
  "code": "UNAUTHORIZED",
  "message": "API key is required",
  "requestId": "9f21ab04-6c3e-4d90-b1a7-8e5f2c0d4416"
}
FieldTypeDescription
codestringStable SCREAMING_SNAKE_CASE identifier. This is the contract — branch on it
messagestringFor humans. May be reworded at any time; never parse it
requestIdstring (uuid)Identifies the request in BLOX logs. Quote it when reporting a 5xx

Adding a code is not a breaking change. Changing or removing one is.

Codes

StatusCodes
400INVALID_REQUEST, VALIDATION_FAILED, CONFLICT, INSUFFICIENT_BALANCE, LIMIT_EXCEEDED, UNKNOWN_BENEFICIARY
401UNAUTHORIZED
403FORBIDDEN, FEATURE_DISABLED, LIMIT_EXCEEDED, ACCOUNT_TERMINATED, ACCOUNT_FROZEN, ACCOUNT_SUSPENDED, ACCOUNT_PENDING_VERIFICATION, ACCOUNT_NOT_ACTIVE
404NOT_FOUND
422VALIDATION_FAILED
429RATE_LIMITED
503SERVICE_UNAVAILABLE
5xxINTERNAL_ERROR

LIMIT_EXCEEDED comes back as 400 when an amount would breach a daily or monthly limit, and as 403 when a count is full — beneficiaries on the account, or allowed senders on a trigger address.

A 5xx never explains itself: the body is INTERNAL_ERROR with a fixed message and the detail goes to our logs under requestId. The exception is a deliberate 503 SERVICE_UNAVAILABLE, which means "retry later, nothing happened" and keeps its own message.


Authentication errors

401 Unauthorized

Everything below returns code: "UNAUTHORIZED". The message narrows it down while you are debugging, but it is prose — never branch on it.

CauseFix
API key missing, unknown, or inactiveSend blox-api-key (or Authorization: Bearer) with an active key
A write is missing Signature, Signature-Input, or Content-DigestSign every POST / PUT / PATCH / DELETE — see Request Signing
Signature or digest does not matchRe-sign over the exact bytes you send. Serializing the body twice is the usual cause
Signature algorithm differs from your registered signerSign with the key you registered
Clock driftKeep created inside the window below, and keep your server clock synchronized
Signature reusedSign each request fresh — a signature is accepted once
{
  "code": "UNAUTHORIZED",
  "message": "…",
  "requestId": "…"
}

Freshness windows:

Surfacecreated must be withinReuse
/v1 (Wallet, Onramp, Checkout)30s past, 5s futureRejected — sign each request fresh
Payout routes (/v1/payouts*, /v1/payout/prefund/balance)±300sRejected — sign each request fresh

403 Forbidden

codeWhen
ACCOUNT_TERMINATEDAccount terminated
ACCOUNT_FROZENAccount frozen
ACCOUNT_PENDING_VERIFICATIONAccount pending verification
ACCOUNT_SUSPENDEDAccount suspended
ACCOUNT_NOT_ACTIVEAccount inactive or closed
FEATURE_DISABLEDRequired product feature not enabled for the account

ACCOUNT_TERMINATED blocks every route, reads included. The others block money-moving routes only, so reads keep working — except ACCOUNT_SUSPENDED, which still permits money out but not money in.

Contact the BLOX team to enable API products (e.g. payout) for your account.


Other common status codes

StatusMeaning
202Not an error — the first request with your Idempotency-Key is still running. Retry with the same key (Idempotency)
400Validation or business-rule failure
404Resource not found
422An Idempotency-Key was reused with a different body
429Rate limit exceeded (see Rate Limits)
500Unexpected server error

Schema validation failures come back as VALIDATION_FAILED, with the offending fields joined into message.


Handling errors

For 429, wait and retry with exponential backoff. Do not rely on rate-limit response headers.


Testing

# Missing / invalid API key
curl https://api.sandbox.blox.my/v1/health \
  -H "blox-api-key: invalid_key"
 
# POST without signature headers (expect 401)
curl -X POST https://api.sandbox.blox.my/v1/payouts/beneficiaries \
  -H "blox-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"TEST","bankCode":"MBBEMYKL","accountNumber":"1234567890"}'

Support