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:

Authorization: Bearer YOUR_API_TOKEN

Permintaan tanpa token yang valid akan menerima respons 401 UNAUTHORIZED.

Base URL

Semua path endpoint di bawah ini relatif terhadap:

https://api.smscode.gg/v1

Format Respons

Setiap respons mengembalikan JSON dengan envelope yang konsisten. Semua respons menyertakan header x-request-id untuk debugging.

Sukses
{
  "success": true,
  "data": { ... }
}
Error
{
  "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.

GET /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

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

Mengembalikan daftar layanan (platform) yang tersedia. Opsional filter berdasarkan negara.

Query Parameter

NamaTipeWajibDeskripsi
country_idintegerTidakFilter 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

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

Mengembalikan daftar produk yang tersedia secara berpaginasi. Filter berdasarkan negara dan/atau platform.

Query Parameter

NamaTipeWajibDeskripsi
country_idintegerTidakFilter berdasarkan ID negara
platform_idintegerTidakFilter berdasarkan ID platform/layanan
sortstringTidakUrutan: price_asc (default), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerTidakHasil per halaman (1-10.000, default 1.000)
pageintegerTidakNomor 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

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

Mengembalikan kurs tukar USD/IDR terkini yang digunakan untuk konversi mata uang.

Query Parameter

NamaTipeWajibDeskripsi
pairstringTidakPasangan 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

200 OK
{
  "success": true,
  "data": {
    "pair": "USD/IDR",
    "base_currency": "USD",
    "quote_currency": "IDR",
    "rate": 16250
  }
}
GET /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

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

Mengembalikan daftar pesanan pengguna yang terautentikasi, diurutkan dari yang terbaru. Mendukung filter berdasarkan status dan paginasi via offset.

Query Parameter

NamaTipeWajibDeskripsi
limitintegerTidakMaksimal hasil (1-100, default 20)
offsetintegerTidakJumlah hasil yang dilewati (default 0)
statusstringTidakFilter 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

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}

Mengembalikan satu pesanan berdasarkan ID. Hanya mengembalikan pesanan milik pengguna yang terautentikasi.

Path Parameter

NamaTipeWajibDeskripsi
idintegerYaID 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

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

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

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

Membuat pesanan nomor virtual baru. Memotong saldo secara otomatis. Mendukung header Idempotency-Key opsional untuk mencegah pesanan duplikat saat retry jaringan.

Request Body

NamaTipeWajibDeskripsi
product_idintegerTidakID produk yang akan dipesan. Isi SALAH SATU saja: product_id ATAU catalog_product_id, jangan keduanya.
catalog_product_idintegerTidakID produk stabil — server memilih penawaran terbaik saat ini (termurah, mempertimbangkan keandalan). Isi salah satu: ini atau product_id.
max_priceinteger · stringTidakBatas harga opsional. v1: bilangan bulat IDR. v2: string desimal USD (mis. "0.50").
prefer_providerstringTidakKode provider opsional yang diutamakan saat beberapa penawaran setara.
policystringTidakKebijakan 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.
quantityintegerTidakJumlah 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

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

Membatalkan pesanan yang aktif. Biaya sewa dikembalikan ke saldo akun kamu.

Request Body

NamaTipeWajibDeskripsi
idintegerYaID 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

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

Tandai pesanan sebagai selesai setelah menerima OTP. Ini melepaskan nomor segera alih-alih menunggu kedaluwarsa.

Request Body

NamaTipeWajibDeskripsi
idintegerYaID 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

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}
POST /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

NamaTipeWajibDeskripsi
idintegerYaID 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

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}
GET /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

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
PATCH /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

NamaTipeWajibDeskripsi
webhook_urlstringTidakURL HTTPS untuk menerima event webhook (string kosong untuk menghapus)
webhook_secretstringTidakShared 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

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
POST /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

200 OK
{
  "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

EventPemicu
order.otp_receivedKode OTP terkirim ke nomor yang disewa
order.completedPesanan ditandai selesai (manual atau kedaluwarsa)
order.expiredPesanan kedaluwarsa tanpa OTP (saldo dikembalikan)
order.canceledPesanan dibatalkan oleh pengguna (saldo dikembalikan)

Payload

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

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:

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

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 EndpointBatasJendela Waktu
Katalog (countries, services, products, exchange-rate)5.000 permintaan60 detik
Saldo600 permintaan60 detik
Baca pesanan (list, get, active)5.000 permintaan60 detik
Buat pesanan3.000 permintaan60 detik
Batalkan pesanan1.000 permintaan60 detik
Aksi pesanan (finish, resend)1.000 permintaan60 detik
Konfigurasi webhook (get, update)600 permintaan60 detik
Tes webhook10 permintaan60 detik

Kode Error

Respons error menyertakan salah satu kode berikut di error.code:

KodeHTTPDeskripsi
UNAUTHORIZED401Token API tidak ada atau tidak valid
FORBIDDEN403Akses ditolak
NOT_FOUND404Sumber daya tidak ditemukan (pesanan, kurs tukar, dll.)
CONFLICT409Permintaan duplikat atau konflik sumber daya
INSUFFICIENT_BALANCE409Saldo tidak cukup untuk membuat pesanan
VALIDATION_ERROR422Parameter permintaan gagal validasi
RATE_LIMIT_EXCEEDED429Terlalu banyak permintaan (cek header Retry-After)
INTERNAL_ERROR500Kesalahan server internal
PROVIDER_ERROR422Penyedia 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_AVAILABLE422Tidak ada penawaran aktif yang cocok dengan produk dan kebijakan yang diminta (batas harga, ketersediaan).
CANCEL_TOO_EARLY409Pesanan terlalu baru untuk dibatalkan — tunggu 2 menit
REQUEST_IN_PROGRESS409Permintaan pembuatan dengan idempotency key ini masih berjalan
IDEMPOTENCY_KEY_REUSED422Idempotency key ini sudah dipakai dengan body permintaan yang berbeda
SERVICE_UNAVAILABLE503Layanan 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.

Migrasi v1 → v2

Autentikasi

Semua permintaan API memerlukan Bearer token. Buat token dari Pengaturan Akun di dashboard, lalu sertakan di setiap permintaan:

Authorization: Bearer YOUR_API_TOKEN

Permintaan tanpa token yang valid akan menerima respons 401 UNAUTHORIZED.

Base URL

Semua path endpoint di bawah ini relatif terhadap:

https://api.smscode.gg/v2

Format Respons

Setiap respons mengembalikan JSON dengan envelope yang konsisten. Semua respons menyertakan header x-request-id untuk debugging.

Sukses
{
  "success": true,
  "data": { ... }
}
Error
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

Error v2: tidak ada kurs yang dapat dipakai (503)

503 Service Unavailable · Retry-After: 60
{
  "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.

GET /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

200 OK
{
  "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).

GET /catalog/services

Mengembalikan daftar layanan (platform) yang tersedia. Opsional filter berdasarkan negara.

Query Parameter

NamaTipeWajibDeskripsi
country_idintegerTidakFilter 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

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

Identik dengan v1 — hanya base path yang berubah (/v1/v2).

GET /catalog/products

Mengembalikan daftar produk yang tersedia secara berpaginasi. Filter berdasarkan negara dan/atau platform.

Query Parameter

NamaTipeWajibDeskripsi
country_idintegerTidakFilter berdasarkan ID negara
platform_idintegerTidakFilter berdasarkan ID platform/layanan
sortstringTidakUrutan: price_asc (default), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerTidakHasil per halaman (1-10.000, default 1.000)
pageintegerTidakNomor 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

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: 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.

GET /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

200 OK
{
  "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.

GET /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

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: 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.

GET /orders

Mengembalikan daftar pesanan pengguna yang terautentikasi, diurutkan dari yang terbaru. Mendukung filter berdasarkan status dan paginasi via offset.

Query Parameter

NamaTipeWajibDeskripsi
limitintegerTidakMaksimal hasil (1-100, default 20)
offsetintegerTidakJumlah hasil yang dilewati (default 0)
statusstringTidakFilter 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

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: 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.

GET /orders/{id}

Mengembalikan satu pesanan berdasarkan ID. Hanya mengembalikan pesanan milik pengguna yang terautentikasi.

Path Parameter

NamaTipeWajibDeskripsi
idintegerYaID 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

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: 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.

GET /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

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 ini bukan pembawa uang — tidak mengembalikan amount maupun meta.fx (bentuknya sama dengan v1, di bawah /v2).

POST /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

NamaTipeWajibDeskripsi
product_idintegerTidakID produk yang akan dipesan. Isi SALAH SATU saja: product_id ATAU catalog_product_id, jangan keduanya.
catalog_product_idintegerTidakID produk stabil — server memilih penawaran terbaik saat ini (termurah, mempertimbangkan keandalan). Isi salah satu: ini atau product_id.
max_priceinteger · stringTidakBatas harga opsional. v1: bilangan bulat IDR. v2: string desimal USD (mis. "0.50").
prefer_providerstringTidakKode provider opsional yang diutamakan saat beberapa penawaran setara.
policystringTidakKebijakan 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.
quantityintegerTidakJumlah 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

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: 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.

POST /orders/cancel

Membatalkan pesanan yang aktif. Biaya sewa dikembalikan ke saldo akun kamu.

Request Body

NamaTipeWajibDeskripsi
idintegerYaID 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

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: 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.

POST /orders/finish

Tandai pesanan sebagai selesai setelah menerima OTP. Ini melepaskan nomor segera alih-alih menunggu kedaluwarsa.

Request Body

NamaTipeWajibDeskripsi
idintegerYaID 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

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

Identik dengan v1 — hanya base path yang berubah (/v1/v2).

POST /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

NamaTipeWajibDeskripsi
idintegerYaID 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

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

Identik dengan v1 — hanya base path yang berubah (/v1/v2).

GET /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

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

Identik dengan v1 — hanya base path yang berubah (/v1/v2).

PATCH /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

NamaTipeWajibDeskripsi
webhook_urlstringTidakURL HTTPS untuk menerima event webhook (string kosong untuk menghapus)
webhook_secretstringTidakShared 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

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

Identik dengan v1 — hanya base path yang berubah (/v1/v2).

POST /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

200 OK
{
  "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

EventPemicu
order.otp_receivedKode OTP terkirim ke nomor yang disewa
order.completedPesanan ditandai selesai (manual atau kedaluwarsa)
order.expiredPesanan kedaluwarsa tanpa OTP (saldo dikembalikan)
order.canceledPesanan dibatalkan oleh pengguna (saldo dikembalikan)

Payload

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

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:

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

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 EndpointBatasJendela Waktu
Katalog (countries, services, products, exchange-rate)5.000 permintaan60 detik
Saldo600 permintaan60 detik
Baca pesanan (list, get, active)5.000 permintaan60 detik
Buat pesanan3.000 permintaan60 detik
Batalkan pesanan1.000 permintaan60 detik
Aksi pesanan (finish, resend)1.000 permintaan60 detik
Konfigurasi webhook (get, update)600 permintaan60 detik
Tes webhook10 permintaan60 detik

Kode Error

Respons error menyertakan salah satu kode berikut di error.code:

KodeHTTPDeskripsi
UNAUTHORIZED401Token API tidak ada atau tidak valid
FORBIDDEN403Akses ditolak
NOT_FOUND404Sumber daya tidak ditemukan (pesanan, kurs tukar, dll.)
CONFLICT409Permintaan duplikat atau konflik sumber daya
INSUFFICIENT_BALANCE409Saldo tidak cukup untuk membuat pesanan
VALIDATION_ERROR422Parameter permintaan gagal validasi
RATE_LIMIT_EXCEEDED429Terlalu banyak permintaan (cek header Retry-After)
INTERNAL_ERROR500Kesalahan server internal
PROVIDER_ERROR422Penyedia 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_AVAILABLE422Tidak ada penawaran aktif yang cocok dengan produk dan kebijakan yang diminta (batas harga, ketersediaan).
CANCEL_TOO_EARLY409Pesanan terlalu baru untuk dibatalkan — tunggu 2 menit
REQUEST_IN_PROGRESS409Permintaan pembuatan dengan idempotency key ini masih berjalan
IDEMPOTENCY_KEY_REUSED422Idempotency key ini sudah dipakai dengan body permintaan yang berbeda
SERVICE_UNAVAILABLE503Layanan sementara tidak tersedia (pemeliharaan)
FX_RATE_UNAVAILABLE503Kurs tukar USD/IDR tidak tersedia (endpoint uang v2) — mengembalikan 503 dengan header Retry-After.
v1 → v2

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.

Aspekv1 · IDRv2 · USD
Field uangIDR bilangan bulat, mis. 15000Objek uang { amount, currency, canonical_amount, canonical_currency }
meta.fxTidak adaWajib pada setiap respons pembawa uang
Mata uangIDRUSD (hardcoded di kode)
FX_RATE_UNAVAILABLE503 + Retry-After baru saat tidak ada kurs yang dapat dipakai
PresisiTotal 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

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 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.

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

Checklist migrasi

  1. Ganti base path /v1/v2.
  2. Parse field uang sebagai objek — baca amount sebagai string desimal; currency bernilai "USD".
  3. Untuk rekonsiliasi ledger gunakan canonical_amount (IDR persis); amount USD adalah proyeksi saat render dan rate-nya diungkap sekali di meta.fx.
  4. Tangani FX_RATE_UNAVAILABLE (503) yang baru — coba lagi setelah Retry-After. v1 tidak pernah mengembalikan ini.