เอกสาร API

การเข้าถึงแบบโปรแกรมสำหรับเบอร์เสมือน คำสั่งซื้อ และยอดเงินในบัญชี

ภาพรวม

ฟิลด์เงินทั้งหมดบน API /v1 เป็น IDR (รูเปียห์อินโดนีเซีย) ในรูปแบบจำนวนเต็ม — ตัวอย่างเช่น "price": 15000 และ "balance": 500000 หมายถึง Rp 15,000 และ Rp 500,000 สำหรับการฉายภาพแบบ USD-native ของบัญชีแยกประเภทเดียวกัน ให้สลับไปใช้ API v2 ด้วยปุ่มสลับเวอร์ชันด้านบน

การยืนยันตัวตน

คำขอ API ทั้งหมดต้องใช้ Bearer token สร้าง token จากตั้งค่าบัญชีในแดชบอร์ด แล้วใส่ในทุกคำขอ:

Authorization: Bearer YOUR_API_TOKEN

คำขอที่ไม่มี token ที่ถูกต้องจะได้รับการตอบกลับ 401 UNAUTHORIZED

Base URL

เส้นทาง endpoint ทั้งหมดด้านล่างเป็น relative กับ:

https://api.smscode.gg/v1

รูปแบบการตอบกลับ

ทุกการตอบกลับเป็น JSON ในรูปแบบ envelope ที่สอดคล้องกัน ทุกการตอบกลับมี header x-request-id สำหรับการดีบัก

สำเร็จ
{
  "success": true,
  "data": { ... }
}
ข้อผิดพลาด
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

ฟิลด์เงินทั้งหมดบน API /v1 เป็น IDR (รูเปียห์อินโดนีเซีย) ในรูปแบบจำนวนเต็ม — ตัวอย่างเช่น "price": 15000 และ "balance": 500000 หมายถึง Rp 15,000 และ Rp 500,000 สำหรับการฉายภาพแบบ USD-native ของบัญชีแยกประเภทเดียวกัน ให้สลับไปใช้ API v2 ด้วยปุ่มสลับเวอร์ชันด้านบน

GET /catalog/countries

ส่งคืนรายการประเทศที่มีทั้งหมด

พารามิเตอร์

ไม่มี

ตัวอย่างคำขอ

curl -s https://api.smscode.gg/v1/catalog/countries \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/countries", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/countries",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": [
    {
      "id": 6,
      "code": "ID",
      "name": "Indonesia",
      "dial_code": "+62",
      "emoji": "🇮🇩",
      "active": true
    }
  ]
}
GET /catalog/services

ส่งคืนรายการบริการ (แพลตฟอร์ม) ที่มี สามารถกรองตามประเทศได้

Query Parameters

ชื่อประเภทจำเป็นรายละเอียด
country_idintegerไม่กรองบริการที่มีสำหรับประเทศนี้

ตัวอย่างคำขอ

curl -s "https://api.smscode.gg/v1/catalog/services?country_id=6" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/services?country_id=6", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/services",
    params={"country_id": 6},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": [
    {
      "id": 3,
      "code": "wa",
      "name": "WhatsApp",
      "active": true
    }
  ]
}
GET /catalog/products

ส่งคืนรายการผลิตภัณฑ์ที่มีแบบแบ่งหน้า กรองตามประเทศและ/หรือแพลตฟอร์มได้

Query Parameters

ชื่อประเภทจำเป็นรายละเอียด
country_idintegerไม่กรองตามรหัสประเทศ
platform_idintegerไม่กรองตามรหัสแพลตฟอร์ม/บริการ
sortstringไม่การเรียงลำดับ: price_asc (ค่าเริ่มต้น), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerไม่จำนวนผลลัพธ์ต่อหน้า (1-10,000 ค่าเริ่มต้น 1,000)
pageintegerไม่หมายเลขหน้า (ขั้นต่ำ 1 ค่าเริ่มต้น 1)

ตัวอย่างคำขอ

curl -s "https://api.smscode.gg/v1/catalog/products?country_id=6&platform_id=3&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "6", platform_id: "3", limit: "10", page: "1",
});
const res = await fetch(`https://api.smscode.gg/v1/catalog/products?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/products",
    params={"country_id": 6, "platform_id": 3, "limit": 10, "page": 1},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": 15000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1 }
}
GET /catalog/exchange-rate

ส่งคืนอัตราแลกเปลี่ยน USD/IDR ปัจจุบันที่ใช้สำหรับการแปลงสกุลเงิน

Query Parameters

ชื่อประเภทจำเป็นรายละเอียด
pairstringไม่คู่สกุลเงิน (ค่าเริ่มต้น: USD/IDR)

ตัวอย่างคำขอ

curl -s "https://api.smscode.gg/v1/catalog/exchange-rate?pair=USD/IDR" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/exchange-rate?pair=USD/IDR", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/exchange-rate",
    params={"pair": "USD/IDR"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "pair": "USD/IDR",
    "base_currency": "USD",
    "quote_currency": "IDR",
    "rate": 16250
  }
}
GET /balance

ส่งคืนยอดเงินในบัญชีของผู้ใช้ที่ผ่านการยืนยันตัวตน

พารามิเตอร์

ไม่มี

ตัวอย่างคำขอ

curl -s https://api.smscode.gg/v1/balance \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/balance", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/balance",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 500000
  }
}
GET /orders

ส่งคืนรายการคำสั่งซื้อของผู้ใช้ที่ผ่านการยืนยันตัวตน เรียงตามล่าสุด รองรับการกรองตามสถานะและแบ่งหน้าด้วย offset

Query Parameters

ชื่อประเภทจำเป็นรายละเอียด
limitintegerไม่จำนวนผลลัพธ์สูงสุด (1-100 ค่าเริ่มต้น 20)
offsetintegerไม่จำนวนผลลัพธ์ที่ข้าม (ค่าเริ่มต้น 0)
statusstringไม่กรองตามสถานะ: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (ไม่คำนึงตัวพิมพ์เล็ก-ใหญ่)

ตัวอย่างคำขอ

curl -s "https://api.smscode.gg/v1/orders?limit=5&status=ACTIVE" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  limit: "5", status: "ACTIVE", offset: "0",
});
const res = await fetch(`https://api.smscode.gg/v1/orders?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders",
    params={"limit": 5, "status": "ACTIVE", "offset": 0},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "ACTIVE",
      "created_at": "2026-02-25T10:00:00+00:00",
      "product_id": 142,
      "phone_number": "+6281234567890",
      "amount": 15000,
      "otp_code": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:20:00+00:00",
      "canceled_at": null,
      "failed_reason": null
    }
  ]
}
GET /orders/{id}

ส่งคืนคำสั่งซื้อเดี่ยวตามรหัส ส่งคืนเฉพาะคำสั่งซื้อที่เป็นของผู้ใช้ที่ผ่านการยืนยันตัวตน

Path Parameters

ชื่อประเภทจำเป็นรายละเอียด
idintegerใช่รหัสคำสั่งซื้อ (path parameter)

ตัวอย่างคำขอ

curl -s https://api.smscode.gg/v1/orders/1001 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/orders/1001", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders/1001",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "id": 1001,
    "status": "OTP_RECEIVED",
    "created_at": "2026-02-25T10:00:00+00:00",
    "product_id": 142,
    "phone_number": "+6281234567890",
    "amount": 15000,
    "otp_code": "123456",
    "otp_received_at": "2026-02-25T10:05:00+00:00",
    "expires_at": "2026-02-25T10:20:00+00:00",
    "canceled_at": null,
    "failed_reason": null
  }
}
GET /orders/active

แสดงคำสั่งซื้อที่กำลังดำเนินการทั้งหมด (ACTIVE + OTP_RECEIVED) ใช้เพื่อตรวจสอบการอัปเดตสถานะ OTP

พารามิเตอร์

ไม่มี

ตัวอย่างคำขอ

curl -s https://api.smscode.gg/v1/orders/active \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/orders/active", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders/active",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "OTP_RECEIVED",
      "otp_code": "123456",
      "otp_message": "Your verification code is 123456",
      "otp_received_at": "2026-02-25T10:05:00+00:00",
      "expires_at": "2026-02-25T10:20:00+00:00",
      "failed_reason": null
    },
    {
      "id": 1002,
      "status": "ACTIVE",
      "otp_code": null,
      "otp_message": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:50:00+00:00",
      "failed_reason": null
    }
  ]
}
POST /orders/create

สร้างคำสั่งซื้อเบอร์เสมือนใหม่ หักยอดเงินอัตโนมัติ รองรับ header Idempotency-Key เพื่อป้องกันคำสั่งซื้อซ้ำเมื่อลองส่งคำขอใหม่

Request Body

ชื่อประเภทจำเป็นรายละเอียด
product_idintegerไม่รหัสของผลิตภัณฑ์ที่จะสั่ง ระบุได้เพียงอย่างใดอย่างหนึ่ง: product_id หรือ catalog_product_id ไม่ใช่ทั้งสองอย่าง
catalog_product_idintegerไม่รหัสผลิตภัณฑ์ที่คงที่ — เซิร์ฟเวอร์จะเลือกข้อเสนอที่ดีที่สุดในขณะนั้น (ถูกที่สุด โดยคำนึงถึงความน่าเชื่อถือ) ระบุรายการนี้หรือ product_id อย่างใดอย่างหนึ่ง
max_priceinteger · stringไม่เพดานราคาแบบไม่บังคับ v1: จำนวนเต็ม IDR v2: สตริงทศนิยม USD (เช่น "0.50")
prefer_providerstringไม่รหัสผู้ให้บริการแบบไม่บังคับ ใช้เป็นตัวตัดสินเมื่อข้อเสนอเท่ากัน
policystringไม่นโยบายการจัดเส้นทางแบบไม่บังคับ ใช้ได้เฉพาะกับ catalog_product_id เท่านั้น ค่าที่ใช้ได้: cheapest (ค่าเริ่มต้น) เลือกข้อเสนอที่พร้อมใช้งานและราคาต่ำสุด; best_success จัดอันดับข้อเสนอโดยให้ความสำเร็จในการส่งล่าสุดมาก่อน โดย best_success ให้คะแนนผู้ให้บริการแต่ละรายจากสัดส่วนคำสั่งซื้อที่ได้รับ OTP ในช่วง 30 วันเต็มล่าสุด แบ่งเป็นช่วงละ 10% และจะนับผู้ให้บริการรายนั้นก็ต่อเมื่อมีคำสั่งซื้ออย่างน้อย 20 รายการในช่วงเวลาดังกล่าว — ผู้ให้บริการที่ต่ำกว่าเกณฑ์นี้หรือยังไม่มีประวัติจะถือว่าเป็นกลาง จึงทำให้ข้อเสนอใหม่ไม่ถูกมองข้าม (ต้องเลือกใช้เอง; สัญญาณเริ่มต้นที่ค่ากลาง) หากตั้งค่า prefer_provider ไว้ด้วย ผู้ให้บริการที่เลือกไว้ก็ยังคงอยู่อันดับแรก
quantityintegerไม่จำนวน (1-100 ค่าเริ่มต้น 1)

ส่ง header Idempotency-Key เพื่อส่งคำขอซ้ำได้อย่างปลอดภัยโดยไม่สร้างคำสั่งซื้อซ้ำ key ประกอบด้วยตัวอักษร ตัวเลข ขีดกลาง และขีดล่าง (A-Z a-z 0-9 _ -) ได้ ยาวไม่เกิน 128 อักขระ key ที่ไม่ถูกต้องจะถูกปฏิเสธด้วย 422 VALIDATION_ERROR การส่งซ้ำด้วย key เดิมและ body เดิมจะเล่นผลลัพธ์เดิมซ้ำ (รวมถึง failed_count ของกรณีสำเร็จบางส่วน) การส่งซ้ำที่ไปถึงผู้ให้บริการแล้วแต่ล้มเหลวจะถูกบันทึกไว้และเล่นข้อผิดพลาดเดิมซ้ำ — ให้ใช้ key ใหม่เพื่อลองอีกครั้ง ความล้มเหลวที่ไม่มีผลข้างเคียง (ยอดเงินไม่พอ ไม่มีข้อเสนอที่ใช้ได้) จะปล่อย key คืน คุณจึงสามารถเติมเงินแล้วลองซ้ำด้วย key เดิมได้ การใช้ key เดิมซ้ำกับ body ที่ต่างออกไปจะคืนค่า 422 IDEMPOTENCY_KEY_REUSED และคำขอที่ยังทำงานอยู่ด้วย key นั้นจะคืนค่า 409 REQUEST_IN_PROGRESS ฟิลด์ failed_reason ในการตอบกลับของ create จะเป็น null เสมอ — จะมีค่าก็ต่อเมื่อ poll/list คำสั่งซื้อเท่านั้น

ตัวอย่างคำขอ

curl -s -X POST https://api.smscode.gg/v1/orders/create \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-123" \
  -d '{"product_id":142,"quantity":1}'

# Or order by stable catalog_product_id — the server picks the best current offer
# (cheapest, reliability-aware). Optional max_price caps the price; on v1 it is an
# IDR integer, on v2 it is a USD decimal string (e.g. "0.50"). prefer_provider
# (provider code) is a soft tie-breaker. Pass EITHER product_id OR catalog_product_id.
curl -s -X POST https://api.smscode.gg/v1/orders/create \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-124" \
  -d '{"catalog_product_id":87,"max_price":20000}'
const res = await fetch("https://api.smscode.gg/v1/orders/create", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-request-id-123",
  },
  body: JSON.stringify({ product_id: 142, quantity: 1 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/create",
    json={"product_id": 142, "quantity": 1},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-123",
    })
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1002,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": 15000,
        "phone_number": "+6281234567891",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T10:50:00+00:00",
        "failed_reason": null
      }
    ],
    "failed_count": 0
  }
}
POST /orders/cancel

ยกเลิกคำสั่งซื้อที่กำลังดำเนินการ ค่าเช่าจะถูกคืนเข้ายอดบัญชีของคุณ

Request Body

ชื่อประเภทจำเป็นรายละเอียด
idintegerใช่รหัสคำสั่งซื้อที่จะยกเลิก

ตัวอย่างคำขอ

curl -s -X POST https://api.smscode.gg/v1/orders/cancel \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v1/orders/cancel", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/cancel",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": 15000,
    "new_balance": 515000
  }
}
POST /orders/finish

ทำเครื่องหมายคำสั่งซื้อว่าเสร็จสิ้นหลังจากได้รับ OTP การดำเนินการนี้จะปล่อยเบอร์ทันทีแทนที่จะรอให้หมดอายุ

Request Body

ชื่อประเภทจำเป็นรายละเอียด
idintegerใช่รหัสคำสั่งซื้อที่จะเสร็จสิ้น

ตัวอย่างคำขอ

curl -s -X POST https://api.smscode.gg/v1/orders/finish \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v1/orders/finish", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/finish",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}
POST /orders/resend

ขอให้แพลตฟอร์มส่ง SMS ซ้ำไปยังเบอร์ที่เช่า แพลตฟอร์มบางแห่งไม่รองรับการส่งซ้ำ — ตรวจสอบฟิลด์ resent ในการตอบกลับ

Request Body

ชื่อประเภทจำเป็นรายละเอียด
idintegerใช่รหัสคำสั่งซื้อที่จะส่ง SMS ซ้ำ

ตัวอย่างคำขอ

curl -s -X POST https://api.smscode.gg/v1/orders/resend \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v1/orders/resend", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/resend",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}
GET /webhook

ส่งคืนการตั้งค่า webhook notification ปัจจุบันของคุณ

พารามิเตอร์

ไม่มี

ตัวอย่างคำขอ

curl -s https://api.smscode.gg/v1/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/webhook", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/webhook",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
PATCH /webhook

อัปเดต URL และ/หรือ secret ของ webhook secret จะถูกสร้างอัตโนมัติเมื่อคุณตั้ง URL ครั้งแรก ส่งสตริงว่างเพื่อล้าง URL ต้องใช้ HTTPS

Request Body

ชื่อประเภทจำเป็นรายละเอียด
webhook_urlstringไม่HTTPS URL สำหรับรับเหตุการณ์ webhook (สตริงว่างเพื่อล้าง)
webhook_secretstringไม่Shared secret สำหรับ HMAC-SHA256 signature (สร้างอัตโนมัติหากไม่ระบุในครั้งแรก)

ต้องระบุอย่างน้อยหนึ่งฟิลด์

ตัวอย่างคำขอ

curl -s -X PATCH https://api.smscode.gg/v1/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url":"https://example.com/webhook"}'
const res = await fetch("https://api.smscode.gg/v1/webhook", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ webhook_url: "https://example.com/webhook" }),
});
const data = await res.json();
import requests

res = requests.patch("https://api.smscode.gg/v1/webhook",
    json={"webhook_url": "https://example.com/webhook"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
POST /webhook/test

ส่งเหตุการณ์ทดสอบไปยัง URL webhook ที่ตั้งค่าไว้ ส่งคืนรหัสสถานะ HTTP จากเซิร์ฟเวอร์ของคุณ มีประโยชน์สำหรับตรวจสอบว่า endpoint ทำงานก่อนเริ่มใช้งานจริง

พารามิเตอร์

ไม่มี

ตัวอย่างคำขอ

curl -s -X POST https://api.smscode.gg/v1/webhook/test \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/webhook/test", {
  method: "POST",
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/webhook/test",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "status_code": 200
  }
}

Webhook Notifications

ตั้งค่า URL webhook เพื่อรับการแจ้งเตือนแบบ push แบบเรียลไทม์สำหรับเหตุการณ์คำสั่งซื้อแทนการ polling นี่คือวิธีที่แนะนำสำหรับ bot script

เหตุการณ์

เหตุการณ์ทริกเกอร์
order.otp_receivedส่ง OTP ไปยังเบอร์ที่เช่าแล้ว
order.completedคำสั่งซื้อถูกทำเครื่องหมายว่าเสร็จสิ้น (ด้วยตนเองหรือเมื่อหมดอายุ)
order.expiredคำสั่งซื้อหมดอายุโดยไม่ได้ OTP (คืนเงินแล้ว)
order.canceledคำสั่งซื้อถูกยกเลิกโดยผู้ใช้ (คืนเงินแล้ว)

Payload

Webhook POST Body
{
  "event": "order.otp_received",
  "timestamp": "2026-02-25T12:00:00+00:00",
  "data": {
    "order_id": 1001,
    "phone_number": "+628123456789",
    "otp_code": "1234",
    "otp_message": "Your verification code is 1234",
    "product_id": 42,
    "catalog_product_id": 87,
    "country": "Indonesia",
    "platform": "WhatsApp"
  }
}

การตรวจสอบ Signature

ทุกคำขอ webhook มี header X-Webhook-Signature พร้อม HMAC-SHA256 signature ของ request body โดยใช้ webhook_secret ของคุณเป็น key:

X-Webhook-Signature: sha256={HMAC-SHA256(body, webhook_secret)}

ตรวจสอบ signature นี้บนเซิร์ฟเวอร์ของคุณเพื่อยืนยันว่าคำขอเป็นของจริง การส่งเป็นแบบ fire-and-forget พร้อม timeout 3 วินาทีและไม่มีการลองใหม่

Rate Limits

คำขอ API ถูกจำกัดอัตราตามกลุ่ม endpoint การเกินขีดจำกัดจะได้รับ 429 Too Many Requests พร้อม header Retry-After ที่ระบุจำนวนวินาทีที่ต้องรอ

กลุ่ม Endpointขีดจำกัดช่วงเวลา
Catalog (countries, services, products, exchange-rate)5,000 คำขอ60 วินาที
Balance600 คำขอ60 วินาที
Order reads (list, get, active)5,000 คำขอ60 วินาที
Order create3,000 คำขอ60 วินาที
Order cancel1,000 คำขอ60 วินาที
Order actions (finish, resend)1,000 คำขอ60 วินาที
Webhook config (get, update)600 คำขอ60 วินาที
Webhook test10 คำขอ60 วินาที

รหัสข้อผิดพลาด

การตอบกลับข้อผิดพลาดจะมีรหัสเหล่านี้ใน error.code:

รหัสHTTPรายละเอียด
UNAUTHORIZED401ไม่มีหรือ API token ไม่ถูกต้อง
FORBIDDEN403การเข้าถึงถูกปฏิเสธ
NOT_FOUND404ไม่พบทรัพยากร (คำสั่งซื้อ อัตราแลกเปลี่ยน ฯลฯ)
CONFLICT409คำขอซ้ำหรือทรัพยากรขัดแย้ง
INSUFFICIENT_BALANCE409ยอดเงินไม่เพียงพอสำหรับสร้างคำสั่งซื้อ
VALIDATION_ERROR422พารามิเตอร์คำขอไม่ผ่านการตรวจสอบ
RATE_LIMIT_EXCEEDED429คำขอมากเกินไป (ตรวจสอบ header Retry-After)
INTERNAL_ERROR500ข้อผิดพลาดภายในเซิร์ฟเวอร์
PROVIDER_ERROR422ผู้ให้บริการ SMS ต้นทางปฏิเสธคำขอ เมื่อการสร้างคำสั่งซื้อล้มเหลว error อาจมี <code>details</code>: <code>cause_counts</code> (คำสั่งซื้อแบบ <code>product_id</code> เดิม — การนับจัดกลุ่มตามสาเหตุ) หรือ <code>attempts</code> (คำสั่งซื้อแบบ <code>catalog_product_id</code> — ผลลัพธ์ต่อความพยายามแต่ละครั้ง) โดยใช้ค่า <code>ok</code>, <code>no_numbers</code>, <code>insufficient_balance</code>, <code>price_rejected</code>, <code>provider_unavailable</code>, <code>provider_account_balance</code>, <code>provider_error</code>
NO_OFFER_AVAILABLE422ไม่มีข้อเสนอที่ใช้งานอยู่ตรงกับผลิตภัณฑ์และนโยบายที่ร้องขอ (เพดานราคา ความพร้อมจำหน่าย)
CANCEL_TOO_EARLY409คำสั่งซื้อใหม่เกินไปที่จะยกเลิก — รอ 2 นาที
REQUEST_IN_PROGRESS409คำขอสร้างคำสั่งซื้อด้วย idempotency key นี้ยังทำงานอยู่
IDEMPOTENCY_KEY_REUSED422idempotency key นี้ถูกใช้กับ body คำขอที่ต่างออกไปแล้ว
SERVICE_UNAVAILABLE503บริการไม่พร้อมให้บริการชั่วคราว (บำรุงรักษา)

ภาพรวม

ฟิลด์เงินทั้งหมดบน API /v2 เป็น USD ส่งคืนเป็นออบเจ็กต์เงิน — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" } amount เป็นสตริงทศนิยม ส่วน canonical_amount คือค่าบัญชีแยกประเภท IDR ที่แม่นยำ (ใช้สำหรับการกระทบยอด) อัตรา rate USD/IDR ที่ใช้จะถูกเปิดเผยหนึ่งครั้งต่อการตอบกลับใน meta.fx v2 คือการฉายภาพ USD ขณะเรนเดอร์บนบัญชีแยกประเภท IDR เดียวกันกับ v1 — ไม่เคยจัดเก็บหรือทำธุรกรรมเป็น USD

การย้ายจาก v1 ไป v2

การยืนยันตัวตน

คำขอ API ทั้งหมดต้องใช้ Bearer token สร้าง token จากตั้งค่าบัญชีในแดชบอร์ด แล้วใส่ในทุกคำขอ:

Authorization: Bearer YOUR_API_TOKEN

คำขอที่ไม่มี token ที่ถูกต้องจะได้รับการตอบกลับ 401 UNAUTHORIZED

Base URL

เส้นทาง endpoint ทั้งหมดด้านล่างเป็น relative กับ:

https://api.smscode.gg/v2

รูปแบบการตอบกลับ

ทุกการตอบกลับเป็น JSON ในรูปแบบ envelope ที่สอดคล้องกัน ทุกการตอบกลับมี header x-request-id สำหรับการดีบัก

สำเร็จ
{
  "success": true,
  "data": { ... }
}
ข้อผิดพลาด
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

ข้อผิดพลาด v2: ไม่มีอัตราแลกเปลี่ยนที่ใช้ได้ (503)

503 Service Unavailable · Retry-After: 60
{
  "success": false,
  "error": { "code": "FX_RATE_UNAVAILABLE", "message": "USD/IDR exchange rate is unavailable" }
}

ฟิลด์เงินทั้งหมดบน API /v2 เป็น USD ส่งคืนเป็นออบเจ็กต์เงิน — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" } amount เป็นสตริงทศนิยม ส่วน canonical_amount คือค่าบัญชีแยกประเภท IDR ที่แม่นยำ (ใช้สำหรับการกระทบยอด) อัตรา rate USD/IDR ที่ใช้จะถูกเปิดเผยหนึ่งครั้งต่อการตอบกลับใน meta.fx v2 คือการฉายภาพ USD ขณะเรนเดอร์บนบัญชีแยกประเภท IDR เดียวกันกับ v1 — ไม่เคยจัดเก็บหรือทำธุรกรรมเป็น USD

GET /catalog/countries

ส่งคืนรายการประเทศที่มีทั้งหมด

พารามิเตอร์

ไม่มี

ตัวอย่างคำขอ

curl -s https://api.smscode.gg/v2/catalog/countries \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/countries", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/countries",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": [
    {
      "id": 6,
      "code": "ID",
      "name": "Indonesia",
      "dial_code": "+62",
      "emoji": "🇮🇩",
      "active": true
    }
  ]
}

เหมือนกับ v1 — เปลี่ยนเฉพาะเส้นทางฐาน (/v1/v2)

GET /catalog/services

ส่งคืนรายการบริการ (แพลตฟอร์ม) ที่มี สามารถกรองตามประเทศได้

Query Parameters

ชื่อประเภทจำเป็นรายละเอียด
country_idintegerไม่กรองบริการที่มีสำหรับประเทศนี้

ตัวอย่างคำขอ

curl -s "https://api.smscode.gg/v2/catalog/services?country_id=6" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/services?country_id=6", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/services",
    params={"country_id": 6},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": [
    {
      "id": 3,
      "code": "wa",
      "name": "WhatsApp",
      "active": true
    }
  ]
}

เหมือนกับ v1 — เปลี่ยนเฉพาะเส้นทางฐาน (/v1/v2)

GET /catalog/products

ส่งคืนรายการผลิตภัณฑ์ที่มีแบบแบ่งหน้า กรองตามประเทศและ/หรือแพลตฟอร์มได้

Query Parameters

ชื่อประเภทจำเป็นรายละเอียด
country_idintegerไม่กรองตามรหัสประเทศ
platform_idintegerไม่กรองตามรหัสแพลตฟอร์ม/บริการ
sortstringไม่การเรียงลำดับ: price_asc (ค่าเริ่มต้น), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerไม่จำนวนผลลัพธ์ต่อหน้า (1-10,000 ค่าเริ่มต้น 1,000)
pageintegerไม่หมายเลขหน้า (ขั้นต่ำ 1 ค่าเริ่มต้น 1)

ตัวอย่างคำขอ

curl -s "https://api.smscode.gg/v2/catalog/products?country_id=6&platform_id=3&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "6", platform_id: "3", limit: "10", page: "1",
});
const res = await fetch(`https://api.smscode.gg/v2/catalog/products?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/products",
    params={"country_id": 6, "platform_id": 3, "limit": 10, "page": 1},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1, "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2: ฟิลด์เงินเป็นออบเจ็กต์เงิน USD และการตอบกลับมี meta.fx { pair, rate, rate_as_of } เพียงรายการเดียว rate คือ IDR จำนวนเต็มต่อ 1 USD ดังนั้น USD = canonical_amount / rate ยอดรวมใช้ทศนิยม 2 ตำแหน่ง ราคา/การคืนเงินต่อรายการใช้ 4 ตำแหน่ง จำนวนเงินที่เป็นบวกอย่างแท้จริงจะไม่ถูกปัดเป็น 0.00 rate_as_of คือเวลาประทับ RFC3339 ของอัตรา (รูปแบบ +00:00) หรือ null เมื่อไม่มีการบันทึกเวลาประทับ

เฉพาะ v2: หากไม่มีอัตรา USD/IDR ที่ใช้ได้ เอนด์พอยต์เงินจะส่งคืน 503 FX_RATE_UNAVAILABLE พร้อมเฮดเดอร์ Retry-After แทนที่จะเป็นเนื้อหาเงิน v1 ไม่เคยส่งคืนค่านี้

GET /catalog/exchange-rate

ส่งคืนอัตราแลกเปลี่ยน USD/IDR ปัจจุบันที่ใช้สำหรับการแปลงสกุลเงิน

พารามิเตอร์

ไม่มี — v2 ส่งคืน USD/IDR เสมอ; พารามิเตอร์ ?pair ของ v1 จะถูกละเว้น

ตัวอย่างคำขอ

curl -s "https://api.smscode.gg/v2/catalog/exchange-rate?pair=USD/IDR" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/exchange-rate?pair=USD/IDR", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/exchange-rate",
    params={"pair": "USD/IDR"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" }
}

v2: ส่งคืน { pair, rate, rate_as_of } (ไม่มี base_currency/quote_currency ไม่มีตัวห่อ meta — อัตราคือข้อมูล) ?pair จะถูกละเว้น — v2 ส่งคืน USD/IDR เสมอ (v1 จะเคารพ ?pair) ส่งคืน 503 FX_RATE_UNAVAILABLE หากไม่มีอัตราที่ใช้ได้

GET /balance

ส่งคืนยอดเงินในบัญชีของผู้ใช้ที่ผ่านการยืนยันตัวตน

พารามิเตอร์

ไม่มี

ตัวอย่างคำขอ

curl -s https://api.smscode.gg/v2/balance \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/balance", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/balance",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "balance": { "amount": "30.77", "currency": "USD", "canonical_amount": 500000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2: ฟิลด์เงินเป็นออบเจ็กต์เงิน USD และการตอบกลับมี meta.fx { pair, rate, rate_as_of } เพียงรายการเดียว rate คือ IDR จำนวนเต็มต่อ 1 USD ดังนั้น USD = canonical_amount / rate ยอดรวมใช้ทศนิยม 2 ตำแหน่ง ราคา/การคืนเงินต่อรายการใช้ 4 ตำแหน่ง จำนวนเงินที่เป็นบวกอย่างแท้จริงจะไม่ถูกปัดเป็น 0.00 rate_as_of คือเวลาประทับ RFC3339 ของอัตรา (รูปแบบ +00:00) หรือ null เมื่อไม่มีการบันทึกเวลาประทับ

เฉพาะ v2: หากไม่มีอัตรา USD/IDR ที่ใช้ได้ เอนด์พอยต์เงินจะส่งคืน 503 FX_RATE_UNAVAILABLE พร้อมเฮดเดอร์ Retry-After แทนที่จะเป็นเนื้อหาเงิน v1 ไม่เคยส่งคืนค่านี้

GET /orders

ส่งคืนรายการคำสั่งซื้อของผู้ใช้ที่ผ่านการยืนยันตัวตน เรียงตามล่าสุด รองรับการกรองตามสถานะและแบ่งหน้าด้วย offset

Query Parameters

ชื่อประเภทจำเป็นรายละเอียด
limitintegerไม่จำนวนผลลัพธ์สูงสุด (1-100 ค่าเริ่มต้น 20)
offsetintegerไม่จำนวนผลลัพธ์ที่ข้าม (ค่าเริ่มต้น 0)
statusstringไม่กรองตามสถานะ: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (ไม่คำนึงตัวพิมพ์เล็ก-ใหญ่)

ตัวอย่างคำขอ

curl -s "https://api.smscode.gg/v2/orders?limit=5&status=ACTIVE" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  limit: "5", status: "ACTIVE", offset: "0",
});
const res = await fetch(`https://api.smscode.gg/v2/orders?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders",
    params={"limit": 5, "status": "ACTIVE", "offset": 0},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "ACTIVE",
      "created_at": "2026-02-25T10:00:00+00:00",
      "product_id": 142,
      "phone_number": "+6281234567890",
      "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
      "otp_code": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:20:00+00:00",
      "canceled_at": null,
      "failed_reason": null
    }
  ],
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2: ฟิลด์เงินเป็นออบเจ็กต์เงิน USD และการตอบกลับมี meta.fx { pair, rate, rate_as_of } เพียงรายการเดียว rate คือ IDR จำนวนเต็มต่อ 1 USD ดังนั้น USD = canonical_amount / rate ยอดรวมใช้ทศนิยม 2 ตำแหน่ง ราคา/การคืนเงินต่อรายการใช้ 4 ตำแหน่ง จำนวนเงินที่เป็นบวกอย่างแท้จริงจะไม่ถูกปัดเป็น 0.00 rate_as_of คือเวลาประทับ RFC3339 ของอัตรา (รูปแบบ +00:00) หรือ null เมื่อไม่มีการบันทึกเวลาประทับ

เฉพาะ v2: หากไม่มีอัตรา USD/IDR ที่ใช้ได้ เอนด์พอยต์เงินจะส่งคืน 503 FX_RATE_UNAVAILABLE พร้อมเฮดเดอร์ Retry-After แทนที่จะเป็นเนื้อหาเงิน v1 ไม่เคยส่งคืนค่านี้

GET /orders/{id}

ส่งคืนคำสั่งซื้อเดี่ยวตามรหัส ส่งคืนเฉพาะคำสั่งซื้อที่เป็นของผู้ใช้ที่ผ่านการยืนยันตัวตน

Path Parameters

ชื่อประเภทจำเป็นรายละเอียด
idintegerใช่รหัสคำสั่งซื้อ (path parameter)

ตัวอย่างคำขอ

curl -s https://api.smscode.gg/v2/orders/1001 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/orders/1001", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders/1001",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "id": 1001,
    "status": "OTP_RECEIVED",
    "created_at": "2026-02-25T10:00:00+00:00",
    "product_id": 142,
    "phone_number": "+6281234567890",
    "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
    "otp_code": "123456",
    "otp_received_at": "2026-02-25T10:05:00+00:00",
    "expires_at": "2026-02-25T10:20:00+00:00",
    "canceled_at": null,
    "failed_reason": null
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2: ฟิลด์เงินเป็นออบเจ็กต์เงิน USD และการตอบกลับมี meta.fx { pair, rate, rate_as_of } เพียงรายการเดียว rate คือ IDR จำนวนเต็มต่อ 1 USD ดังนั้น USD = canonical_amount / rate ยอดรวมใช้ทศนิยม 2 ตำแหน่ง ราคา/การคืนเงินต่อรายการใช้ 4 ตำแหน่ง จำนวนเงินที่เป็นบวกอย่างแท้จริงจะไม่ถูกปัดเป็น 0.00 rate_as_of คือเวลาประทับ RFC3339 ของอัตรา (รูปแบบ +00:00) หรือ null เมื่อไม่มีการบันทึกเวลาประทับ

เฉพาะ v2: หากไม่มีอัตรา USD/IDR ที่ใช้ได้ เอนด์พอยต์เงินจะส่งคืน 503 FX_RATE_UNAVAILABLE พร้อมเฮดเดอร์ Retry-After แทนที่จะเป็นเนื้อหาเงิน v1 ไม่เคยส่งคืนค่านี้

GET /orders/active

แสดงคำสั่งซื้อที่กำลังดำเนินการทั้งหมด (ACTIVE + OTP_RECEIVED) ใช้เพื่อตรวจสอบการอัปเดตสถานะ OTP

พารามิเตอร์

ไม่มี

ตัวอย่างคำขอ

curl -s https://api.smscode.gg/v2/orders/active \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/orders/active", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders/active",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "OTP_RECEIVED",
      "otp_code": "123456",
      "otp_message": "Your verification code is 123456",
      "otp_received_at": "2026-02-25T10:05:00+00:00",
      "expires_at": "2026-02-25T10:20:00+00:00",
      "failed_reason": null
    },
    {
      "id": 1002,
      "status": "ACTIVE",
      "otp_code": null,
      "otp_message": null,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:50:00+00:00",
      "failed_reason": null
    }
  ]
}

v2: เอนด์พอยต์นี้ไม่เกี่ยวข้องกับเงิน — ไม่ส่งคืน amount และไม่ส่งคืน meta.fx (โครงสร้างเดียวกับ v1 ภายใต้ /v2)

POST /orders/create

สร้างคำสั่งซื้อเบอร์เสมือนใหม่ หักยอดเงินอัตโนมัติ รองรับ header Idempotency-Key เพื่อป้องกันคำสั่งซื้อซ้ำเมื่อลองส่งคำขอใหม่

Request Body

ชื่อประเภทจำเป็นรายละเอียด
product_idintegerไม่รหัสของผลิตภัณฑ์ที่จะสั่ง ระบุได้เพียงอย่างใดอย่างหนึ่ง: product_id หรือ catalog_product_id ไม่ใช่ทั้งสองอย่าง
catalog_product_idintegerไม่รหัสผลิตภัณฑ์ที่คงที่ — เซิร์ฟเวอร์จะเลือกข้อเสนอที่ดีที่สุดในขณะนั้น (ถูกที่สุด โดยคำนึงถึงความน่าเชื่อถือ) ระบุรายการนี้หรือ product_id อย่างใดอย่างหนึ่ง
max_priceinteger · stringไม่เพดานราคาแบบไม่บังคับ v1: จำนวนเต็ม IDR v2: สตริงทศนิยม USD (เช่น "0.50")
prefer_providerstringไม่รหัสผู้ให้บริการแบบไม่บังคับ ใช้เป็นตัวตัดสินเมื่อข้อเสนอเท่ากัน
policystringไม่นโยบายการจัดเส้นทางแบบไม่บังคับ ใช้ได้เฉพาะกับ catalog_product_id เท่านั้น ค่าที่ใช้ได้: cheapest (ค่าเริ่มต้น) เลือกข้อเสนอที่พร้อมใช้งานและราคาต่ำสุด; best_success จัดอันดับข้อเสนอโดยให้ความสำเร็จในการส่งล่าสุดมาก่อน โดย best_success ให้คะแนนผู้ให้บริการแต่ละรายจากสัดส่วนคำสั่งซื้อที่ได้รับ OTP ในช่วง 30 วันเต็มล่าสุด แบ่งเป็นช่วงละ 10% และจะนับผู้ให้บริการรายนั้นก็ต่อเมื่อมีคำสั่งซื้ออย่างน้อย 20 รายการในช่วงเวลาดังกล่าว — ผู้ให้บริการที่ต่ำกว่าเกณฑ์นี้หรือยังไม่มีประวัติจะถือว่าเป็นกลาง จึงทำให้ข้อเสนอใหม่ไม่ถูกมองข้าม (ต้องเลือกใช้เอง; สัญญาณเริ่มต้นที่ค่ากลาง) หากตั้งค่า prefer_provider ไว้ด้วย ผู้ให้บริการที่เลือกไว้ก็ยังคงอยู่อันดับแรก
quantityintegerไม่จำนวน (1-100 ค่าเริ่มต้น 1)

ส่ง header Idempotency-Key เพื่อส่งคำขอซ้ำได้อย่างปลอดภัยโดยไม่สร้างคำสั่งซื้อซ้ำ key ประกอบด้วยตัวอักษร ตัวเลข ขีดกลาง และขีดล่าง (A-Z a-z 0-9 _ -) ได้ ยาวไม่เกิน 128 อักขระ key ที่ไม่ถูกต้องจะถูกปฏิเสธด้วย 422 VALIDATION_ERROR การส่งซ้ำด้วย key เดิมและ body เดิมจะเล่นผลลัพธ์เดิมซ้ำ (รวมถึง failed_count ของกรณีสำเร็จบางส่วน) การส่งซ้ำที่ไปถึงผู้ให้บริการแล้วแต่ล้มเหลวจะถูกบันทึกไว้และเล่นข้อผิดพลาดเดิมซ้ำ — ให้ใช้ key ใหม่เพื่อลองอีกครั้ง ความล้มเหลวที่ไม่มีผลข้างเคียง (ยอดเงินไม่พอ ไม่มีข้อเสนอที่ใช้ได้) จะปล่อย key คืน คุณจึงสามารถเติมเงินแล้วลองซ้ำด้วย key เดิมได้ การใช้ key เดิมซ้ำกับ body ที่ต่างออกไปจะคืนค่า 422 IDEMPOTENCY_KEY_REUSED และคำขอที่ยังทำงานอยู่ด้วย key นั้นจะคืนค่า 409 REQUEST_IN_PROGRESS ฟิลด์ failed_reason ในการตอบกลับของ create จะเป็น null เสมอ — จะมีค่าก็ต่อเมื่อ poll/list คำสั่งซื้อเท่านั้น

ตัวอย่างคำขอ

curl -s -X POST https://api.smscode.gg/v2/orders/create \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-123" \
  -d '{"product_id":142,"quantity":1}'

# Or order by stable catalog_product_id — the server picks the best current offer
# (cheapest, reliability-aware). Optional max_price caps the price; on v1 it is an
# IDR integer, on v2 it is a USD decimal string (e.g. "0.50"). prefer_provider
# (provider code) is a soft tie-breaker. Pass EITHER product_id OR catalog_product_id.
curl -s -X POST https://api.smscode.gg/v2/orders/create \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-124" \
  -d '{"catalog_product_id":87,"max_price":20000}'
const res = await fetch("https://api.smscode.gg/v2/orders/create", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-request-id-123",
  },
  body: JSON.stringify({ product_id: 142, quantity: 1 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/create",
    json={"product_id": 142, "quantity": 1},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-123",
    })
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1002,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
        "phone_number": "+6281234567891",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T10:50:00+00:00",
        "failed_reason": null
      }
    ],
    "failed_count": 0
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2: ฟิลด์เงินเป็นออบเจ็กต์เงิน USD และการตอบกลับมี meta.fx { pair, rate, rate_as_of } เพียงรายการเดียว rate คือ IDR จำนวนเต็มต่อ 1 USD ดังนั้น USD = canonical_amount / rate ยอดรวมใช้ทศนิยม 2 ตำแหน่ง ราคา/การคืนเงินต่อรายการใช้ 4 ตำแหน่ง จำนวนเงินที่เป็นบวกอย่างแท้จริงจะไม่ถูกปัดเป็น 0.00 rate_as_of คือเวลาประทับ RFC3339 ของอัตรา (รูปแบบ +00:00) หรือ null เมื่อไม่มีการบันทึกเวลาประทับ

เฉพาะ v2: หากไม่มีอัตรา USD/IDR ที่ใช้ได้ เอนด์พอยต์เงินจะส่งคืน 503 FX_RATE_UNAVAILABLE พร้อมเฮดเดอร์ Retry-After แทนที่จะเป็นเนื้อหาเงิน v1 ไม่เคยส่งคืนค่านี้

POST /orders/cancel

ยกเลิกคำสั่งซื้อที่กำลังดำเนินการ ค่าเช่าจะถูกคืนเข้ายอดบัญชีของคุณ

Request Body

ชื่อประเภทจำเป็นรายละเอียด
idintegerใช่รหัสคำสั่งซื้อที่จะยกเลิก

ตัวอย่างคำขอ

curl -s -X POST https://api.smscode.gg/v2/orders/cancel \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v2/orders/cancel", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/cancel",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
    "new_balance": { "amount": "31.69", "currency": "USD", "canonical_amount": 515000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

v2: ฟิลด์เงินเป็นออบเจ็กต์เงิน USD และการตอบกลับมี meta.fx { pair, rate, rate_as_of } เพียงรายการเดียว rate คือ IDR จำนวนเต็มต่อ 1 USD ดังนั้น USD = canonical_amount / rate ยอดรวมใช้ทศนิยม 2 ตำแหน่ง ราคา/การคืนเงินต่อรายการใช้ 4 ตำแหน่ง จำนวนเงินที่เป็นบวกอย่างแท้จริงจะไม่ถูกปัดเป็น 0.00 rate_as_of คือเวลาประทับ RFC3339 ของอัตรา (รูปแบบ +00:00) หรือ null เมื่อไม่มีการบันทึกเวลาประทับ

เฉพาะ v2: หากไม่มีอัตรา USD/IDR ที่ใช้ได้ เอนด์พอยต์เงินจะส่งคืน 503 FX_RATE_UNAVAILABLE พร้อมเฮดเดอร์ Retry-After แทนที่จะเป็นเนื้อหาเงิน v1 ไม่เคยส่งคืนค่านี้

POST /orders/finish

ทำเครื่องหมายคำสั่งซื้อว่าเสร็จสิ้นหลังจากได้รับ OTP การดำเนินการนี้จะปล่อยเบอร์ทันทีแทนที่จะรอให้หมดอายุ

Request Body

ชื่อประเภทจำเป็นรายละเอียด
idintegerใช่รหัสคำสั่งซื้อที่จะเสร็จสิ้น

ตัวอย่างคำขอ

curl -s -X POST https://api.smscode.gg/v2/orders/finish \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v2/orders/finish", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/finish",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}

เหมือนกับ v1 — เปลี่ยนเฉพาะเส้นทางฐาน (/v1/v2)

POST /orders/resend

ขอให้แพลตฟอร์มส่ง SMS ซ้ำไปยังเบอร์ที่เช่า แพลตฟอร์มบางแห่งไม่รองรับการส่งซ้ำ — ตรวจสอบฟิลด์ resent ในการตอบกลับ

Request Body

ชื่อประเภทจำเป็นรายละเอียด
idintegerใช่รหัสคำสั่งซื้อที่จะส่ง SMS ซ้ำ

ตัวอย่างคำขอ

curl -s -X POST https://api.smscode.gg/v2/orders/resend \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"id":1001}'
const res = await fetch("https://api.smscode.gg/v2/orders/resend", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/resend",
    json={"id": 1001},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}

เหมือนกับ v1 — เปลี่ยนเฉพาะเส้นทางฐาน (/v1/v2)

GET /webhook

ส่งคืนการตั้งค่า webhook notification ปัจจุบันของคุณ

พารามิเตอร์

ไม่มี

ตัวอย่างคำขอ

curl -s https://api.smscode.gg/v2/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/webhook", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/webhook",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}

เหมือนกับ v1 — เปลี่ยนเฉพาะเส้นทางฐาน (/v1/v2)

PATCH /webhook

อัปเดต URL และ/หรือ secret ของ webhook secret จะถูกสร้างอัตโนมัติเมื่อคุณตั้ง URL ครั้งแรก ส่งสตริงว่างเพื่อล้าง URL ต้องใช้ HTTPS

Request Body

ชื่อประเภทจำเป็นรายละเอียด
webhook_urlstringไม่HTTPS URL สำหรับรับเหตุการณ์ webhook (สตริงว่างเพื่อล้าง)
webhook_secretstringไม่Shared secret สำหรับ HMAC-SHA256 signature (สร้างอัตโนมัติหากไม่ระบุในครั้งแรก)

ต้องระบุอย่างน้อยหนึ่งฟิลด์

ตัวอย่างคำขอ

curl -s -X PATCH https://api.smscode.gg/v2/webhook \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url":"https://example.com/webhook"}'
const res = await fetch("https://api.smscode.gg/v2/webhook", {
  method: "PATCH",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ webhook_url: "https://example.com/webhook" }),
});
const data = await res.json();
import requests

res = requests.patch("https://api.smscode.gg/v2/webhook",
    json={"webhook_url": "https://example.com/webhook"},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}

เหมือนกับ v1 — เปลี่ยนเฉพาะเส้นทางฐาน (/v1/v2)

POST /webhook/test

ส่งเหตุการณ์ทดสอบไปยัง URL webhook ที่ตั้งค่าไว้ ส่งคืนรหัสสถานะ HTTP จากเซิร์ฟเวอร์ของคุณ มีประโยชน์สำหรับตรวจสอบว่า endpoint ทำงานก่อนเริ่มใช้งานจริง

พารามิเตอร์

ไม่มี

ตัวอย่างคำขอ

curl -s -X POST https://api.smscode.gg/v2/webhook/test \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/webhook/test", {
  method: "POST",
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/webhook/test",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

ตัวอย่างการตอบกลับ

200 OK
{
  "success": true,
  "data": {
    "status_code": 200
  }
}

เหมือนกับ v1 — เปลี่ยนเฉพาะเส้นทางฐาน (/v1/v2)

Webhook Notifications

ตั้งค่า URL webhook เพื่อรับการแจ้งเตือนแบบ push แบบเรียลไทม์สำหรับเหตุการณ์คำสั่งซื้อแทนการ polling นี่คือวิธีที่แนะนำสำหรับ bot script

เหตุการณ์

เหตุการณ์ทริกเกอร์
order.otp_receivedส่ง OTP ไปยังเบอร์ที่เช่าแล้ว
order.completedคำสั่งซื้อถูกทำเครื่องหมายว่าเสร็จสิ้น (ด้วยตนเองหรือเมื่อหมดอายุ)
order.expiredคำสั่งซื้อหมดอายุโดยไม่ได้ OTP (คืนเงินแล้ว)
order.canceledคำสั่งซื้อถูกยกเลิกโดยผู้ใช้ (คืนเงินแล้ว)

Payload

Webhook POST Body
{
  "event": "order.otp_received",
  "timestamp": "2026-02-25T12:00:00+00:00",
  "data": {
    "order_id": 1001,
    "phone_number": "+628123456789",
    "otp_code": "1234",
    "otp_message": "Your verification code is 1234",
    "product_id": 42,
    "catalog_product_id": 87,
    "country": "Indonesia",
    "platform": "WhatsApp"
  }
}

การตรวจสอบ Signature

ทุกคำขอ webhook มี header X-Webhook-Signature พร้อม HMAC-SHA256 signature ของ request body โดยใช้ webhook_secret ของคุณเป็น key:

X-Webhook-Signature: sha256={HMAC-SHA256(body, webhook_secret)}

ตรวจสอบ signature นี้บนเซิร์ฟเวอร์ของคุณเพื่อยืนยันว่าคำขอเป็นของจริง การส่งเป็นแบบ fire-and-forget พร้อม timeout 3 วินาทีและไม่มีการลองใหม่

Rate Limits

คำขอ API ถูกจำกัดอัตราตามกลุ่ม endpoint การเกินขีดจำกัดจะได้รับ 429 Too Many Requests พร้อม header Retry-After ที่ระบุจำนวนวินาทีที่ต้องรอ

กลุ่ม Endpointขีดจำกัดช่วงเวลา
Catalog (countries, services, products, exchange-rate)5,000 คำขอ60 วินาที
Balance600 คำขอ60 วินาที
Order reads (list, get, active)5,000 คำขอ60 วินาที
Order create3,000 คำขอ60 วินาที
Order cancel1,000 คำขอ60 วินาที
Order actions (finish, resend)1,000 คำขอ60 วินาที
Webhook config (get, update)600 คำขอ60 วินาที
Webhook test10 คำขอ60 วินาที

รหัสข้อผิดพลาด

การตอบกลับข้อผิดพลาดจะมีรหัสเหล่านี้ใน error.code:

รหัสHTTPรายละเอียด
UNAUTHORIZED401ไม่มีหรือ API token ไม่ถูกต้อง
FORBIDDEN403การเข้าถึงถูกปฏิเสธ
NOT_FOUND404ไม่พบทรัพยากร (คำสั่งซื้อ อัตราแลกเปลี่ยน ฯลฯ)
CONFLICT409คำขอซ้ำหรือทรัพยากรขัดแย้ง
INSUFFICIENT_BALANCE409ยอดเงินไม่เพียงพอสำหรับสร้างคำสั่งซื้อ
VALIDATION_ERROR422พารามิเตอร์คำขอไม่ผ่านการตรวจสอบ
RATE_LIMIT_EXCEEDED429คำขอมากเกินไป (ตรวจสอบ header Retry-After)
INTERNAL_ERROR500ข้อผิดพลาดภายในเซิร์ฟเวอร์
PROVIDER_ERROR422ผู้ให้บริการ SMS ต้นทางปฏิเสธคำขอ เมื่อการสร้างคำสั่งซื้อล้มเหลว error อาจมี <code>details</code>: <code>cause_counts</code> (คำสั่งซื้อแบบ <code>product_id</code> เดิม — การนับจัดกลุ่มตามสาเหตุ) หรือ <code>attempts</code> (คำสั่งซื้อแบบ <code>catalog_product_id</code> — ผลลัพธ์ต่อความพยายามแต่ละครั้ง) โดยใช้ค่า <code>ok</code>, <code>no_numbers</code>, <code>insufficient_balance</code>, <code>price_rejected</code>, <code>provider_unavailable</code>, <code>provider_account_balance</code>, <code>provider_error</code>
NO_OFFER_AVAILABLE422ไม่มีข้อเสนอที่ใช้งานอยู่ตรงกับผลิตภัณฑ์และนโยบายที่ร้องขอ (เพดานราคา ความพร้อมจำหน่าย)
CANCEL_TOO_EARLY409คำสั่งซื้อใหม่เกินไปที่จะยกเลิก — รอ 2 นาที
REQUEST_IN_PROGRESS409คำขอสร้างคำสั่งซื้อด้วย idempotency key นี้ยังทำงานอยู่
IDEMPOTENCY_KEY_REUSED422idempotency key นี้ถูกใช้กับ body คำขอที่ต่างออกไปแล้ว
SERVICE_UNAVAILABLE503บริการไม่พร้อมให้บริการชั่วคราว (บำรุงรักษา)
FX_RATE_UNAVAILABLE503อัตราแลกเปลี่ยน USD/IDR ไม่พร้อมใช้งาน (เอนด์พอยต์เงิน v2) — ส่งคืน 503 พร้อมเฮดเดอร์ Retry-After
v1 → v2

การย้ายจาก v1 ไป v2

v1 ให้บริการ IDR; v2 ให้บริการ USD ทั้งสองเวอร์ชันอยู่ร่วมกันถาวร — ไม่มีการยกเลิก เลือกหนึ่งเวอร์ชันต่อหนึ่งการเชื่อมต่อ อย่าปนเส้นทางฐาน v2 เหมือนกับ v1 ทุกประการ ยกเว้นวิธีการแสดงค่าเงิน

ด้านv1 · IDRv2 · USD
ฟิลด์เงินIDR จำนวนเต็ม เช่น 15000ออบเจ็กต์เงิน { amount, currency, canonical_amount, canonical_currency }
meta.fxไม่มีจำเป็นในทุกการตอบกลับที่เกี่ยวข้องกับเงิน
สกุลเงินIDRUSD (ฮาร์ดโค้ด)
FX_RATE_UNAVAILABLE503 + Retry-After ใหม่ เมื่อไม่มีอัตราที่ใช้ได้
ความแม่นยำยอดรวม 2 ตำแหน่ง ราคา/คืนเงิน 4 ตำแหน่ง ปัดขึ้นสำหรับค่าบวก
GET /catalog/exchange-rate{pair, base_currency, quote_currency, rate}; เคารพ ?pair{pair, rate, rate_as_of}; ละเว้น ?pair (USD/IDR เท่านั้น)

ตัวอย่างเทียบเคียง

GET/balance
v1
{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 500000
  }
}
v2
{
  "success": true,
  "data": {
    "balance": { "amount": "30.77", "currency": "USD", "canonical_amount": 500000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}
GET/catalog/products
v1
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": 15000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1 }
}
v2
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1, "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

catalog_product_id คือรหัสระบุสินค้าที่คงที่ (หนึ่งรายการต่อประเทศ + แพลตฟอร์ม) — ปลอดภัยที่จะเก็บไว้ในระบบของคุณ และยังใช้สั่งซื้อได้โดยตรง: สั่งซื้อด้วย catalog_product_id (พร้อม max_price และ prefer_provider แบบไม่บังคับ) แล้วเซิร์ฟเวอร์จะเลือกข้อเสนอที่ใช้งานได้ให้คุณ ส่วนเส้นทางที่ใช้ product_id ของแต่ละข้อเสนอยังรองรับอยู่และเข้ากันได้แบบย้อนหลัง — id นั้นเปลี่ยนแปลงได้และจะเปลี่ยนทุกครั้งที่ระดับราคาของผู้ให้บริการขยับ ดังนั้นคำแนะนำให้ดึงแคตตาล็อกใหม่ก่อนสั่งซื้อจึงใช้กับเส้นทางนั้นเท่านั้น

POST/orders/cancel
v1
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": 15000,
    "new_balance": 515000
  }
}
v2
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
    "new_balance": { "amount": "31.69", "currency": "USD", "canonical_amount": 515000, "canonical_currency": "IDR" }
  },
  "meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}

เช็กลิสต์การย้าย

  1. เปลี่ยนเส้นทางฐาน /v1/v2
  2. แยกวิเคราะห์ฟิลด์เงินเป็นออบเจ็กต์ — อ่าน amount เป็นสตริงทศนิยม; currency คือ "USD"
  3. สำหรับการกระทบยอดบัญชีแยกประเภท ให้ใช้ canonical_amount (IDR ที่แม่นยำ) ส่วน amount USD เป็นการฉายภาพขณะเรนเดอร์ และ rate จะถูกเปิดเผยหนึ่งครั้งใน meta.fx
  4. จัดการ FX_RATE_UNAVAILABLE (503) ใหม่ — ลองใหม่หลัง Retry-After v1 ไม่เคยส่งคืนค่านี้