{"openapi":"3.0.0","paths":{"/api/v1/verify":{"post":{"description":"ส่งสลิปได้ 5 แบบ: **raw bytes** (`application/octet-stream`) · `multipart/form-data` (ฟิลด์ไฟล์ `file`) · JSON `{ \"image\": \"<base64>\" }` · JSON `{ \"url\": \"https://…\" }` · JSON `{ \"qr\": \"<ข้อความใน QR>\" }` · ใช้โควตาก้อนเดียวกับสลิปจาก LINE · ส่งรูปเดิมซ้ำภายใน 24 ชม. ได้ผลเดิม (`cached: true`) โดยไม่หักโควตา — retry ได้ปลอดภัย · แนะนำส่ง header `Idempotency-Key` (เช่น UUID ต่อออเดอร์) — retry ด้วย key เดิมได้คำตอบชุดเดิมเป๊ะ พร้อม header `Idempotent-Replayed: true` · key เดิมกับสลิปอื่น = 422 `IDEMPOTENCY_KEY_REUSED` · ครั้งแรกยังไม่จบ = 409 `IDEMPOTENCY_IN_PROGRESS` + `Retry-After` · เก็บ 24 ชม. ต่อร้าน","operationId":"verifySlip","parameters":[{"name":"Idempotency-Key","in":"header","description":"ค่าเฉพาะต่อคำขอ (1–255 ตัว ASCII เช่น UUID) — retry ด้วยค่าเดิมไม่ตรวจซ้ำและไม่หักโควตาซ้ำ · อายุ 24 ชม.","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"สลิป (รูป JPEG/PNG · ทั้งคำขอไม่เกิน 5 MB) — เลือกส่งแบบใดแบบหนึ่ง","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary","description":"ไฟล์รูปสลิปเป็น raw bytes"}},"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"ไฟล์รูปสลิป"}}}},"application/json":{"schema":{"type":"object","description":"ส่ง **อย่างใดอย่างหนึ่ง** ใน `image` · `url` · `qr`","minProperties":1,"maxProperties":1,"properties":{"image":{"type":"string","format":"byte","description":"ไฟล์รูปสลิปเข้ารหัส base64 (มี prefix `data:image/…;base64,` ก็ได้)"},"url":{"type":"string","format":"uri","description":"ลิงก์ `https://` ของรูปสลิปที่เข้าถึงได้จากอินเทอร์เน็ต · ไม่ตาม redirect · ไฟล์ไม่เกิน 5 MB","example":"https://cdn.example.com/slips/1001.jpg"},"qr":{"type":"string","description":"ข้อความที่อ่านได้จาก QR บนสลิป (ไม่ต้องมีรูป)","example":"0041000600000101030040220015170106978BTF012345102TH9104A1B2"}}}}}},"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"description":"id ของคำขอ — ค่าเดียวกับ `meta.requestId` / `requestId` ใน error","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"เพดานคำขอ/นาที **ต่อ IP** ของ endpoint นี้ (120 · `POST /verify` 240)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"เหลือในหน้าต่างปัจจุบัน (ต่อ IP)","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"**วินาที** จนกว่าหน้าต่างจะรีเซ็ต (ไม่ใช่ epoch)","schema":{"type":"integer"}},"X-Slipbolt-Quota-Limit":{"description":"เพดานสลิป/เดือนของแพ็กปัจจุบัน","schema":{"type":"integer"}},"X-Slipbolt-Quota-Used":{"description":"ใช้ไปแล้วในรอบนี้","schema":{"type":"integer"}},"X-Slipbolt-Quota-Remaining":{"description":"คงเหลือ","schema":{"type":"integer"}},"X-Slipbolt-Quota-Reset-Days":{"description":"กี่วันก่อนรีเซ็ตรอบใหม่","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","meta"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/VerifyResult"},"meta":{"type":"object","properties":{"requestId":{"type":"string","example":"3b1f0c9e-6a2d-4f5e-8b7c-1d2e3f4a5b6c"},"timestamp":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"request ไม่ถูกรูปแบบ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":400,"error":"Bad Request","message":"Send the slip image as raw bytes (Content-Type: application/octet-stream), as multipart/form-data (file field \"file\"), or as JSON with one of: {\"image\":\"<base64>\"}, {\"url\":\"https://...\"}, {\"qr\":\"<slip QR text>\"}"}}}}},"401":{"description":"ไม่มี header `Authorization: Bearer …` (`Missing API key — …`) หรือคีย์ผิด/ถูก revoke (`Invalid or revoked API key`) · `X-API-Key` และ Basic auth ใช้ไม่ได้ · revoke มีผลทันที ไม่มีช่วง cache","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":401,"error":"Unauthorized","message":"Invalid or revoked API key"}}}}},"402":{"description":"`QUOTA_EXCEEDED` — โควตารายเดือนและเครดิตของร้านหมด (ซื้อเครดิต/อัปเกรดแพ็ก/รอรอบใหม่) · `TEST_QUOTA_EXCEEDED` — คีย์ `sk_test_` ใช้ครบเพดานเดือนนี้ (ย้ายไปคีย์ live)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":402,"error":"PaymentRequired","code":"QUOTA_EXCEEDED","message":"โควตาสลิปรายเดือนและเครดิตของร้านถูกใช้หมดแล้ว — ซื้อเครดิต อัปเกรดแพ็ก หรือรอรอบบิลถัดไป","quota":{"used":500,"limit":500,"remaining":0}}}}}},"403":{"description":"scope ไม่พอ — ต้องมี `verify` (`error: \"Forbidden\"` · `message: \"API key missing scope(s): verify\"` · ไม่มี `code`) · IP ไม่อยู่ใน allowlist ของคีย์ (`code: IP_NOT_ALLOWED`) · หรือแพ็กปัจจุบันไม่รวม REST API (`code: PLAN_API_ACCESS_REQUIRED` — ตัวอย่างด้านล่าง)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":403,"error":"PlanFeatureRequired","code":"PLAN_API_ACCESS_REQUIRED","feature":"apiAccess","message":"แพ็กปัจจุบันยังไม่รวม REST API และ Webhook — อัปเกรดเป็น Starter ขึ้นไปเพื่อเปิดใช้"}}}}},"405":{"description":"method ผิดบน path ที่มีอยู่ (`code: METHOD_NOT_ALLOWED`) — header `Allow` บอก method ที่ใช้ได้ · path ที่ไม่มีอยู่ = 404","headers":{"Allow":{"description":"method ที่ path นี้รับ เช่น `POST`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":405,"error":"MethodNotAllowed","code":"METHOD_NOT_ALLOWED","message":"Method GET is not allowed on this path — use POST"}}}}},"409":{"description":"`IDEMPOTENCY_IN_PROGRESS` — คำขอที่ใช้ `Idempotency-Key` เดียวกันยังประมวลผลอยู่ · รอตาม `Retry-After` แล้วส่งซ้ำด้วย key เดิม","headers":{"Retry-After":{"description":"วินาทีที่ต้องรอ","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":409,"error":"Conflict","code":"IDEMPOTENCY_IN_PROGRESS","message":"คำขอที่ใช้ Idempotency-Key นี้กำลังประมวลผลอยู่ — รอสักครู่แล้วส่งซ้ำ","retryAfterSec":2}}}}},"413":{"description":"`code: PAYLOAD_TOO_LARGE` — ทั้งคำขอเกิน 5 MB · ย่อรูปสลิปแล้วส่งใหม่ (retry ด้วยไฟล์เดิมไม่ช่วย)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":413,"error":"PayloadTooLarge","code":"PAYLOAD_TOO_LARGE","message":"Request body is larger than 5 MB — send a smaller slip image"}}}}},"422":{"description":"`IDEMPOTENCY_KEY_REUSED` — `Idempotency-Key` นี้เคยใช้กับสลิปใบอื่นแล้ว (ใช้ key ใหม่ต่อหนึ่งคำขอ)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":422,"error":"UnprocessableEntity","code":"IDEMPOTENCY_KEY_REUSED","message":"Idempotency-Key นี้ถูกใช้กับสลิปใบอื่นไปแล้ว — ใช้ key ใหม่ต่อหนึ่งคำขอ"}}}}},"429":{"description":"`code: RATE_LIMITED` = เกิน rate limit (120/นาที ต่อคีย์) — รอตาม header `Retry-After` (วินาที) · `code: SPEND_CAP_EXCEEDED` (เฉพาะ verify) = ถึงเพดานต่อคีย์ที่ร้านตั้งเอง ⇒ **อย่า retry วน** รอรอบใหม่ตาม `Retry-After` · `code: ASYNC_QUEUE_FULL` (เฉพาะ `POST /verify/async`) = งานรอตรวจของร้านเต็ม","headers":{"Retry-After":{"description":"วินาทีที่ต้องรอ","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":429,"error":"TooManyRequests","code":"RATE_LIMITED","message":"ส่งบ่อยเกินไป กรุณารออีก 42 วินาที","retryAfterSec":42}}}}},"500":{"description":"ระบบเราขัดข้องชั่วคราว (รวม 502/503/504) — ลองใหม่แบบเว้นช่วง · ส่งรูปเดิมซ้ำปลอดภัย ไม่หักโควตาซ้ำ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":500,"error":"InternalServerError","message":"Internal server error"}}}}}},"security":[{"apiKey":[]}],"summary":"ตรวจสลิปโอนเงิน 1 ใบ","tags":["public-api"]}},"/api/v1/verify/async":{"post":{"description":"body แบบเดียวกับ `POST /verify` ทุกประการ · ตอบ **202** พร้อม `id` (`vrf_…`) + header `Location` ภายในไม่กี่ ms — ผลส่งมาทาง webhook `verification.completed` (ต้องติ๊ก event นี้ที่ endpoint) และ/หรือดึงที่ `GET /verifications/{id}` · ผล (`result`) เหมือน `POST /verify` ทุกฟิลด์ · กรณีที่ sync ตอบ 402/429 งานจะจบเป็น `FAILED` พร้อม `error.code` เดียวกัน (ไม่หักโควตา) · ค้างคิวเกิน 5 นาที = `FAILED` `VERIFY_TIMEOUT` · ใช้ `Idempotency-Key` ได้ (retry ได้ `id` เดิม) · งานรอตรวจต่อร้านมีเพดาน — เต็มได้ 429 `ASYNC_QUEUE_FULL`","operationId":"verifySlipAsync","parameters":[{"name":"Idempotency-Key","in":"header","description":"เหมือน `POST /verify` — retry ด้วยค่าเดิมได้คำตอบ 202 ชุดเดิม (`id` เดิม) ไม่สร้างงานซ้ำ","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"description":"สลิป (รูป JPEG/PNG · ทั้งคำขอไม่เกิน 5 MB) — เลือกส่งแบบใดแบบหนึ่ง","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary","description":"ไฟล์รูปสลิปเป็น raw bytes"}},"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"ไฟล์รูปสลิป"}}}},"application/json":{"schema":{"type":"object","description":"ส่ง **อย่างใดอย่างหนึ่ง** ใน `image` · `url` · `qr`","minProperties":1,"maxProperties":1,"properties":{"image":{"type":"string","format":"byte","description":"ไฟล์รูปสลิปเข้ารหัส base64 (มี prefix `data:image/…;base64,` ก็ได้)"},"url":{"type":"string","format":"uri","description":"ลิงก์ `https://` ของรูปสลิปที่เข้าถึงได้จากอินเทอร์เน็ต · ไม่ตาม redirect · ไฟล์ไม่เกิน 5 MB","example":"https://cdn.example.com/slips/1001.jpg"},"qr":{"type":"string","description":"ข้อความที่อ่านได้จาก QR บนสลิป (ไม่ต้องมีรูป)","example":"0041000600000101030040220015170106978BTF012345102TH9104A1B2"}}}}}},"responses":{"202":{"description":"รับงานแล้ว — ดึงผลที่ `Location` / `data.statusUrl` หรือรอ webhook `verification.completed`","headers":{"X-Request-Id":{"description":"id ของคำขอ — ค่าเดียวกับ `meta.requestId` / `requestId` ใน error","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"เพดานคำขอ/นาที **ต่อ IP** ของ endpoint นี้ (120 · `POST /verify` 240)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"เหลือในหน้าต่างปัจจุบัน (ต่อ IP)","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"**วินาที** จนกว่าหน้าต่างจะรีเซ็ต (ไม่ใช่ epoch)","schema":{"type":"integer"}},"Location":{"description":"URL ของ `GET /verifications/{id}`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","meta"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AsyncVerification"},"meta":{"type":"object","properties":{"requestId":{"type":"string","example":"3b1f0c9e-6a2d-4f5e-8b7c-1d2e3f4a5b6c"},"timestamp":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"request ไม่ถูกรูปแบบ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":400,"error":"Bad Request","message":"Send the slip image as raw bytes (Content-Type: application/octet-stream), as multipart/form-data (file field \"file\"), or as JSON with one of: {\"image\":\"<base64>\"}, {\"url\":\"https://...\"}, {\"qr\":\"<slip QR text>\"}"}}}}},"401":{"description":"ไม่มี header `Authorization: Bearer …` (`Missing API key — …`) หรือคีย์ผิด/ถูก revoke (`Invalid or revoked API key`) · `X-API-Key` และ Basic auth ใช้ไม่ได้ · revoke มีผลทันที ไม่มีช่วง cache","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":401,"error":"Unauthorized","message":"Invalid or revoked API key"}}}}},"403":{"description":"scope ไม่พอ — ต้องมี `verify` (`error: \"Forbidden\"` · `message: \"API key missing scope(s): verify\"` · ไม่มี `code`) · IP ไม่อยู่ใน allowlist ของคีย์ (`code: IP_NOT_ALLOWED`) · หรือแพ็กปัจจุบันไม่รวม REST API (`code: PLAN_API_ACCESS_REQUIRED` — ตัวอย่างด้านล่าง)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":403,"error":"PlanFeatureRequired","code":"PLAN_API_ACCESS_REQUIRED","feature":"apiAccess","message":"แพ็กปัจจุบันยังไม่รวม REST API และ Webhook — อัปเกรดเป็น Starter ขึ้นไปเพื่อเปิดใช้"}}}}},"405":{"description":"method ผิดบน path ที่มีอยู่ (`code: METHOD_NOT_ALLOWED`) — header `Allow` บอก method ที่ใช้ได้ · path ที่ไม่มีอยู่ = 404","headers":{"Allow":{"description":"method ที่ path นี้รับ เช่น `POST`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":405,"error":"MethodNotAllowed","code":"METHOD_NOT_ALLOWED","message":"Method GET is not allowed on this path — use POST"}}}}},"409":{"description":"`IDEMPOTENCY_IN_PROGRESS` — คำขอที่ใช้ `Idempotency-Key` เดียวกันยังประมวลผลอยู่ · รอตาม `Retry-After` แล้วส่งซ้ำด้วย key เดิม","headers":{"Retry-After":{"description":"วินาทีที่ต้องรอ","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":409,"error":"Conflict","code":"IDEMPOTENCY_IN_PROGRESS","message":"คำขอที่ใช้ Idempotency-Key นี้กำลังประมวลผลอยู่ — รอสักครู่แล้วส่งซ้ำ","retryAfterSec":2}}}}},"413":{"description":"`code: PAYLOAD_TOO_LARGE` — ทั้งคำขอเกิน 5 MB · ย่อรูปสลิปแล้วส่งใหม่ (retry ด้วยไฟล์เดิมไม่ช่วย)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":413,"error":"PayloadTooLarge","code":"PAYLOAD_TOO_LARGE","message":"Request body is larger than 5 MB — send a smaller slip image"}}}}},"422":{"description":"`IDEMPOTENCY_KEY_REUSED` — `Idempotency-Key` นี้เคยใช้กับสลิปใบอื่นแล้ว (ใช้ key ใหม่ต่อหนึ่งคำขอ)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":422,"error":"UnprocessableEntity","code":"IDEMPOTENCY_KEY_REUSED","message":"Idempotency-Key นี้ถูกใช้กับสลิปใบอื่นไปแล้ว — ใช้ key ใหม่ต่อหนึ่งคำขอ"}}}}},"429":{"description":"`code: RATE_LIMITED` = เกิน rate limit (120/นาที ต่อคีย์) — รอตาม header `Retry-After` (วินาที) · `code: SPEND_CAP_EXCEEDED` (เฉพาะ verify) = ถึงเพดานต่อคีย์ที่ร้านตั้งเอง ⇒ **อย่า retry วน** รอรอบใหม่ตาม `Retry-After` · `code: ASYNC_QUEUE_FULL` (เฉพาะ `POST /verify/async`) = งานรอตรวจของร้านเต็ม","headers":{"Retry-After":{"description":"วินาทีที่ต้องรอ","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":429,"error":"TooManyRequests","code":"RATE_LIMITED","message":"ส่งบ่อยเกินไป กรุณารออีก 42 วินาที","retryAfterSec":42}}}}},"500":{"description":"ระบบเราขัดข้องชั่วคราว (รวม 502/503/504) — ลองใหม่แบบเว้นช่วง · ส่งรูปเดิมซ้ำปลอดภัย ไม่หักโควตาซ้ำ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":500,"error":"InternalServerError","message":"Internal server error"}}}}}},"security":[{"apiKey":[]}],"summary":"ตรวจสลิปแบบ async (ตอบ 202 ทันที)","tags":["public-api"]}},"/api/v1/verifications/{id}":{"get":{"description":"poll ได้ (แนะนำทุก 1–2 วินาที) · `PENDING`/`PROCESSING` = ยังไม่จบ · `COMPLETED` → `result` · `FAILED` → `error` · อายุ 24 ชม. หลังจากนั้น 404 (ผลถาวรอยู่ที่ `GET /transactions/{result.id}`) · ไม่เปลือง verify","operationId":"getVerification","parameters":[{"name":"id","required":true,"in":"path","description":"`id` (`vrf_…`) จาก `POST /verify/async`","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"description":"id ของคำขอ — ค่าเดียวกับ `meta.requestId` / `requestId` ใน error","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"เพดานคำขอ/นาที **ต่อ IP** ของ endpoint นี้ (120 · `POST /verify` 240)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"เหลือในหน้าต่างปัจจุบัน (ต่อ IP)","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"**วินาที** จนกว่าหน้าต่างจะรีเซ็ต (ไม่ใช่ epoch)","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","meta"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/AsyncVerification"},"meta":{"type":"object","properties":{"requestId":{"type":"string","example":"3b1f0c9e-6a2d-4f5e-8b7c-1d2e3f4a5b6c"},"timestamp":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"ไม่มี header `Authorization: Bearer …` (`Missing API key — …`) หรือคีย์ผิด/ถูก revoke (`Invalid or revoked API key`) · `X-API-Key` และ Basic auth ใช้ไม่ได้ · revoke มีผลทันที ไม่มีช่วง cache","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":401,"error":"Unauthorized","message":"Invalid or revoked API key"}}}}},"403":{"description":"scope ไม่พอ — ต้องมี `verify` (`error: \"Forbidden\"` · `message: \"API key missing scope(s): verify\"` · ไม่มี `code`) · IP ไม่อยู่ใน allowlist ของคีย์ (`code: IP_NOT_ALLOWED`) · หรือแพ็กปัจจุบันไม่รวม REST API (`code: PLAN_API_ACCESS_REQUIRED` — ตัวอย่างด้านล่าง)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":403,"error":"PlanFeatureRequired","code":"PLAN_API_ACCESS_REQUIRED","feature":"apiAccess","message":"แพ็กปัจจุบันยังไม่รวม REST API และ Webhook — อัปเกรดเป็น Starter ขึ้นไปเพื่อเปิดใช้"}}}}},"404":{"description":"ไม่พบรายการ (หรือเป็นของร้านอื่น)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":404,"error":"Not Found","message":"Transaction not found"}}}}},"405":{"description":"method ผิดบน path ที่มีอยู่ (`code: METHOD_NOT_ALLOWED`) — header `Allow` บอก method ที่ใช้ได้ · path ที่ไม่มีอยู่ = 404","headers":{"Allow":{"description":"method ที่ path นี้รับ เช่น `POST`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":405,"error":"MethodNotAllowed","code":"METHOD_NOT_ALLOWED","message":"Method GET is not allowed on this path — use POST"}}}}},"429":{"description":"`code: RATE_LIMITED` = เกิน rate limit (120/นาที ต่อคีย์) — รอตาม header `Retry-After` (วินาที) · `code: SPEND_CAP_EXCEEDED` (เฉพาะ verify) = ถึงเพดานต่อคีย์ที่ร้านตั้งเอง ⇒ **อย่า retry วน** รอรอบใหม่ตาม `Retry-After` · `code: ASYNC_QUEUE_FULL` (เฉพาะ `POST /verify/async`) = งานรอตรวจของร้านเต็ม","headers":{"Retry-After":{"description":"วินาทีที่ต้องรอ","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":429,"error":"TooManyRequests","code":"RATE_LIMITED","message":"ส่งบ่อยเกินไป กรุณารออีก 42 วินาที","retryAfterSec":42}}}}},"500":{"description":"ระบบเราขัดข้องชั่วคราว (รวม 502/503/504) — ลองใหม่แบบเว้นช่วง · ส่งรูปเดิมซ้ำปลอดภัย ไม่หักโควตาซ้ำ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":500,"error":"InternalServerError","message":"Internal server error"}}}}}},"security":[{"apiKey":[]}],"summary":"ผลของงาน `POST /verify/async`","tags":["public-api"]}},"/api/v1/quota":{"get":{"description":"ไม่เปลือง verify — เหมาะให้ระบบ poll ก่อนยิงงานชุดใหญ่","operationId":"getQuota","parameters":[],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"description":"id ของคำขอ — ค่าเดียวกับ `meta.requestId` / `requestId` ใน error","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"เพดานคำขอ/นาที **ต่อ IP** ของ endpoint นี้ (120 · `POST /verify` 240)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"เหลือในหน้าต่างปัจจุบัน (ต่อ IP)","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"**วินาที** จนกว่าหน้าต่างจะรีเซ็ต (ไม่ใช่ epoch)","schema":{"type":"integer"}},"X-Slipbolt-Quota-Limit":{"description":"เพดานสลิป/เดือนของแพ็กปัจจุบัน","schema":{"type":"integer"}},"X-Slipbolt-Quota-Used":{"description":"ใช้ไปแล้วในรอบนี้","schema":{"type":"integer"}},"X-Slipbolt-Quota-Remaining":{"description":"คงเหลือ","schema":{"type":"integer"}},"X-Slipbolt-Quota-Reset-Days":{"description":"กี่วันก่อนรีเซ็ตรอบใหม่","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","meta"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Quota"},"meta":{"type":"object","properties":{"requestId":{"type":"string","example":"3b1f0c9e-6a2d-4f5e-8b7c-1d2e3f4a5b6c"},"timestamp":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"ไม่มี header `Authorization: Bearer …` (`Missing API key — …`) หรือคีย์ผิด/ถูก revoke (`Invalid or revoked API key`) · `X-API-Key` และ Basic auth ใช้ไม่ได้ · revoke มีผลทันที ไม่มีช่วง cache","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":401,"error":"Unauthorized","message":"Invalid or revoked API key"}}}}},"403":{"description":"scope ไม่พอ — ต้องมี `read` (`error: \"Forbidden\"` · `message: \"API key missing scope(s): read\"` · ไม่มี `code`) · IP ไม่อยู่ใน allowlist ของคีย์ (`code: IP_NOT_ALLOWED`) · หรือแพ็กปัจจุบันไม่รวม REST API (`code: PLAN_API_ACCESS_REQUIRED` — ตัวอย่างด้านล่าง)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":403,"error":"PlanFeatureRequired","code":"PLAN_API_ACCESS_REQUIRED","feature":"apiAccess","message":"แพ็กปัจจุบันยังไม่รวม REST API และ Webhook — อัปเกรดเป็น Starter ขึ้นไปเพื่อเปิดใช้"}}}}},"405":{"description":"method ผิดบน path ที่มีอยู่ (`code: METHOD_NOT_ALLOWED`) — header `Allow` บอก method ที่ใช้ได้ · path ที่ไม่มีอยู่ = 404","headers":{"Allow":{"description":"method ที่ path นี้รับ เช่น `POST`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":405,"error":"MethodNotAllowed","code":"METHOD_NOT_ALLOWED","message":"Method GET is not allowed on this path — use POST"}}}}},"429":{"description":"`code: RATE_LIMITED` = เกิน rate limit (120/นาที ต่อคีย์) — รอตาม header `Retry-After` (วินาที) · `code: SPEND_CAP_EXCEEDED` (เฉพาะ verify) = ถึงเพดานต่อคีย์ที่ร้านตั้งเอง ⇒ **อย่า retry วน** รอรอบใหม่ตาม `Retry-After` · `code: ASYNC_QUEUE_FULL` (เฉพาะ `POST /verify/async`) = งานรอตรวจของร้านเต็ม","headers":{"Retry-After":{"description":"วินาทีที่ต้องรอ","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":429,"error":"TooManyRequests","code":"RATE_LIMITED","message":"ส่งบ่อยเกินไป กรุณารออีก 42 วินาที","retryAfterSec":42}}}}},"500":{"description":"ระบบเราขัดข้องชั่วคราว (รวม 502/503/504) — ลองใหม่แบบเว้นช่วง · ส่งรูปเดิมซ้ำปลอดภัย ไม่หักโควตาซ้ำ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":500,"error":"InternalServerError","message":"Internal server error"}}}}}},"security":[{"apiKey":[]}],"summary":"อ่านโควตาคงเหลือ","tags":["public-api"]}},"/api/v1/transactions":{"get":{"description":"ใหม่สุดก่อน · แบ่งหน้าแบบ cursor · รวมสลิปทุกช่องทาง (API / LINE / Dashboard) — กรองด้วย `source`","operationId":"listTransactions","parameters":[{"name":"status","required":false,"in":"query","description":"กรองตามผลตรวจ","schema":{"type":"string","enum":["SUCCESS","DUPLICATE","INVALID","AMOUNT_MISMATCH","RECEIVER_MISMATCH","FRAUD","PROVIDER_ERROR","QUOTA_EXCEEDED"]}},{"name":"source","required":false,"in":"query","description":"กรองตามช่องทางที่สลิปเข้ามา","schema":{"type":"string","enum":["LINE","API","DASHBOARD","LIFF"]}},{"name":"transRef","required":false,"in":"query","description":"เลขอ้างอิงธนาคาร (ตรงตัว)","schema":{"example":"015170106978BTF01234","type":"string"}},{"name":"from","required":false,"in":"query","description":"ตั้งแต่ (รวม) — ISO-8601 พร้อม timezone","schema":{"format":"date-time","example":"2026-06-01T00:00:00+07:00","type":"string"}},{"name":"to","required":false,"in":"query","description":"ถึง (รวม) — ISO-8601 พร้อม timezone","schema":{"format":"date-time","example":"2026-06-30T23:59:59+07:00","type":"string"}},{"name":"cursor","required":false,"in":"query","description":"ค่า `nextCursor` จากหน้าก่อน","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","schema":{"minimum":1,"maximum":100,"default":50,"type":"integer"}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"description":"id ของคำขอ — ค่าเดียวกับ `meta.requestId` / `requestId` ใน error","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"เพดานคำขอ/นาที **ต่อ IP** ของ endpoint นี้ (120 · `POST /verify` 240)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"เหลือในหน้าต่างปัจจุบัน (ต่อ IP)","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"**วินาที** จนกว่าหน้าต่างจะรีเซ็ต (ไม่ใช่ epoch)","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","meta"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/TransactionList"},"meta":{"type":"object","properties":{"requestId":{"type":"string","example":"3b1f0c9e-6a2d-4f5e-8b7c-1d2e3f4a5b6c"},"timestamp":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"request ไม่ถูกรูปแบบ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":400,"error":"Bad Request","message":"limit must not be greater than 100"}}}}},"401":{"description":"ไม่มี header `Authorization: Bearer …` (`Missing API key — …`) หรือคีย์ผิด/ถูก revoke (`Invalid or revoked API key`) · `X-API-Key` และ Basic auth ใช้ไม่ได้ · revoke มีผลทันที ไม่มีช่วง cache","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":401,"error":"Unauthorized","message":"Invalid or revoked API key"}}}}},"403":{"description":"scope ไม่พอ — ต้องมี `read` (`error: \"Forbidden\"` · `message: \"API key missing scope(s): read\"` · ไม่มี `code`) · IP ไม่อยู่ใน allowlist ของคีย์ (`code: IP_NOT_ALLOWED`) · หรือแพ็กปัจจุบันไม่รวม REST API (`code: PLAN_API_ACCESS_REQUIRED` — ตัวอย่างด้านล่าง)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":403,"error":"PlanFeatureRequired","code":"PLAN_API_ACCESS_REQUIRED","feature":"apiAccess","message":"แพ็กปัจจุบันยังไม่รวม REST API และ Webhook — อัปเกรดเป็น Starter ขึ้นไปเพื่อเปิดใช้"}}}}},"405":{"description":"method ผิดบน path ที่มีอยู่ (`code: METHOD_NOT_ALLOWED`) — header `Allow` บอก method ที่ใช้ได้ · path ที่ไม่มีอยู่ = 404","headers":{"Allow":{"description":"method ที่ path นี้รับ เช่น `POST`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":405,"error":"MethodNotAllowed","code":"METHOD_NOT_ALLOWED","message":"Method GET is not allowed on this path — use POST"}}}}},"429":{"description":"`code: RATE_LIMITED` = เกิน rate limit (120/นาที ต่อคีย์) — รอตาม header `Retry-After` (วินาที) · `code: SPEND_CAP_EXCEEDED` (เฉพาะ verify) = ถึงเพดานต่อคีย์ที่ร้านตั้งเอง ⇒ **อย่า retry วน** รอรอบใหม่ตาม `Retry-After` · `code: ASYNC_QUEUE_FULL` (เฉพาะ `POST /verify/async`) = งานรอตรวจของร้านเต็ม","headers":{"Retry-After":{"description":"วินาทีที่ต้องรอ","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":429,"error":"TooManyRequests","code":"RATE_LIMITED","message":"ส่งบ่อยเกินไป กรุณารออีก 42 วินาที","retryAfterSec":42}}}}},"500":{"description":"ระบบเราขัดข้องชั่วคราว (รวม 502/503/504) — ลองใหม่แบบเว้นช่วง · ส่งรูปเดิมซ้ำปลอดภัย ไม่หักโควตาซ้ำ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":500,"error":"InternalServerError","message":"Internal server error"}}}}}},"security":[{"apiKey":[]}],"summary":"ลิสต์ผลตรวจสลิปของร้าน","tags":["public-api"]}},"/api/v1/transactions/{id}":{"get":{"description":"ใช้คู่กับ webhook — รับ `data.transactionId` แล้วมาดึงรายละเอียดเต็ม","operationId":"getTransaction","parameters":[{"name":"id","required":true,"in":"path","description":"`id` จาก `POST /verify` หรือ `transactionId` จาก webhook","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"description":"id ของคำขอ — ค่าเดียวกับ `meta.requestId` / `requestId` ใน error","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"เพดานคำขอ/นาที **ต่อ IP** ของ endpoint นี้ (120 · `POST /verify` 240)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"เหลือในหน้าต่างปัจจุบัน (ต่อ IP)","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"**วินาที** จนกว่าหน้าต่างจะรีเซ็ต (ไม่ใช่ epoch)","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","meta"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Transaction"},"meta":{"type":"object","properties":{"requestId":{"type":"string","example":"3b1f0c9e-6a2d-4f5e-8b7c-1d2e3f4a5b6c"},"timestamp":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"ไม่มี header `Authorization: Bearer …` (`Missing API key — …`) หรือคีย์ผิด/ถูก revoke (`Invalid or revoked API key`) · `X-API-Key` และ Basic auth ใช้ไม่ได้ · revoke มีผลทันที ไม่มีช่วง cache","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":401,"error":"Unauthorized","message":"Invalid or revoked API key"}}}}},"403":{"description":"scope ไม่พอ — ต้องมี `read` (`error: \"Forbidden\"` · `message: \"API key missing scope(s): read\"` · ไม่มี `code`) · IP ไม่อยู่ใน allowlist ของคีย์ (`code: IP_NOT_ALLOWED`) · หรือแพ็กปัจจุบันไม่รวม REST API (`code: PLAN_API_ACCESS_REQUIRED` — ตัวอย่างด้านล่าง)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":403,"error":"PlanFeatureRequired","code":"PLAN_API_ACCESS_REQUIRED","feature":"apiAccess","message":"แพ็กปัจจุบันยังไม่รวม REST API และ Webhook — อัปเกรดเป็น Starter ขึ้นไปเพื่อเปิดใช้"}}}}},"404":{"description":"ไม่พบรายการ (หรือเป็นของร้านอื่น)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":404,"error":"Not Found","message":"Transaction not found"}}}}},"405":{"description":"method ผิดบน path ที่มีอยู่ (`code: METHOD_NOT_ALLOWED`) — header `Allow` บอก method ที่ใช้ได้ · path ที่ไม่มีอยู่ = 404","headers":{"Allow":{"description":"method ที่ path นี้รับ เช่น `POST`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":405,"error":"MethodNotAllowed","code":"METHOD_NOT_ALLOWED","message":"Method GET is not allowed on this path — use POST"}}}}},"429":{"description":"`code: RATE_LIMITED` = เกิน rate limit (120/นาที ต่อคีย์) — รอตาม header `Retry-After` (วินาที) · `code: SPEND_CAP_EXCEEDED` (เฉพาะ verify) = ถึงเพดานต่อคีย์ที่ร้านตั้งเอง ⇒ **อย่า retry วน** รอรอบใหม่ตาม `Retry-After` · `code: ASYNC_QUEUE_FULL` (เฉพาะ `POST /verify/async`) = งานรอตรวจของร้านเต็ม","headers":{"Retry-After":{"description":"วินาทีที่ต้องรอ","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":429,"error":"TooManyRequests","code":"RATE_LIMITED","message":"ส่งบ่อยเกินไป กรุณารออีก 42 วินาที","retryAfterSec":42}}}}},"500":{"description":"ระบบเราขัดข้องชั่วคราว (รวม 502/503/504) — ลองใหม่แบบเว้นช่วง · ส่งรูปเดิมซ้ำปลอดภัย ไม่หักโควตาซ้ำ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":500,"error":"InternalServerError","message":"Internal server error"}}}}}},"security":[{"apiKey":[]}],"summary":"ดึงผลตรวจสลิป 1 รายการ","tags":["public-api"]}},"/api/v1/transactions/{id}/receipt.png":{"get":{"description":"เฉพาะรายการที่ `status: SUCCESS` · PNG กว้าง 784px · มี QR + เลขยืนยัน (`receipt.code`) ที่สแกนแล้วเปิดหน้าเช็คบน slip-bolt.com · สร้างใหม่ทุกครั้งที่ขอ ไม่เก็บไฟล์ — เก็บรูปไว้เองถ้าต้องใช้ซ้ำ","operationId":"getReceiptImage","parameters":[{"name":"id","required":true,"in":"path","description":"`id` จาก `POST /verify` หรือ `transactionId` จาก webhook","schema":{"type":"string"}},{"name":"maskSender","required":false,"in":"query","description":"ปิดชื่อผู้โอนบางส่วน (\"สมชาย ใจ***\") และไม่พิมพ์เลขบัญชีผู้โอน — ใช้เมื่อจะโพสต์รูปในที่ที่หลายคนเห็น (กลุ่มแชต ฯลฯ)","schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"description":"รูป PNG","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"ไม่มี header `Authorization: Bearer …` (`Missing API key — …`) หรือคีย์ผิด/ถูก revoke (`Invalid or revoked API key`) · `X-API-Key` และ Basic auth ใช้ไม่ได้ · revoke มีผลทันที ไม่มีช่วง cache","content":{"image/png":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":401,"error":"Unauthorized","message":"Invalid or revoked API key"}}}}},"403":{"description":"scope ไม่พอ — ต้องมี `read` (`error: \"Forbidden\"` · `message: \"API key missing scope(s): read\"` · ไม่มี `code`) · IP ไม่อยู่ใน allowlist ของคีย์ (`code: IP_NOT_ALLOWED`) · หรือแพ็กปัจจุบันไม่รวม REST API (`code: PLAN_API_ACCESS_REQUIRED` — ตัวอย่างด้านล่าง)","content":{"image/png":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":403,"error":"PlanFeatureRequired","code":"PLAN_API_ACCESS_REQUIRED","feature":"apiAccess","message":"แพ็กปัจจุบันยังไม่รวม REST API และ Webhook — อัปเกรดเป็น Starter ขึ้นไปเพื่อเปิดใช้"}}}}},"404":{"description":"ไม่พบรายการ (หรือเป็นของร้านอื่น)","content":{"image/png":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":404,"error":"Not Found","message":"Transaction not found"}}}}},"405":{"description":"method ผิดบน path ที่มีอยู่ (`code: METHOD_NOT_ALLOWED`) — header `Allow` บอก method ที่ใช้ได้ · path ที่ไม่มีอยู่ = 404","headers":{"Allow":{"description":"method ที่ path นี้รับ เช่น `POST`","schema":{"type":"string"}}},"content":{"image/png":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":405,"error":"MethodNotAllowed","code":"METHOD_NOT_ALLOWED","message":"Method GET is not allowed on this path — use POST"}}}}},"409":{"description":"ออกใบยืนยันไม่ได้ — สลิปไม่ได้ผ่านการตรวจ (`status` ไม่ใช่ `SUCCESS`) หรือข้อมูลถูกลบตามระยะเก็บข้อมูลของร้านแล้ว","content":{"image/png":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":409,"error":"Conflict","message":"ออกใบยืนยันได้เฉพาะสลิปที่ตรวจผ่านแล้วเท่านั้น"}}}}},"429":{"description":"`code: RATE_LIMITED` = เกิน rate limit (120/นาที ต่อคีย์) — รอตาม header `Retry-After` (วินาที) · `code: SPEND_CAP_EXCEEDED` (เฉพาะ verify) = ถึงเพดานต่อคีย์ที่ร้านตั้งเอง ⇒ **อย่า retry วน** รอรอบใหม่ตาม `Retry-After` · `code: ASYNC_QUEUE_FULL` (เฉพาะ `POST /verify/async`) = งานรอตรวจของร้านเต็ม","headers":{"Retry-After":{"description":"วินาทีที่ต้องรอ","schema":{"type":"integer"}}},"content":{"image/png":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":429,"error":"TooManyRequests","code":"RATE_LIMITED","message":"ส่งบ่อยเกินไป กรุณารออีก 42 วินาที","retryAfterSec":42}}}}},"500":{"description":"ระบบเราขัดข้องชั่วคราว (รวม 502/503/504) — ลองใหม่แบบเว้นช่วง · ส่งรูปเดิมซ้ำปลอดภัย ไม่หักโควตาซ้ำ","content":{"image/png":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":500,"error":"InternalServerError","message":"Internal server error"}}}}}},"security":[{"apiKey":[]}],"summary":"รูปใบยืนยันการตรวจสลิป (PNG)","tags":["public-api"]}},"/api/v1/transactions/{id}/receipt.pdf":{"get":{"description":"หน้าตาเดียวกับ `receipt.png` (หน้าเดียว ขนาดเท่าการ์ด) · เฉพาะรายการที่ `status: SUCCESS`","operationId":"getReceiptPdf","parameters":[{"name":"id","required":true,"in":"path","description":"`id` จาก `POST /verify` หรือ `transactionId` จาก webhook","schema":{"type":"string"}},{"name":"maskSender","required":false,"in":"query","description":"ปิดชื่อผู้โอนบางส่วน (\"สมชาย ใจ***\") และไม่พิมพ์เลขบัญชีผู้โอน — ใช้เมื่อจะโพสต์รูปในที่ที่หลายคนเห็น (กลุ่มแชต ฯลฯ)","schema":{"default":false,"type":"boolean"}}],"responses":{"200":{"description":"ไฟล์ PDF","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"ไม่มี header `Authorization: Bearer …` (`Missing API key — …`) หรือคีย์ผิด/ถูก revoke (`Invalid or revoked API key`) · `X-API-Key` และ Basic auth ใช้ไม่ได้ · revoke มีผลทันที ไม่มีช่วง cache","content":{"application/pdf":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":401,"error":"Unauthorized","message":"Invalid or revoked API key"}}}}},"403":{"description":"scope ไม่พอ — ต้องมี `read` (`error: \"Forbidden\"` · `message: \"API key missing scope(s): read\"` · ไม่มี `code`) · IP ไม่อยู่ใน allowlist ของคีย์ (`code: IP_NOT_ALLOWED`) · หรือแพ็กปัจจุบันไม่รวม REST API (`code: PLAN_API_ACCESS_REQUIRED` — ตัวอย่างด้านล่าง)","content":{"application/pdf":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":403,"error":"PlanFeatureRequired","code":"PLAN_API_ACCESS_REQUIRED","feature":"apiAccess","message":"แพ็กปัจจุบันยังไม่รวม REST API และ Webhook — อัปเกรดเป็น Starter ขึ้นไปเพื่อเปิดใช้"}}}}},"404":{"description":"ไม่พบรายการ (หรือเป็นของร้านอื่น)","content":{"application/pdf":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":404,"error":"Not Found","message":"Transaction not found"}}}}},"405":{"description":"method ผิดบน path ที่มีอยู่ (`code: METHOD_NOT_ALLOWED`) — header `Allow` บอก method ที่ใช้ได้ · path ที่ไม่มีอยู่ = 404","headers":{"Allow":{"description":"method ที่ path นี้รับ เช่น `POST`","schema":{"type":"string"}}},"content":{"application/pdf":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":405,"error":"MethodNotAllowed","code":"METHOD_NOT_ALLOWED","message":"Method GET is not allowed on this path — use POST"}}}}},"409":{"description":"ออกใบยืนยันไม่ได้ — สลิปไม่ได้ผ่านการตรวจ (`status` ไม่ใช่ `SUCCESS`) หรือข้อมูลถูกลบตามระยะเก็บข้อมูลของร้านแล้ว","content":{"application/pdf":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":409,"error":"Conflict","message":"ออกใบยืนยันได้เฉพาะสลิปที่ตรวจผ่านแล้วเท่านั้น"}}}}},"429":{"description":"`code: RATE_LIMITED` = เกิน rate limit (120/นาที ต่อคีย์) — รอตาม header `Retry-After` (วินาที) · `code: SPEND_CAP_EXCEEDED` (เฉพาะ verify) = ถึงเพดานต่อคีย์ที่ร้านตั้งเอง ⇒ **อย่า retry วน** รอรอบใหม่ตาม `Retry-After` · `code: ASYNC_QUEUE_FULL` (เฉพาะ `POST /verify/async`) = งานรอตรวจของร้านเต็ม","headers":{"Retry-After":{"description":"วินาทีที่ต้องรอ","schema":{"type":"integer"}}},"content":{"application/pdf":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":429,"error":"TooManyRequests","code":"RATE_LIMITED","message":"ส่งบ่อยเกินไป กรุณารออีก 42 วินาที","retryAfterSec":42}}}}},"500":{"description":"ระบบเราขัดข้องชั่วคราว (รวม 502/503/504) — ลองใหม่แบบเว้นช่วง · ส่งรูปเดิมซ้ำปลอดภัย ไม่หักโควตาซ้ำ","content":{"application/pdf":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":500,"error":"InternalServerError","message":"Internal server error"}}}}}},"security":[{"apiKey":[]}],"summary":"ใบยืนยันการตรวจสลิป (PDF)","tags":["public-api"]}},"/api/v1/me":{"get":{"description":"ไม่เปลืองโควตา · เหมาะเรียกตอนบูตระบบเพื่อตรวจว่าคีย์ถูกร้านและ scope ครบ","operationId":"getAccount","parameters":[],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"description":"id ของคำขอ — ค่าเดียวกับ `meta.requestId` / `requestId` ใน error","schema":{"type":"string","format":"uuid"}},"X-RateLimit-Limit":{"description":"เพดานคำขอ/นาที **ต่อ IP** ของ endpoint นี้ (120 · `POST /verify` 240)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"เหลือในหน้าต่างปัจจุบัน (ต่อ IP)","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"**วินาที** จนกว่าหน้าต่างจะรีเซ็ต (ไม่ใช่ epoch)","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","required":["success","data","meta"],"properties":{"success":{"type":"boolean","enum":[true]},"data":{"$ref":"#/components/schemas/Account"},"meta":{"type":"object","properties":{"requestId":{"type":"string","example":"3b1f0c9e-6a2d-4f5e-8b7c-1d2e3f4a5b6c"},"timestamp":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"ไม่มี header `Authorization: Bearer …` (`Missing API key — …`) หรือคีย์ผิด/ถูก revoke (`Invalid or revoked API key`) · `X-API-Key` และ Basic auth ใช้ไม่ได้ · revoke มีผลทันที ไม่มีช่วง cache","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":401,"error":"Unauthorized","message":"Invalid or revoked API key"}}}}},"403":{"description":"scope ไม่พอ — ต้องมี `read` (`error: \"Forbidden\"` · `message: \"API key missing scope(s): read\"` · ไม่มี `code`) · IP ไม่อยู่ใน allowlist ของคีย์ (`code: IP_NOT_ALLOWED`) · หรือแพ็กปัจจุบันไม่รวม REST API (`code: PLAN_API_ACCESS_REQUIRED` — ตัวอย่างด้านล่าง)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":403,"error":"PlanFeatureRequired","code":"PLAN_API_ACCESS_REQUIRED","feature":"apiAccess","message":"แพ็กปัจจุบันยังไม่รวม REST API และ Webhook — อัปเกรดเป็น Starter ขึ้นไปเพื่อเปิดใช้"}}}}},"405":{"description":"method ผิดบน path ที่มีอยู่ (`code: METHOD_NOT_ALLOWED`) — header `Allow` บอก method ที่ใช้ได้ · path ที่ไม่มีอยู่ = 404","headers":{"Allow":{"description":"method ที่ path นี้รับ เช่น `POST`","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":405,"error":"MethodNotAllowed","code":"METHOD_NOT_ALLOWED","message":"Method GET is not allowed on this path — use POST"}}}}},"429":{"description":"`code: RATE_LIMITED` = เกิน rate limit (120/นาที ต่อคีย์) — รอตาม header `Retry-After` (วินาที) · `code: SPEND_CAP_EXCEEDED` (เฉพาะ verify) = ถึงเพดานต่อคีย์ที่ร้านตั้งเอง ⇒ **อย่า retry วน** รอรอบใหม่ตาม `Retry-After` · `code: ASYNC_QUEUE_FULL` (เฉพาะ `POST /verify/async`) = งานรอตรวจของร้านเต็ม","headers":{"Retry-After":{"description":"วินาทีที่ต้องรอ","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":429,"error":"TooManyRequests","code":"RATE_LIMITED","message":"ส่งบ่อยเกินไป กรุณารออีก 42 วินาที","retryAfterSec":42}}}}},"500":{"description":"ระบบเราขัดข้องชั่วคราว (รวม 502/503/504) — ลองใหม่แบบเว้นช่วง · ส่งรูปเดิมซ้ำปลอดภัย ไม่หักโควตาซ้ำ","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorEnvelope"}],"example":{"success":false,"statusCode":500,"error":"InternalServerError","message":"Internal server error"}}}}}},"security":[{"apiKey":[]}],"summary":"ข้อมูลร้าน · คีย์ · แพ็ก · โควตาคงเหลือ","tags":["public-api"]}},"/api/v1/health":{"get":{"operationId":"getHealth","summary":"Liveness","description":"ไม่ต้องใช้คีย์ · ไม่แตะ dependency · ไม่นับ rate limit ของคีย์","tags":["public-api"],"security":[],"responses":{"200":{"description":"พร้อม","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"timestamp":{"type":"string","format":"date-time"}}}}}}}}},"/api/v1/ready":{"get":{"operationId":"getReady","summary":"Readiness","description":"ไม่ต้องใช้คีย์ · เช็คฐานข้อมูล / คิว / ระบบตรวจสลิป (แคชผลสั้น ๆ) · 503 เมื่อมีตัวล้ม","tags":["public-api"],"security":[],"responses":{"200":{"description":"พร้อม","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"checks":{"type":"object","additionalProperties":{"type":"string","enum":["ok","fail"]}}}}}}},"503":{"description":"บาง dependency ล้ม — ดู `checks`"}}}}},"info":{"title":"SlipBolt Public API","description":"REST API สำหรับเชื่อมระบบของคุณ (POS / ERP / บอท) เข้ากับ SlipBolt — ตรวจสลิป · อ่านโควตา · ดึงผลย้อนหลัง\n\nคู่มือพร้อมตัวอย่างโค้ด: https://docs.slip-bolt.com/docs/api","version":"1","contact":{}},"tags":[{"name":"public-api","description":"Public API (ยืนยันตัวตนด้วย API key)"}],"servers":[{"url":"https://api.slip-bolt.com","description":"Production"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"sk_live_… | sk_test_…","description":"API key จาก Settings → API Keys · ส่งเป็น `Authorization: Bearer sk_live_…`"}},"schemas":{"Account":{"type":"object","properties":{"shop":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"ร้าน ข."}}},"apiKey":{"type":"object","properties":{"name":{"type":"string","example":"POS หน้าร้าน"},"prefix":{"type":"string","example":"sk_live_a1b2c3"},"environment":{"type":"string","enum":["live","test"]},"scopes":{"type":"array","items":{"type":"string","enum":["verify","read","webhook"]}},"lastUsedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"},"spendCap":{"type":"object","description":"เพดานการหักโควตาที่ร้านตั้งต่อคีย์ (null = ไม่จำกัด) · นับเฉพาะคำขอที่ `quotaCharged: true` · \"วัน\" = เวลาไทย","properties":{"perHour":{"type":"integer","nullable":true,"example":500},"perDay":{"type":"integer","nullable":true,"example":5000},"usedThisHour":{"type":"integer","example":12},"usedToday":{"type":"integer","example":340}}},"ipAllowlist":{"type":"array","description":"IP / CIDR ที่คีย์นี้ใช้ได้ — ว่าง = ทุก IP · IP อื่นได้ 403 `IP_NOT_ALLOWED`","items":{"type":"string","example":"203.0.113.10"}}}},"plan":{"type":"object","properties":{"code":{"type":"string","nullable":true,"example":"STARTER"},"name":{"type":"string","nullable":true,"example":"Starter"}}},"quota":{"type":"object","description":"โควตาสลิปรายเดือนของร้าน (pool เดียวกับ LINE)","properties":{"used":{"type":"integer","example":132,"description":"สลิปที่นับเข้าโควตาแพ็กเดือนนี้ (ไม่รวมใบที่จ่ายด้วยเครดิต)"},"limit":{"type":"integer","example":500},"remaining":{"type":"integer","example":368},"percentUsed":{"type":"integer","example":26},"allowOverage":{"type":"boolean","example":false},"planCode":{"type":"string","nullable":true,"example":"STARTER"},"periodMonth":{"type":"string","example":"2026-06"},"daysUntilReset":{"type":"integer","example":13},"overage":{"type":"integer","description":"ใบที่เกินเพดาน (เฉพาะแพ็กที่เปิด allowOverage)","example":0}}},"credits":{"type":"object","description":"เครดิตสลิปแบบจ่ายตามใช้ — ตัดเมื่อโควตาแพ็กหมด (หรือร้านไม่มีแพ็ก) ก่อนคิดส่วนเกิน · อายุ 12 เดือน","properties":{"available":{"type":"integer","example":250},"usedThisMonth":{"type":"integer","example":30},"nextExpiry":{"type":"object","nullable":true,"properties":{"at":{"type":"string","format":"date-time"},"amount":{"type":"integer","example":250}}}}},"testQuota":{"type":"object","description":"เพดานของคีย์ `sk_test_` เดือนนี้ · `used: null` = วัดไม่ได้ชั่วคราว (คีย์ test จะกินโควตาปกติแทน)","properties":{"used":{"type":"integer","nullable":true,"example":12},"limit":{"type":"integer","example":200},"remaining":{"type":"integer","nullable":true,"example":188}}},"rateLimit":{"type":"object","properties":{"perKeyPerMinute":{"type":"integer","example":120},"verifyPerMinute":{"type":"integer","example":240}}},"payeeAccounts":{"type":"array","description":"บัญชีรับเงินของร้านที่ใช้เทียบผู้รับบนสลิป (`RECEIVER_MISMATCH` ถ้าไม่ตรง)","items":{"type":"object","properties":{"bankCode":{"type":"string","example":"kbank","description":"ชื่อย่อ (slug) ที่ร้านเลือกตอนเพิ่มบัญชี เช่น `kbank` · `scb` · `promptpay` — **คนละรูปแบบกับ `bankCode` 3 หลักในผลตรวจ** เทียบกันด้วยตารางที่ https://docs.slip-bolt.com/docs/api#รหัสธนาคาร"},"kind":{"type":"string","enum":["BANK","PROMPTPAY","WALLET"]},"holder":{"type":"string","example":"ร้าน ข."},"accountLast4":{"type":"string","example":"5678"},"isPrimary":{"type":"boolean"},"status":{"type":"string","enum":["PENDING","ACTIVE","DISABLED"]}}}}}},"AsyncVerification":{"type":"object","required":["id","status","createdAt","statusUrl"],"properties":{"id":{"type":"string","example":"vrf_3f9a1c0b7e2d4a6f8b1c2d3e","description":"id ของงาน — อายุ 24 ชม."},"status":{"type":"string","enum":["PENDING","PROCESSING","COMPLETED","FAILED"],"description":"`COMPLETED` = ตรวจจบ ผลอยู่ใน `result` (สถานะสลิปดูที่ `result.status` — ไม่ผ่านก็ยังเป็น COMPLETED) · `FAILED` = ไม่ได้ตรวจ ดู `error` · ไม่หักโควตา","example":"PENDING"},"createdAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time","nullable":true},"statusUrl":{"type":"string","example":"/api/v1/verifications/vrf_3f9a1c0b7e2d4a6f8b1c2d3e"},"result":{"allOf":[{"$ref":"#/components/schemas/VerifyResult"}],"nullable":true,"description":"รูปเดียวกับ `data` ของ `POST /verify` ทุกฟิลด์ · `null` จนกว่าจะ `COMPLETED`"},"error":{"type":"object","nullable":true,"required":["httpStatus","code","message"],"properties":{"httpStatus":{"type":"integer","example":402,"description":"status ที่ `POST /verify` จะตอบในกรณีเดียวกัน"},"code":{"type":"string","example":"QUOTA_EXCEEDED","description":"`QUOTA_EXCEEDED` · `TEST_QUOTA_EXCEEDED` · `SPEND_CAP_EXCEEDED` · `VERIFY_TIMEOUT` · `API_KEY_REVOKED` · `INTERNAL_ERROR`"},"message":{"type":"string"}}}}},"ErrorEnvelope":{"type":"object","required":["success","statusCode","error","message"],"properties":{"success":{"type":"boolean","enum":[false]},"statusCode":{"type":"integer"},"error":{"type":"string","description":"ชื่อชนิด error (เช่น `Unauthorized` · `PaymentRequired`) — แตกกิ่งด้วย `statusCode` + `code` ดีกว่า"},"message":{"description":"ข้อความสำหรับคนอ่าน · เป็น **array** เมื่อ validation ไม่ผ่านหลายข้อ (400)","oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"code":{"type":"string","description":"โค้ดสัญญา (SCREAMING_SNAKE) — **มีเฉพาะบาง error**: `QUOTA_EXCEEDED` · `TEST_QUOTA_EXCEEDED` · `PLAN_API_ACCESS_REQUIRED` · `IP_NOT_ALLOWED` · `RATE_LIMITED` · `SPEND_CAP_EXCEEDED` · `ASYNC_QUEUE_FULL` · `ASYNC_UNAVAILABLE` · `PAYLOAD_TOO_LARGE` · `METHOD_NOT_ALLOWED` · `IDEMPOTENCY_KEY_INVALID` · `IDEMPOTENCY_IN_PROGRESS` · `IDEMPOTENCY_KEY_REUSED` · error อื่น (400/401/403 scope/404/429) ไม่มีฟิลด์นี้ ให้แตกกิ่งด้วย `statusCode`"},"path":{"type":"string"},"requestId":{"type":"string","description":"ค่าเดียวกับ header `X-Request-Id` — แนบมาเมื่อติดต่อทีมงาน"},"timestamp":{"type":"string","format":"date-time"},"retryAfterSec":{"type":"integer","description":"มากับ 429 — ค่าเดียวกับ header `Retry-After`"},"feature":{"type":"string","description":"มากับ `PLAN_API_ACCESS_REQUIRED`","example":"apiAccess"},"quota":{"$ref":"#/components/schemas/QuotaSnapshot"},"testQuota":{"type":"object","properties":{"used":{"type":"integer"},"limit":{"type":"integer"}}}}},"Quota":{"type":"object","properties":{"used":{"type":"integer","example":132,"description":"สลิปที่นับเข้าโควตาแพ็กเดือนนี้ (ไม่รวมใบที่จ่ายด้วยเครดิต)"},"limit":{"type":"integer","example":500},"remaining":{"type":"integer","example":368},"percentUsed":{"type":"integer","example":26},"allowOverage":{"type":"boolean","example":false},"planCode":{"type":"string","nullable":true,"example":"STARTER"},"periodMonth":{"type":"string","example":"2026-06"},"daysUntilReset":{"type":"integer","example":13},"creditsAvailable":{"type":"integer","example":0,"description":"เครดิตสลิปคงเหลือ — ใช้ต่อเมื่อโควตาแพ็กหมด (หรือร้านไม่มีแพ็ก) ก่อนคิดส่วนเกิน"}}},"QuotaSnapshot":{"type":"object","required":["used","limit","remaining"],"properties":{"used":{"type":"integer","example":132},"limit":{"type":"integer","example":500},"remaining":{"type":"integer","example":368}}},"Transaction":{"type":"object","required":["id","status","verified","source","amount","warnings","createdAt"],"properties":{"id":{"type":"string","format":"uuid","example":"8f3c2a1e-5b7d-4c9a-9e21-0d6f4b8a3c11"},"status":{"type":"string","enum":["SUCCESS","DUPLICATE","INVALID","AMOUNT_MISMATCH","RECEIVER_MISMATCH","FRAUD","PROVIDER_ERROR","QUOTA_EXCEEDED"],"example":"SUCCESS","description":"ผลตรวจ — มีแค่ `SUCCESS` ที่ `verified: true` และหักโควตา/เครดิต · รายละเอียดต่อสถานะ: https://docs.slip-bolt.com/docs/api#transaction-status"},"verified":{"type":"boolean","description":"`true` เฉพาะเมื่อ `status === \"SUCCESS\"`","example":true},"source":{"type":"string","enum":["LINE","API","DASHBOARD","LIFF"],"example":"API"},"transRef":{"type":"string","nullable":true,"description":"เลขอ้างอิงธนาคาร · `null` เมื่ออ่านจากสลิปไม่ได้","example":"015170106978BTF01234"},"amount":{"type":"number","example":250},"bankCode":{"type":"string","nullable":true,"description":"รหัสธนาคารผู้โอน 3 หลัก (ตามที่ธนาคารส่งมา) — ตาราง: https://docs.slip-bolt.com/docs/api#รหัสธนาคาร","example":"002"},"bankName":{"type":"string","nullable":true,"example":"ธนาคารกรุงเทพ"},"senderName":{"type":"string","nullable":true,"example":"นาย ก. ตัวอย่าง"},"senderAccount":{"type":"string","nullable":true,"example":"xxx-x-x1234-x"},"receiverName":{"type":"string","nullable":true,"example":"ร้าน ข."},"receiverAccount":{"type":"string","nullable":true,"description":"บัญชีธนาคารที่เงินเข้า — โอนผ่านพร้อมเพย์จะเป็นบัญชีที่ผูกกับพร้อมเพย์ ไม่ใช่เลขที่เห็นบนรูปสลิป","example":"xxx-x-x5678-x"},"receiverProxyAccount":{"type":"string","nullable":true,"description":"เลขพร้อมเพย์ของผู้รับ (เบอร์/เลขบัตร/e-Wallet) เมื่อโอนผ่านพร้อมเพย์ — เลขที่เห็นบนรูปสลิป · `null` เมื่อโอนเข้าบัญชีตรง","example":"xxx-xxx-6053"},"receiverBankCode":{"type":"string","nullable":true,"description":"รหัสธนาคารผู้รับ 3 หลัก","example":"014"},"receiverBankName":{"type":"string","nullable":true,"example":"ธนาคารไทยพาณิชย์"},"transDate":{"type":"string","format":"date-time","nullable":true,"example":"2026-06-17T03:21:05.000Z"},"errorCode":{"type":"string","nullable":true,"description":"โค้ดเหตุผล/คำเตือนของผลตรวจ — ค่าที่มีตอนนี้: `STALE_SLIP` · `NO_PAYEE_CONFIGURED` · `INVALID` · `FRAUD` · `PROVIDER_ERROR` · `QUOTA_EXCEEDED` · **ไม่ใช่ `null` เสมอไปเมื่อ `SUCCESS`**: `STALE_SLIP` มากับ `SUCCESS` ได้ (คำเตือนสลิปเก่า โหมด alert) · ไม่ประกาศเป็น enum โดยเจตนา — อาจมีค่าใหม่เพิ่มโดยไม่ถือเป็น breaking change ให้ถือค่าที่ไม่รู้จักเป็น \"ไม่ผ่านด้วยเหตุอื่น\"","example":"STALE_SLIP"},"errorMessage":{"type":"string","nullable":true,"description":"ข้อความสำหรับคนอ่าน — ปรับได้โดยไม่ถือเป็น breaking change ห้ามใช้แตกกิ่ง"},"warnings":{"type":"array","description":"คำเตือนของสลิปที่ **ผ่าน** (`SUCCESS`) — ว่างเมื่อไม่มี หรือเมื่อไม่ผ่าน (เหตุผลอยู่ที่ `errorCode`) · ตอนนี้มีตัวเดียว `STALE_SLIP` (ค่าเดียวกับ `errorCode` — คงไว้ทั้งคู่เพื่อไม่ทำโค้ดเดิมพัง)","items":{"type":"object","required":["code"],"properties":{"code":{"type":"string","example":"STALE_SLIP"},"message":{"type":"string","nullable":true,"example":"สลิปลงวันที่ย้อนหลังเกิน 24 ชม. (เก่า 25 ชม.)"}}}},"createdAt":{"type":"string","format":"date-time","example":"2026-06-17T03:21:06.412Z"},"receipt":{"type":"object","nullable":true,"description":"ใบยืนยันการตรวจสลิป — มีเฉพาะ `SUCCESS` (`null` เมื่อไม่ผ่าน หรือข้อมูลถูกลบตามระยะเก็บของร้านแล้ว) · รูป/PDF: `GET /transactions/{id}/receipt.png` · `receipt.pdf`","required":["code","checkUrl"],"properties":{"code":{"type":"string","description":"เลขยืนยันที่พิมพ์บนใบ — คงที่ต่อรายการ","example":"SB-7K2F-9QX4"},"checkUrl":{"type":"string","format":"uri","description":"ลิงก์ใน QR บนใบ — เปิดหน้าเช็คสาธารณะที่โชว์ข้อมูลปิดบางส่วนไว้เทียบกับรูป","example":"https://slip-bolt.com/c/PyuMHlp9Tp-wwdLj9KW2xzQ1dGhVb2xr"}}}}},"TransactionList":{"type":"object","required":["items","nextCursor"],"properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Transaction"}},"nextCursor":{"type":"string","nullable":true,"description":"ส่งเป็น `cursor` เพื่อดึงหน้าถัดไป · `null` = หมดแล้ว"}}},"VerifyResult":{"allOf":[{"$ref":"#/components/schemas/Transaction"},{"type":"object","required":["duplicate","cached","quotaCharged","environment","quota"],"properties":{"duplicate":{"type":"boolean","description":"เลขอ้างอิงนี้เคยตรวจแล้ว (status = DUPLICATE)"},"cached":{"type":"boolean","description":"ส่งรูปเดิมซ้ำภายใน 24 ชม. → คืนผลของรายการเดิม ไม่ตรวจซ้ำและไม่หักโควตา"},"quotaCharged":{"type":"boolean","description":"คำขอนี้หักโควตา/เครดิตของร้านไหม — `true` เฉพาะ `SUCCESS` ที่ตรวจใหม่ด้วยคีย์ live · ไม่ผ่านทุกแบบ · `DUPLICATE` · `cached: true` · คีย์ `sk_test_` = `false`","example":true},"latencyMs":{"type":"integer","nullable":true,"example":480},"environment":{"type":"string","enum":["live","test"]},"quota":{"$ref":"#/components/schemas/QuotaSnapshot"}}}]}}},"security":[{"apiKey":[]}]}