Dokumentasi API
Akses programatik ke nomor virtual, pesanan, dan saldo akun.
Gambaran Umum
Semua field uang pada API /v1 menggunakan IDR (Rupiah), sebagai satuan bilangan bulat — misalnya "price": 15000 dan "balance": 500000 berarti Rp 15.000 dan Rp 500.000. Untuk proyeksi USD-native dari ledger yang sama, beralihlah ke API v2 menggunakan tombol versi di atas.
⟩Autentikasi
Semua permintaan API memerlukan Bearer token. Buat token dari Pengaturan Akun di dashboard, lalu sertakan di setiap permintaan:
Permintaan tanpa token yang valid akan menerima respons 401 UNAUTHORIZED.
⟩Base URL
Semua path endpoint di bawah ini relatif terhadap:
⟩Format Respons
Setiap respons mengembalikan JSON dengan envelope yang konsisten. Semua respons menyertakan header x-request-id untuk debugging.
{
"success": true,
"data": { ... }
} {
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message"
}
} Semua field uang pada API /v1 menggunakan IDR (Rupiah), sebagai satuan bilangan bulat — misalnya "price": 15000 dan "balance": 500000 berarti Rp 15.000 dan Rp 500.000. Untuk proyeksi USD-native dari ledger yang sama, beralihlah ke API v2 menggunakan tombol versi di atas.
/catalog/countries Mengembalikan daftar semua negara yang tersedia.
Parameter
Tidak ada
Contoh Request
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()Contoh Response
{
"success": true,
"data": [
{
"id": 6,
"code": "ID",
"name": "Indonesia",
"dial_code": "+62",
"emoji": "🇮🇩",
"active": true
}
]
} /catalog/services Mengembalikan daftar layanan (platform) yang tersedia. Opsional filter berdasarkan negara.
Query Parameter
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
country_id | integer | Tidak | Filter layanan yang tersedia untuk negara ini |
Contoh Request
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()Contoh Response
{
"success": true,
"data": [
{
"id": 3,
"code": "wa",
"name": "WhatsApp",
"active": true
}
]
} /catalog/products Mengembalikan daftar produk yang tersedia secara berpaginasi. Filter berdasarkan negara dan/atau platform.
Query Parameter
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
country_id | integer | Tidak | Filter berdasarkan ID negara |
platform_id | integer | Tidak | Filter berdasarkan ID platform/layanan |
sort | string | Tidak | Urutan: price_asc (default), price_desc, available_asc, available_desc, name_asc, name_desc |
limit | integer | Tidak | Hasil per halaman (1-10.000, default 1.000) |
page | integer | Tidak | Nomor halaman (min 1, default 1) |
Contoh Request
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()Contoh Response
{
"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 }
} /catalog/exchange-rate Mengembalikan kurs tukar USD/IDR terkini yang digunakan untuk konversi mata uang.
Query Parameter
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
pair | string | Tidak | Pasangan mata uang (default: USD/IDR) |
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"pair": "USD/IDR",
"base_currency": "USD",
"quote_currency": "IDR",
"rate": 16250
}
} /balance Mengembalikan saldo akun pengguna yang terautentikasi.
Parameter
Tidak ada
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"currency": "IDR",
"balance": 500000
}
} /orders Mengembalikan daftar pesanan pengguna yang terautentikasi, diurutkan dari yang terbaru. Mendukung filter berdasarkan status dan paginasi via offset.
Query Parameter
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
limit | integer | Tidak | Maksimal hasil (1-100, default 20) |
offset | integer | Tidak | Jumlah hasil yang dilewati (default 0) |
status | string | Tidak | Filter berdasarkan status: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (tidak peka huruf besar/kecil) |
Contoh Request
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()Contoh Response
{
"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
}
]
} /orders/{id} Mengembalikan satu pesanan berdasarkan ID. Hanya mengembalikan pesanan milik pengguna yang terautentikasi.
Path Parameter
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
id | integer | Ya | ID Pesanan (path parameter) |
Contoh Request
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()Contoh Response
{
"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
}
} /orders/active Daftar semua pesanan yang sedang aktif (ACTIVE + OTP_RECEIVED). Gunakan ini untuk polling pembaruan status OTP.
Parameter
Tidak ada
Contoh Request
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()Contoh Response
{
"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
}
]
} /orders/create Membuat pesanan nomor virtual baru. Memotong saldo secara otomatis. Mendukung header Idempotency-Key opsional untuk mencegah pesanan duplikat saat retry jaringan.
Request Body
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
product_id | integer | Tidak | ID produk yang akan dipesan. Isi SALAH SATU saja: product_id ATAU catalog_product_id, jangan keduanya. |
catalog_product_id | integer | Tidak | ID produk stabil — server memilih penawaran terbaik saat ini (termurah, mempertimbangkan keandalan). Isi salah satu: ini atau product_id. |
max_price | integer · string | Tidak | Batas harga opsional. v1: bilangan bulat IDR. v2: string desimal USD (mis. "0.50"). |
prefer_provider | string | Tidak | Kode provider opsional yang diutamakan saat beberapa penawaran setara. |
policy | string | Tidak | Kebijakan routing opsional, hanya berlaku dengan catalog_product_id. Nilai: cheapest (default) memilih penawaran sehat termurah; best_success mengurutkan penawaran berdasarkan keberhasilan pengiriman terbaru lebih dulu. best_success menilai setiap provider dari proporsi pesanan yang menerima OTP selama 30 hari penuh terakhir, dalam pita 10%, dan baru menghitung suatu provider setelah punya minimal 20 pesanan pada rentang itu — provider di bawah ambang tersebut atau tanpa riwayat dianggap netral, sehingga penawaran baru tidak pernah terabaikan (opsional; sinyalnya mulai dari netral). Bila prefer_provider juga diisi, provider pilihan tetap diutamakan. |
quantity | integer | Tidak | Jumlah item (1-100, default 1) |
Kirim header Idempotency-Key untuk melakukan retry dengan aman tanpa membuat pesanan duplikat. Key boleh berisi huruf, angka, tanda hubung, dan garis bawah (A-Z a-z 0-9 _ -), maksimal 128 karakter; key yang tidak valid ditolak dengan 422 VALIDATION_ERROR. Retry dengan key dan body yang sama akan memutar ulang hasil aslinya (termasuk failed_count pada sukses sebagian). Retry yang sudah sampai ke penyedia tetapi gagal akan dicatat dan memutar ulang error yang sama — gunakan key BARU untuk mencoba lagi. Kegagalan tanpa efek samping (saldo tidak cukup, tidak ada penawaran tersedia) melepaskan key, sehingga Anda bisa top up dan retry dengan key yang sama. Menggunakan kembali key dengan body berbeda menghasilkan 422 IDEMPOTENCY_KEY_REUSED, dan permintaan yang masih berjalan dengan key tersebut menghasilkan 409 REQUEST_IN_PROGRESS. Field failed_reason pada respons create selalu null — field ini hanya terisi saat poll/list pesanan.
Contoh Request
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()Contoh Response
{
"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
}
} /orders/cancel Membatalkan pesanan yang aktif. Biaya sewa dikembalikan ke saldo akun kamu.
Request Body
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
id | integer | Ya | ID Pesanan yang akan dibatalkan |
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": 15000,
"new_balance": 515000
}
} /orders/finish Tandai pesanan sebagai selesai setelah menerima OTP. Ini melepaskan nomor segera alih-alih menunggu kedaluwarsa.
Request Body
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
id | integer | Ya | ID Pesanan yang akan diselesaikan |
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"order_id": 1001,
"status": "COMPLETED"
}
} /orders/resend Minta platform untuk mengirim ulang SMS ke nomor yang disewa. Tidak semua platform mendukung pengiriman ulang — periksa field resent di respons.
Request Body
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
id | integer | Ya | ID Pesanan untuk pengiriman ulang SMS |
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"order_id": 1001,
"status": "ACTIVE",
"resent": true
}
} /webhook Mengembalikan konfigurasi notifikasi webhook kamu saat ini.
Parameter
Tidak ada
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} /webhook Perbarui URL dan/atau secret webhook kamu. Secret dibuat otomatis saat kamu menetapkan URL untuk pertama kali. Kirim string kosong untuk menghapus. URL harus menggunakan HTTPS.
Request Body
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
webhook_url | string | Tidak | URL HTTPS untuk menerima event webhook (string kosong untuk menghapus) |
webhook_secret | string | Tidak | Shared secret untuk tanda tangan HMAC-SHA256 (dibuat otomatis jika tidak disertakan saat pertama kali) |
Minimal satu field wajib diisi.
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} /webhook/test Kirim event tes ke URL webhook yang sudah dikonfigurasi. Mengembalikan kode status HTTP dari server kamu. Berguna untuk memverifikasi endpoint berfungsi sebelum go live.
Parameter
Tidak ada
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"status_code": 200
}
} ⟩Notifikasi Webhook
Konfigurasi URL webhook untuk menerima notifikasi push real-time untuk event pesanan alih-alih polling. Ini adalah pendekatan yang direkomendasikan untuk bot script.
Event
| Event | Pemicu |
|---|---|
order.otp_received | Kode OTP terkirim ke nomor yang disewa |
order.completed | Pesanan ditandai selesai (manual atau kedaluwarsa) |
order.expired | Pesanan kedaluwarsa tanpa OTP (saldo dikembalikan) |
order.canceled | Pesanan dibatalkan oleh pengguna (saldo dikembalikan) |
Payload
{
"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"
}
} Verifikasi Tanda Tangan
Setiap permintaan webhook menyertakan header X-Webhook-Signature dengan tanda tangan HMAC-SHA256 dari body permintaan, menggunakan webhook_secret kamu sebagai kunci:
Verifikasi tanda tangan ini di server kamu untuk memastikan permintaan autentik. Pengiriman bersifat fire-and-forget dengan timeout 3 detik dan tanpa retry.
⟩Rate Limit
Permintaan API dibatasi per grup endpoint. Melebihi batas mengembalikan 429 Too Many Requests dengan header Retry-After yang menunjukkan berapa detik harus menunggu.
| Grup Endpoint | Batas | Jendela Waktu |
|---|---|---|
| Katalog (countries, services, products, exchange-rate) | 5.000 permintaan | 60 detik |
| Saldo | 600 permintaan | 60 detik |
| Baca pesanan (list, get, active) | 5.000 permintaan | 60 detik |
| Buat pesanan | 3.000 permintaan | 60 detik |
| Batalkan pesanan | 1.000 permintaan | 60 detik |
| Aksi pesanan (finish, resend) | 1.000 permintaan | 60 detik |
| Konfigurasi webhook (get, update) | 600 permintaan | 60 detik |
| Tes webhook | 10 permintaan | 60 detik |
⟩Kode Error
Respons error menyertakan salah satu kode berikut di error.code:
| Kode | HTTP | Deskripsi |
|---|---|---|
UNAUTHORIZED | 401 | Token API tidak ada atau tidak valid |
FORBIDDEN | 403 | Akses ditolak |
NOT_FOUND | 404 | Sumber daya tidak ditemukan (pesanan, kurs tukar, dll.) |
CONFLICT | 409 | Permintaan duplikat atau konflik sumber daya |
INSUFFICIENT_BALANCE | 409 | Saldo tidak cukup untuk membuat pesanan |
VALIDATION_ERROR | 422 | Parameter permintaan gagal validasi |
RATE_LIMIT_EXCEEDED | 429 | Terlalu banyak permintaan (cek header Retry-After) |
INTERNAL_ERROR | 500 | Kesalahan server internal |
PROVIDER_ERROR | 422 | Penyedia SMS upstream menolak permintaan. Pada kegagalan pembuatan pesanan, error dapat membawa <code>details</code>: <code>cause_counts</code> (pesanan <code>product_id</code> lama — rekap yang dikelompokkan per penyebab) atau <code>attempts</code> (pesanan <code>catalog_product_id</code> — hasil per percobaan), menggunakan nilai <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_AVAILABLE | 422 | Tidak ada penawaran aktif yang cocok dengan produk dan kebijakan yang diminta (batas harga, ketersediaan). |
CANCEL_TOO_EARLY | 409 | Pesanan terlalu baru untuk dibatalkan — tunggu 2 menit |
REQUEST_IN_PROGRESS | 409 | Permintaan pembuatan dengan idempotency key ini masih berjalan |
IDEMPOTENCY_KEY_REUSED | 422 | Idempotency key ini sudah dipakai dengan body permintaan yang berbeda |
SERVICE_UNAVAILABLE | 503 | Layanan sementara tidak tersedia (pemeliharaan) |
Gambaran Umum
Semua field uang pada API /v2 menggunakan USD, dikembalikan sebagai objek uang — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount adalah string desimal; canonical_amount adalah nilai ledger IDR yang persis (pakai ini untuk rekonsiliasi). rate USD/IDR yang dipakai diungkap sekali per respons di meta.fx. v2 adalah proyeksi USD saat render di atas ledger IDR yang sama dengan v1 — tidak pernah menyimpan atau mentransaksikan USD.
⟩Autentikasi
Semua permintaan API memerlukan Bearer token. Buat token dari Pengaturan Akun di dashboard, lalu sertakan di setiap permintaan:
Permintaan tanpa token yang valid akan menerima respons 401 UNAUTHORIZED.
⟩Base URL
Semua path endpoint di bawah ini relatif terhadap:
⟩Format Respons
Setiap respons mengembalikan JSON dengan envelope yang konsisten. Semua respons menyertakan header x-request-id untuk debugging.
{
"success": true,
"data": { ... }
} {
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message"
}
} Error v2: tidak ada kurs yang dapat dipakai (503)
{
"success": false,
"error": { "code": "FX_RATE_UNAVAILABLE", "message": "USD/IDR exchange rate is unavailable" }
} Semua field uang pada API /v2 menggunakan USD, dikembalikan sebagai objek uang — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount adalah string desimal; canonical_amount adalah nilai ledger IDR yang persis (pakai ini untuk rekonsiliasi). rate USD/IDR yang dipakai diungkap sekali per respons di meta.fx. v2 adalah proyeksi USD saat render di atas ledger IDR yang sama dengan v1 — tidak pernah menyimpan atau mentransaksikan USD.
/catalog/countries Mengembalikan daftar semua negara yang tersedia.
Parameter
Tidak ada
Contoh Request
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()Contoh Response
{
"success": true,
"data": [
{
"id": 6,
"code": "ID",
"name": "Indonesia",
"dial_code": "+62",
"emoji": "🇮🇩",
"active": true
}
]
} Identik dengan v1 — hanya base path yang berubah (/v1 → /v2).
/catalog/services Mengembalikan daftar layanan (platform) yang tersedia. Opsional filter berdasarkan negara.
Query Parameter
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
country_id | integer | Tidak | Filter layanan yang tersedia untuk negara ini |
Contoh Request
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()Contoh Response
{
"success": true,
"data": [
{
"id": 3,
"code": "wa",
"name": "WhatsApp",
"active": true
}
]
} Identik dengan v1 — hanya base path yang berubah (/v1 → /v2).
/catalog/products Mengembalikan daftar produk yang tersedia secara berpaginasi. Filter berdasarkan negara dan/atau platform.
Query Parameter
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
country_id | integer | Tidak | Filter berdasarkan ID negara |
platform_id | integer | Tidak | Filter berdasarkan ID platform/layanan |
sort | string | Tidak | Urutan: price_asc (default), price_desc, available_asc, available_desc, name_asc, name_desc |
limit | integer | Tidak | Hasil per halaman (1-10.000, default 1.000) |
page | integer | Tidak | Nomor halaman (min 1, default 1) |
Contoh Request
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()Contoh Response
{
"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: field uang berupa objek uang USD dan respons membawa satu meta.fx { pair, rate, rate_as_of }. rate adalah IDR bulat per 1 USD, jadi USD = canonical_amount / rate. Total memakai 2 desimal; harga/refund per item memakai 4. Nilai yang positif tidak pernah dibulatkan menjadi 0.00. rate_as_of adalah timestamp RFC3339 dari kurs tersebut (bentuk +00:00) atau null bila tidak ada timestamp yang tercatat.
v2 saja: jika tidak ada kurs USD/IDR yang dapat dipakai, endpoint uang mengembalikan 503 FX_RATE_UNAVAILABLE dengan header Retry-After alih-alih body uang. v1 tidak pernah mengembalikan ini.
/catalog/exchange-rate Mengembalikan kurs tukar USD/IDR terkini yang digunakan untuk konversi mata uang.
Parameter
Tidak ada — v2 selalu mengembalikan USD/IDR; parameter ?pair dari v1 diabaikan.
Contoh Request
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()Contoh Response
{
"success": true,
"data": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" }
} v2: mengembalikan { pair, rate, rate_as_of } (tanpa base_currency/quote_currency, tanpa pembungkus meta — kursnya adalah datanya). ?pair diabaikan — v2 selalu mengembalikan USD/IDR (v1 menghormati ?pair). Mengembalikan 503 FX_RATE_UNAVAILABLE jika tidak ada kurs yang dapat dipakai.
/balance Mengembalikan saldo akun pengguna yang terautentikasi.
Parameter
Tidak ada
Contoh Request
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()Contoh Response
{
"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: field uang berupa objek uang USD dan respons membawa satu meta.fx { pair, rate, rate_as_of }. rate adalah IDR bulat per 1 USD, jadi USD = canonical_amount / rate. Total memakai 2 desimal; harga/refund per item memakai 4. Nilai yang positif tidak pernah dibulatkan menjadi 0.00. rate_as_of adalah timestamp RFC3339 dari kurs tersebut (bentuk +00:00) atau null bila tidak ada timestamp yang tercatat.
v2 saja: jika tidak ada kurs USD/IDR yang dapat dipakai, endpoint uang mengembalikan 503 FX_RATE_UNAVAILABLE dengan header Retry-After alih-alih body uang. v1 tidak pernah mengembalikan ini.
/orders Mengembalikan daftar pesanan pengguna yang terautentikasi, diurutkan dari yang terbaru. Mendukung filter berdasarkan status dan paginasi via offset.
Query Parameter
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
limit | integer | Tidak | Maksimal hasil (1-100, default 20) |
offset | integer | Tidak | Jumlah hasil yang dilewati (default 0) |
status | string | Tidak | Filter berdasarkan status: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (tidak peka huruf besar/kecil) |
Contoh Request
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()Contoh Response
{
"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: field uang berupa objek uang USD dan respons membawa satu meta.fx { pair, rate, rate_as_of }. rate adalah IDR bulat per 1 USD, jadi USD = canonical_amount / rate. Total memakai 2 desimal; harga/refund per item memakai 4. Nilai yang positif tidak pernah dibulatkan menjadi 0.00. rate_as_of adalah timestamp RFC3339 dari kurs tersebut (bentuk +00:00) atau null bila tidak ada timestamp yang tercatat.
v2 saja: jika tidak ada kurs USD/IDR yang dapat dipakai, endpoint uang mengembalikan 503 FX_RATE_UNAVAILABLE dengan header Retry-After alih-alih body uang. v1 tidak pernah mengembalikan ini.
/orders/{id} Mengembalikan satu pesanan berdasarkan ID. Hanya mengembalikan pesanan milik pengguna yang terautentikasi.
Path Parameter
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
id | integer | Ya | ID Pesanan (path parameter) |
Contoh Request
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()Contoh Response
{
"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: field uang berupa objek uang USD dan respons membawa satu meta.fx { pair, rate, rate_as_of }. rate adalah IDR bulat per 1 USD, jadi USD = canonical_amount / rate. Total memakai 2 desimal; harga/refund per item memakai 4. Nilai yang positif tidak pernah dibulatkan menjadi 0.00. rate_as_of adalah timestamp RFC3339 dari kurs tersebut (bentuk +00:00) atau null bila tidak ada timestamp yang tercatat.
v2 saja: jika tidak ada kurs USD/IDR yang dapat dipakai, endpoint uang mengembalikan 503 FX_RATE_UNAVAILABLE dengan header Retry-After alih-alih body uang. v1 tidak pernah mengembalikan ini.
/orders/active Daftar semua pesanan yang sedang aktif (ACTIVE + OTP_RECEIVED). Gunakan ini untuk polling pembaruan status OTP.
Parameter
Tidak ada
Contoh Request
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()Contoh Response
{
"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 ini bukan pembawa uang — tidak mengembalikan amount maupun meta.fx (bentuknya sama dengan v1, di bawah /v2).
/orders/create Membuat pesanan nomor virtual baru. Memotong saldo secara otomatis. Mendukung header Idempotency-Key opsional untuk mencegah pesanan duplikat saat retry jaringan.
Request Body
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
product_id | integer | Tidak | ID produk yang akan dipesan. Isi SALAH SATU saja: product_id ATAU catalog_product_id, jangan keduanya. |
catalog_product_id | integer | Tidak | ID produk stabil — server memilih penawaran terbaik saat ini (termurah, mempertimbangkan keandalan). Isi salah satu: ini atau product_id. |
max_price | integer · string | Tidak | Batas harga opsional. v1: bilangan bulat IDR. v2: string desimal USD (mis. "0.50"). |
prefer_provider | string | Tidak | Kode provider opsional yang diutamakan saat beberapa penawaran setara. |
policy | string | Tidak | Kebijakan routing opsional, hanya berlaku dengan catalog_product_id. Nilai: cheapest (default) memilih penawaran sehat termurah; best_success mengurutkan penawaran berdasarkan keberhasilan pengiriman terbaru lebih dulu. best_success menilai setiap provider dari proporsi pesanan yang menerima OTP selama 30 hari penuh terakhir, dalam pita 10%, dan baru menghitung suatu provider setelah punya minimal 20 pesanan pada rentang itu — provider di bawah ambang tersebut atau tanpa riwayat dianggap netral, sehingga penawaran baru tidak pernah terabaikan (opsional; sinyalnya mulai dari netral). Bila prefer_provider juga diisi, provider pilihan tetap diutamakan. |
quantity | integer | Tidak | Jumlah item (1-100, default 1) |
Kirim header Idempotency-Key untuk melakukan retry dengan aman tanpa membuat pesanan duplikat. Key boleh berisi huruf, angka, tanda hubung, dan garis bawah (A-Z a-z 0-9 _ -), maksimal 128 karakter; key yang tidak valid ditolak dengan 422 VALIDATION_ERROR. Retry dengan key dan body yang sama akan memutar ulang hasil aslinya (termasuk failed_count pada sukses sebagian). Retry yang sudah sampai ke penyedia tetapi gagal akan dicatat dan memutar ulang error yang sama — gunakan key BARU untuk mencoba lagi. Kegagalan tanpa efek samping (saldo tidak cukup, tidak ada penawaran tersedia) melepaskan key, sehingga Anda bisa top up dan retry dengan key yang sama. Menggunakan kembali key dengan body berbeda menghasilkan 422 IDEMPOTENCY_KEY_REUSED, dan permintaan yang masih berjalan dengan key tersebut menghasilkan 409 REQUEST_IN_PROGRESS. Field failed_reason pada respons create selalu null — field ini hanya terisi saat poll/list pesanan.
Contoh Request
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()Contoh Response
{
"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: field uang berupa objek uang USD dan respons membawa satu meta.fx { pair, rate, rate_as_of }. rate adalah IDR bulat per 1 USD, jadi USD = canonical_amount / rate. Total memakai 2 desimal; harga/refund per item memakai 4. Nilai yang positif tidak pernah dibulatkan menjadi 0.00. rate_as_of adalah timestamp RFC3339 dari kurs tersebut (bentuk +00:00) atau null bila tidak ada timestamp yang tercatat.
v2 saja: jika tidak ada kurs USD/IDR yang dapat dipakai, endpoint uang mengembalikan 503 FX_RATE_UNAVAILABLE dengan header Retry-After alih-alih body uang. v1 tidak pernah mengembalikan ini.
/orders/cancel Membatalkan pesanan yang aktif. Biaya sewa dikembalikan ke saldo akun kamu.
Request Body
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
id | integer | Ya | ID Pesanan yang akan dibatalkan |
Contoh Request
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()Contoh Response
{
"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: field uang berupa objek uang USD dan respons membawa satu meta.fx { pair, rate, rate_as_of }. rate adalah IDR bulat per 1 USD, jadi USD = canonical_amount / rate. Total memakai 2 desimal; harga/refund per item memakai 4. Nilai yang positif tidak pernah dibulatkan menjadi 0.00. rate_as_of adalah timestamp RFC3339 dari kurs tersebut (bentuk +00:00) atau null bila tidak ada timestamp yang tercatat.
v2 saja: jika tidak ada kurs USD/IDR yang dapat dipakai, endpoint uang mengembalikan 503 FX_RATE_UNAVAILABLE dengan header Retry-After alih-alih body uang. v1 tidak pernah mengembalikan ini.
/orders/finish Tandai pesanan sebagai selesai setelah menerima OTP. Ini melepaskan nomor segera alih-alih menunggu kedaluwarsa.
Request Body
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
id | integer | Ya | ID Pesanan yang akan diselesaikan |
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"order_id": 1001,
"status": "COMPLETED"
}
} Identik dengan v1 — hanya base path yang berubah (/v1 → /v2).
/orders/resend Minta platform untuk mengirim ulang SMS ke nomor yang disewa. Tidak semua platform mendukung pengiriman ulang — periksa field resent di respons.
Request Body
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
id | integer | Ya | ID Pesanan untuk pengiriman ulang SMS |
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"order_id": 1001,
"status": "ACTIVE",
"resent": true
}
} Identik dengan v1 — hanya base path yang berubah (/v1 → /v2).
/webhook Mengembalikan konfigurasi notifikasi webhook kamu saat ini.
Parameter
Tidak ada
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} Identik dengan v1 — hanya base path yang berubah (/v1 → /v2).
/webhook Perbarui URL dan/atau secret webhook kamu. Secret dibuat otomatis saat kamu menetapkan URL untuk pertama kali. Kirim string kosong untuk menghapus. URL harus menggunakan HTTPS.
Request Body
| Nama | Tipe | Wajib | Deskripsi |
|---|---|---|---|
webhook_url | string | Tidak | URL HTTPS untuk menerima event webhook (string kosong untuk menghapus) |
webhook_secret | string | Tidak | Shared secret untuk tanda tangan HMAC-SHA256 (dibuat otomatis jika tidak disertakan saat pertama kali) |
Minimal satu field wajib diisi.
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} Identik dengan v1 — hanya base path yang berubah (/v1 → /v2).
/webhook/test Kirim event tes ke URL webhook yang sudah dikonfigurasi. Mengembalikan kode status HTTP dari server kamu. Berguna untuk memverifikasi endpoint berfungsi sebelum go live.
Parameter
Tidak ada
Contoh Request
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()Contoh Response
{
"success": true,
"data": {
"status_code": 200
}
} Identik dengan v1 — hanya base path yang berubah (/v1 → /v2).
⟩Notifikasi Webhook
Konfigurasi URL webhook untuk menerima notifikasi push real-time untuk event pesanan alih-alih polling. Ini adalah pendekatan yang direkomendasikan untuk bot script.
Event
| Event | Pemicu |
|---|---|
order.otp_received | Kode OTP terkirim ke nomor yang disewa |
order.completed | Pesanan ditandai selesai (manual atau kedaluwarsa) |
order.expired | Pesanan kedaluwarsa tanpa OTP (saldo dikembalikan) |
order.canceled | Pesanan dibatalkan oleh pengguna (saldo dikembalikan) |
Payload
{
"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"
}
} Verifikasi Tanda Tangan
Setiap permintaan webhook menyertakan header X-Webhook-Signature dengan tanda tangan HMAC-SHA256 dari body permintaan, menggunakan webhook_secret kamu sebagai kunci:
Verifikasi tanda tangan ini di server kamu untuk memastikan permintaan autentik. Pengiriman bersifat fire-and-forget dengan timeout 3 detik dan tanpa retry.
⟩Rate Limit
Permintaan API dibatasi per grup endpoint. Melebihi batas mengembalikan 429 Too Many Requests dengan header Retry-After yang menunjukkan berapa detik harus menunggu.
| Grup Endpoint | Batas | Jendela Waktu |
|---|---|---|
| Katalog (countries, services, products, exchange-rate) | 5.000 permintaan | 60 detik |
| Saldo | 600 permintaan | 60 detik |
| Baca pesanan (list, get, active) | 5.000 permintaan | 60 detik |
| Buat pesanan | 3.000 permintaan | 60 detik |
| Batalkan pesanan | 1.000 permintaan | 60 detik |
| Aksi pesanan (finish, resend) | 1.000 permintaan | 60 detik |
| Konfigurasi webhook (get, update) | 600 permintaan | 60 detik |
| Tes webhook | 10 permintaan | 60 detik |
⟩Kode Error
Respons error menyertakan salah satu kode berikut di error.code:
| Kode | HTTP | Deskripsi |
|---|---|---|
UNAUTHORIZED | 401 | Token API tidak ada atau tidak valid |
FORBIDDEN | 403 | Akses ditolak |
NOT_FOUND | 404 | Sumber daya tidak ditemukan (pesanan, kurs tukar, dll.) |
CONFLICT | 409 | Permintaan duplikat atau konflik sumber daya |
INSUFFICIENT_BALANCE | 409 | Saldo tidak cukup untuk membuat pesanan |
VALIDATION_ERROR | 422 | Parameter permintaan gagal validasi |
RATE_LIMIT_EXCEEDED | 429 | Terlalu banyak permintaan (cek header Retry-After) |
INTERNAL_ERROR | 500 | Kesalahan server internal |
PROVIDER_ERROR | 422 | Penyedia SMS upstream menolak permintaan. Pada kegagalan pembuatan pesanan, error dapat membawa <code>details</code>: <code>cause_counts</code> (pesanan <code>product_id</code> lama — rekap yang dikelompokkan per penyebab) atau <code>attempts</code> (pesanan <code>catalog_product_id</code> — hasil per percobaan), menggunakan nilai <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_AVAILABLE | 422 | Tidak ada penawaran aktif yang cocok dengan produk dan kebijakan yang diminta (batas harga, ketersediaan). |
CANCEL_TOO_EARLY | 409 | Pesanan terlalu baru untuk dibatalkan — tunggu 2 menit |
REQUEST_IN_PROGRESS | 409 | Permintaan pembuatan dengan idempotency key ini masih berjalan |
IDEMPOTENCY_KEY_REUSED | 422 | Idempotency key ini sudah dipakai dengan body permintaan yang berbeda |
SERVICE_UNAVAILABLE | 503 | Layanan sementara tidak tersedia (pemeliharaan) |
FX_RATE_UNAVAILABLE | 503 | Kurs tukar USD/IDR tidak tersedia (endpoint uang v2) — mengembalikan 503 dengan header Retry-After. |
⟩Migrasi v1 → v2
v1 menyajikan IDR; v2 menyajikan USD. Kedua versi berdampingan secara permanen — tidak ada penghentian. Pilih satu versi per integrasi; jangan mencampur base path. v2 identik dengan v1 kecuali cara uang direpresentasikan.
| Aspek | v1 · IDR | v2 · USD |
|---|---|---|
| Field uang | IDR bilangan bulat, mis. 15000 | Objek uang { amount, currency, canonical_amount, canonical_currency } |
meta.fx | Tidak ada | Wajib pada setiap respons pembawa uang |
| Mata uang | IDR | USD (hardcoded di kode) |
FX_RATE_UNAVAILABLE | — | 503 + Retry-After baru saat tidak ada kurs yang dapat dipakai |
| Presisi | — | Total 2 desimal, harga/refund 4 desimal, positive-floor |
GET /catalog/exchange-rate | {pair, base_currency, quote_currency, rate}; menghormati ?pair | {pair, rate, rate_as_of}; ?pair diabaikan (hanya USD/IDR) |
Contoh berdampingan
{
"success": true,
"data": {
"currency": "IDR",
"balance": 500000
}
}{
"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" } }
}{
"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 }
}{
"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 adalah identitas produk yang STABIL (satu per negara + platform) — aman disimpan di sistem kamu DAN bisa langsung dipesan: order dengan catalog_product_id (plus max_price dan prefer_provider opsional) dan server akan memilih penawaran live untukmu. Jalur product_id per-penawaran tetap didukung dan kompatibel-mundur — id itu bersifat volatil dan berubah setiap kali tier harga provider bergeser, jadi anjuran ambil ulang katalog sebelum order hanya berlaku untuk jalur tersebut.
{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": 15000,
"new_balance": 515000
}
}{
"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" } }
}Checklist migrasi
- Ganti base path
/v1→/v2. - Parse field uang sebagai objek — baca
amountsebagai string desimal;currencybernilai"USD". - Untuk rekonsiliasi ledger gunakan
canonical_amount(IDR persis);amountUSD adalah proyeksi saat render danrate-nya diungkap sekali dimeta.fx. - Tangani
FX_RATE_UNAVAILABLE(503) yang baru — coba lagi setelahRetry-After. v1 tidak pernah mengembalikan ini.