Error Codes
รหัสข้อผิดพลาด
Error response format
รูปแบบ Error response
When a request fails, the API returns a non-200 status code with a JSON body shaped like this:
เมื่อ request ล้มเหลว API จะส่งกลับ status ที่ไม่ใช่ 200 พร้อม JSON body ในรูปแบบนี้:
{
"code": 422,
"error": "invalid-inputs",
"success": false,
"message": "amount must be at least 20"
}
code— the HTTP status code mirrored in the body (number).code— HTTP status code ของ response (number) ที่ใส่ใน body ด้วยerror— a stable machine-readable identifier (kebab-case). Use this in code branching.error— รหัสคงที่ใช้เช็คในโค้ด (kebab-case)success— alwaysfalseon error responses (boolean).success— เป็นfalseเสมอใน error response (boolean)message— a human-readable description. May change wording without notice; do not parse this.message— ข้อความสำหรับมนุษย์อ่าน อาจเปลี่ยนได้โดยไม่แจ้งล่วงหน้า
ดังนั้น อย่าใช้ parse
Every API endpoint (including /slip/upload) returns errors in this format. Build one error handler, use it everywhere.
ทุก endpoint (รวม /slip/upload) ใช้รูปแบบ error แบบเดียวกัน
จึงสร้าง handler ตัวเดียวใช้ได้กับทุก endpoint
Authentication errors
Errors ของ Authentication
These can be returned by any authenticated endpoint.
ทุก endpoint ที่ต้องยืนยันตัวตนมีโอกาสส่งกลับ error เหล่านี้
| HTTP | Code | Meaning | What to do | รหัส | ความหมาย | ทำอย่างไร |
|---|---|---|---|---|---|---|
| 405 | method-not-allowed |
Used a non-POST method on a POST-only endpoint. | Switch to POST. Only /health uses GET. |
method-not-allowed |
ใช้ method ที่ไม่ใช่ POST | เปลี่ยนเป็น POST — มีเฉพาะ /health เท่านั้นที่ใช้ GET |
| 403 | authentication-failed |
Wrong merchant_id, wrong token, or merchant not found. |
Re-check credentials with your payment gateway admin. | authentication-failed |
merchant_id หรือ token ผิด หรือไม่พบ merchant |
ตรวจ credentials กับ payment gateway admin อีกครั้ง |
| 403 | signature-required |
Missing X-SIGNATURE header. |
Compute and add the header. | signature-required |
ไม่ได้ส่ง header X-SIGNATURE |
คำนวณแล้วใส่ header |
| 403 | signature-error |
HMAC doesn't match the body. | Most likely cause: re-serializing JSON before sending. Sign the exact bytes you send. See Authentication. | signature-error |
HMAC ไม่ตรงกับ body | สาเหตุที่พบบ่อย: serialize JSON ใหม่ก่อนส่ง ต้องเซ็น bytes ตัวเดียวกับที่ส่ง ดู Authentication |
| 403 | ip-not-whitelisted |
Source IP not in your merchant's whitelist (when configured). | Ask admin to add your egress IP, or remove the whitelist. | ip-not-whitelisted |
Source IP ไม่อยู่ใน whitelist (ถ้าเปิดใช้) | ขอ admin เพิ่ม egress IP หรือ remove whitelist |
| 400 | invalid-inputs |
Body is missing or not valid JSON. | Verify Content-Type and JSON syntax. |
invalid-inputs |
Body หาย หรือ JSON ไม่ถูกต้อง | ตรวจ Content-Type และ syntax JSON |
Endpoint-specific errors
Errors เฉพาะ Endpoint
| HTTP | Code | Endpoints | When it happens | รหัส | Endpoints | เกิดเมื่อ |
|---|---|---|---|---|---|---|
| 400 | invalid-inputs |
All | Missing or invalid required fields (e.g. time missing or out of acceptable range, amount < min, bank not recognized, account_no length, malformed URL), insufficient balance for create operations. |
invalid-inputs |
ทุก endpoint | Field ขาดหรือรูปแบบผิด (amount < min, bank ผิด, account_no ผิด, URL ผิด) หรือยอดไม่พอสำหรับ create operation |
| 409 | duplicate-entry |
/payment/create, /payment/create-transfer, /withdraw/create, /thb-settlement/create | Duplicate merchant_order_id within 7 days (payment, settlement) or duplicate bank+account+amount within 3 minutes (withdraw, when enabled). |
duplicate-entry |
/payment/create, /payment/create-transfer, /withdraw/create, /thb-settlement/create | merchant_order_id ซ้ำใน 7 วัน (payment, settlement) หรือ bank+account+amount ซ้ำใน 3 นาที (withdraw — ถ้าเปิดใช้) |
| 403 | permission-denied |
/payment/create, /withdraw/create, /thb-settlement/create | Operation disabled on your merchant, or settlement called outside operating hours. | permission-denied |
/payment/create, /withdraw/create, /thb-settlement/create | Operation ปิดที่ merchant ของคุณ หรือเรียก settlement นอกเวลาที่อนุญาต |
| 503 | service-unavailable |
/payment/create, /payment/create-transfer | No deposit account currently available to receive payment. Retry shortly. | service-unavailable |
/payment/create, /payment/create-transfer | ไม่มีบัญชีรับเงินที่ว่างขณะนี้ — ลองใหม่ในอีกสักครู่ |
| 429 | channel-limit-reached |
/payment/create, /payment/create-transfer | Deposit channel hit its hourly or daily limit. | channel-limit-reached |
/payment/create, /payment/create-transfer | Channel รับเงินถึงโควตารายชั่วโมงหรือรายวัน |
| 429 | too-many-requests |
/slip/upload | Rate limit hit — either >1 slip upload per order in the last 5 minutes, or order is too fresh and the configured wait time has not elapsed. | too-many-requests |
/slip/upload | Rate limit — อัปโหลดสลิปเกิน 1 ครั้งต่อ order ใน 5 นาที หรือ order ยังใหม่เกิน รอตามเวลาที่ตั้งไว้ก่อน |
| 404 | not-found |
All query endpoints, all create endpoints (partner config) | Order not found / not owned by you, or partner config missing on the merchant. Contact admin if the create endpoints return this. | not-found |
ทุก query endpoint, ทุก create endpoint (partner config) | ไม่พบ order หรือ order เป็นของ merchant อื่น หรือไม่มี partner config ของ merchant ถ้า create endpoint ส่งกลับ error นี้ ให้ติดต่อ admin |
Slip upload errors
Errors ของการอัปโหลดสลิป
The /slip/upload endpoint reports failures with a different envelope from the create/query endpoints above. Here error is a human-readable string (the error name), and the stable machine-readable code lives at data.code. Branch your code on data.code, never on the wording of error or on the HTTP status alone.
Endpoint /slip/upload รายงาน error ด้วย envelope คนละแบบ กับ create/query ด้านบน
ตรงนี้ error เป็น string ที่มนุษย์อ่าน (ชื่อ error) ส่วนรหัสคงที่สำหรับให้โค้ดเช็คอยู่ที่ data.code
ดังนั้นให้ branch ในโค้ดด้วย data.code อย่าใช้ข้อความใน error หรือ HTTP status เพียงอย่างเดียว
| HTTP | data.code | When it happens | data.code | เกิดเมื่อ |
|---|---|---|---|---|
| 403 | SLIP_UPLOAD_DISABLED |
Slip upload is turned off for your merchant. Contact your payment gateway admin to enable it. | SLIP_UPLOAD_DISABLED |
การอัปโหลดสลิปถูกปิดที่ merchant ของคุณ — ติดต่อ payment gateway admin ให้เปิดใช้ |
| 429 | MAX_UPLOADS |
This order has already reached its maximum number of slip uploads. Do not retry — the limit is per order. | MAX_UPLOADS |
order นี้อัปโหลดสลิปครบจำนวนสูงสุดแล้ว — อย่า retry (limit นับต่อ order) |
| 429 | UPLOAD_COOLDOWN |
Too soon since the last upload for this order. The response carries retry_after (seconds) — wait that long, then retry. |
UPLOAD_COOLDOWN |
อัปโหลดเร็วเกินไปนับจากครั้งก่อนของ order นี้ response มี retry_after (วินาที)รอครบแล้วค่อย retry |
| 429 | — (no code) | Per-IP rate limit — more than 10 upload requests per minute from your source IP. Slow down your request rate. | — (ไม่มี code) | Rate limit ต่อ IP — ส่งคำขออัปโหลดเกิน 10 ครั้งต่อนาทีจาก IP ต้นทางของคุณ ให้ลดอัตราการยิงคำขอ |
| 422 | ORDER_NOT_OPEN |
The order is no longer accepting slips (already paid, expired, or cancelled). | ORDER_NOT_OPEN |
order ไม่รับสลิปแล้ว (จ่ายแล้ว หมดอายุ หรือถูกยกเลิก) |
| 422 | SLIP_NOT_SUPPORTED |
Slip upload is not supported for this order's payment type or channel. | SLIP_NOT_SUPPORTED |
ประเภทการชำระเงินหรือ channel ของ order นี้ไม่รองรับการอัปโหลดสลิป |
| 422 | UPLOAD_TOO_OLD |
The order is too old to accept a slip — the upload window has passed. | UPLOAD_TOO_OLD |
order เก่าเกินกว่าจะรับสลิป — พ้นช่วงเวลาที่อัปโหลดได้แล้ว |
| 422 | IMAGE_TOO_SMALL, IMAGE_TOO_LARGE, INVALID_IMAGE |
The image failed validation — below the minimum dimensions/size, above the maximum, or not a readable image. Re-check the file before retrying. | IMAGE_TOO_SMALL, IMAGE_TOO_LARGE, INVALID_IMAGE |
รูปไม่ผ่านการตรวจสอบ — เล็ก/ใหญ่เกินเกณฑ์ หรือไม่ใช่ไฟล์รูปที่อ่านได้ ตรวจไฟล์ก่อน retry |
Slip validation failures return HTTP 422 (not 400). Do not switch on the HTTP status — many distinct outcomes share 422 and 429. The error field is a string (the error name, for humans); the machine-readable code you must branch on lives at json.data.code. When you see UPLOAD_COOLDOWN, read json.data.retry_after and wait that many seconds before retrying.
การตรวจสลิปที่ล้มเหลวส่งกลับ HTTP 422 (ไม่ใช่ 400)
อย่าตัดสินใจจาก HTTP status เพราะหลายกรณีต่างกันใช้ 422 และ 429 ร่วมกัน
field error เป็น string (ชื่อ error ไว้ให้มนุษย์อ่าน) ส่วนรหัสที่ต้องใช้ branch ในโค้ดอยู่ที่ json.data.code
เมื่อเจอ UPLOAD_COOLDOWN ให้อ่าน json.data.retry_after แล้วรอกี่วินาทีตามนั้นก่อน retry
Flex (/payment/create-flex) errors
Errors ของ Flex (/payment/create-flex)
/payment/create-flex can return a 503 with a sub-code that tells you why no order could be created. It can also reject the request up front when the platform prerequisite is missing.
/payment/create-flex อาจส่งกลับ 503 พร้อม sub-code ที่บอกว่า ทำไม ถึงสร้าง order ไม่ได้
และอาจปฏิเสธคำขอตั้งแต่ต้นถ้าแพลตฟอร์มยังตั้งค่าที่จำเป็นไม่ครบ
| HTTP | Code / sub-code | When it happens | Code / sub-code | เกิดเมื่อ |
|---|---|---|---|---|
| 503 | killswitch_off |
Flex is globally switched off by the platform kill switch. Nothing you can do from the caller side — contact your admin. | killswitch_off |
Flex ถูกปิดทั้งระบบด้วย kill switch ของแพลตฟอร์ม ฝั่ง caller ทำอะไรไม่ได้ ให้ติดต่อ admin |
| 503 | outside_open_period |
Flex is only accepting orders during its configured operating window, and the request arrived outside it. Retry within operating hours. | outside_open_period |
Flex รับ order เฉพาะช่วงเวลาทำการที่ตั้งไว้ และคำขอมานอกช่วงนั้น — ลองใหม่ในเวลาทำการ |
| 503 | no-channel |
No deposit channel across the whole pool could accept this order right now. Retry shortly with backoff. | no-channel |
ไม่มี channel รับเงินไหนใน pool ที่รับ order นี้ได้ตอนนี้ — ลองใหม่ในอีกสักครู่แบบ backoff |
| 422 | invalid-inputs |
No payment_page_domain is configured for your platform — flex has nowhere to send the customer. This is a one-time platform setup; your admin sets it. |
invalid-inputs |
แพลตฟอร์มยังไม่ได้ตั้ง payment_page_domain ทำให้ flex ไม่มีหน้าให้ส่งลูกค้าไปหมายเหตุ: เป็นการตั้งค่าครั้งเดียวของแพลตฟอร์ม โดย admin เป็นผู้ตั้ง |
Recommended handling
วิธี handle ที่แนะนำ
| Error class | Strategy | กลุ่ม Error | วิธีรับมือ |
|---|---|---|---|
signature-error, signature-required |
Bug in your code — fix and redeploy. Do not retry the same request. | signature-error, signature-required |
เป็น bug ในโค้ดคุณ แก้และ deploy ใหม่ อย่า retry request เดิม |
authentication-failed, ip-not-whitelisted |
Configuration issue — escalate to your payment gateway admin. | authentication-failed, ip-not-whitelisted |
ปัญหา configuration — แจ้ง payment gateway admin |
invalid-inputs, permission-denied |
Caller error — log and surface to your operator. No retry. | invalid-inputs, permission-denied |
Caller error log แล้วแจ้ง operator ไม่ต้อง retry |
duplicate-entry |
Treat as success if you intended an idempotent retry — query the existing order to confirm. | duplicate-entry |
ถ้าตั้งใจ retry แบบ idempotent ให้ถือว่าสำเร็จ — query order เดิมยืนยันสถานะ |
service-unavailable, channel-limit-reached |
Retry with exponential backoff (e.g. 30s → 1m → 2m → 5m). Surface to operator after a few minutes of failures. | service-unavailable, channel-limit-reached |
Retry แบบ exponential backoff (เช่น 30s → 1m → 2m → 5m) — แจ้ง operator ถ้ายังพังหลายนาที |
not-found |
For queries: order doesn't exist on our side — investigate. For creates: contact admin. | not-found |
สำหรับ query: order ไม่มีฝั่งเรา ต้องตรวจสอบ สำหรับ create: ติดต่อ admin |
| 5xx (server error) | Retry with backoff. If persistent, escalate. | 5xx (server error) | Retry แบบ backoff — ถ้ายังเป็น escalate |
A signature or authentication error indicates a deterministic bug — retrying with the same payload will fail every time. Worse, retries against payment/withdraw create endpoints can create duplicate orders if the failure is in your code path that builds the body.
Signature หรือ auth error แปลว่ามีบั๊กที่ deterministic ดังนั้นการ retry payload เดิมจะ fail ทุกครั้ง
ยิ่งกว่านั้น การ retry create endpoint อาจสร้าง order ซ้ำถ้า bug อยู่ใน code ที่สร้าง body