Dokumentasi API

Akses programatik ke nomor virtual, pesanan, dan saldo akun.

Direkomendasikan

Mulai dengan SDK resmi

Gunakan SDK TypeScript/JavaScript atau Python untuk integrasi baru. Kedua SDK default ke API publik /v2, menjaga idempotency key saat retry aman, mengekspos error bertipe, dan menjaga lifecycle OTP tetap konsisten.

01

Buat dengan aman

Buat order dengan product_id untuk tier-slot stabil yang spesifik, atau dengan catalog_product_id, opsional operator_id, min_price/max_price, dan idempotency key agar panggilan berbayar aman saat retry.

catalog_product_idmax_priceIdempotency-Key
import { SmscodeClient, OtpTimeoutError } from "@smscode/sdk";

const client = new SmscodeClient({ token: process.env.SMSCODE_TOKEN! });

let orderId: number | undefined;

try {
  const created = await client.orders.create({
    catalog_product_id: Number(process.env.SMSCODE_CATALOG_PRODUCT_ID),
    max_price: "0.50",
    quantity: 1,
  });

  const order = created.orders[0]!;
  orderId = order.id;
  const first = await client.orders.waitForOtp(orderId, { timeoutMs: 120_000 });

  console.log(first.otpCode); // Kirim kode ini di aplikasi target.
  await client.orders.finish(orderId);
} catch (err) {
  if (err instanceof OtpTimeoutError && orderId !== undefined) {
    const current = await client.orders.get(orderId);
    if (current.can_cancel) await client.orders.cancel(orderId);
  }
  throw err;
}
import os

from smscode import OtpTimeoutError, SmscodeClient

with SmscodeClient(token=os.environ["SMSCODE_TOKEN"]) as client:
    created = client.orders.create(
        catalog_product_id=int(os.environ["SMSCODE_CATALOG_PRODUCT_ID"]),
        max_price="0.50",
        quantity=1,
    )

    order = created.orders[0]
    order_id = int(order["id"])

    try:
        first = client.orders.wait_for_otp(order_id, timeout_ms=120_000)
        print(first.otp_code)  # Kirim kode ini di aplikasi target.
        client.orders.finish(order_id)
    except OtpTimeoutError:
        current = client.orders.get(order_id)
        if current["can_cancel"]:
            client.orders.cancel(order_id)
        raise

Gunakan can_resend dan resend_available_at untuk timing resend. Timestamp resend level rendah bersifat internal dan bukan field respons publik.

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.

URL Dasar

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": { ... }
}
Kesalahan
{
  "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=7" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/services?country_id=7", {
  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": 7},
    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/operators

Mengembalikan daftar operator yang dapat dipilih untuk negara + layanan. Jika operator spesifik dan stok Any sama-sama tersedia, respons menyertakan baris Any dengan operator_id null; jika tidak ada produk operator-specific, daftar kosong.

Query Parameter

NamaTipeWajibDeskripsi
country_idintegerYaID negara
platform_idintegerYaID platform/layanan

Contoh Request

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

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

Contoh Response

200 OK
{
  "success": true,
  "data": [
    {
      "operator_id": null,
      "code": "any",
      "name": "Any",
      "local_name": null
    },
    {
      "operator_id": 433,
      "code": "axis",
      "name": "AXIS (XL Axiata)",
      "local_name": "AXIS (XL Axiata)"
    }
  ]
}
GET/catalog/products

Mengembalikan daftar produk yang tersedia secara berpaginasi. Filter berdasarkan negara, platform, dan opsional operator.

Query Parameter

NamaTipeWajibDeskripsi
country_idintegerTidakFilter berdasarkan ID negara
platform_idintegerTidakFilter berdasarkan ID platform/layanan
operator_idintegerTidakID operator opsional dari /catalog/operators. Kosongkan untuk produk Any.
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=7&platform_id=3&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "7", 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": 7, "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": 7,
      "platform_id": 3,
      "catalog_product_id": 87,
      "operator_id": null,
      "operator_name": null,
      "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,
      "catalog_product_id": 87,
      "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,
      "operator_id": null,
      "operator_name": 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,
    "catalog_product_id": 87,
    "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,
    "operator_id": null,
    "operator_name": 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": null,
      "otp_message": "Confirm your login: https://example.com/confirm",
      "sms_revision": 1,
      "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,
      "sms_revision": 0,
      "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.

Isi Permintaan

NamaTipeWajibDeskripsi
product_idintegerTidakID produk tier-slot stabil yang dipesan langsung. Isi SALAH SATU saja: product_id ATAU catalog_product_id, jangan keduanya.
catalog_product_idintegerTidakID umbrella negara+platform untuk routed order. Server memilih tier live yang cocok. Isi salah satu: ini atau product_id.
operator_idintegerTidakID operator opsional dari /catalog/operators. Hanya valid bersama catalog_product_id; kosongkan untuk Any.
min_priceintegerTidakBatas bawah harga opsional. Bilangan bulat IDR. Hanya valid bersama catalog_product_id.
max_priceintegerTidakBatas atas harga opsional. Bilangan bulat IDR. Hanya valid bersama catalog_product_id.
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 route by catalog_product_id — the server picks a current tier.
# product_id is the stable exact tier-slot id. catalog_product_id is the
# country+platform umbrella for routed ordering. Optional min_price/max_price
# bound the tier; operator_id scopes to a carrier from /catalog/operators.
# 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,"min_price":12000,"max_price":20000,"operator_id":433}'
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,
        "operator_id": null,
        "operator_name": null
      }
    ],
    "failed_count": 0
  }
}
POST/orders/cancel

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

Isi Permintaan

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.

Isi Permintaan

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.

Isi Permintaan

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
  }
}
POST/orders/reactivate

Mengaktifkan kembali nomor yang sudah selesai — memesan ulang nomor yang sama untuk kode verifikasi lain, tanpa menyewa nomor baru. Hanya pesanan yang sudah selesai yang nomornya mendukung reaktivasi yang memenuhi syarat (periksa can_reactivate pada pesanan, atau lihat pratinjau dengan reactivate-options). Pesanan anak hasil reaktivasi adalah pesanan BARU, dikembalikan dalam bentuk yang sama seperti create; saldo dipotong secara otomatis.

Isi Permintaan

NamaTipeWajibDeskripsi
idintegerYaPesanan selesai yang akan diaktifkan kembali.
max_priceintegerTidakBatas atas biaya opsional. Bilangan bulat IDR. Reaktivasi ditolak dengan 422 VALIDATION_ERROR jika biaya terkini melebihinya.

Seperti create, ini adalah mutasi uang — kirim header Idempotency-Key untuk retry yang aman (sebuah create dan sebuah reactivate tidak akan pernah bertabrakan pada satu key). Menggunakan kembali key dengan body berbeda menghasilkan 422 IDEMPOTENCY_KEY_REUSED, dan permintaan yang masih diproses dengan key tersebut menghasilkan 409 REQUEST_IN_PROGRESS. Nomor yang tidak bisa diaktifkan kembali menghasilkan 409 CONFLICT; saldo yang terlalu sedikit menghasilkan 409 INSUFFICIENT_BALANCE.

Contoh Request

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

res = requests.post("https://api.smscode.gg/v1/orders/reactivate",
    json={"id": 1001, "max_price": 20000},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-901",
    })
data = res.json()

Contoh Response

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1044,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": 15000,
        "phone_number": "+6281234567890",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T11:20:00+00:00",
        "failed_reason": null,
        "operator_id": null,
        "operator_name": null
      }
    ],
    "failed_count": 0
  }
}
GET/orders/{id}/reactivate-options

Melihat pratinjau berapa biaya reaktivasi saat ini. Hanya-baca — tidak memakai Idempotency-Key dan tidak membuat apa pun. Mengembalikan biaya sebagai bilangan bulat IDR. Hanya tersedia untuk pesanan yang sudah selesai yang nomornya mendukung reaktivasi.

Path Parameter

NamaTipeWajibDeskripsi
idintegerYaID Pesanan untuk melihat pratinjau biaya reaktivasi (parameter path).

Contoh Request

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

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

Contoh Response

200 OK
{
  "success": true,
  "data": {
    "cost": 15000
  }
}
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.

Isi Permintaan

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

PeristiwaPemicu
order.otp_receivedSMS baru diterima; kode hasil parsing dapat null
order.completedPesanan ditandai selesai (manual atau kedaluwarsa)
order.expiredPesanan kedaluwarsa sebelum ada SMS yang diterima (saldo dikembalikan)
order.canceledPesanan dibatalkan oleh pengguna (saldo dikembalikan)

Setiap SMS baru memicu event ini. otp_code dapat null saat otp_message tersedia. Beberapa event SMS dapat tiba tidak berurutan; gunakan sms_revision untuk mengabaikan pasangan agregat yang lebih lama.

Muatan

Isi POST Webhook
{
  "event": "order.otp_received",
  "timestamp": "2026-02-25T12:00:00+00:00",
  "data": {
    "order_id": 1001,
    "phone_number": "+628123456789",
    "otp_code": null,
    "otp_message": "Confirm your login: https://example.com/confirm",
    "sms_revision": 1,
    "product_id": 142,
    "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.

Batas Permintaan

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 details: cause_counts (pesanan product_id lama — rekap yang dikelompokkan per penyebab) atau attempts (pesanan catalog_product_id — hasil per percobaan), menggunakan nilai ok, no_numbers, insufficient_balance, price_rejected, provider_unavailable, provider_account_balance, provider_error.
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.

URL Dasar

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": { ... }
}
Kesalahan
{
  "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=7" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/services?country_id=7", {
  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": 7},
    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/operators

Mengembalikan daftar operator yang dapat dipilih untuk negara + layanan. Jika operator spesifik dan stok Any sama-sama tersedia, respons menyertakan baris Any dengan operator_id null; jika tidak ada produk operator-specific, daftar kosong.

Query Parameter

NamaTipeWajibDeskripsi
country_idintegerYaID negara
platform_idintegerYaID platform/layanan

Contoh Request

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

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

Contoh Response

200 OK
{
  "success": true,
  "data": [
    {
      "operator_id": null,
      "code": "any",
      "name": "Any",
      "local_name": null
    },
    {
      "operator_id": 433,
      "code": "axis",
      "name": "AXIS (XL Axiata)",
      "local_name": "AXIS (XL Axiata)"
    }
  ]
}

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

GET/catalog/products

Mengembalikan daftar produk yang tersedia secara berpaginasi. Filter berdasarkan negara, platform, dan opsional operator.

Query Parameter

NamaTipeWajibDeskripsi
country_idintegerTidakFilter berdasarkan ID negara
platform_idintegerTidakFilter berdasarkan ID platform/layanan
operator_idintegerTidakID operator opsional dari /catalog/operators. Kosongkan untuk produk Any.
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=7&platform_id=3&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "7", 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": 7, "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": 7,
      "platform_id": 3,
      "catalog_product_id": 87,
      "operator_id": null,
      "operator_name": null,
      "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,
      "catalog_product_id": 87,
      "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,
      "operator_id": null,
      "operator_name": 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,
    "catalog_product_id": 87,
    "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,
    "operator_id": null,
    "operator_name": 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": null,
      "otp_message": "Confirm your login: https://example.com/confirm",
      "sms_revision": 1,
      "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,
      "sms_revision": 0,
      "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.

Isi Permintaan

NamaTipeWajibDeskripsi
product_idintegerTidakID produk tier-slot stabil yang dipesan langsung. Isi SALAH SATU saja: product_id ATAU catalog_product_id, jangan keduanya.
catalog_product_idintegerTidakID umbrella negara+platform untuk routed order. Server memilih tier live yang cocok. Isi salah satu: ini atau product_id.
operator_idintegerTidakID operator opsional dari /catalog/operators. Hanya valid bersama catalog_product_id; kosongkan untuk Any.
min_pricestringTidakBatas bawah harga opsional. String desimal USD (mis. "0.30"). Hanya valid bersama catalog_product_id.
max_pricestringTidakBatas atas harga opsional. String desimal USD (mis. "0.50"). Hanya valid bersama catalog_product_id.
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 '{"catalog_product_id":87,"min_price":"0.30","max_price":"0.50","operator_id":433,"quantity":1}'
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({
    catalog_product_id: 87,
    min_price: "0.30",
    max_price: "0.50",
    operator_id: 433,
    quantity: 1,
  }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/create",
    json={
        "catalog_product_id": 87,
        "min_price": "0.30",
        "max_price": "0.50",
        "operator_id": 433,
        "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,
        "operator_id": null,
        "operator_name": 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.

Isi Permintaan

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.

Isi Permintaan

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.

Isi Permintaan

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

POST/orders/reactivate

Mengaktifkan kembali nomor yang sudah selesai — memesan ulang nomor yang sama untuk kode verifikasi lain, tanpa menyewa nomor baru. Hanya pesanan yang sudah selesai yang nomornya mendukung reaktivasi yang memenuhi syarat (periksa can_reactivate pada pesanan, atau lihat pratinjau dengan reactivate-options). Pesanan anak hasil reaktivasi adalah pesanan BARU, dikembalikan dalam bentuk yang sama seperti create; saldo dipotong secara otomatis.

Isi Permintaan

NamaTipeWajibDeskripsi
idintegerYaPesanan selesai yang akan diaktifkan kembali.
max_pricestringTidakBatas atas biaya opsional. String desimal USD (mis. "0.50"). Reaktivasi ditolak dengan 422 VALIDATION_ERROR jika biaya terkini melebihinya.

Seperti create, ini adalah mutasi uang — kirim header Idempotency-Key untuk retry yang aman (sebuah create dan sebuah reactivate tidak akan pernah bertabrakan pada satu key). Menggunakan kembali key dengan body berbeda menghasilkan 422 IDEMPOTENCY_KEY_REUSED, dan permintaan yang masih diproses dengan key tersebut menghasilkan 409 REQUEST_IN_PROGRESS. Nomor yang tidak bisa diaktifkan kembali menghasilkan 409 CONFLICT; saldo yang terlalu sedikit menghasilkan 409 INSUFFICIENT_BALANCE.

Contoh Request

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

res = requests.post("https://api.smscode.gg/v2/orders/reactivate",
    json={"id": 1001, "max_price": "0.50"},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-901",
    })
data = res.json()

Contoh Response

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1044,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
        "phone_number": "+6281234567890",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T11:20:00+00:00",
        "failed_reason": null,
        "operator_id": null,
        "operator_name": 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.

GET/orders/{id}/reactivate-options

Melihat pratinjau berapa biaya reaktivasi saat ini. Hanya-baca — tidak memakai Idempotency-Key dan tidak membuat apa pun. Mengembalikan biaya sebagai objek uang USD dengan tanda terima FX. Hanya tersedia untuk pesanan yang sudah selesai yang nomornya mendukung reaktivasi.

Path Parameter

NamaTipeWajibDeskripsi
idintegerYaID Pesanan untuk melihat pratinjau biaya reaktivasi (parameter path).

Contoh Request

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

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

Contoh Response

200 OK
{
  "success": true,
  "data": {
    "cost": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "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/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.

Isi Permintaan

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

PeristiwaPemicu
order.otp_receivedSMS baru diterima; kode hasil parsing dapat null
order.completedPesanan ditandai selesai (manual atau kedaluwarsa)
order.expiredPesanan kedaluwarsa sebelum ada SMS yang diterima (saldo dikembalikan)
order.canceledPesanan dibatalkan oleh pengguna (saldo dikembalikan)

Setiap SMS baru memicu event ini. otp_code dapat null saat otp_message tersedia. Beberapa event SMS dapat tiba tidak berurutan; gunakan sms_revision untuk mengabaikan pasangan agregat yang lebih lama.

Muatan

Isi POST Webhook
{
  "event": "order.otp_received",
  "timestamp": "2026-02-25T12:00:00+00:00",
  "data": {
    "order_id": 1001,
    "phone_number": "+628123456789",
    "otp_code": null,
    "otp_message": "Confirm your login: https://example.com/confirm",
    "sms_revision": 1,
    "product_id": 142,
    "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.

Batas Permintaan

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 details: cause_counts (pesanan product_id lama — rekap yang dikelompokkan per penyebab) atau attempts (pesanan catalog_product_id — hasil per percobaan), menggunakan nilai ok, no_numbers, insufficient_balance, price_rejected, provider_unavailable, provider_account_balance, provider_error.
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": 7,
      "platform_id": 3,
      "catalog_product_id": 87,
      "operator_id": null,
      "operator_name": null,
      "available": 42,
      "price": 15000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1 }
}
v2
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 7,
      "platform_id": 3,
      "catalog_product_id": 87,
      "operator_id": null,
      "operator_name": null,
      "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" } }
}

product_id adalah ID tier-slot SMSCode yang stabil. Simpan ini jika kamu ingin memesan tier spesifik yang sama; harga dan stoknya bisa berubah di baris yang sama. catalog_product_id adalah umbrella negara+platform yang stabil untuk routed order; gunakan bersama operator_id, min_price, max_price, prefer_provider, dan policy opsional saat ingin server memilih tier live yang cocok.

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.