SafeDigits API

Akses programmatic ke stok nomor virtual SafeDigits. Cocok buat reseller, automation, atau integrasi dengan tool lain.

Pengenalan

SafeDigits API memungkinkan Anda untuk:

  • Cek stok nomor real-time per negara/service
  • Beli nomor secara programmatic
  • Polling untuk OTP code
  • Cancel order & dapat refund otomatis
  • Reuse nomor yang udah pernah dibeli
  • Webhooks real-time — push notification <5s untuk code received, deposit, transfer, dll (lihat section Webhooks)

API key & webhook gratis untuk semua user (free / SaaS / membership). Membership cuma kasih harga reseller (~25% lebih murah).

Authentication

Semua endpoint authenticated butuh API key di header Authorization: Bearer YOUR_API_KEY.

bash
curl https://api.safedigits.app/api/v1/stock \
  -H "Authorization: Bearer sd_xxxxxxxxxxxxxxxxxxxxxxxx"

⚠️ Jangan share API key. Treat seperti password. Kalau bocor, langsung revoke via dashboard dan generate yang baru.

🚀 Quick Start

Full flow lengkap: beli nomor → polling kode → cancel kalau ga dapet. Ini contoh end-to-end yang siap pakai.

Python (requests)

python
import requests, time

API_KEY = "sd_live_xxxxxxxxxxxxxxxxxxxxxxxx"
BASE = "https://api.safedigits.app/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# 1. Beli nomor
r = requests.post(f"{BASE}/order",
    json={"country": "USA", "service": "TG"},
    headers=HEADERS, timeout=15)
order = r.json()
order_ref = order["order_ref"]
phone = order["number"]
price = order["price"]
print(f"Beli {phone} ($" + str(price) + "), ref: {order_ref}")

# 2. Polling kode SMS — max 5 menit (60 x 5s)
code = None
for attempt in range(60):
    time.sleep(5)
    r = requests.get(f"{BASE}/order/{order_ref}/code",
        headers=HEADERS, timeout=10)
    data = r.json()

    if data.get("status") in ("received", "found"):
        code = data["code"]
        print(f"Kode: {code}")
        break

    if data.get("status") == "error":
        print(f"Error: {data}")
        break

    print(f"Attempt {attempt+1}/60: belum dapet, retry...")

# 3. Kalau ga dapet kode -> cancel & refund
if not code:
    r = requests.post(f"{BASE}/order/{order_ref}/cancel",
        headers=HEADERS, timeout=10)
    print(f"Timeout, cancel: {r.json()}")

Node.js (axios)

javascript
import axios from "axios";

const API_KEY = "sd_live_xxxxxxxxxxxxxxxxxxxxxxxx";
const BASE = "https://api.safedigits.app/api/v1";
const HEADERS = { Authorization: `Bearer ${API_KEY}` };

async function buyAndPoll() {
  // 1. Beli nomor
  const order = (await axios.post(`${BASE}/order`,
    { country: "USA", service: "TG" },
    { headers: HEADERS, timeout: 15000 })).data;

  console.log(`📞 Beli ${order.number}, ref: ${order.order_ref}`);

  // 2. Polling kode (max 5 menit)
  for (let i = 0; i < 60; i++) {
    await new Promise(r => setTimeout(r, 5000));
    const r = await axios.get(`${BASE}/order/${order.order_ref}/code`,
      { headers: HEADERS, timeout: 10000 });

    if (["received", "found"].includes(r.data.status)) {
      console.log(`✅ Kode: ${r.data.code}`);
      return r.data.code;
    }
    console.log(`⏳ Attempt ${i + 1}/60: ${r.data.status}`);
  }

  // 3. Cancel kalau timeout
  await axios.post(`${BASE}/order/${order.order_ref}/cancel`,
    {}, { headers: HEADERS });
  console.log("❌ Timeout, cancelled");
  return null;
}

buyAndPoll().catch(console.error);

PHP (Guzzle)

php
<?php
require "vendor/autoload.php";
use GuzzleHttp\Client;

$apiKey = "sd_live_xxxxxxxxxxxxxxxxxxxxxxxx";
$client = new Client([
    "base_uri" => "https://api.safedigits.app/api/v1/",
    "headers"  => ["Authorization" => "Bearer $apiKey"],
    "timeout"  => 15,
]);

// 1. Beli nomor
$res = $client->post("order", [
    "json" => ["country" => "USA", "service" => "TG"]
]);
$order = json_decode($res->getBody(), true);
$ref = $order["order_ref"];
echo "📞 Beli {$order['number']}, ref: $ref\n";

// 2. Polling kode
$code = null;
for ($i = 0; $i < 60; $i++) {
    sleep(5);
    $res = $client->get("order/$ref/code");
    $data = json_decode($res->getBody(), true);

    if (in_array($data["status"], ["received", "found"])) {
        $code = $data["code"];
        echo "✅ Kode: $code\n";
        break;
    }
    echo "⏳ Attempt " . ($i + 1) . "/60\n";
}

// 3. Cancel kalau timeout
if (!$code) {
    $client->post("order/$ref/cancel");
    echo "❌ Timeout, cancelled\n";
}

Go (net/http)

go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

const (
    apiKey = "sd_live_xxxxxxxxxxxxxxxxxxxxxxxx"
    base   = "https://api.safedigits.app/api/v1"
)

func req(method, path string, body []byte) (map[string]interface{}, error) {
    r, _ := http.NewRequest(method, base+path, bytes.NewReader(body))
    r.Header.Set("Authorization", "Bearer "+apiKey)
    r.Header.Set("Content-Type", "application/json")
    resp, err := (&http.Client{Timeout: 15 * time.Second}).Do(r)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    b, _ := io.ReadAll(resp.Body)
    var m map[string]interface{}
    json.Unmarshal(b, &m)
    return m, nil
}

func main() {
    // 1. Beli nomor
    body, _ := json.Marshal(map[string]string{"country": "USA", "service": "TG"})
    order, _ := req("POST", "/order", body)
    ref := order["order_ref"].(string)
    fmt.Printf("📞 Beli %v, ref: %s\n", order["number"], ref)

    // 2. Polling
    var code string
    for i := 0; i < 60; i++ {
        time.Sleep(5 * time.Second)
        data, _ := req("GET", "/order/"+ref+"/code", nil)
        if status, _ := data["status"].(string); status == "received" || status == "found" {
            code = data["code"].(string)
            fmt.Printf("✅ Kode: %s\n", code)
            break
        }
        fmt.Printf("⏳ Attempt %d/60\n", i+1)
    }

    // 3. Cancel kalau timeout
    if code == "" {
        req("POST", "/order/"+ref+"/cancel", nil)
        fmt.Println("❌ Timeout, cancelled")
    }
}

Bash (curl)

bash
#!/bin/bash
API_KEY="sd_live_xxxxxxxxxxxxxxxxxxxxxxxx"
BASE="https://api.safedigits.app/api/v1"
H="Authorization: Bearer $API_KEY"

# 1. Beli nomor
ORDER=$(curl -s -X POST "$BASE/order" \
  -H "$H" -H "Content-Type: application/json" \
  -d '{"country":"USA","service":"TG"}')
REF=$(echo "$ORDER" | jq -r '.order_ref')
PHONE=$(echo "$ORDER" | jq -r '.number')
echo "📞 Beli $PHONE, ref: $REF"

# 2. Polling kode
CODE=""
for i in $(seq 1 60); do
    sleep 5
    RES=$(curl -s "$BASE/order/$REF/code" -H "$H")
    STATUS=$(echo "$RES" | jq -r '.status')
    if [[ "$STATUS" == "received" || "$STATUS" == "found" ]]; then
        CODE=$(echo "$RES" | jq -r '.code')
        echo "✅ Kode: $CODE"
        break
    fi
    echo "⏳ Attempt $i/60"
done

# 3. Cancel kalau timeout
if [[ -z "$CODE" ]]; then
    curl -s -X POST "$BASE/order/$REF/cancel" -H "$H"
    echo "❌ Timeout, cancelled"
fi

💡 Tip: Polling tiap 3-5 detik. Hindari kurang dari 1 detik (rate limited). Untuk avoid polling sama sekali, pakai Webhooks.

Get Stock

GET/api/v1/stock

Cek stok nomor yang available per negara dan service.

Example Request

bash
curl https://api.safedigits.app/api/v1/stock \
  -H "Authorization: Bearer YOUR_API_KEY"

Example Response

json
{
  "status": "ok",
  "stock": [
    {
      "country": "USA",
      "service": "TG",
      "qty": 38,
      "price": 0.30
    },
    ...
  ]
}

Buy Number

POST/api/v1/order

Beli nomor virtual. Saldo akan dipotong dari client_balance.

Body

json
{
  "country": "USA",
  "service": "TG"
}

Response

json
{
  "status": "ok",
  "order_ref": "abc123-xyz",
  "number": "+13215341918",
  "country": "USA",
  "service": "TG",
  "price": 0.30,
  "new_balance": 19.70
}

Get OTP Code

GET/api/v1/order/{order_ref}/code

Polling untuk SMS code yang masuk ke nomor.

Response (Code Found)

json
{
  "status": "received",
  "code": "123456"
}

Response (Still Waiting)

json
{
  "status": "waiting"
}

💡 Tip: Polling tiap 3-5 detik. Hindari polling kurang dari 1 detik (rate limited).

Cancel Order

POST/api/v1/order/{order_ref}/cancel

Cancel order & full refund. Hanya bisa kalau belum dapat code.

Response

json
{
  "status": "ok",
  "refunded_amount": 0.30,
  "new_balance": 20.00
}

Error Codes

401
INVALID_API_KEY
API key tidak valid atau revoked
402
INSUFFICIENT_BALANCE
Saldo tidak cukup
404
ORDER_NOT_FOUND
Order_ref tidak ada / bukan milik Anda
404
OUT_OF_STOCK
Tidak ada nomor untuk negara/service yang diminta
429
RATE_LIMITED
Terlalu banyak request, retry setelah header `Retry-After`
403
MEMBERSHIP_EXPIRED
Membership expired, perpanjang via /pricing
500
SERVER_ERROR
Internal error, retry beberapa saat

Rate Limits

Default: 60 request/menit per API key. Untuk plan custom, hubungi admin.

Setiap response include rate limit headers:

bash
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1717234567

💰 Deposit (Reseller)

Reseller / member yang punya bot sendiri bisa bikin tombol "Deposit ke SafeDigits" di admin panel bot mereka. Endpoint ini generate QRIS dinamis langsung dari SafeDigits dan saldo otomatis masuk ke akun pemilik API key (sama seperti deposit dari bot/web SafeDigits).

✨ Cara Kerja

  1. Bot reseller call POST /api/v1/deposit dengan nominal IDR/USD.
  2. SafeDigits balikin qris_url + total_idr + deposit_id.
  3. Bot reseller display QRIS itu ke user (download image dari URL atau forward).
  4. Setelah user transfer, Tasker SafeDigits auto-detect (~30-60 detik) dan saldo masuk.
  5. Bot reseller bisa polling status via GET /api/v1/deposit/{id}/status atau pakai webhook event deposit_confirmed.

POST /api/v1/deposit

bash
curl -X POST https://api.safedigits.app/api/v1/deposit \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount_idr": 100000}'

Response 201

json
{
  "status": "pending",
  "deposit_id": 482,
  "transaction_id": "API-DEP-1716543210-A1B2C3D4",
  "amount_idr": 100000,
  "amount_usd": 6.06,
  "unique_code": 217,
  "total_idr": 100217,
  "exchange_rate": 16500.0,
  "qris_url": "/api/miniapp/deposit/qris/qris_100217.png",
  "expires_at": "2026-05-25T08:30:00",
  "expires_minutes": 15,
  "owner_type": "telegram",
  "instructions": "Transfer tepat Rp 100,217 ke QRIS yang ditampilkan..."
}

Body Parameters

  • amount_idr (int, optional) — Nominal Rupiah. Min Rp 8.500, Max Rp 10.000.000.
  • amount_usd (float, optional) — Nominal USD. Auto-convert ke IDR pakai kurs realtime.
  • ⚠️ Salah satu wajib diisi (boleh hanya satu).

GET /api/v1/deposit/{deposit_id}/status

bash
curl https://api.safedigits.app/api/v1/deposit/482/status \
  -H "Authorization: Bearer YOUR_API_KEY"

Response 200

json
{
  "status": "completed",
  "deposit_id": 482,
  "transaction_id": "API-DEP-1716543210-A1B2C3D4",
  "amount_idr": 100000,
  "amount_usd": 6.06,
  "unique_code": 217,
  "total_idr": 100217,
  "created_at": "2026-05-25T08:15:00"
}

Status bisa: pending, completed, atau expired (lewat 15 menit tanpa transfer).

Python Example

python
import requests, time

API_KEY = "sd_dev_xxx"
BASE = "https://api.safedigits.app"

# 1. Create deposit
r = requests.post(
    f"{BASE}/api/v1/deposit",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={"amount_idr": 50000}
).json()

deposit_id = r["deposit_id"]
total_idr = r["total_idr"]
qris_url = BASE + r["qris_url"]
print(f"Bayar Rp {total_idr:,} di QRIS: {qris_url}")

# 2. Poll status (atau pakai webhook 'deposit_confirmed')
while True:
    s = requests.get(
        f"{BASE}/api/v1/deposit/{deposit_id}/status",
        headers={"Authorization": f"Bearer {API_KEY}"}
    ).json()
    if s["status"] == "completed":
        print(f"Sukses! Saldo + ${s['amount_usd']}")
        break
    if s["status"] == "expired":
        print("Deposit expired, bikin baru.")
        break
    time.sleep(5)

Node.js Example

javascript
const API_KEY = "sd_dev_xxx";
const BASE = "https://api.safedigits.app";
const headers = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" };

// 1. Create deposit
const create = await fetch(`${BASE}/api/v1/deposit`, {
  method: "POST", headers,
  body: JSON.stringify({ amount_idr: 50000 })
}).then(r => r.json());

console.log("Total:", create.total_idr, "QRIS:", BASE + create.qris_url);

// 2. Poll status
while (true) {
  const s = await fetch(
    `${BASE}/api/v1/deposit/${create.deposit_id}/status`,
    { headers }
  ).then(r => r.json());

  if (s.status === "completed") { console.log("Saldo +$", s.amount_usd); break; }
  if (s.status === "expired") { console.log("Expired"); break; }
  await new Promise(r => setTimeout(r, 5000));
}

⚠️ Catatan Penting

  • QRIS expired 15 menit. Setelah itu user harus bikin deposit baru.
  • Saldo masuk ke akun owner API key (TG-linked → users.balance, web-only → web_users.balance).
  • Kode unik unique_code WAJIB jadi bagian dari nominal transfer (sudah include di total_idr).
  • Untuk hindari polling, set webhook event deposit_confirmed di /app/webhooks.
  • Min: Rp 8.500 — Max: Rp 10.000.000 per deposit.

Webhooks

Webhook bikin SafeDigits push event real-timeke endpoint server Anda. Tanpa polling — instant notification <5 detik dari event terjadi.

Cocok untuk reseller yang punya bot/aplikasi sendiri, integrasi ke CRM, Discord bot, atau automation tool seperti Zapier/n8n/Make.

Setup di 3 langkah:

  1. Buka /app/webhooks di dashboard
  2. Set URL HTTPS Anda + pilih events yang mau diterima
  3. Klik "Save" — secret auto-generate. SafeDigits langsung kirim event saat trigger

Request Format

http
POST /your-webhook-endpoint HTTP/1.1
Content-Type: application/json
User-Agent: SafeDigits-Webhook/1.0
X-Safedigits-Event: code_received
X-Safedigits-Timestamp: 1716542123
X-Safedigits-Signature: sha256=<hex_hmac>
X-Safedigits-Delivery-Id: 42

{
  "event": "code_received",
  "data": { ... },
  "timestamp": 1716542123
}

Endpoint Anda harus return HTTP 2xxdalam <10 detik untuk menandakan delivery sukses. Kalau non-2xx, SafeDigits auto-retry dengan exponential backoff: 1m → 5m → 30m → 2h → 12h (max 5x).

Event Types

Semua event punya envelope yang sama: { event, data, timestamp }. Field data beda content per event.

📱 code_received

Trigger saat SMS code masuk untuk order Anda. Use case: forward kode ke user via Telegram/Discord/email.

json
{
  "event": "code_received",
  "data": {
    "order_id": "1175420208604349067",
    "phone_number": "+18782632528",
    "service": "telegram",
    "code": "415862"
  },
  "timestamp": 1716542123
}
✅ deposit_confirmed

Trigger saat deposit dikonfirmasi via tasker. Use case: update saldo user di CRM, kirim notif email/Slack.

json
{
  "event": "deposit_confirmed",
  "data": {
    "transaction_id": "WEB-1234567890ABCDEF",
    "amount_usd": 1.5,
    "amount_idr": 24750
  },
  "timestamp": 1716542123
}
💰 transfer_received

Trigger saat menerima transfer saldo dari user lain.

json
{
  "event": "transfer_received",
  "data": {
    "amount": 0.75,
    "sender_label": "@friend",
    "new_balance": 10.75,
    "transfer_ref": "TRF-abc123xyz"
  },
  "timestamp": 1716542123
}
❌ order_canceled

Trigger saat order dibatalkan dan saldo direfund.

json
{
  "event": "order_canceled",
  "data": {
    "order_id": "1234567890",
    "refunded_amount": 0.40,
    "new_balance": 11.15
  },
  "timestamp": 1716542123
}
👑 membership_activated

Trigger saat membership aktif/diperpanjang setelah pembayaran QRIS.

json
{
  "event": "membership_activated",
  "data": {
    "client_id": "123",
    "expires_at": "2026-06-24T12:34:56",
    "duration_days": 30
  },
  "timestamp": 1716542123
}
🛒 order_created

Trigger saat berhasil beli nomor (lewat web/bot/API).

json
{
  "event": "order_created",
  "data": {
    "order_id": "1234567890",
    "phone_number": "+18782632528",
    "country": "USA",
    "service": "telegram",
    "price": 0.40
  },
  "timestamp": 1716542123
}

Verify Signature

Setiap webhook di-sign dengan HMAC-SHA256 untuk mencegah spoofing. Verify signature di server Anda WAJIB sebelum process payload.

Algoritma: signature = "sha256=" + HMAC_SHA256(secret, "{timestamp}.{body}")

Python (FastAPI)

python
import hmac, hashlib
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()
SECRET = "your_webhook_secret_from_safedigits"

@app.post("/sd-hook")
async def receive(request: Request):
    timestamp = request.headers.get("X-Safedigits-Timestamp")
    signature = request.headers.get("X-Safedigits-Signature")
    body = await request.body()

    sig_input = f"{timestamp}.{body.decode()}"
    expected = "sha256=" + hmac.new(
        SECRET.encode(), sig_input.encode(), hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        raise HTTPException(403, "Invalid signature")

    payload = await request.json()
    event = payload["event"]
    data = payload["data"]
    # process: forward ke user, save ke DB, dll
    return {"status": "ok"}

Node.js (Express)

javascript
import express from "express";
import crypto from "crypto";

const app = express();
const SECRET = "your_webhook_secret_from_safedigits";

app.post("/sd-hook", express.raw({ type: "application/json" }), (req, res) => {
  const timestamp = req.header("x-safedigits-timestamp");
  const signature = req.header("x-safedigits-signature");
  const body = req.body.toString();

  const sigInput = `${timestamp}.${body}`;
  const expected = "sha256=" + crypto
    .createHmac("sha256", SECRET)
    .update(sigInput)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
    return res.status(403).send("Invalid signature");
  }

  const payload = JSON.parse(body);
  console.log("Received event:", payload.event, payload.data);
  res.json({ status: "ok" });
});

app.listen(3000);

PHP

php
<?php
$secret = "your_webhook_secret_from_safedigits";
$timestamp = $_SERVER["HTTP_X_SAFEDIGITS_TIMESTAMP"] ?? "";
$signature = $_SERVER["HTTP_X_SAFEDIGITS_SIGNATURE"] ?? "";
$body = file_get_contents("php://input");

$sig_input = $timestamp . "." . $body;
$expected = "sha256=" . hash_hmac("sha256", $sig_input, $secret);

if (!hash_equals($expected, $signature)) {
    http_response_code(403);
    echo json_encode(["error" => "Invalid signature"]);
    exit;
}

$payload = json_decode($body, true);
$event = $payload["event"];
$data = $payload["data"];
// process
http_response_code(200);
echo json_encode(["status" => "ok"]);
?>

Go

go
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "io"
    "net/http"
)

const SECRET = "your_webhook_secret_from_safedigits"

func handler(w http.ResponseWriter, r *http.Request) {
    timestamp := r.Header.Get("X-Safedigits-Timestamp")
    signature := r.Header.Get("X-Safedigits-Signature")
    body, _ := io.ReadAll(r.Body)

    sigInput := timestamp + "." + string(body)
    h := hmac.New(sha256.New, []byte(SECRET))
    h.Write([]byte(sigInput))
    expected := "sha256=" + hex.EncodeToString(h.Sum(nil))

    if !hmac.Equal([]byte(expected), []byte(signature)) {
        http.Error(w, "Invalid signature", 403)
        return
    }
    // process payload...
    w.WriteHeader(200)
}

Best Practices

✅ Selalu return 2xx cepat

Process minimal di handler, jangan blocking. Kalau butuh proses berat (e.g. kirim ke 100 user), enqueue ke job queue lalu return 200 dulu. Timeout SafeDigits 10 detik — non-2xx atau timeout akan trigger retry.

🔁 Idempotency

Setiap delivery punya unique X-Safedigits-Delivery-Id. Save ID ke DB dan skip kalau udah pernah diproses (kalau retry kena, payload identik).

⏰ Verify timestamp

Reject request kalau timestamp selisih lebih dari 5 menit dari waktu sekarang — anti replay attack.

🛠 Test pakai webhook.site

Untuk eksplorasi cepat: pakai webhook.site → set URL ke unique link mereka → klik "Send Test" di dashboard SafeDigits → liat payload masuk real-time.

🔒 Rotasi secret kalau bocor

Kalau secret bocor (mis: commit ke git public), langsung edit secret di /app/webhooks → save dengan secret baru → update server Anda. Old secret langsung tidak valid.

🔌 No-code option (Zapier / n8n / Make)

Untuk integrasi tanpa coding: bikin Zap/Workflow dengan trigger "Webhook by Zapier" → copy URL Zapier → paste di SafeDigits. Kemudian connect ke Slack, Discord, Google Sheet, Telegram, dll. (Note: Zapier ga support HMAC verify built-in, tapi karena URL random + secret optional, masih cukup aman untuk use case low-stakes.)

📦 SDK / Libraries

Tidak ada SDK official saat ini. Tapi karena REST API standar, lu bisa langsung pakai HTTP client favorit lu di bahasa apapun.

🐍 Python

Pakai requests atau httpx

pip install requests

⚡ Node.js

Pakai axios atau native fetch

npm install axios

🐘 PHP

Pakai Guzzle atau cURL built-in

composer require guzzlehttp/guzzle

🐹 Go

Pakai net/http built-in

go mod init

Reusable Python Client

Save sebagai safedigits_client.py di project lu — full reusable class.

python
import time, requests

class SafeDigitsClient:
    def __init__(self, api_key, base_url="https://api.safedigits.app"):
        self.base = base_url.rstrip("/") + "/api/v1"
        self.session = requests.Session()
        self.session.headers.update({"Authorization": f"Bearer {api_key}"})
        self.session.timeout = 15

    # ── Read-only ──
    def get_balance(self):
        return self.session.get(f"{self.base}/balance").json()["balance_usd"]

    def get_stock(self):
        return self.session.get(f"{self.base}/stock").json()["stock"]

    def get_membership(self):
        return self.session.get(f"{self.base}/membership").json()

    # ── Order management ──
    def buy_number(self, country, service):
        return self.session.post(f"{self.base}/order",
            json={"country": country.upper(), "service": service.upper()}).json()

    def get_code(self, order_ref):
        return self.session.get(f"{self.base}/order/{order_ref}/code").json()

    def cancel(self, order_ref):
        return self.session.post(f"{self.base}/order/{order_ref}/cancel").json()

    def reuse(self, number, service):
        return self.session.post(f"{self.base}/number/reuse",
            json={"number": number, "service": service.upper()}).json()

    # ── High-level: poll until code received ──
    def buy_and_wait(self, country, service, max_wait_sec=300, interval=5):
        order = self.buy_number(country, service)
        ref = order["order_ref"]
        elapsed = 0
        while elapsed < max_wait_sec:
            time.sleep(interval)
            elapsed += interval
            r = self.get_code(ref)
            if r.get("status") in ("received", "found"):
                return {"order_ref": ref, "number": order["number"], "code": r["code"]}
        # Timeout — cancel
        self.cancel(ref)
        return None


# Usage:
client = SafeDigitsClient(api_key="sd_live_xxxxx")
print("Balance:", client.get_balance())
print("Stock:", client.get_stock())

result = client.buy_and_wait("USA", "TG", max_wait_sec=300)
if result:
    print(f"✅ {result['number']} → {result['code']}")
else:
    print("❌ Timeout")

📥 OpenAPI / Swagger spec juga tersedia di api.safedigits.app/docs — bisa pake auto-generator (openapi-generator-cli) untuk SDK di bahasa lain (C#, Ruby, Java, Swift, Kotlin, Rust, dll).

Auto-generate SDK dari OpenAPI

bash
# Install openapi-generator-cli
npm install -g @openapitools/openapi-generator-cli

# Generate SDK untuk bahasa pilihan lu
openapi-generator-cli generate \
  -i https://api.safedigits.app/openapi.json \
  -g python \
  -o ./safedigits-sdk-python

# Bahasa lain: java, ruby, csharp, php, go, kotlin, swift5, dart, rust, ...
# List lengkap: openapi-generator-cli list

Siap mulai integrasi?

API key & webhook gratis untuk semua user. Subscribe Membership untuk dapat harga reseller lebih murah.