Skip to content
UnknownPay
ไทย
Core Concepts

Error envelope & codes

Every error uses one JSON envelope — branch on error.code, never the message — plus the consolidated cross-cutting error reference

Every error the system returns uses the same JSON structure — a merchant can branch logic on the error.code field (machine-readable, stable), with no need to parse the message text.

1. JSON structure

{
  "error": {
    "code": "INVALID_AMOUNT",
    "message": "amount must have at most 2 decimal places",
    "request_id": "0H1K2L3M4N5P6Q7R8S9T"
  }
}

Fields in the envelope:

fieldtypedescription
error.codestringA stable error code for branch logic
error.messagestringA safe human-readable message (no internal/secret data)
error.request_idstringA request id to quote when reporting an issue to the team
error.detailsobject(present in some cases) extra safe key-value data — dropped if absent
For server-level errors (HTTP 5xx) the system will not reveal internal detailmessage will be a generic string (e.g. "internal error") while the real cause is logged on the system side. Use the request_id so the team can trace it.

2. HTTP status + error code table the merchant must handle

HTTPcodeOccurs whenWhat the merchant should do
400IDEMPOTENCY_KEY_REQUIREDA money POST is called without an Idempotency-KeyAdd the header and resend
400BAD_REQUESTA generally malformed requestFix the request
401UNAUTHORIZEDCredential invalid/expired/bad signatureCheck the API key + signing
403FORBIDDENNo permission / out of allowed scope (e.g. IP not in the allowlist)Check permission/allowlist
404NOT_FOUNDResource not found, or not the caller's (existence not revealed)Check the referenced id
405METHOD_NOT_ALLOWEDWrong HTTP method for the pathCheck the method
409IDEMPOTENCY_IN_PROGRESSA record with the same key is currently processingWait and retry
413PAYLOAD_TOO_LARGEBody exceeds the limitReduce the body size
422INVALID_AMOUNTMalformed money field (negative/>2dp/scientific/out-of-range/empty)Fix the amount per the rules in Money Format
422INVALID_CURRENCYCurrency is not THBSend "THB" only
422VALIDATIONRequest fails general validation (e.g. body is not valid JSON)Fix the request
422IDEMPOTENCY_KEY_MISMATCHReused the same Idempotency-Key with a different bodyUse a new key, or send the same body
500INTERNALA system-side errorRetry later + keep the request_id to report to the team
Pending confirmation — domain-level error codes (e.g. pool/limit/state) exist per endpoint — collect the deposit/withdrawal-specific codes into each endpoint's docs; this table only covers cross-cutting codes (foundation + money + idempotency + HTTP mapping) confirmed from the code
Business-specific codes (e.g. pool out of funds, over a limit, a state that does not allow cancel) are additional codes defined per endpoint — a merchant should branch on error.code in a default-safe way: treat any unknown code as an error and log the request_id.

3. HTTP & Content-Type conventions

  • Content-Type: a request with a body must be application/json, and every response (including errors and idempotency replays) is application/json.
  • Request ID: every error response has error.request_id for referencing the issue (keep it every time you hit an error).
  • 2xx = success, 4xx = merchant-side request error (fix then resend), 5xx = system-side (safe to retry if you use Idempotency-Key on a money POST).
  • Always use HTTPS.
Pending confirmation — confirm whether an X-Request-Id response header exists — from the code, request_id appears mainly in the error body; if we communicate an echo header, confirm the requestid middleware first
Diagram coming soon — sequence diagram showing the flow of retrying a money POST with the same Idempotency-Key → the system returns the original response with header Idempotent-Replay: true