Errors
The error response shape, HTTP status codes and the full list of error codes.
Generated from public-api.mdVersion 1.2.0Last updated 2026-09-20
#Error Responses
All errors follow this format:
{
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": { "reason": "…", "trace_id": "…" }
}details is optional. When present it may carry reason (a client-safe
explanation from the payment service or provider) and trace_id — quote the
trace_id when contacting support. The same value is returned in the
X-Request-ID response header on every request.
Validation failures on deposit/withdraw instead fill details with one entry per
rejected field, keyed by JSON field name, described in Thai.
#HTTP Status Codes
| Status | Description |
|---|---|
200 | Success |
201 | Created (deposit / withdraw) |
400 | Bad request — validation or business rule |
401 | Unauthorized — missing/invalid key, signature or timestamp |
403 | Forbidden — merchant inactive, or IP not whitelisted |
404 | Not found |
409 | Conflict (e.g. an already-processed payment) |
413 | Payload too large (slip upload over 10 MB) |
429 | Rate limit exceeded |
500 | Internal error |
502 | The payment or reporting service could not be reached |
503 | Temporarily unavailable — maintenance window or operational pause |
504 | Gateway timeout (also the sandbox timeout scenario) |
#Error Codes
Emitted by the API gateway:
| Code | HTTP | Description |
|---|---|---|
MISSING_API_KEY | 401 | X-API-Key header missing |
MISSING_SIGNATURE | 401 | X-Signature header missing |
MISSING_TIMESTAMP | 401 | X-Timestamp header missing |
INVALID_TIMESTAMP | 401 | X-Timestamp is not a valid Unix timestamp |
TIMESTAMP_EXPIRED | 401 | Timestamp outside the ±5-minute window |
INVALID_API_KEY | 401 | Unknown API key |
INVALID_SIGNATURE | 401 | Signature verification failed |
MISSING_CONTENT_HASH | 401 | X-Content-SHA256 absent on a slip upload |
UNAUTHORIZED | 401 | Merchant could not be resolved from the auth context |
AUTHENTICATION_ERROR | 500 | Credential lookup failed upstream |
MERCHANT_INACTIVE | 403 | Merchant account is not active |
IP_NOT_WHITELISTED | 403 | Caller IP is not in your whitelist |
INVALID_BODY | 400 | Request body could not be read |
INVALID_REQUEST | 400 | Request body validation failed |
MISSING_PAYER_INFO | 400 | Only the payer trio is missing on a deposit |
CONTENT_HASH_MISMATCH | 400 | Uploaded file does not match X-Content-SHA256 |
FILE_REQUIRED | 400 | Slip upload without a file part |
FILE_READ_ERROR | 400 | Uploaded file could not be read |
FILE_TOO_LARGE | 413 | Slip over 10 MB |
RATE_LIMIT_EXCEEDED | 429 | Per-merchant rate limit exceeded |
TIMEOUT | 504 | Sandbox timeout scenario |
UPSTREAM_UNAVAILABLE | 502 | Payment/reporting service unreachable — safe to retry |
INTERNAL_ERROR | 500 | Unexpected upstream failure (carries trace_id) |
Passed through from the payment service:
| Code | HTTP | Description |
|---|---|---|
INVALID_AMOUNT | 400 | Amount outside the allowed range for your account |
INSUFFICIENT_BALANCE | 400 | Not enough available balance for the withdrawal + fee |
PAYMENT_CANNOT_BE_CANCELLED | 400 | Payment is not in pending |
INVALID_ACCOUNT | 400 | Invalid bank account (sandbox invalid_account scenario) |
NOT_ELIGIBLE | 400 | Slip upload against a transaction that is not expired |
EXPIRED_TOO_OLD | 400 | Slip upload more than 24 h after expiry |
AMOUNT_MISMATCH, RECEIVER_NOT_RECOGNIZED, SLIP_TOO_OLD, SLIP_ALREADY_USED, PAYER_BANK_MISMATCH, SLIP_INVALID | 400 | Slip verification failures |
NOT_FOUND | 404 | Payment not found, or not yours |
CONFLICT | 409 | Payment already processed |
NO_DEPOSIT_ACCOUNT | 503 | No receiving account is available for this deposit right now — none configured, all paused, or all at their daily limit. Retryable; if it persists, contact support (message is in Thai) |
SERVICE_UNAVAILABLE | 503 | Maintenance window, operational pause, or pay-in/pay-out disabled for your account (message is in Thai) |
Error-code stability. The gateway forwards upstream
codevalues verbatim, so the list above is the set observed today rather than a frozen contract. Branch on HTTP status first and treat unknowncodevalues as retryable/non-retryable according to the status.
A
503always explains itself. Unlike a500, a 503 is a state the platform is reporting deliberately, so itscodeandmessagereach you unchanged and are safe to show an operator. A500with no businessreasonis reduced to a generic message on purpose — its upstream text could be an internal error string.
Codes that appeared in earlier revisions of this document and are not
emitted: PAYMENT_NOT_FOUND (it is NOT_FOUND), PAYMENT_ERROR,
DUPLICATE_REFERENCE (duplicate reference_id is not detected),
INVALID_BANK_CODE (bank codes are only length-checked).

