Cara Pakai API SMSCode untuk Otomasi Verifikasi (2026)

Cara Pakai API SMSCode untuk Otomasi Verifikasi (2026)

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 jika can_cancel=true), dan GET /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:

  1. Daftar atau login di smscode.gg/auth/signup
  2. Masuk ke dashboard
  3. Buka menu “API” di sidebar
  4. 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 dari GET /v1/catalog/countries
  • platform_id — ID integer dari GET /v1/catalog/services?country_id=...
  • limit — jumlah hasil per halaman
  • page — 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.

Siap mencoba SMSCode?

Buat akun dan dapatkan nomor virtual pertamamu dalam dua menit.

Mulai sekarang →