Tài liệu API

Truy cập lập trình đến số ảo, đơn hàng và số dư tài khoản.

Tong Quan

Tất cả trường tiền tệ trên API /v1 đều bằng IDR (Rupiah Indonesia), dưới dạng số nguyên — ví dụ "price": 15000"balance": 500000 nghĩa là Rp 15.000 và Rp 500.000. Để có bản chiếu USD-native của cùng một sổ cái, hãy chuyển sang API v2 bằng nút chuyển phiên bản ở trên.

Xác thực

Tất cả yêu cầu API đều yêu cầu Bearer token. Tạo token từ Cài đặt tài khoản trong bảng điều khiển, sau đó đính kèm vào mỗi yêu cầu:

Authorization: Bearer YOUR_API_TOKEN

Yêu cầu không có token hợp lệ sẽ nhận phản hồi 401 UNAUTHORIZED.

URL cơ sở

Tất cả đường dẫn endpoint bên dưới tương đối với:

https://api.smscode.gg/v1

Định dạng phản hồi

Mọi phản hồi trả về JSON với cấu trúc nhất quán. Tất cả phản hồi bao gồm header x-request-id để gỡ lỗi.

Thành công
{
  "success": true,
  "data": { ... }
}
Lỗi
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

Tất cả trường tiền tệ trên API /v1 đều bằng IDR (Rupiah Indonesia), dưới dạng số nguyên — ví dụ "price": 15000"balance": 500000 nghĩa là Rp 15.000 và Rp 500.000. Để có bản chiếu USD-native của cùng một sổ cái, hãy chuyển sang API v2 bằng nút chuyển phiên bản ở trên.

GET /catalog/countries

Trả về danh sách tất cả quốc gia có sẵn.

Tham số

Không có

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Trả về danh sách dịch vụ (nền tảng) có sẵn. Có thể lọc theo quốc gia.

Tham số truy vấn

TênKiểuBắt buộcMô tả
country_idintegerKhôngLọc dịch vụ có sẵn cho quốc gia này

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Trả về danh sách sản phẩm có sẵn (phân trang). Lọc theo quốc gia và/hoặc nền tảng.

Tham số truy vấn

TênKiểuBắt buộcMô tả
country_idintegerKhôngLọc theo ID quốc gia
platform_idintegerKhôngLọc theo ID nền tảng/dịch vụ
sortstringKhôngThứ tự sắp xếp: price_asc (mặc định), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerKhôngSố kết quả mỗi trang (1-10.000, mặc định 1.000)
pageintegerKhôngSố trang (tối thiểu 1, mặc định 1)

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Trả về tỷ giá hối đoái USD/IDR hiện tại được dùng để chuyển đổi tiền tệ.

Tham số truy vấn

TênKiểuBắt buộcMô tả
pairstringKhôngCặp tiền tệ (mặc định: USD/IDR)

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Trả về số dư tài khoản của người dùng đã xác thực.

Tham số

Không có

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Trả về danh sách đơn hàng của người dùng đã xác thực, sắp xếp theo mới nhất. Hỗ trợ lọc theo trạng thái và phân trang qua offset.

Tham số truy vấn

TênKiểuBắt buộcMô tả
limitintegerKhôngSố kết quả tối đa (1-100, mặc định 20)
offsetintegerKhôngSố kết quả bỏ qua (mặc định 0)
statusstringKhôngLọc theo trạng thái: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (không phân biệt hoa thường)

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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}

Trả về một đơn hàng theo ID. Chỉ trả về đơn hàng của người dùng đã xác thực.

Tham số đường dẫn

TênKiểuBắt buộcMô tả
idintegerMã đơn hàng (tham số đường dẫn)

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Liệt kê tất cả đơn hàng đang hoạt động (ACTIVE + OTP_RECEIVED). Sử dụng để theo dõi cập nhật trạng thái OTP.

Tham số

Không có

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Tạo đơn hàng số ảo mới. Tự động trừ số dư. Hỗ trợ header Idempotency-Key tùy chọn để tránh tạo đơn trùng lặp khi thử lại do lỗi mạng.

Nội dung yêu cầu

TênKiểuBắt buộcMô tả
product_idintegerKhôngID sản phẩm cần đặt hàng. Chỉ cung cấp MỘT trong hai: product_id HOẶC catalog_product_id, không dùng cả hai.
catalog_product_idintegerKhôngID sản phẩm ổn định — máy chủ tự chọn ưu đãi tốt nhất hiện tại (rẻ nhất, có cân nhắc độ tin cậy). Cung cấp trường này hoặc product_id.
max_priceinteger · stringKhôngGiới hạn giá tùy chọn. v1: số nguyên IDR. v2: chuỗi thập phân USD (vd. "0.50").
prefer_providerstringKhôngMã nhà cung cấp tùy chọn được ưu tiên khi các ưu đãi ngang nhau.
policystringKhôngChính sách định tuyến tùy chọn, chỉ hợp lệ khi dùng catalog_product_id. Giá trị: cheapest (mặc định) chọn ưu đãi tốt có giá thấp nhất; best_success xếp hạng các ưu đãi theo tỷ lệ giao thành công gần đây trước. best_success chấm điểm mỗi nhà cung cấp theo tỷ lệ đơn nhận được OTP trong 30 ngày trọn vẹn gần nhất, theo dải 10%, và chỉ tính một nhà cung cấp khi họ có ít nhất 20 đơn trong khoảng đó — nhà cung cấp dưới ngưỡng này hoặc chưa có lịch sử được coi là trung tính, nên các ưu đãi mới không bao giờ bị bỏ rơi (tùy chọn; tín hiệu khởi đầu ở mức trung tính). Khi prefer_provider cũng được đặt, nhà cung cấp ưu tiên vẫn được xếp trước.
quantityintegerKhôngSố lượng (1-100, mặc định 1)

Truyền header Idempotency-Key để thử lại an toàn mà không tạo đơn trùng lặp. Key có thể chứa chữ cái, chữ số, dấu gạch ngang và gạch dưới (A-Z a-z 0-9 _ -), tối đa 128 ký tự; key không hợp lệ sẽ bị từ chối với 422 VALIDATION_ERROR. Thử lại với cùng key và cùng body sẽ phát lại kết quả ban đầu (bao gồm failed_count khi thành công một phần). Lần thử lại đã đến nhà cung cấp nhưng thất bại sẽ được ghi lại và phát lại đúng lỗi đó — hãy dùng key MỚI để thử lại. Lỗi không có tác dụng phụ (không đủ số dư, không có ưu đãi khả dụng) sẽ giải phóng key, nên bạn có thể nạp tiền và thử lại với cùng key. Dùng lại một key với body khác sẽ trả về 422 IDEMPOTENCY_KEY_REUSED, và yêu cầu vẫn đang xử lý với key đó sẽ trả về 409 REQUEST_IN_PROGRESS. Trường failed_reason trong phản hồi create luôn là null — nó chỉ được điền khi poll/liệt kê đơn hàng.

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Hủy đơn hàng đang hoạt động. Phí thuê sẽ được hoàn vào số dư tài khoản.

Nội dung yêu cầu

TênKiểuBắt buộcMô tả
idintegerMã đơn hàng cần hủy

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Đánh dấu đơn hàng đã hoàn thành sau khi nhận OTP. Giải phóng số ngay lập tức thay vì đợi hết hạn.

Nội dung yêu cầu

TênKiểuBắt buộcMô tả
idintegerMã đơn hàng cần hoàn thành

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Yêu cầu nền tảng gửi lại SMS đến số đã thuê. Không phải tất cả nền tảng đều hỗ trợ gửi lại — kiểm tra trường resent trong phản hồi.

Nội dung yêu cầu

TênKiểuBắt buộcMô tả
idintegerMã đơn hàng cần gửi lại SMS

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Trả về cấu hình webhook notification hiện tại của bạn.

Tham số

Không có

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Cập nhật URL webhook và/hoặc secret. Secret được tự động tạo khi bạn đặt URL lần đầu. Gửi chuỗi rỗng để xóa. URL phải sử dụng HTTPS.

Nội dung yêu cầu

TênKiểuBắt buộcMô tả
webhook_urlstringKhôngURL HTTPS để nhận sự kiện webhook (chuỗi rỗng để xóa)
webhook_secretstringKhôngSecret chia sẻ cho chữ ký HMAC-SHA256 (tự động tạo nếu bỏ qua lần đặt đầu tiên)

Cần ít nhất một trường.

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Gửi sự kiện thử nghiệm đến URL webhook đã cấu hình. Trả về mã trạng thái HTTP từ máy chủ của bạn. Hữu ích để kiểm tra endpoint hoạt động trước khi vận hành chính thức.

Tham số

Không có

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Webhook Notifications

Cấu hình URL webhook để nhận thông báo đẩy theo thời gian thực cho các sự kiện đơn hàng thay vì polling. Đây là phương pháp được khuyến nghị cho bot script.

Sự kiện

Sự kiệnKích hoạt
order.otp_receivedMã OTP đã được gửi đến số thuê
order.completedĐơn hàng đã hoàn thành (thủ công hoặc khi hết hạn)
order.expiredĐơn hàng hết hạn mà không có OTP (đã hoàn tiền)
order.canceledĐơn hàng bị hủy bởi người dùng (đã hoàn tiền)

Payload

Nội dung POST webhook
{
  "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"
  }
}

Xác minh chữ ký

Mỗi yêu cầu webhook bao gồm header X-Webhook-Signature chứa chữ ký HMAC-SHA256 của nội dung yêu cầu, sử dụng webhook_secret làm khóa:

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

Xác minh chữ ký này trên máy chủ để đảm bảo yêu cầu là xác thực. Gửi theo kiểu fire-and-forget với timeout 3 giây và không thử lại.

Rate Limits

Yêu cầu API được giới hạn tốc độ theo nhóm endpoint. Vượt quá giới hạn trả về 429 Too Many Requests với header Retry-After cho biết số giây cần đợi.

Nhóm endpointGiới hạnCửa sổ
Danh mục (quốc gia, dịch vụ, sản phẩm, tỷ giá)5.000 yêu cầu60 giây
Số dư600 yêu cầu60 giây
Đọc đơn hàng (danh sách, chi tiết, hoạt động)5.000 yêu cầu60 giây
Tạo đơn hàng3.000 yêu cầu60 giây
Hủy đơn hàng1.000 yêu cầu60 giây
Thao tác đơn hàng (hoàn thành, gửi lại)1.000 yêu cầu60 giây
Cấu hình webhook (xem, cập nhật)600 yêu cầu60 giây
Thử webhook10 yêu cầu60 giây

Mã lỗi

Phản hồi lỗi bao gồm một trong các mã sau trong error.code:

HTTPMô tả
UNAUTHORIZED401Thiếu hoặc API token không hợp lệ
FORBIDDEN403Truy cập bị từ chối
NOT_FOUND404Không tìm thấy tài nguyên (đơn hàng, tỷ giá, v.v.)
CONFLICT409Yêu cầu trùng lặp hoặc xung đột tài nguyên
INSUFFICIENT_BALANCE409Số dư không đủ để tạo đơn hàng
VALIDATION_ERROR422Tham số yêu cầu không hợp lệ
RATE_LIMIT_EXCEEDED429Quá nhiều yêu cầu (kiểm tra header Retry-After)
INTERNAL_ERROR500Lỗi máy chủ nội bộ
PROVIDER_ERROR422Nhà cung cấp SMS thượng nguồn từ chối yêu cầu. Khi tạo đơn thất bại, lỗi có thể kèm <code>details</code>: <code>cause_counts</code> (đơn dùng <code>product_id</code> cũ — bảng đếm nhóm theo nguyên nhân) hoặc <code>attempts</code> (đơn dùng <code>catalog_product_id</code> — kết quả từng lần thử), với các giá trị <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_AVAILABLE422Không có ưu đãi đang hoạt động nào khớp với sản phẩm và chính sách được yêu cầu (giới hạn giá, tình trạng còn hàng).
CANCEL_TOO_EARLY409Đơn hàng quá mới để hủy — hãy đợi 2 phút
REQUEST_IN_PROGRESS409Một yêu cầu tạo đơn với idempotency key này vẫn đang được xử lý
IDEMPOTENCY_KEY_REUSED422Idempotency key này đã được dùng với một body yêu cầu khác
SERVICE_UNAVAILABLE503Dịch vụ tạm thời không khả dụng (bảo trì)

Tong Quan

Tất cả trường tiền tệ trên API /v2 đều bằng USD, được trả về dưới dạng đối tượng tiền tệ — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount là một chuỗi thập phân; canonical_amount là giá trị sổ cái IDR chính xác (dùng nó để đối soát). Tỷ giá USD/IDR rate được áp dụng được công bố một lần cho mỗi phản hồi trong meta.fx. v2 là bản chiếu USD tại thời điểm hiển thị trên cùng một sổ cái IDR như v1 — nó không bao giờ lưu trữ hay giao dịch USD.

Chuyển từ v1 sang v2

Xác thực

Tất cả yêu cầu API đều yêu cầu Bearer token. Tạo token từ Cài đặt tài khoản trong bảng điều khiển, sau đó đính kèm vào mỗi yêu cầu:

Authorization: Bearer YOUR_API_TOKEN

Yêu cầu không có token hợp lệ sẽ nhận phản hồi 401 UNAUTHORIZED.

URL cơ sở

Tất cả đường dẫn endpoint bên dưới tương đối với:

https://api.smscode.gg/v2

Định dạng phản hồi

Mọi phản hồi trả về JSON với cấu trúc nhất quán. Tất cả phản hồi bao gồm header x-request-id để gỡ lỗi.

Thành công
{
  "success": true,
  "data": { ... }
}
Lỗi
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

Lỗi v2: không có tỷ giá khả dụng (503)

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

Tất cả trường tiền tệ trên API /v2 đều bằng USD, được trả về dưới dạng đối tượng tiền tệ — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount là một chuỗi thập phân; canonical_amount là giá trị sổ cái IDR chính xác (dùng nó để đối soát). Tỷ giá USD/IDR rate được áp dụng được công bố một lần cho mỗi phản hồi trong meta.fx. v2 là bản chiếu USD tại thời điểm hiển thị trên cùng một sổ cái IDR như v1 — nó không bao giờ lưu trữ hay giao dịch USD.

GET /catalog/countries

Trả về danh sách tất cả quốc gia có sẵn.

Tham số

Không có

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Giống hệt v1 — chỉ đường dẫn gốc thay đổi (/v1/v2).

GET /catalog/services

Trả về danh sách dịch vụ (nền tảng) có sẵn. Có thể lọc theo quốc gia.

Tham số truy vấn

TênKiểuBắt buộcMô tả
country_idintegerKhôngLọc dịch vụ có sẵn cho quốc gia này

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Giống hệt v1 — chỉ đường dẫn gốc thay đổi (/v1/v2).

GET /catalog/products

Trả về danh sách sản phẩm có sẵn (phân trang). Lọc theo quốc gia và/hoặc nền tảng.

Tham số truy vấn

TênKiểuBắt buộcMô tả
country_idintegerKhôngLọc theo ID quốc gia
platform_idintegerKhôngLọc theo ID nền tảng/dịch vụ
sortstringKhôngThứ tự sắp xếp: price_asc (mặc định), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerKhôngSố kết quả mỗi trang (1-10.000, mặc định 1.000)
pageintegerKhôngSố trang (tối thiểu 1, mặc định 1)

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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: các trường tiền tệ là đối tượng tiền tệ USD và phản hồi mang theo một meta.fx { pair, rate, rate_as_of } duy nhất. rate là số IDR nguyên trên 1 USD, nên USD = canonical_amount / rate. Tổng dùng 2 chữ số thập phân; giá/hoàn tiền theo từng mục dùng 4. Một số tiền dương thực sự không bao giờ được làm tròn thành 0.00. rate_as_of là dấu thời gian RFC3339 của tỷ giá (dạng +00:00) hoặc null khi không có dấu thời gian được ghi lại.

Chỉ v2: nếu không có tỷ giá USD/IDR khả dụng, các endpoint tiền tệ trả về 503 FX_RATE_UNAVAILABLE kèm header Retry-After thay vì phần thân tiền tệ. v1 không bao giờ trả về điều này.

GET /catalog/exchange-rate

Trả về tỷ giá hối đoái USD/IDR hiện tại được dùng để chuyển đổi tiền tệ.

Tham số

Không có — v2 luôn trả về USD/IDR; tham số ?pair của v1 bị bỏ qua.

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

v2: trả về { pair, rate, rate_as_of } (không có base_currency/quote_currency, không có lớp bọc meta — tỷ giá chính là dữ liệu). ?pair bị bỏ qua — v2 luôn trả về USD/IDR (v1 tôn trọng ?pair). Trả về 503 FX_RATE_UNAVAILABLE nếu không có tỷ giá khả dụng.

GET /balance

Trả về số dư tài khoản của người dùng đã xác thực.

Tham số

Không có

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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: các trường tiền tệ là đối tượng tiền tệ USD và phản hồi mang theo một meta.fx { pair, rate, rate_as_of } duy nhất. rate là số IDR nguyên trên 1 USD, nên USD = canonical_amount / rate. Tổng dùng 2 chữ số thập phân; giá/hoàn tiền theo từng mục dùng 4. Một số tiền dương thực sự không bao giờ được làm tròn thành 0.00. rate_as_of là dấu thời gian RFC3339 của tỷ giá (dạng +00:00) hoặc null khi không có dấu thời gian được ghi lại.

Chỉ v2: nếu không có tỷ giá USD/IDR khả dụng, các endpoint tiền tệ trả về 503 FX_RATE_UNAVAILABLE kèm header Retry-After thay vì phần thân tiền tệ. v1 không bao giờ trả về điều này.

GET /orders

Trả về danh sách đơn hàng của người dùng đã xác thực, sắp xếp theo mới nhất. Hỗ trợ lọc theo trạng thái và phân trang qua offset.

Tham số truy vấn

TênKiểuBắt buộcMô tả
limitintegerKhôngSố kết quả tối đa (1-100, mặc định 20)
offsetintegerKhôngSố kết quả bỏ qua (mặc định 0)
statusstringKhôngLọc theo trạng thái: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (không phân biệt hoa thường)

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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: các trường tiền tệ là đối tượng tiền tệ USD và phản hồi mang theo một meta.fx { pair, rate, rate_as_of } duy nhất. rate là số IDR nguyên trên 1 USD, nên USD = canonical_amount / rate. Tổng dùng 2 chữ số thập phân; giá/hoàn tiền theo từng mục dùng 4. Một số tiền dương thực sự không bao giờ được làm tròn thành 0.00. rate_as_of là dấu thời gian RFC3339 của tỷ giá (dạng +00:00) hoặc null khi không có dấu thời gian được ghi lại.

Chỉ v2: nếu không có tỷ giá USD/IDR khả dụng, các endpoint tiền tệ trả về 503 FX_RATE_UNAVAILABLE kèm header Retry-After thay vì phần thân tiền tệ. v1 không bao giờ trả về điều này.

GET /orders/{id}

Trả về một đơn hàng theo ID. Chỉ trả về đơn hàng của người dùng đã xác thực.

Tham số đường dẫn

TênKiểuBắt buộcMô tả
idintegerMã đơn hàng (tham số đường dẫn)

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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: các trường tiền tệ là đối tượng tiền tệ USD và phản hồi mang theo một meta.fx { pair, rate, rate_as_of } duy nhất. rate là số IDR nguyên trên 1 USD, nên USD = canonical_amount / rate. Tổng dùng 2 chữ số thập phân; giá/hoàn tiền theo từng mục dùng 4. Một số tiền dương thực sự không bao giờ được làm tròn thành 0.00. rate_as_of là dấu thời gian RFC3339 của tỷ giá (dạng +00:00) hoặc null khi không có dấu thời gian được ghi lại.

Chỉ v2: nếu không có tỷ giá USD/IDR khả dụng, các endpoint tiền tệ trả về 503 FX_RATE_UNAVAILABLE kèm header Retry-After thay vì phần thân tiền tệ. v1 không bao giờ trả về điều này.

GET /orders/active

Liệt kê tất cả đơn hàng đang hoạt động (ACTIVE + OTP_RECEIVED). Sử dụng để theo dõi cập nhật trạng thái OTP.

Tham số

Không có

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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: endpoint này không mang tiền tệ — nó không trả về amount cũng không trả về meta.fx (cùng cấu trúc như v1, dưới /v2).

POST /orders/create

Tạo đơn hàng số ảo mới. Tự động trừ số dư. Hỗ trợ header Idempotency-Key tùy chọn để tránh tạo đơn trùng lặp khi thử lại do lỗi mạng.

Nội dung yêu cầu

TênKiểuBắt buộcMô tả
product_idintegerKhôngID sản phẩm cần đặt hàng. Chỉ cung cấp MỘT trong hai: product_id HOẶC catalog_product_id, không dùng cả hai.
catalog_product_idintegerKhôngID sản phẩm ổn định — máy chủ tự chọn ưu đãi tốt nhất hiện tại (rẻ nhất, có cân nhắc độ tin cậy). Cung cấp trường này hoặc product_id.
max_priceinteger · stringKhôngGiới hạn giá tùy chọn. v1: số nguyên IDR. v2: chuỗi thập phân USD (vd. "0.50").
prefer_providerstringKhôngMã nhà cung cấp tùy chọn được ưu tiên khi các ưu đãi ngang nhau.
policystringKhôngChính sách định tuyến tùy chọn, chỉ hợp lệ khi dùng catalog_product_id. Giá trị: cheapest (mặc định) chọn ưu đãi tốt có giá thấp nhất; best_success xếp hạng các ưu đãi theo tỷ lệ giao thành công gần đây trước. best_success chấm điểm mỗi nhà cung cấp theo tỷ lệ đơn nhận được OTP trong 30 ngày trọn vẹn gần nhất, theo dải 10%, và chỉ tính một nhà cung cấp khi họ có ít nhất 20 đơn trong khoảng đó — nhà cung cấp dưới ngưỡng này hoặc chưa có lịch sử được coi là trung tính, nên các ưu đãi mới không bao giờ bị bỏ rơi (tùy chọn; tín hiệu khởi đầu ở mức trung tính). Khi prefer_provider cũng được đặt, nhà cung cấp ưu tiên vẫn được xếp trước.
quantityintegerKhôngSố lượng (1-100, mặc định 1)

Truyền header Idempotency-Key để thử lại an toàn mà không tạo đơn trùng lặp. Key có thể chứa chữ cái, chữ số, dấu gạch ngang và gạch dưới (A-Z a-z 0-9 _ -), tối đa 128 ký tự; key không hợp lệ sẽ bị từ chối với 422 VALIDATION_ERROR. Thử lại với cùng key và cùng body sẽ phát lại kết quả ban đầu (bao gồm failed_count khi thành công một phần). Lần thử lại đã đến nhà cung cấp nhưng thất bại sẽ được ghi lại và phát lại đúng lỗi đó — hãy dùng key MỚI để thử lại. Lỗi không có tác dụng phụ (không đủ số dư, không có ưu đãi khả dụng) sẽ giải phóng key, nên bạn có thể nạp tiền và thử lại với cùng key. Dùng lại một key với body khác sẽ trả về 422 IDEMPOTENCY_KEY_REUSED, và yêu cầu vẫn đang xử lý với key đó sẽ trả về 409 REQUEST_IN_PROGRESS. Trường failed_reason trong phản hồi create luôn là null — nó chỉ được điền khi poll/liệt kê đơn hàng.

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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: các trường tiền tệ là đối tượng tiền tệ USD và phản hồi mang theo một meta.fx { pair, rate, rate_as_of } duy nhất. rate là số IDR nguyên trên 1 USD, nên USD = canonical_amount / rate. Tổng dùng 2 chữ số thập phân; giá/hoàn tiền theo từng mục dùng 4. Một số tiền dương thực sự không bao giờ được làm tròn thành 0.00. rate_as_of là dấu thời gian RFC3339 của tỷ giá (dạng +00:00) hoặc null khi không có dấu thời gian được ghi lại.

Chỉ v2: nếu không có tỷ giá USD/IDR khả dụng, các endpoint tiền tệ trả về 503 FX_RATE_UNAVAILABLE kèm header Retry-After thay vì phần thân tiền tệ. v1 không bao giờ trả về điều này.

POST /orders/cancel

Hủy đơn hàng đang hoạt động. Phí thuê sẽ được hoàn vào số dư tài khoản.

Nội dung yêu cầu

TênKiểuBắt buộcMô tả
idintegerMã đơn hàng cần hủy

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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: các trường tiền tệ là đối tượng tiền tệ USD và phản hồi mang theo một meta.fx { pair, rate, rate_as_of } duy nhất. rate là số IDR nguyên trên 1 USD, nên USD = canonical_amount / rate. Tổng dùng 2 chữ số thập phân; giá/hoàn tiền theo từng mục dùng 4. Một số tiền dương thực sự không bao giờ được làm tròn thành 0.00. rate_as_of là dấu thời gian RFC3339 của tỷ giá (dạng +00:00) hoặc null khi không có dấu thời gian được ghi lại.

Chỉ v2: nếu không có tỷ giá USD/IDR khả dụng, các endpoint tiền tệ trả về 503 FX_RATE_UNAVAILABLE kèm header Retry-After thay vì phần thân tiền tệ. v1 không bao giờ trả về điều này.

POST /orders/finish

Đánh dấu đơn hàng đã hoàn thành sau khi nhận OTP. Giải phóng số ngay lập tức thay vì đợi hết hạn.

Nội dung yêu cầu

TênKiểuBắt buộcMô tả
idintegerMã đơn hàng cần hoàn thành

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Giống hệt v1 — chỉ đường dẫn gốc thay đổi (/v1/v2).

POST /orders/resend

Yêu cầu nền tảng gửi lại SMS đến số đã thuê. Không phải tất cả nền tảng đều hỗ trợ gửi lại — kiểm tra trường resent trong phản hồi.

Nội dung yêu cầu

TênKiểuBắt buộcMô tả
idintegerMã đơn hàng cần gửi lại SMS

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Giống hệt v1 — chỉ đường dẫn gốc thay đổi (/v1/v2).

GET /webhook

Trả về cấu hình webhook notification hiện tại của bạn.

Tham số

Không có

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Giống hệt v1 — chỉ đường dẫn gốc thay đổi (/v1/v2).

PATCH /webhook

Cập nhật URL webhook và/hoặc secret. Secret được tự động tạo khi bạn đặt URL lần đầu. Gửi chuỗi rỗng để xóa. URL phải sử dụng HTTPS.

Nội dung yêu cầu

TênKiểuBắt buộcMô tả
webhook_urlstringKhôngURL HTTPS để nhận sự kiện webhook (chuỗi rỗng để xóa)
webhook_secretstringKhôngSecret chia sẻ cho chữ ký HMAC-SHA256 (tự động tạo nếu bỏ qua lần đặt đầu tiên)

Cần ít nhất một trường.

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Giống hệt v1 — chỉ đường dẫn gốc thay đổi (/v1/v2).

POST /webhook/test

Gửi sự kiện thử nghiệm đến URL webhook đã cấu hình. Trả về mã trạng thái HTTP từ máy chủ của bạn. Hữu ích để kiểm tra endpoint hoạt động trước khi vận hành chính thức.

Tham số

Không có

Ví dụ yêu cầu

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()

Ví dụ phản hồi

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

Giống hệt v1 — chỉ đường dẫn gốc thay đổi (/v1/v2).

Webhook Notifications

Cấu hình URL webhook để nhận thông báo đẩy theo thời gian thực cho các sự kiện đơn hàng thay vì polling. Đây là phương pháp được khuyến nghị cho bot script.

Sự kiện

Sự kiệnKích hoạt
order.otp_receivedMã OTP đã được gửi đến số thuê
order.completedĐơn hàng đã hoàn thành (thủ công hoặc khi hết hạn)
order.expiredĐơn hàng hết hạn mà không có OTP (đã hoàn tiền)
order.canceledĐơn hàng bị hủy bởi người dùng (đã hoàn tiền)

Payload

Nội dung POST webhook
{
  "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"
  }
}

Xác minh chữ ký

Mỗi yêu cầu webhook bao gồm header X-Webhook-Signature chứa chữ ký HMAC-SHA256 của nội dung yêu cầu, sử dụng webhook_secret làm khóa:

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

Xác minh chữ ký này trên máy chủ để đảm bảo yêu cầu là xác thực. Gửi theo kiểu fire-and-forget với timeout 3 giây và không thử lại.

Rate Limits

Yêu cầu API được giới hạn tốc độ theo nhóm endpoint. Vượt quá giới hạn trả về 429 Too Many Requests với header Retry-After cho biết số giây cần đợi.

Nhóm endpointGiới hạnCửa sổ
Danh mục (quốc gia, dịch vụ, sản phẩm, tỷ giá)5.000 yêu cầu60 giây
Số dư600 yêu cầu60 giây
Đọc đơn hàng (danh sách, chi tiết, hoạt động)5.000 yêu cầu60 giây
Tạo đơn hàng3.000 yêu cầu60 giây
Hủy đơn hàng1.000 yêu cầu60 giây
Thao tác đơn hàng (hoàn thành, gửi lại)1.000 yêu cầu60 giây
Cấu hình webhook (xem, cập nhật)600 yêu cầu60 giây
Thử webhook10 yêu cầu60 giây

Mã lỗi

Phản hồi lỗi bao gồm một trong các mã sau trong error.code:

HTTPMô tả
UNAUTHORIZED401Thiếu hoặc API token không hợp lệ
FORBIDDEN403Truy cập bị từ chối
NOT_FOUND404Không tìm thấy tài nguyên (đơn hàng, tỷ giá, v.v.)
CONFLICT409Yêu cầu trùng lặp hoặc xung đột tài nguyên
INSUFFICIENT_BALANCE409Số dư không đủ để tạo đơn hàng
VALIDATION_ERROR422Tham số yêu cầu không hợp lệ
RATE_LIMIT_EXCEEDED429Quá nhiều yêu cầu (kiểm tra header Retry-After)
INTERNAL_ERROR500Lỗi máy chủ nội bộ
PROVIDER_ERROR422Nhà cung cấp SMS thượng nguồn từ chối yêu cầu. Khi tạo đơn thất bại, lỗi có thể kèm <code>details</code>: <code>cause_counts</code> (đơn dùng <code>product_id</code> cũ — bảng đếm nhóm theo nguyên nhân) hoặc <code>attempts</code> (đơn dùng <code>catalog_product_id</code> — kết quả từng lần thử), với các giá trị <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_AVAILABLE422Không có ưu đãi đang hoạt động nào khớp với sản phẩm và chính sách được yêu cầu (giới hạn giá, tình trạng còn hàng).
CANCEL_TOO_EARLY409Đơn hàng quá mới để hủy — hãy đợi 2 phút
REQUEST_IN_PROGRESS409Một yêu cầu tạo đơn với idempotency key này vẫn đang được xử lý
IDEMPOTENCY_KEY_REUSED422Idempotency key này đã được dùng với một body yêu cầu khác
SERVICE_UNAVAILABLE503Dịch vụ tạm thời không khả dụng (bảo trì)
FX_RATE_UNAVAILABLE503Tỷ giá hối đoái USD/IDR không khả dụng (các endpoint tiền tệ v2) — trả về 503 kèm header Retry-After.
v1 → v2

Chuyển từ v1 sang v2

v1 phục vụ IDR; v2 phục vụ USD. Cả hai phiên bản cùng tồn tại vĩnh viễn — không có việc ngừng hỗ trợ. Chọn một phiên bản cho mỗi tích hợp; đừng trộn lẫn đường dẫn gốc. v2 giống hệt v1 ngoại trừ cách biểu diễn tiền tệ.

Khía cạnhv1 · IDRv2 · USD
Trường tiền tệIDR nguyên, vd. 15000Đối tượng tiền tệ { amount, currency, canonical_amount, canonical_currency }
meta.fxKhông cóBắt buộc trên mọi phản hồi mang tiền tệ
Tiền tệIDRUSD (cố định trong mã)
FX_RATE_UNAVAILABLE503 + Retry-After mới khi không có tỷ giá khả dụng
Độ chính xácTổng 2 chữ số, giá/hoàn tiền 4 chữ số, làm tròn lên với số dương
GET /catalog/exchange-rate{pair, base_currency, quote_currency, rate}; tôn trọng ?pair{pair, rate, rate_as_of}; ?pair bị bỏ qua (chỉ USD/IDR)

Ví dụ song song

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 là định danh sản phẩm ỔN ĐỊNH (mỗi quốc gia + nền tảng có một) — an toàn để lưu trong hệ thống của bạn VÀ có thể đặt hàng trực tiếp: đặt hàng bằng catalog_product_id (kèm max_price và prefer_provider tùy chọn) và máy chủ sẽ chọn ưu đãi đang hoạt động cho bạn. Đường dẫn qua product_id theo từng ưu đãi vẫn được hỗ trợ và tương thích ngược — id đó không ổn định và thay đổi mỗi khi bậc giá của nhà cung cấp dịch chuyển, nên khuyến nghị tải lại danh mục trước khi đặt hàng chỉ áp dụng cho đường dẫn này.

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" } }
}

Danh sách kiểm tra khi chuyển đổi

  1. Đổi đường dẫn gốc /v1/v2.
  2. Phân tích các trường tiền tệ dưới dạng đối tượng — đọc amount như một chuỗi thập phân; currency"USD".
  3. Để đối soát sổ cái, dùng canonical_amount (IDR chính xác); số tiền USD amount là bản chiếu tại thời điểm hiển thị và rate được công bố một lần trong meta.fx.
  4. Xử lý FX_RATE_UNAVAILABLE (503) mới — thử lại sau Retry-After. v1 không bao giờ trả về điều này.