Kalau kamu developer yang butuh verifikasi SMS secara otomatis — untuk QA testing, CI/CD pipeline, atau integrasi ke aplikasi — SMSCode menyediakan REST API yang bisa dipanggil langsung dari kode. Beli nomor virtual, tunggu OTP, cancel: semuanya bisa dilakukan programatik dengan response JSON yang konsisten.
Artikel ini adalah tutorial praktis: dari setup autentikasi, tiga endpoint utama yang perlu kamu tahu, sampai contoh kode siap pakai dalam curl, Python, dan JavaScript.
TL;DR: API SMSCode menggunakan Bearer token auth. Endpoint utama:
GET /v1/catalog/products(cek stok dan harga),POST /v1/orders/create(beli nomor),GET /v1/orders/{id}(polling SMS),POST /v1/orders/cancel(cancel jikacan_cancel=true), danGET /v1/balance(cek saldo). Daftar gratis, langsung bisa test.
Butuh referensi teknis lebih lengkap? Baca panduan lengkap API SMSCode untuk developer yang mencakup semua detail endpoint.
Siapa yang Butuh API Ini?
API SMSCode cocok untuk beberapa skenario yang sangat umum di kalangan developer Indonesia:
QA dan end-to-end testing. Aplikasi yang punya alur signup dengan verifikasi nomor HP butuh nomor nyata untuk testing. Pakai nomor pribadi di environment production atau staging itu buruk secara praktik. API memungkinkan kamu generate nomor fresh untuk setiap test run.
CI/CD pipeline. Integrasikan verifikasi SMS ke pipeline continuous integration. Setiap push ke main branch bisa memicu test otomatis yang termasuk alur verifikasi nomor HP.
Setup infrastruktur otomatis. Kalau perlu mendaftarkan beberapa akun di layanan tertentu sebagai bagian dari setup sistem (monitoring, testing environment, sandbox), API memungkinkan proses ini berjalan tanpa intervensi manual.
SaaS yang punya fitur verifikasi. Kalau kamu build aplikasi yang menawarkan fitur verifikasi nomor ke pengguna kamu, API SMSCode bisa jadi backend-nya.
Dari pola penggunaan di SMSCode, developer yang menggunakan API rata-rata melakukan 50-200 order per bulan, dengan puncak penggunaan saat sprint QA sebelum release. Use case QA testing dan CI/CD mendominasi penggunaan API dibanding otomasi skala besar.
Setup: Dapatkan API Token
Tidak ada setup yang rumit. Token tersedia langsung setelah kamu punya akun.
Cara dapat token:
- Daftar atau login di smscode.gg/auth/signup
- Masuk ke dashboard
- Buka menu “API” di sidebar
- Copy API token kamu
Token ini unik per akun dan bersifat permanen sampai kamu rotasi. Jangan share token ini — siapapun yang punya token bisa menggunakan saldo akun kamu.
Best practice keamanan token:
# Simpan di environment variable, JANGAN di kode
export SMSCODE_API_TOKEN="your_token_here"
Jangan pernah commit token ke repository Git. Gunakan secrets manager seperti GitHub Secrets, AWS Secrets Manager, atau HashiCorp Vault untuk production.
Base URL dan Format Response
Base URL semua endpoint:
https://api.smscode.gg/v1
Format response sukses — konsisten di semua endpoint:
{
"success": true,
"data": {}
}
Format response error:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Deskripsi error"
}
}
Konsistensi ini memudahkan error handling — cukup cek success field, lalu akses data atau error sesuai hasilnya.
Endpoint 1 — Cek Catalog: Nomor Apa yang Tersedia?
Sebelum beli nomor, kamu perlu tahu layanan dan negara apa yang tersedia beserta harganya.
GET /v1/catalog/products
Query parameters (semua opsional):
country_id— ID integer dariGET /v1/catalog/countriesplatform_id— ID integer dariGET /v1/catalog/services?country_id=...limit— jumlah hasil per halamanpage— nomor halaman mulai dari 1
Contoh curl
# Cari nomor Indonesia untuk WhatsApp
curl -X GET "https://api.smscode.gg/v1/catalog/products?country_id=7&platform_id=1" \
-H "Authorization: Bearer $SMSCODE_API_TOKEN"
Contoh response
{
"success": true,
"data": [
{
"id": 1024,
"name": "WhatsApp - Indonesia",
"country_id": 7,
"platform_id": 1,
"price": 350,
"available": 87,
"active": true,
"catalog_product_id": 88
}
],
"meta": {
"page": 1,
"limit": 1000,
"count": 1
}
}
Field available adalah perkiraan jumlah nomor yang bisa disewa saat ini. Pilih produk aktif dengan available > 0; stok tetap bisa berubah sebelum order dibuat, jadi tangani NO_OFFER_AVAILABLE.
Contoh Python
import requests
import os
API_TOKEN = os.environ["SMSCODE_API_TOKEN"]
BASE_URL = "https://api.smscode.gg/v1"
HEADERS = {"Authorization": f"Bearer {API_TOKEN}"}
def get_products(country_id, platform_id):
params = {"country_id": country_id, "platform_id": platform_id}
resp = requests.get(f"{BASE_URL}/catalog/products", headers=HEADERS, params=params)
resp.raise_for_status()
return resp.json()["data"]
# ID 7 dan 1 hanya contoh; resolve lewat endpoint countries/services.
products = get_products(country_id=7, platform_id=1)
for product in products:
print(f"ID: {product['id']} | Harga: Rp {product['price']} | Tersedia: {product['available']}")
Contoh JavaScript (Node.js)
const axios = require('axios');
const API_TOKEN = process.env.SMSCODE_API_TOKEN;
const BASE_URL = 'https://api.smscode.gg/v1';
const headers = { Authorization: `Bearer ${API_TOKEN}` };
async function getProducts(countryId, platformId) {
const params = { country_id: countryId, platform_id: platformId };
const resp = await axios.get(`${BASE_URL}/catalog/products`, { headers, params });
return resp.data.data;
}
// Contoh penggunaan
(async () => {
const products = await getProducts(7, 1);
products.forEach(product => {
console.log(`ID: ${product.id} | Harga: Rp ${product.price} | Tersedia: ${product.available}`);
});
})();
Endpoint 2 — Buat Order: Beli Nomor Virtual
Setelah dapat product_id dari catalog, buat order untuk mendapatkan nomor virtual.
POST /v1/orders/create
Request body (JSON):
{
"product_id": 42
}
Contoh curl
IDEMPOTENCY_KEY="$(uuidgen)" # Buat sekali untuk order logis ini
curl -X POST "https://api.smscode.gg/v1/orders/create" \
-H "Authorization: Bearer $SMSCODE_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{"product_id": 42}'
Contoh response (order berhasil)
{
"success": true,
"data": {
"orders": [
{
"id": 90210,
"phone_number": "+6281234567890",
"status": "ACTIVE",
"expires_at": "2026-03-16T10:35:00Z",
"product_id": 42,
"amount": 350
}
],
"failed_count": 0
}
}
Simpan id dari item pertama data.orders — kamu butuh ini untuk polling OTP dan cancel.
Contoh Python
import uuid
def create_order(product_id, idempotency_key):
resp = requests.post(
f"{BASE_URL}/orders/create",
headers={
**HEADERS,
"Content-Type": "application/json",
"Idempotency-Key": idempotency_key,
},
json={"product_id": product_id}
)
resp.raise_for_status()
return resp.json()["data"]["orders"][0]
# Beli nomor Indonesia untuk WhatsApp
products = get_products(country_id=7, platform_id=1)
# Pilih produk tersedia dengan harga paling murah
best = min((p for p in products if p["available"] > 0), key=lambda p: p["price"])
create_key = str(uuid.uuid4()) # Satu key untuk body ini
order = create_order(best["id"], idempotency_key=create_key)
print(f"Nomor: {order['phone_number']}")
print(f"Order ID: {order['id']}")
print(f"Expired: {order['expires_at']}")
Contoh JavaScript
const { randomUUID } = require('node:crypto');
async function createOrder(productId, idempotencyKey) {
const resp = await axios.post(
`${BASE_URL}/orders/create`,
{ product_id: productId },
{
headers: {
...headers,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey
}
}
);
return resp.data.data.orders[0];
}
const createKey = randomUUID(); // Buat sekali sebelum request/retry
const order = await createOrder(42, createKey);
Pembuatan order memotong saldo. Jika response hilang atau request perlu diulang,
gunakan kembali Idempotency-Key yang sama dengan body yang sama. Jangan buat
key baru di dalam retry loop karena itu dapat membuat dan mendebit order kedua.
Endpoint 3 — Polling OTP: Tunggu SMS Masuk
Setelah memasukkan nomor virtual ke layanan target dan meminta OTP, kamu perlu polling status order untuk menunggu SMS masuk.
GET /v1/orders/{order_id}
Status yang Mungkin
| Status | Artinya |
|---|---|
ACTIVE |
Order aktif, belum ada SMS |
OTP_RECEIVED |
SMS sudah masuk; lihat otp_code dan otp_message |
COMPLETED |
Order sudah diselesaikan setelah delivery |
CANCELED |
Order dibatalkan; refund hanya jika belum ada SMS |
EXPIRED |
Waktu habis tanpa SMS (saldo dikembalikan) |
Contoh curl
curl -X GET "https://api.smscode.gg/v1/orders/90210" \
-H "Authorization: Bearer $SMSCODE_API_TOKEN"
Contoh response (OTP masuk)
{
"success": true,
"data": {
"id": 90210,
"phone_number": "+6281234567890",
"status": "OTP_RECEIVED",
"otp_code": "847293",
"otp_message": "WhatsApp code 847293. You can also tap on this link to verify...",
"otp_received_at": "2026-03-16T10:32:45Z",
"sms_revision": 1,
"expires_at": "2026-03-16T10:35:00Z"
}
}
Implementasi Polling Python (Lengkap)
import time
def poll_for_sms(order_id, timeout=120, interval=5):
"""
Poll status order sampai SMS masuk, status terminal, atau timeout lokal.
Args:
order_id: ID order yang dipoll
timeout: Maksimum detik tunggu (default 120)
interval: Interval polling dalam detik (default 5)
Returns:
dict: Snapshot order saat SMS masuk atau status terminal
None: kalau timeout lokal
"""
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
resp = requests.get(
f"{BASE_URL}/orders/{order_id}",
headers=HEADERS
)
resp.raise_for_status()
data = resp.json()["data"]
if data.get("otp_received_at") is not None:
return data
elif data["status"] in ("COMPLETED", "CANCELED", "EXPIRED"):
print(f"Order {order_id} berakhir dengan status: {data['status']}")
return data
# Masih ACTIVE — tunggu
time.sleep(interval)
print(f"Timeout setelah {timeout} detik")
return None
Implementasi Polling JavaScript
async function pollForSms(orderId, timeout = 120000, interval = 5000) {
const deadline = Date.now() + timeout;
while (Date.now() < deadline) {
const resp = await axios.get(`${BASE_URL}/orders/${orderId}`, { headers });
const data = resp.data.data;
if (data.otp_received_at != null) return data;
if (['COMPLETED', 'CANCELED', 'EXPIRED'].includes(data.status)) return data;
// Masih ACTIVE — tunggu sebentar
await new Promise(r => setTimeout(r, interval));
}
return null; // Timeout
}
Best practice polling:
- Interval 5 detik sudah cukup — jangan polling lebih agresif dari itu
- OTP biasanya masuk dalam 15-90 detik setelah nomor dimasukkan ke layanan target
- Set timeout sesuai batas aktif nomor (~20 menit), tapi 2-3 menit biasanya cukup
Endpoint 4 — Cancel Order
Kalau nomor tidak diperlukan lagi atau mau ganti ke negara lain, cancel order untuk mendapatkan saldo kembali.
POST /v1/orders/cancel
curl -X POST "https://api.smscode.gg/v1/orders/cancel" \
-H "Authorization: Bearer $SMSCODE_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id": 90210}'
Order hanya bisa di-cancel saat can_cancel=true dan belum ada SMS masuk. SMS teks/link tanpa kode juga menutup cancel dan refund.
def cancel_order(order_id):
current = requests.get(f"{BASE_URL}/orders/{order_id}", headers=HEADERS)
current.raise_for_status()
if not current.json()["data"]["can_cancel"]:
return None
resp = requests.post(
f"{BASE_URL}/orders/cancel",
headers={**HEADERS, "Content-Type": "application/json"},
json={"id": order_id},
)
resp.raise_for_status()
return resp.json()
Endpoint 5 — Cek Saldo
Pastikan ada cukup saldo sebelum membuat order — terutama penting untuk automation supaya tidak ada order yang gagal karena saldo habis.
GET /v1/balance
curl -X GET "https://api.smscode.gg/v1/balance" \
-H "Authorization: Bearer $SMSCODE_API_TOKEN"
{
"success": true,
"data": {
"balance": 45250,
"currency": "IDR"
}
}
def get_balance():
resp = requests.get(f"{BASE_URL}/balance", headers=HEADERS)
resp.raise_for_status()
return resp.json()["data"]["balance"]
# Cek sebelum order
balance = get_balance()
if balance < 500: # threshold minimal
raise Exception(f"Saldo tidak cukup: Rp {balance}")
Contoh Lengkap: Alur Verifikasi Penuh
Berikut contoh alur lengkap dari cek catalog sampai dapat OTP — siap dipakai sebagai template:
Python — Alur Lengkap
import requests
import time
import os
import uuid
API_TOKEN = os.environ["SMSCODE_API_TOKEN"]
BASE_URL = "https://api.smscode.gg/v1"
HEADERS = {"Authorization": f"Bearer {API_TOKEN}"}
def get_best_product(platform_id, country_id, max_price=None):
"""Cari produk tersedia dengan harga terbaik."""
params = {"platform_id": platform_id, "country_id": country_id}
resp = requests.get(f"{BASE_URL}/catalog/products", headers=HEADERS, params=params)
resp.raise_for_status()
products = resp.json()["data"]
# Filter stok tersedia
available = [product for product in products if product["available"] > 0]
if max_price:
available = [i for i in available if i["price"] <= max_price]
if not available:
return None
# Urutkan berdasarkan harga, lalu jumlah stok tertinggi.
available.sort(key=lambda product: (product["price"], -product["available"]))
return available[0]
def verify_with_sms(platform_id, country_id, submit_number_fn, max_price=None):
"""
Alur verifikasi lengkap.
Args:
platform_id: ID layanan dari /catalog/services
country_id: ID negara dari /catalog/countries
submit_number_fn: Fungsi yang menerima phone_number dan submit ke layanan target
max_price: Batas harga maksimal (opsional)
Returns:
dict: Snapshot SMS kalau delivery berhasil, None kalau belum ada delivery
"""
# 1. Cek saldo
balance_resp = requests.get(f"{BASE_URL}/balance", headers=HEADERS)
balance_resp.raise_for_status()
balance = balance_resp.json()["data"]["balance"]
print(f"Saldo: Rp {balance}")
# 2. Cari produk
product = get_best_product(platform_id, country_id, max_price)
if not product:
print("Tidak ada nomor tersedia")
return None
print(f"Produk: {product['id']} | Harga: Rp {product['price']} | Tersedia: {product['available']}")
# 3. Cek saldo cukup
if balance < product["price"]:
print("Saldo tidak cukup")
return None
# 4. Buat order
create_key = str(uuid.uuid4())
order_resp = requests.post(
f"{BASE_URL}/orders/create",
headers={
**HEADERS,
"Content-Type": "application/json",
"Idempotency-Key": create_key,
},
json={"product_id": product["id"]}
)
order_resp.raise_for_status()
order = order_resp.json()["data"]["orders"][0]
order_id = order["id"]
phone = order["phone_number"]
print(f"Nomor: {phone} | Order: {order_id}")
try:
# 5. Submit nomor ke layanan target
submit_number_fn(phone)
# 6. Poll untuk OTP
deadline = time.monotonic() + 120
while time.monotonic() < deadline:
poll_resp = requests.get(
f"{BASE_URL}/orders/{order_id}",
headers=HEADERS
)
poll_resp.raise_for_status()
data = poll_resp.json()["data"]
if data.get("otp_received_at") is not None:
if data.get("otp_code"):
print(f"OTP: {data['otp_code']}")
else:
print(f"SMS tanpa kode terklasifikasi: {data.get('otp_message')}")
return data
elif data["status"] in ("COMPLETED", "CANCELED", "EXPIRED"):
return None
time.sleep(5)
print("Timeout — baca ulang can_finish/can_cancel sebelum bertindak")
return None
except Exception:
# Cancel hanya jika server masih mengizinkan refund.
cancel_order(order_id)
raise
# Contoh penggunaan
def my_app_submit_number(phone):
"""
Implementasi submit nomor ke aplikasi target kamu.
Contoh: pakai Playwright, requests, atau SDK layanan target.
"""
print(f"Submitting number {phone} to target service...")
# page.fill('#phone-input', phone)
# page.click('#send-otp-btn')
pass
delivery = verify_with_sms(
platform_id=1,
country_id=7,
submit_number_fn=my_app_submit_number,
max_price=500
)
if delivery and delivery.get("otp_code"):
print(f"Kode yang bisa digunakan: {delivery['otp_code']}")
JavaScript (Node.js) — Alur Lengkap
const axios = require('axios');
const { randomUUID } = require('node:crypto');
const API_TOKEN = process.env.SMSCODE_API_TOKEN;
const BASE_URL = 'https://api.smscode.gg/v1';
const headers = { Authorization: `Bearer ${API_TOKEN}` };
async function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function verifyWithSms({ platformId, countryId, submitNumberFn, maxPrice = null }) {
// 1. Cek catalog
const catalogResp = await axios.get(`${BASE_URL}/catalog/products`, {
headers,
params: { platform_id: platformId, country_id: countryId }
});
let products = catalogResp.data.data.filter(product => product.available > 0);
if (maxPrice) products = products.filter(product => product.price <= maxPrice);
if (!products.length) throw new Error('Tidak ada nomor tersedia');
// Pilih terbaik
products.sort((a, b) => a.price - b.price || b.available - a.available);
const product = products[0];
// 2. Buat order
const createKey = randomUUID();
const orderResp = await axios.post(
`${BASE_URL}/orders/create`,
{ product_id: product.id },
{
headers: {
...headers,
'Content-Type': 'application/json',
'Idempotency-Key': createKey
}
}
);
const order = orderResp.data.data.orders[0];
const { id: orderId, phone_number: phone } = order;
console.log(`Nomor: ${phone} | Order: ${orderId}`);
try {
// 3. Submit nomor ke layanan target
await submitNumberFn(phone);
// 4. Polling
const deadline = Date.now() + 120_000;
while (Date.now() < deadline) {
const pollResp = await axios.get(`${BASE_URL}/orders/${orderId}`, { headers });
const data = pollResp.data.data;
if (data.otp_received_at != null) {
if (data.otp_code) console.log(`OTP: ${data.otp_code}`);
else console.log(`SMS tanpa kode terklasifikasi: ${data.otp_message}`);
return data;
}
if (['COMPLETED', 'CANCELED', 'EXPIRED'].includes(data.status)) return null;
await sleep(5000);
}
return null;
} catch (err) {
// Re-read capability; jangan mengasumsikan timeout berarti refundable.
const current = (await axios.get(`${BASE_URL}/orders/${orderId}`, { headers })).data.data;
if (current.can_cancel) {
await axios.post(`${BASE_URL}/orders/cancel`, { id: orderId }, { headers });
}
throw err;
}
}
// Contoh penggunaan
(async () => {
const delivery = await verifyWithSms({
platformId: 1,
countryId: 7,
submitNumberFn: async (phone) => {
console.log(`Submit ${phone} ke Instagram...`);
// await page.fill('#phone', phone);
// await page.click('#send-otp');
},
maxPrice: 500
});
console.log('SMS diterima:', delivery);
})();
Rate Limits
SMSCode API punya rate limit untuk mencegah penyalahgunaan:
| Scope | Limit |
|---|---|
| Semua request untuk satu token | 300 request/menit |
Kalau melewati limit, API return 429 Too Many Requests dengan header Retry-After yang menunjukkan berapa detik harus menunggu.
import time
def retry_after_seconds(headers, fallback=60):
"""Parse Retry-After safely; invalid or non-positive values use fallback."""
raw_value = headers.get("Retry-After")
try:
seconds = int(raw_value) if raw_value is not None else fallback
except (TypeError, ValueError):
return fallback
return seconds if seconds > 0 else fallback
def safe_request(fn, *args, **kwargs):
"""Wrapper request dengan handling rate limit."""
while True:
try:
return fn(*args, **kwargs)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
retry_after = retry_after_seconds(e.response.headers)
print(f"Rate limited. Tunggu {retry_after} detik...")
time.sleep(retry_after)
else:
raise
Error Codes yang Perlu Diketahui
| HTTP Status | Kode | Arti |
|---|---|---|
| 401 | UNAUTHORIZED |
Token tidak valid atau tidak ada |
| 409 | INSUFFICIENT_BALANCE |
Saldo tidak cukup |
| 404 | NOT_FOUND |
Order atau produk tidak ditemukan |
| 409 | CONFLICT |
Order tidak bisa di-cancel pada status saat ini |
| 422 | VALIDATION_ERROR |
Parameter request tidak valid |
| 429 | RATE_LIMIT_EXCEEDED |
Terlalu banyak request |
| 500 | INTERNAL_ERROR |
Error di sisi server |
def handle_api_error(e):
"""Handler error API yang informatif."""
if not hasattr(e, 'response') or e.response is None:
print(f"Network error: {e}")
return
status = e.response.status_code
try:
error = e.response.json()["error"]
code = error.get("code", "UNKNOWN")
msg = error.get("message", "")
except Exception:
code, msg = "PARSE_ERROR", e.response.text
if code == "INSUFFICIENT_BALANCE":
print("Saldo tidak cukup. Top up dulu di smscode.gg/deposit")
elif code in ("CONFLICT", "CANCEL_TOO_EARLY", "REQUEST_IN_PROGRESS"):
print(f"Order belum dapat menjalankan aksi ini: {code}")
elif status == 429:
retry = retry_after_seconds(e.response.headers)
print(f"Rate limited. Tunggu {retry} detik.")
elif status == 500:
print("Server error. Coba lagi dalam beberapa saat.")
else:
print(f"API Error [{code}]: {msg}")
Tips untuk Production
Simpan log setiap order tanpa rahasia SMS. Catat id, product_id, amount, dan status akhir. Jangan log OTP atau isi SMS; mask nomor telepon jika tidak diperlukan untuk debugging.
import logging
import json
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("smscode")
def log_order(order, status):
logger.info(json.dumps({
"order_id": order["id"],
"product_id": order["product_id"],
"amount": order["amount"],
"status": status
}))
Monitor saldo secara berkala. Jangan biarkan saldo habis di tengah proses automation. Tambahkan check saldo sebelum setiap batch order, atau buat alerting kalau saldo di bawah threshold.
Implement retry dengan backoff. Kalau order gagal karena error sementara, jangan langsung retry — tunggu beberapa detik dengan exponential backoff.
Dari pengalaman developer yang integrasikan SMSCode ke pipeline CI/CD, pattern yang paling robust adalah: cek saldo → cek catalog (pilih available > 0) → buat order → submit ke layanan target dalam 5 detik → poll selama 2 menit. Setelah timeout, baca can_finish/can_cancel; cancel hanya jika masih diizinkan, lalu pilih negara lain maksimal 3 kali sebelum throw error ke CI.
Integrasi dengan Testing Framework
Pytest Fixture (Python)
# conftest.py
import pytest
import requests
import time
import os
import uuid
SMSCODE_TOKEN = os.environ["SMSCODE_API_TOKEN"]
BASE = "https://api.smscode.gg/v1"
HDRS = {"Authorization": f"Bearer {SMSCODE_TOKEN}"}
@pytest.fixture
def sms_verifier():
"""Fixture untuk verifikasi SMS di test."""
orders = []
def buy_number(platform_id, country_id):
# Cek catalog
products = requests.get(
f"{BASE}/catalog/products",
headers=HDRS,
params={"platform_id": platform_id, "country_id": country_id},
).json()["data"]
available = [product for product in products if product["available"] > 0]
if not available:
pytest.skip(f"Tidak ada nomor untuk platform {platform_id} di negara {country_id}")
# Order
create_key = str(uuid.uuid4())
order = requests.post(
f"{BASE}/orders/create",
headers={**HDRS, "Idempotency-Key": create_key},
json={"product_id": available[0]["id"]},
).json()["data"]["orders"][0]
orders.append(order["id"])
return order["phone_number"], order["id"]
def get_sms(order_id, timeout=90):
for _ in range(timeout // 5):
data = requests.get(f"{BASE}/orders/{order_id}", headers=HDRS).json()["data"]
if data.get("otp_received_at") is not None:
return data
if data["status"] in ("COMPLETED", "CANCELED", "EXPIRED"):
return data
time.sleep(5)
return None
yield buy_number, get_sms
# Cleanup — cancel hanya order yang masih can_cancel=true
for oid in orders:
try:
current = requests.get(f"{BASE}/orders/{oid}", headers=HDRS).json()["data"]
if current["can_cancel"]:
requests.post(f"{BASE}/orders/cancel", headers=HDRS, json={"id": oid})
except Exception:
pass
# test_signup.py
def test_signup_flow(sms_verifier):
buy_number, get_sms = sms_verifier
phone, order_id = buy_number(platform_id=1, country_id=7)
# Submit nomor ke aplikasi kamu
# result = your_app.request_otp(phone)
delivery = get_sms(order_id)
assert delivery is not None, "Order tidak mencapai hasil dalam batas waktu"
assert delivery.get("otp_received_at") is not None, "SMS tidak masuk"
otp = delivery.get("otp_code")
assert otp is not None, f"SMS masuk tanpa kode: {delivery.get('otp_message')}"
# Verifikasi OTP
# assert your_app.verify_otp(otp)
FAQ
Apakah API tersedia 24/7?
Ya, API SMSCode beroperasi 24/7. Ketersediaan nomor tergantung stok dari provider — beberapa layanan atau negara mungkin kehabisan stok di waktu tertentu. Selalu cek available > 0 di catalog sebelum order.
Bagaimana kalau OTP tidak masuk — apakah saldo hilang?
Tidak. Kalau order expired tanpa SMS masuk, saldo otomatis dikembalikan penuh. SMS yang terkirim tetap billable meski tidak memuat kode OTP; periksa otp_message dan capability server sebelum cancel atau membuat order baru.
Berapa lama order aktif sebelum expire?
Nomor aktif selama sekitar 20 menit sejak order dibuat. Selama periode itu, nomor bisa menerima SMS berkali-kali. Setelah expire, order otomatis tutup dan saldo dikembalikan kalau belum pernah ada SMS masuk.
Apakah ada SDK resmi untuk Python atau JavaScript?
Ada. SDK resmi JavaScript dan Python tersedia; keduanya menyediakan resource order, helper polling, tipe webhook, serta akses ke otp_message dan sms_revision. Lihat dokumentasi API untuk tautan paket dan contoh terbaru.
Apakah bisa pakai webhook instead of polling?
Bisa. Atur URL HTTPS dan signing secret melalui PATCH /v1/webhook, lalu verifikasi header X-Webhook-Signature: sha256=<hex> terhadap raw request body. Tetap gunakan polling sebagai recovery path bila delivery webhook tertunda.
Siap integrasi? Daftar gratis di SMSCode, top up minimum Rp 10.000, dan test API dengan curl atau kode di atas. Baca panduan API lengkap untuk referensi endpoint yang lebih detail, atau cek halaman pricing untuk rate per layanan.