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 ในรูปแบบนี้:

JSON
{
  "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 — always false on error responses (boolean).
  • success — เป็น false เสมอใน error response (boolean)
  • message — a human-readable description. May change wording without notice; do not parse this.
  • message — ข้อความสำหรับมนุษย์อ่าน อาจเปลี่ยนได้โดยไม่แจ้งล่วงหน้า
    ดังนั้น อย่าใช้ parse
All endpoints use the same shape
ทุก endpoint ใช้ shape เดียวกัน

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 เหล่านี้

HTTPCodeMeaningWhat to do รหัสความหมายทำอย่างไร
405method-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
403authentication-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 อีกครั้ง
403signature-required Missing X-SIGNATURE header. Compute and add the header. signature-required ไม่ได้ส่ง header X-SIGNATURE คำนวณแล้วใส่ header
403signature-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
403ip-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
400invalid-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

HTTPCodeEndpointsWhen it happens รหัสEndpointsเกิดเมื่อ
400invalid-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
409duplicate-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 — ถ้าเปิดใช้)
403permission-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 นอกเวลาที่อนุญาต
503service-unavailable /payment/create, /payment/create-transfer No deposit account currently available to receive payment. Retry shortly. service-unavailable /payment/create, /payment/create-transfer ไม่มีบัญชีรับเงินที่ว่างขณะนี้ — ลองใหม่ในอีกสักครู่
429channel-limit-reached /payment/create, /payment/create-transfer Deposit channel hit its hourly or daily limit. channel-limit-reached /payment/create, /payment/create-transfer Channel รับเงินถึงโควตารายชั่วโมงหรือรายวัน
429too-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 ยังใหม่เกิน รอตามเวลาที่ตั้งไว้ก่อน
404not-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 เพียงอย่างเดียว

HTTPdata.codeWhen it happens data.codeเกิดเมื่อ
403SLIP_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 ให้เปิดใช้
429MAX_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)
429UPLOAD_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 ต้นทางของคุณ
ให้ลดอัตราการยิงคำขอ
422ORDER_NOT_OPEN The order is no longer accepting slips (already paid, expired, or cancelled). ORDER_NOT_OPEN order ไม่รับสลิปแล้ว (จ่ายแล้ว หมดอายุ หรือถูกยกเลิก)
422SLIP_NOT_SUPPORTED Slip upload is not supported for this order's payment type or channel. SLIP_NOT_SUPPORTED ประเภทการชำระเงินหรือ channel ของ order นี้ไม่รองรับการอัปโหลดสลิป
422UPLOAD_TOO_OLD The order is too old to accept a slip — the upload window has passed. UPLOAD_TOO_OLD order เก่าเกินกว่าจะรับสลิป — พ้นช่วงเวลาที่อัปโหลดได้แล้ว
422IMAGE_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 errors are HTTP 422, not 400 — branch on data.code
Slip error เป็น HTTP 422 ไม่ใช่ 400 — ให้ branch ที่ data.code

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 ไม่ได้
และอาจปฏิเสธคำขอตั้งแต่ต้นถ้าแพลตฟอร์มยังตั้งค่าที่จำเป็นไม่ครบ

HTTPCode / sub-codeWhen it happens Code / sub-codeเกิดเมื่อ
503killswitch_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
503outside_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 เฉพาะช่วงเวลาทำการที่ตั้งไว้ และคำขอมานอกช่วงนั้น — ลองใหม่ในเวลาทำการ
503no-channel No deposit channel across the whole pool could accept this order right now. Retry shortly with backoff. no-channel ไม่มี channel รับเงินไหนใน pool ที่รับ order นี้ได้ตอนนี้ — ลองใหม่ในอีกสักครู่แบบ backoff
422invalid-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
Never retry signature/auth errors
ห้าม retry signature/auth error

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