Branch on code rather than HTTP status; several counterintuitive cases all return 400.
Error Contract
Errors use a common envelope. Branch on code, not message: Accept-Language changes message but not code.
{
"type": "invalid_request_error",
"code": "insufficient_balance",
"message": "The member's available balance is not enough.",
"request_id": "01J8X4K9…"
}
Every response includes X-Request-Id, also repeated in the error body. Include it first in your support request.
type → HTTP → Recommended Action
| type | HTTP | Meaning | Action |
|---|---|---|---|
invalid_request_error | 400 | Invalid input or unmet business prerequisite | Correct and resend with a new idempotency key |
authentication_error | 401 | Invalid / expired token or signature mismatch | Obtain a new token; inspect signing-string construction if failures persist |
permission_error | 403 | Insufficient scope / IP not allowed / merchant disabled | Update portal configuration; do not retry |
not_found | 404 | Object does not exist or belongs to another merchant | Do not retry |
idempotency_error | 409 | Same key with a different body / original request still processing | See Idempotency |
rate_limit_error | 429 | Quota exceeded | Exponential backoff with random jitter |
api_error | 500 | Internal error on our side | Retry with the same key; report persistent failures with request_id |
upstream_error | 502 | Upstream unavailable | Retry with the same key |
upstream_timeout | 504 | Unknown outcome; execution may have occurred | Retry with the same key or query the order. Never use a new key. |
Common Codes
| code | Meaning |
|---|---|
member_not_found | Member does not exist, or belongs to another merchant, or is disabled; all three return the same response |
member_context_required | A member-scoped endpoint is missing x-on-behalf-of |
kyc_required | The required KYC level is not met. The response does not include the level. Fetch business-specific thresholds from GET /v1/kyc/requirements, which lists required_level by business; do not infer it from the error |
insufficient_balance | Insufficient member available balance |
merchant_insufficient_funds | Your funding account has insufficient funds; top up in the merchant portal |
product_not_available | Product not authorized for this merchant, withdrawn from sale, or eligibility unmet |
limit_exceeded | Includes limit_type (single/daily/monthly) and limit_scope (member/merchant) |
request_rejected | The only public risk-rejection code. No reason, rule name, or score is provided |
step_up_required | Step-up authentication required; includes hosted_url or challenge_id |
order_not_cancellable | The order has reached an irreversible stage |
asset_not_allowed | The asset is not in your allowlist |
environment_mismatch | Sandbox credentials used on a live domain, or vice versa |
Two Things We Deliberately Do Not Disclose
Risk-control criteria never leave our system. Match reasons, rule names, thresholds, and scores are never returned. A risk rejection returns only request_rejected. Submit manual appeals through merchant-portal support tickets.
Errors do not disclose information across merchants. Responses such as email already registered or document number already used for card issuance would reveal the existence of another merchant's member. They therefore use a single indistinguishable response. Creating an account with an email that belongs elsewhere consequently does not return 409.