{"openapi":"3.1.0","info":{"title":"TUNGKAO API","version":"1.0.0","summary":"Verify Thai bank transfer slips, record slip deposits and top up credits through one API.","description":"## Basics\n- Base URL: `https://api.tungkao.com`. JSON in, JSON out, UTF-8.\n- Authenticate every `/v1/*` call with `Authorization: Bearer <api_key>` (keys look like `tk_live_…` or `tk_test_…`). Keys are created in the merchant portal and shown once.\n- Every response has `request_id`. Every error has `error.code` (stable, machine-readable), `error.message`, `error.hint` and `error.docs_url`.\n- `success` is `true` only when the business outcome is positive (e.g. slip `VALID`). An HTTP 200 with `success: false` means the call worked but the slip was rejected: read `status`.\n## Credits\n- 1 THB = 100 credits. Each verification costs credits depending on the resulting `status` (see `GET /v1/account` → `slip_prices`; defaults: VALID 250, RECEIVER_MISMATCH 250, AMOUNT_MISMATCH 250, DATE_MISMATCH 250, NOT_FOUND 250, FRAUD 250, EXPIRED 250, DUPLICATE 50, PENDING 0, INVALID_INPUT 0, SERVICE_UNAVAILABLE 0).\n- Credits are reserved before the upstream call and settled after it; `billing.credits_charged` is what this call cost.\n## Slip status values\n- `VALID`: Slip is genuine and passed every check you asked for. The slip is now marked as used for this API key.\n- `RECEIVER_MISMATCH`: Slip is genuine but was paid to a different account than `receivers`.\n- `AMOUNT_MISMATCH`: Slip is genuine but the amount differs from `expected_amount`.\n- `DATE_MISMATCH`: Slip is genuine but older than `max_age_minutes`.\n- `DUPLICATE`: This slip was already accepted (VALID) for this API key before.\n- `NOT_FOUND`: The bank has no such transaction.\n- `FRAUD`: The slip is forged or altered.\n- `PENDING`: The bank has not finished processing. Call again later.\n- `EXPIRED`: The slip is too old for the bank to verify.\n- `INVALID_INPUT`: `qr_payload` is not a slip QR code.\n- `SERVICE_UNAVAILABLE`: All upstream verifiers are down (HTTP 503). Nothing is charged; retry later.\n## Test mode (sandbox)\nTest keys (`tk_test_…`) run in sandbox mode: no real bank lookup and no credits charged; responses carry `\"sandbox\": true`. Control the simulated result with tokens inside `qr_payload`: `AMT=250.50` (amount, default 100.00), `ACC=1234567890` (receiver account), `PP=0812345678` (receiver PromptPay), `BANK=KBANK`, `NAME=…`, `AGE=90` (minutes since transfer), `STATUS=NOT_FOUND|FRAUD|PENDING|EXPIRED|DOWN` (DOWN simulates an outage → `SERVICE_UNAVAILABLE`). The same `qr_payload` twice on one key returns `DUPLICATE`. Deposits and webhooks work the same way. Top-ups always need a real slip.\n## Duplicate protection\nA slip (bank transaction reference) can be `VALID` once per API key. Slips rejected for receiver/amount/age are not marked used, so you can fix the condition and resend.\n## Webhooks\nConfigure a webhook URL per API key in the portal. Deliveries are `POST` with JSON body `{id, type, created_at, data}` and header `X-Tungkao-Signature: t=<unix seconds>,v1=<hex>`.\nVerify: `v1` must equal `HMAC_SHA256(webhook_secret, t + \".\" + raw_body)` in lowercase hex, and `t` must be within 5 minutes of now. Respond with any 2xx within 10 seconds.\nFailed deliveries are retried after 1, 5, 30, 120, 360, 720, 1080 minutes (8 attempts in total). Deliveries can repeat: deduplicate on `id`.\n## Domain verification (one key = one domain)\nEach live key is bound to one website domain. In the portal: API keys → Verify domain → enter your domain, then either upload the file `tungkao-verify-<code>.html` to `https://<domain>/` or add a TXT record `_tungkao-verify.<domain>`, and press Verify. Until verified, calls fail with `DOMAIN_NOT_VERIFIED`. After verification the domain cannot change (create a new key for another site), sign-in redirect URIs and webhook URLs must be on that domain, and no other account can verify it. Server-to-server calls can't prove which site sent them, so also lock the key to your server IPs or enable request signing.\n## Request signing (optional, per key)\nTurn on 'Signed requests' for a key in the portal to get a signing secret (`tksign_…`, shown once). Every request with that key must then carry `X-Tungkao-Timestamp` (unix seconds, within 5 minutes) and `X-Tungkao-Signature` = lowercase hex `HMAC_SHA256(signing_secret, timestamp + \".\" + METHOD + \".\" + path_with_query + \".\" + sha256_hex(raw_body))`. A leaked API key alone is then useless.\n## API key lifetime\nEvery API key, for every product, has a lifetime chosen at creation: 1, 2, 3 or 6 months, 1 year, 2 years, or never. The portal shows a live countdown.\nWe warn 30 days, 7 days and 1 day before expiry and at expiry with the `api_key.expiring` / `api_key.expired` webhooks (`data.stage` is `30d`, `7d`, `1d` or `expired`). After expiry, calls fail with HTTP 403 `KEY_EXPIRED`.\nRenewing in the portal extends from the current expiry (or from now if already expired). The key string does not change, so no code change is needed."},"servers":[{"url":"https://api.tungkao.com"}],"security":[{"apiKey":[]}],"tags":[{"name":"Slip verification"},{"name":"Slip deposit"},{"name":"Credits"},{"name":"Account"},{"name":"Crypto (preview)","description":"Specified and tested, not enabled in production yet"}],"paths":{"/health":{"get":{"operationId":"health","summary":"Liveness check","security":[],"tags":["Account"],"responses":{"200":{"description":"Server is up","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"const":"ok"}}}}}}}}},"/v1/account":{"get":{"operationId":"getAccount","summary":"Credits left, current prices and the key in use","tags":["Account"],"parameters":[{"name":"Accept-Language","in":"header","required":false,"schema":{"type":"string","enum":["th","en","lo","my","zh-CN","ko","ja","vi","km","id"],"default":"th"},"description":"Language of `message` / `hint`. For languages other than th and en, error responses also carry `detail` in English."}],"responses":{"200":{"description":"Account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountResponse"}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/banks":{"get":{"operationId":"listBanks","summary":"Bank codes used in responses and accepted in `receivers[].bank`","tags":["Account"],"parameters":[{"name":"Accept-Language","in":"header","required":false,"schema":{"type":"string","enum":["th","en","lo","my","zh-CN","ko","ja","vi","km","id"],"default":"th"},"description":"Language of `message` / `hint`. For languages other than th and en, error responses also carry `detail` in English."}],"responses":{"200":{"description":"Banks","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BanksResponse"}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/slip/verify":{"post":{"operationId":"verifySlip","summary":"Verify a Thai bank transfer slip","tags":["Slip verification"],"description":"Requires a `slip_verify` key. Checks with the bank network that the slip is genuine, then applies your own checks (receiver, amount, age, duplicate). Returns HTTP 200 for every verdict except `SERVICE_UNAVAILABLE` (503).","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":128},"description":"Any unique string per logical request (e.g. your order id). Repeating a request with the same key returns the first result and does not charge again. Use it when retrying after a timeout."},{"name":"Accept-Language","in":"header","required":false,"schema":{"type":"string","enum":["th","en","lo","my","zh-CN","ko","ja","vi","km","id"],"default":"th"},"description":"Language of `message` / `hint`. For languages other than th and en, error responses also carry `detail` in English."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["qr_payload"],"properties":{"qr_payload":{"type":"string","minLength":10,"maxLength":2000,"description":"The exact text decoded from the QR code printed on the Thai bank transfer slip. Decode the QR on your side; do not send the image."},"expected_amount":{"type":["string","number"],"description":"Amount in THB the slip must equal, e.g. \"500.00\". Omit to skip the amount check.","examples":["500.00"]},"receivers":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/Receiver"},"description":"Accounts that count as yours; the slip passes if it matches any one. Omit to use the receivers configured on the API key."},"max_age_minutes":{"type":"number","exclusiveMinimum":0,"description":"Reject slips paid more than this many minutes ago. Omit to skip the age check."}}},"example":{"qr_payload":"0041000600000101030040220014242082547BPM049885102TH9104xxxx","expected_amount":"500.00","receivers":[{"bank":"KBANK","account_number":"1234567890"}],"max_age_minutes":30}}}},"responses":{"200":{"description":"Verdict (any status except SERVICE_UNAVAILABLE)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyResponse"}}}},"400":{"description":"`VALIDATION_ERROR`: The request body is malformed. `message` says which field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"`INSUFFICIENT_CREDITS`: Not enough credits. Nothing was charged and no provider was called. Top up and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist. / `WRONG_PRODUCT`: The key belongs to another product (e.g. a slip_verify key used on /v1/deposits/slip).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`BODY_TOO_LARGE`: Request body exceeds 64 KB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`status: SERVICE_UNAVAILABLE` — same body shape as 200, nothing charged","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyResponse"}}}}}}},"/v1/deposits/slip":{"post":{"operationId":"createSlipDeposit","summary":"Record a customer deposit paid by bank slip","tags":["Slip deposit"],"description":"Requires a `slip_deposit` key. Verifies the slip against your destination account (from `receivers` in the request, otherwise from the API key — one of them is mandatory), stores a deposit for `customer_ref`, and sends a `deposit.succeeded` / `deposit.failed` / `deposit.pending` webhook. Credit your customer only when `deposit.status` is `succeeded`.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":128},"description":"Any unique string per logical request (e.g. your order id). Repeating a request with the same key returns the first result and does not charge again. Use it when retrying after a timeout."},{"name":"Accept-Language","in":"header","required":false,"schema":{"type":"string","enum":["th","en","lo","my","zh-CN","ko","ja","vi","km","id"],"default":"th"},"description":"Language of `message` / `hint`. For languages other than th and en, error responses also carry `detail` in English."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["qr_payload","customer_ref"],"properties":{"qr_payload":{"type":"string","minLength":10,"maxLength":2000,"description":"The exact text decoded from the QR code printed on the Thai bank transfer slip. Decode the QR on your side; do not send the image."},"expected_amount":{"type":["string","number"],"description":"Amount in THB the slip must equal, e.g. \"500.00\". Omit to skip the amount check.","examples":["500.00"]},"receivers":{"type":"array","maxItems":20,"items":{"$ref":"#/components/schemas/Receiver"},"description":"Accounts that count as yours; the slip passes if it matches any one. Omit to use the receivers configured on the API key."},"max_age_minutes":{"type":"number","exclusiveMinimum":0,"description":"Reject slips paid more than this many minutes ago. Omit to skip the age check."},"customer_ref":{"type":"string","minLength":1,"maxLength":128,"description":"Your own id for the end customer who paid."},"metadata":{"type":"object","description":"Free-form JSON (max 2000 chars) echoed back in the deposit and its webhook."}}},"example":{"qr_payload":"0041000600000101030040220014242082547BPM049885102TH9104xxxx","customer_ref":"user-1001","expected_amount":"300.00","metadata":{"order_id":"A-77"}}}}},"responses":{"200":{"description":"Verdict plus the stored deposit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepositResponse"}}}},"400":{"description":"`VALIDATION_ERROR`: The request body is malformed. `message` says which field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"`INSUFFICIENT_CREDITS`: Not enough credits. Nothing was charged and no provider was called. Top up and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist. / `WRONG_PRODUCT`: The key belongs to another product (e.g. a slip_verify key used on /v1/deposits/slip).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`BODY_TOO_LARGE`: Request body exceeds 64 KB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Verification unavailable: no deposit stored (`deposit.deposit_id` is null), nothing charged, retry later","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepositResponse"}}}}}}},"/v1/deposits/{deposit_id}":{"get":{"operationId":"getDeposit","summary":"Fetch one deposit created with this API key","tags":["Slip deposit"],"parameters":[{"name":"deposit_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"Accept-Language","in":"header","required":false,"schema":{"type":"string","enum":["th","en","lo","my","zh-CN","ko","ja","vi","km","id"],"default":"th"},"description":"Language of `message` / `hint`. For languages other than th and en, error responses also carry `detail` in English."}],"responses":{"200":{"description":"Deposit","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"request_id":{"type":"string"},"data":{"$ref":"#/components/schemas/Deposit"}}}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`NOT_FOUND`: Unknown route or resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/crypto/assets":{"get":{"operationId":"listCryptoAssets","summary":"[Preview] Enabled crypto assets with limits and fees","tags":["Crypto (preview)"],"parameters":[{"name":"Accept-Language","in":"header","required":false,"schema":{"type":"string","enum":["th","en","lo","my","zh-CN","ko","ja","vi","km","id"],"default":"th"},"description":"Language of `message` / `hint`. For languages other than th and en, error responses also carry `detail` in English."}],"responses":{"200":{"description":"Assets","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"request_id":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CryptoAsset"}}}}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist. / `WRONG_PRODUCT`: The key belongs to another product (e.g. a slip_verify key used on /v1/deposits/slip).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/crypto/addresses":{"post":{"operationId":"getCryptoAddress","summary":"[Preview] Get the deposit address of one of your customers","tags":["Crypto (preview)"],"description":"Requires a `crypto` key. One address per `customer_ref` per asset per API key; calling again returns the same address. Funds sent there are credited to your balance after `confirmations_required` blocks, if at least `min_deposit`.","parameters":[{"name":"Accept-Language","in":"header","required":false,"schema":{"type":"string","enum":["th","en","lo","my","zh-CN","ko","ja","vi","km","id"],"default":"th"},"description":"Language of `message` / `hint`. For languages other than th and en, error responses also carry `detail` in English."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["asset","customer_ref"],"properties":{"asset":{"type":"string","examples":["USDT-TRC20"]},"customer_ref":{"type":"string","minLength":1,"maxLength":128}}}}}},"responses":{"200":{"description":"Address","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"request_id":{"type":"string"},"data":{"type":"object","properties":{"asset":{"type":"string"},"chain":{"enum":["tron","bitcoin","bsc"]},"customer_ref":{"type":"string"},"address":{"type":"string"},"min_deposit":{"type":"string"},"confirmations_required":{"type":"integer"}}}}}}}},"400":{"description":"`VALIDATION_ERROR`: The request body is malformed. `message` says which field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist. / `WRONG_PRODUCT`: The key belongs to another product (e.g. a slip_verify key used on /v1/deposits/slip).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/crypto/deposits":{"get":{"operationId":"listCryptoDeposits","summary":"[Preview] Deposits received on this key's addresses","tags":["Crypto (preview)"],"parameters":[{"name":"customer_ref","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"Accept-Language","in":"header","required":false,"schema":{"type":"string","enum":["th","en","lo","my","zh-CN","ko","ja","vi","km","id"],"default":"th"},"description":"Language of `message` / `hint`. For languages other than th and en, error responses also carry `detail` in English."}],"responses":{"200":{"description":"Deposits, newest first","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"request_id":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CryptoDeposit"}}}}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist. / `WRONG_PRODUCT`: The key belongs to another product (e.g. a slip_verify key used on /v1/deposits/slip).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/crypto/balance":{"get":{"operationId":"getCryptoBalance","summary":"[Preview] Your withdrawable balance per asset","tags":["Crypto (preview)"],"parameters":[{"name":"Accept-Language","in":"header","required":false,"schema":{"type":"string","enum":["th","en","lo","my","zh-CN","ko","ja","vi","km","id"],"default":"th"},"description":"Language of `message` / `hint`. For languages other than th and en, error responses also carry `detail` in English."}],"responses":{"200":{"description":"Balances","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"request_id":{"type":"string"},"data":{"type":"array","items":{"type":"object","properties":{"asset":{"type":"string"},"symbol":{"type":"string"},"balance":{"type":"string","description":"Decimal string in asset units"},"balance_minor":{"type":"string"},"pending_deposits":{"type":"integer"},"pending_withdrawals":{"type":"string"}}}}}}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist. / `WRONG_PRODUCT`: The key belongs to another product (e.g. a slip_verify key used on /v1/deposits/slip).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/crypto/withdrawals":{"get":{"operationId":"listCryptoWithdrawals","summary":"[Preview] Your withdrawal requests","tags":["Crypto (preview)"],"parameters":[{"name":"Accept-Language","in":"header","required":false,"schema":{"type":"string","enum":["th","en","lo","my","zh-CN","ko","ja","vi","km","id"],"default":"th"},"description":"Language of `message` / `hint`. For languages other than th and en, error responses also carry `detail` in English."}],"responses":{"200":{"description":"Withdrawals, newest first","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"request_id":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/CryptoWithdrawal"}}}}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist. / `WRONG_PRODUCT`: The key belongs to another product (e.g. a slip_verify key used on /v1/deposits/slip).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"requestCryptoWithdrawal","summary":"[Preview] Request a withdrawal","tags":["Crypto (preview)"],"description":"Deducts `amount` from your balance immediately and queues the request for manual approval. The platform keeps `fee` (a percentage, rounded up) and sends `net = amount - fee`. Rejected requests are refunded in full. Transfers are signed manually by the operator on a hardware wallet, so allow time. KYC approval may be required first.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":128},"description":"Any unique string per logical request (e.g. your order id). Repeating a request with the same key returns the first result and does not charge again. Use it when retrying after a timeout."},{"name":"Accept-Language","in":"header","required":false,"schema":{"type":"string","enum":["th","en","lo","my","zh-CN","ko","ja","vi","km","id"],"default":"th"},"description":"Language of `message` / `hint`. For languages other than th and en, error responses also carry `detail` in English."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["asset","to_address","amount"],"properties":{"asset":{"type":"string"},"to_address":{"type":"string","description":"Destination on the asset's own chain"},"amount":{"type":"string","description":"Decimal string in asset units, e.g. \"100\""}}},"example":{"asset":"USDT-TRC20","to_address":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t","amount":"100"}}}},"responses":{"200":{"description":"Request accepted (status `requested`)","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"request_id":{"type":"string"},"data":{"$ref":"#/components/schemas/CryptoWithdrawal"}}}}}},"400":{"description":"`VALIDATION_ERROR`: The request body is malformed. `message` says which field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"`INSUFFICIENT_BALANCE`: Crypto only: the asset balance is too low. Nothing was deducted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist. / `WRONG_PRODUCT`: The key belongs to another product (e.g. a slip_verify key used on /v1/deposits/slip). / `KYC_REQUIRED`: Crypto only: identity verification must be approved before the first withdrawal.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/oauth/authorize":{"get":{"operationId":"loginAuthorize","summary":"Sign-in page (open in the user's browser, not from your server)","tags":["Sign-in"],"security":[],"description":"Redirect the user here from your 'Sign in' button. `client_id` is the API key id shown in the portal (not the secret key). `redirect_uri` must exactly match one you registered. `state` must be a fresh random value (8–512 chars) that you store in the user's session and compare on return. The user comes back to `redirect_uri?code=…&state=…` or `?error=access_denied&state=…`.","parameters":[{"name":"client_id","in":"query","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string"}},{"name":"state","in":"query","required":true,"schema":{"type":"string","minLength":8,"maxLength":512}}],"responses":{"200":{"description":"HTML sign-in page"},"400":{"description":"Invalid client, redirect_uri or state (the user is never redirected)"}}}},"/v1/login/token":{"post":{"operationId":"loginToken","summary":"Exchange the one-time code for the user's profile","tags":["Sign-in"],"description":"Key product `google_login`, `line_login`, `facebook_login`, `tiktok_login` or `discord_login`. Call from your server within 2 minutes of the redirect. A code works once. Credits are charged when the user signs in (per sign-in + per new member).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code","redirect_uri"],"properties":{"code":{"type":"string"},"redirect_uri":{"type":"string"}}}}}},"responses":{"200":{"description":"Profile","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"type":"object","properties":{"user":{"type":"string","description":"Stable member id for your site, e.g. 7F3K9QX2-shopcom"},"provider":{"type":"string"},"email":{"type":["string","null"]},"email_verified":{"type":"boolean"},"name":{"type":["string","null"]},"picture":{"type":["string","null"]},"is_new_user":{"type":"boolean"},"first_login_at":{"type":"string"},"last_login_at":{"type":"string"},"login_count":{"type":"integer"},"credits_charged":{"type":"integer"}}}}}}}},"400":{"description":"`VALIDATION_ERROR`: The request body is malformed. `message` says which field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/login/users":{"get":{"operationId":"loginUsers","summary":"Members who signed in through your key","tags":["Sign-in"],"parameters":[{"name":"q","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer"}}],"responses":{"200":{"description":"`{total, items[]}`"},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/telegram/link":{"post":{"operationId":"telegramLink","summary":"Get a link the user taps to connect Telegram","tags":["Notifications"],"description":"Key product `telegram_notify`. Body `ref` = your id for this user. Returns `url` (t.me/…?start=…) valid 30 minutes and usable once.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ref"],"properties":{"ref":{"type":"string","pattern":"^[A-Za-z0-9_.:@-]{1,128}$"}}}}}},"responses":{"200":{"description":"`{ref, url, expires_at}`"},"400":{"description":"`VALIDATION_ERROR`: The request body is malformed. `message` says which field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/telegram/send":{"post":{"operationId":"telegramSend","summary":"Send a Telegram message to one or more connected users","tags":["Notifications"],"description":"Body `ref` or `refs` (≤100), `text` (≤4096), optional `parse_mode` (HTML | MarkdownV2) and `button` {text, url}. Charged per delivered message; failures are refunded. Each result `status`: sent, not_linked, not_sent_blocked, recipient_unreachable, delivery_failed.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"ref":{"type":"string"},"refs":{"type":"array","items":{"type":"string"}},"text":{"type":"string"},"parse_mode":{"enum":["HTML","MarkdownV2"]},"button":{"type":"object"}}}}}},"responses":{"200":{"description":"`{sent, credits_charged, results[]}`"},"400":{"description":"`VALIDATION_ERROR`: The request body is malformed. `message` says which field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"`INSUFFICIENT_CREDITS`: Not enough credits. Nothing was charged and no provider was called. Top up and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/telegram/recipients":{"get":{"operationId":"telegramRecipients","summary":"Connected users","tags":["Notifications"],"responses":{"200":{"description":"List"},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/telegram/recipients/{ref}":{"delete":{"operationId":"telegramRemove","summary":"Stop sending to this user","tags":["Notifications"],"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removed"},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`NOT_FOUND`: Unknown route or resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/telegram/messages":{"get":{"operationId":"telegramMessages","summary":"Message log","tags":["Notifications"],"responses":{"200":{"description":"List"},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/discord/webhooks":{"post":{"operationId":"discordAddWebhook","summary":"Register a Discord channel webhook","tags":["Notifications"],"description":"Key product `discord_notify`. Body `ref` + `url` (https://discord.com/api/webhooks/…). Stored encrypted; never returned.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["ref","url"],"properties":{"ref":{"type":"string"},"url":{"type":"string"}}}}}},"responses":{"200":{"description":"Registered"},"400":{"description":"`VALIDATION_ERROR`: The request body is malformed. `message` says which field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"discordWebhooks","summary":"Registered channels","tags":["Notifications"],"responses":{"200":{"description":"List"},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/discord/webhooks/{ref}":{"delete":{"operationId":"discordRemove","summary":"Remove a channel","tags":["Notifications"],"parameters":[{"name":"ref","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Removed"},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`NOT_FOUND`: Unknown route or resource.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/discord/send":{"post":{"operationId":"discordSend","summary":"Post a message to one or more registered channels","tags":["Notifications"],"description":"Body `ref`/`refs`, `text` (≤2000), optional `username`. Mentions are disabled.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"ref":{"type":"string"},"refs":{"type":"array","items":{"type":"string"}},"text":{"type":"string"},"username":{"type":"string"}}}}}},"responses":{"200":{"description":"`{sent, credits_charged, results[]}`"},"400":{"description":"`VALIDATION_ERROR`: The request body is malformed. `message` says which field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"`INSUFFICIENT_CREDITS`: Not enough credits. Nothing was charged and no provider was called. Top up and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/discord/messages":{"get":{"operationId":"discordMessages","summary":"Message log","tags":["Notifications"],"responses":{"200":{"description":"List"},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/topups/slip":{"post":{"operationId":"topupBySlip","summary":"Add credits by submitting a slip paid to the TUNGKAO top-up account","tags":["Credits"],"description":"Works with any key of the merchant. The slip must be paid to the platform's top-up bank account, be recent enough and at least the minimum amount. Each slip can be used once. Limited to a few calls per minute. This call is free.","parameters":[{"name":"Accept-Language","in":"header","required":false,"schema":{"type":"string","enum":["th","en","lo","my","zh-CN","ko","ja","vi","km","id"],"default":"th"},"description":"Language of `message` / `hint`. For languages other than th and en, error responses also carry `detail` in English."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["qr_payload"],"properties":{"qr_payload":{"type":"string","minLength":10,"maxLength":2000,"description":"The exact text decoded from the QR code printed on the Thai bank transfer slip. Decode the QR on your side; do not send the image."}}}}}},"responses":{"200":{"description":"Result (`status` CREDITED or the reason it was not credited)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupResponse"}}}},"400":{"description":"`VALIDATION_ERROR`: The request body is malformed. `message` says which field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`UNAUTHORIZED`: API key is missing or wrong. Send header `Authorization: Bearer <api_key>`. / `BAD_SIGNATURE`: This key requires signed requests and the signature is missing, older than 5 minutes or wrong. Sign `<timestamp>.<METHOD>.<path+query>.<sha256 hex of body>` with HMAC-SHA256 using the signing secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`DOMAIN_NOT_VERIFIED`: Live keys created in the portal must verify their website domain first (upload the verification .html file or add the DNS TXT record). One key = one domain. / `KEY_DISABLED`: The API key is paused, or the merchant account is suspended. / `KEY_EXPIRED`: The API key passed its expiry date. Renew it in the merchant portal; the key string stays the same. / `IP_NOT_ALLOWED`: The caller IP is not in the key's IP whitelist.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`BODY_TOO_LARGE`: Request body exceeds 64 KB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`RATE_LIMITED`: Too many requests for this key. Wait the number of seconds in the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`INTERNAL_ERROR`: Unexpected server error. Report the `request_id`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`status: SERVICE_UNAVAILABLE` — retry later","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TopupResponse"}}}}}}}},"x-crypto-status":"PREVIEW. The /v1/crypto/* endpoints are specified and tested against a simulated chain, but are not enabled in production yet: blockchain watchers and Ledger-based address derivation are still to be connected.","webhooks":{"deposit.succeeded":{"post":{"summary":"Sent when a slip deposit is succeeded","parameters":[{"name":"X-Tungkao-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your webhook secret>"},{"name":"X-Tungkao-Event","in":"header","required":true,"schema":{"const":"deposit.succeeded"}},{"name":"X-Tungkao-Delivery","in":"header","required":true,"schema":{"type":"string"},"description":"Event id; identical across retries"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepositWebhook"}}}},"responses":{"2XX":{"description":"Acknowledge within 10 seconds; anything else is retried"}}}},"deposit.failed":{"post":{"summary":"Sent when a slip deposit is failed","parameters":[{"name":"X-Tungkao-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your webhook secret>"},{"name":"X-Tungkao-Event","in":"header","required":true,"schema":{"const":"deposit.failed"}},{"name":"X-Tungkao-Delivery","in":"header","required":true,"schema":{"type":"string"},"description":"Event id; identical across retries"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepositWebhook"}}}},"responses":{"2XX":{"description":"Acknowledge within 10 seconds; anything else is retried"}}}},"deposit.pending":{"post":{"summary":"Sent when a slip deposit is pending","parameters":[{"name":"X-Tungkao-Signature","in":"header","required":true,"schema":{"type":"string"},"description":"t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your webhook secret>"},{"name":"X-Tungkao-Event","in":"header","required":true,"schema":{"const":"deposit.pending"}},{"name":"X-Tungkao-Delivery","in":"header","required":true,"schema":{"type":"string"},"description":"Event id; identical across retries"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepositWebhook"}}}},"responses":{"2XX":{"description":"Acknowledge within 10 seconds; anything else is retried"}}}},"api_key.expiring":{"post":{"summary":"Sent 30 days, 7 days and 1 day before the API key expires","parameters":[{"name":"X-Tungkao-Signature","in":"header","required":true,"schema":{"type":"string"}},{"name":"X-Tungkao-Event","in":"header","required":true,"schema":{"const":"api_key.expiring"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"api_key_id":{"type":"string","format":"uuid"},"label":{"type":"string"},"product":{"type":"string"},"key_prefix":{"type":"string"},"expires_at":{"type":"string","format":"date-time"},"stage":{"enum":["30d","7d","1d","expired"]}}}}}},"responses":{"2XX":{"description":"Acknowledge within 10 seconds"}}}},"api_key.expired":{"post":{"summary":"Sent once when the API key expires","parameters":[{"name":"X-Tungkao-Signature","in":"header","required":true,"schema":{"type":"string"}},{"name":"X-Tungkao-Event","in":"header","required":true,"schema":{"const":"api_key.expired"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"api_key_id":{"type":"string","format":"uuid"},"label":{"type":"string"},"product":{"type":"string"},"key_prefix":{"type":"string"},"expires_at":{"type":"string","format":"date-time"},"stage":{"enum":["30d","7d","1d","expired"]}}}}}},"responses":{"2XX":{"description":"Acknowledge within 10 seconds"}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key from the merchant portal, e.g. tk_live_…"}},"schemas":{"Error":{"type":"object","required":["success","request_id","error"],"properties":{"success":{"const":false},"request_id":{"type":"string"},"error":{"type":"object","required":["code","message","hint","docs_url"],"properties":{"code":{"type":"string","enum":["UNAUTHORIZED","KEY_DISABLED","DOMAIN_NOT_VERIFIED","BAD_SIGNATURE","INVALID_CODE","KEY_EXPIRED","IP_NOT_ALLOWED","WRONG_PRODUCT","RATE_LIMITED","VALIDATION_ERROR","INSUFFICIENT_CREDITS","NOT_FOUND","BODY_TOO_LARGE","KYC_REQUIRED","INSUFFICIENT_BALANCE","INTERNAL_ERROR"]},"message":{"type":"string"},"hint":{"type":"string","description":"What to do next"},"detail":{"type":"string","description":"English specifics; present only when Accept-Language is neither th nor en"},"docs_url":{"type":"string"}}}}},"Receiver":{"type":"object","description":"An account of yours. Give `account_number` or `promptpay` (matched against the masked value on the slip) and/or a name. `bank` alone is not enough.","properties":{"bank":{"type":"string","description":"Bank code (\"004\") or short name (\"KBANK\"); see GET /v1/banks"},"account_number":{"type":"string","description":"Full account number, digits only or with dashes"},"promptpay":{"type":"string","description":"Full PromptPay id (phone, citizen id or e-wallet id)"},"name_th":{"type":"string"},"name_en":{"type":"string"}}},"Party":{"type":["object","null"],"properties":{"bank_code":{"type":["string","null"],"description":"3-digit Bank of Thailand code"},"bank_short":{"type":["string","null"]},"name_th":{"type":["string","null"]},"name_en":{"type":["string","null"]},"account_masked":{"type":["string","null"],"description":"Partly masked, as printed on the slip"},"proxy_masked":{"type":["string","null"],"description":"Masked PromptPay / wallet id"}}},"SlipStatus":{"type":"string","enum":["VALID","RECEIVER_MISMATCH","AMOUNT_MISMATCH","DATE_MISMATCH","DUPLICATE","NOT_FOUND","FRAUD","PENDING","EXPIRED","INVALID_INPUT","SERVICE_UNAVAILABLE"],"description":"VALID: Slip is genuine and passed every check you asked for. The slip is now marked as used for this API key. | RECEIVER_MISMATCH: Slip is genuine but was paid to a different account than `receivers`. | AMOUNT_MISMATCH: Slip is genuine but the amount differs from `expected_amount`. | DATE_MISMATCH: Slip is genuine but older than `max_age_minutes`. | DUPLICATE: This slip was already accepted (VALID) for this API key before. | NOT_FOUND: The bank has no such transaction. | FRAUD: The slip is forged or altered. | PENDING: The bank has not finished processing. Call again later. | EXPIRED: The slip is too old for the bank to verify. | INVALID_INPUT: `qr_payload` is not a slip QR code. | SERVICE_UNAVAILABLE: All upstream verifiers are down (HTTP 503). Nothing is charged; retry later."},"VerifyResponse":{"type":"object","required":["success","request_id","status","message","data","billing"],"properties":{"success":{"type":"boolean","description":"true only when status is VALID"},"request_id":{"type":"string"},"status":{"$ref":"#/components/schemas/SlipStatus"},"message":{"type":"string"},"data":{"type":"object","properties":{"slip_id":{"type":"string"},"trans_ref":{"type":["string","null"],"description":"Bank transaction reference"},"amount":{"type":["string","null"],"description":"THB with 2 decimals, e.g. \"500.00\""},"currency":{"const":"THB"},"paid_at":{"type":["string","null"],"format":"date-time"},"sender":{"$ref":"#/components/schemas/Party"},"receiver":{"$ref":"#/components/schemas/Party"},"checks":{"type":"object","properties":{"duplicate":{"type":"boolean"},"receiver":{"enum":["pass","fail","skipped"]},"amount":{"enum":["pass","fail","skipped"]},"date":{"enum":["pass","fail","skipped"]}}}}},"billing":{"type":"object","properties":{"credits_charged":{"type":"integer"},"credits_remaining":{"type":"integer"}}}}},"DepositResponse":{"allOf":[{"$ref":"#/components/schemas/VerifyResponse"},{"type":"object","properties":{"success":{"type":"boolean","description":"true only when deposit.status is succeeded"},"deposit":{"type":"object","properties":{"deposit_id":{"type":["string","null"]},"customer_ref":{"type":"string"},"status":{"enum":["succeeded","failed","pending","unavailable"]}}}}}]},"Deposit":{"type":"object","properties":{"deposit_id":{"type":"string"},"customer_ref":{"type":"string"},"status":{"enum":["succeeded","failed","pending"]},"slip_status":{"$ref":"#/components/schemas/SlipStatus"},"slip_id":{"type":"string"},"amount":{"type":["string","null"]},"currency":{"const":"THB"},"metadata":{"type":["object","null"]},"created_at":{"type":"string","format":"date-time"}}},"DepositWebhook":{"type":"object","properties":{"id":{"type":"string","description":"Event id; deduplicate on this"},"type":{"enum":["deposit.succeeded","deposit.failed","deposit.pending"]},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","properties":{"deposit_id":{"type":"string"},"customer_ref":{"type":"string"},"status":{"enum":["succeeded","failed","pending"]},"slip_status":{"$ref":"#/components/schemas/SlipStatus"},"slip_id":{"type":"string"},"amount":{"type":["string","null"]},"currency":{"const":"THB"},"trans_ref":{"type":["string","null"]},"paid_at":{"type":["string","null"],"format":"date-time"},"sender":{"$ref":"#/components/schemas/Party"},"receiver":{"$ref":"#/components/schemas/Party"},"metadata":{"type":["object","null"]}}}}},"TopupResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"true only when status is CREDITED"},"request_id":{"type":"string"},"status":{"enum":["CREDITED","DUPLICATE","RECEIVER_MISMATCH","TOO_OLD","BELOW_MINIMUM","NOT_CONFIGURED","SERVICE_UNAVAILABLE","NOT_FOUND","FRAUD","PENDING","EXPIRED","INVALID_INPUT"]},"message":{"type":"string"},"data":{"type":"object","properties":{"topup_id":{"type":["string","null"]},"amount":{"type":["string","null"]},"currency":{"const":"THB"},"credits_added":{"type":"integer"},"credits_remaining":{"type":"integer"}}}}},"CryptoAsset":{"type":"object","properties":{"asset":{"type":"string"},"chain":{"enum":["tron","bitcoin","bsc"]},"symbol":{"type":"string"},"decimals":{"type":"integer"},"confirmations_required":{"type":"integer"},"min_deposit":{"type":"string"},"withdraw_fee_percent":{"type":"number"},"withdraw_min":{"type":"string"},"withdraw_max":{"type":["string","null"]}}},"CryptoDeposit":{"type":"object","properties":{"deposit_id":{"type":"string"},"asset":{"type":"string"},"customer_ref":{"type":"string"},"address":{"type":"string"},"txid":{"type":"string"},"vout":{"type":"integer"},"amount":{"type":"string","description":"Decimal string in asset units"},"amount_minor":{"type":"string"},"status":{"enum":["pending","confirmed","ignored"],"description":"ignored = below min_deposit, not credited"},"confirmations":{"type":"integer"},"confirmations_required":{"type":"integer"},"detected_at":{"type":"string","format":"date-time"},"confirmed_at":{"type":["string","null"],"format":"date-time"}}},"CryptoWithdrawal":{"type":"object","properties":{"withdrawal_id":{"type":"string"},"asset":{"type":"string"},"status":{"enum":["requested","approved","rejected","sent"]},"to_address":{"type":"string"},"amount":{"type":"string"},"fee":{"type":"string"},"net":{"type":"string"},"txid":{"type":["string","null"]},"note":{"type":["string","null"]}}},"AccountResponse":{"type":"object","properties":{"success":{"const":true},"request_id":{"type":"string"},"data":{"type":"object","properties":{"api_key":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"product":{"enum":["slip_verify","slip_deposit","crypto"]},"mode":{"enum":["live","test"]}}},"credits_remaining":{"type":"integer"},"credits_per_thb":{"const":100},"slip_prices":{"type":"object","description":"Credits charged per resulting status","additionalProperties":{"type":"integer"}}}}}},"BanksResponse":{"type":"object","properties":{"success":{"const":true},"request_id":{"type":"string"},"data":{"type":"array","items":{"type":"object","properties":{"bank_code":{"type":"string"},"bank_short":{"type":"string"},"name_th":{"type":"string"}}}}}}}}}