SMSTwins

Do you have SIM cards? Become a supplier today and start earning from every activation.

Apply now
Developer API

SMSTwins Integration API

Complete reference for integrating SMS verification into your apps, automating OTP flows, and connecting provider hardware to sell virtual numbers on SMSTwins.

Base URL
https://smstwins.com/v1

Overview

SMSTwins exposes a REST API under /v1. Customer integration covers temporary numbers, OTP codes, rentals, and webhooks. Provider integration uses the WebSocket gateway with device tokens.

Customer integrationbuy temporary numbers, receive OTP codes, rent long-term numbers, and receive webhook events. Authenticate with an API key.

Provider integrationconnect SIM hardware via the WebSocket gateway to supply numbers and forward SMS. Uses device tokens, not API keys. See Provider gateway.

  • GET /health — service health (no auth)
  • Catalog endpoints — public, no auth
  • Activations, rentals, balance, webhooks — API key required
  • GET /user/loyalty — rank, discount %, effective API tier

Authentication

Create API keys in your dashboard. Keys start with sk_live_ or sk_test_ (sandbox). Store keys securely and never expose them in browser code.

Create API keys in your dashboard. Keys always start with sk_live_ (production) or sk_test_ (sandbox) and are shown only once at creation. Sandbox keys can read catalog and balance but cannot purchase activations. Store keys securely. See also API Terms of Use.

All of the following are supported and equivalent:

# Recommended
Authorization: ApiKey sk_live_...

# Compatible with OAuth2/Bearer clients
Authorization: Bearer sk_live_...

# Alternative header (Postman, many HTTP libraries)
X-API-Key: sk_live_...

# Simple API scripts only
GET /simple?action=balance&api_key=sk_live_...

Invalid or revoked keys return 401 Unauthorized. Keys are hashed at rest (SHA-256); only the prefix is stored for display.

Rate limits & security

Rate limits apply per API key or JWT session (same account limits). Exceeding the limit returns 429 Too Many Requests.

Your tier follows your loyalty rank — not a manual upgrade. Rank changes update limits automatically.

  • FREE — no loyalty rank — 60 requests / minute
  • STANDARD — Bronze, Silver, or Gold rank — 120 requests / minute
  • ENTERPRISE — Platinum rank — 600 requests / minute

Unauthenticated catalog reads are limited by IP (120/min). Loyalty discounts apply to your purchasePrice on all API purchases — the discounted amount is what you are charged for every number.

Security practices:

  • Never expose API keys in client-side browser code
  • Revoke compromised keys immediately from the dashboard
  • Use webhooks instead of aggressive polling when possible
  • Verify webhook signatures (see Webhooks section)
  • HTTPS is required in production

Catalog

Discover countries, services, operators, and live prices. No authentication required. Pass your API key (same as authenticated routes) to include loyalty discounts on purchasePrice.

GET /countries
GET /countries?withStock=true     # include availableCount per country

GET /services
GET /services?withStock=true      # countriesWithStock, totalAvailable

GET /operators?country=6

GET /prices?service=tg&country=6
GET /prices?service=wa              # all countries for WhatsApp

GET /prices/quote?service=tg&country=6   # exact activation price for one pair

GET /rental-options?service=tg    # required — per-country rental pricing & stock
GET /rental-quote?service=tg&country=6&hours=24

Price object: purchasePrice (what you pay — includes loyalty discount when authenticated), price (same as purchasePrice), availableCount / available, plus country and service metadata. With a loyalty rank you may also see listPrice and loyaltyDiscountPercent.

Rental options: each row includes purchasePricePerHour (tier-1 rate), preset durations (1h, 6h, 24h, 168h, 720h) with tiered purchasePrice totals, and availableCount.

API requests use numeric country IDs and string service codes. Example: POST /activations with "country": 6 and "service": "tg" for Telegram in Indonesia.

For live stock and prices, call GET /countries and GET /services with your API key. Your SMSTwins account may expose a subset of active routes.

Activations

Request a temporary virtual number for OTP verification. Session lasts 30 minutes. You are charged when the number is assigned; cancel for a refund if no SMS arrives.

# Request a number
POST /activations
{
  "service": "tg",
  "country": 6,
  "operator": "telkomsel",      // optional
  "maxPrice": 0.50        // optional — reject if price exceeds this
}

# Response
{
  "id": "clx...",
  "phone": "+6281234567890",
  "status": "WAITING_SMS",
  "purchasePrice": 0.35,
  "price": 0.35,
  "amountCharged": 0.35,
  "canCancel": false,
  "sms": null,
  "service": { "code": "tg", "name": "Telegram" },
  "country": { "code": "ID", "name": "Indonesia", "flag": "🇮🇩" },
  "expiresAt": "2026-06-19T18:30:00.000Z"
}

GET /activations                  # list all (optional ?status=WAITING_SMS)
GET /activations/active           # waiting for SMS only
GET /activations/{id}             # poll for code — sms.code when received
POST /activations/{id}/cancel     # cancel & refund amountCharged (after 1 min, before SMS)

GET /activations/reactivatable    # numbers you can reuse (last 24h, SIM online)
POST /activations/{id}/reactivate # new 30-min session on same SIM

Statuses: WAITING_SMS → COMPLETED (code received), CANCELLED, EXPIRED.

Typical flow: POST activation → poll GET /activations/{id} every 3–5s (or use webhooks) → read sms.code.

Rentals

Dedicated numbers by service and country with tiered hourly pricing. Presets: 1h, 6h, 24h, 7 days, 30 days. Cancel for full refund within 30 minutes if no OTP received.

You may cancel for a full refund if no OTP was received — but only within 30 minutes of purchase. After 30 minutes, or once an SMS code arrives, cancellation is not available.

GET /rentals/durations          # [1, 6, 24, 168, 720]
GET /rentals                    # your rentals

POST /rentals
{
  "service": "tg",
  "country": 6,
  "hours": 24
}

POST /rentals/{id}/extend
{ "hours": 24 }                 # same allowed hour presets

POST /rentals/{id}/cancel
# Full refund of purchasePrice if no OTP — only within 30 min of purchase

Webhooks

Configure in your webhook settings. SMSTwins POSTs JSON to your URL with HMAC-SHA256 signature.

PUT /webhooks
{
  "url": "https://your-server.com/smstwins-hook",
  "events": ["activation.assigned", "sms.received", "activation.completed"]
}

GET /webhooks                     # current config
DELETE /webhooks                  # disable

Events:

  • activation.assigned — number allocated
  • sms.received — OTP text arrived
  • activation.completed — session finished
  • activation.cancelled / activation.expired

Verify signatures: HMAC-SHA256 of the raw JSON body using your webhook secret. Compare to the X-SMSTwins-Signature header. Event type is in X-SMSTwins-Event.

# Payload shape
{
  "event": "sms.received",
  "timestamp": "2026-06-19T12:00:00.000Z",
  "data": { ... }
}

# Node.js verification
const crypto = require('crypto');
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET)
  .update(rawBody)
  .digest('hex');
if (expected !== req.headers['x-smstwins-signature']) throw new Error('Invalid');

Simple API

Plain-text responses for shell scripts and legacy integrations. Returns text/plain with SMSTWINS_OK / SMSTWINS_ERR prefixes.

GET /simple?action=balance&api_key=sk_live_...
→ SMSTWINS_OK:BALANCE:25.50

GET /simple?action=request_number&service=tg&country=6&max_price=0.50&api_key=...
→ SMSTWINS_OK:NUMBER:{id}:{phone}:{price}

GET /simple?action=check_status&id={activationId}&api_key=...
→ SMSTWINS_OK:CODE:123456 | SMSTWINS_OK:WAITING | SMSTWINS_OK:CANCELLED

GET /simple?action=cancel_number&id={id}&api_key=...
→ SMSTWINS_OK:CANCELLED:{refunded}

GET /simple?action=list_countries&api_key=...
GET /simple?action=list_services&api_key=...
GET /simple?action=list_prices&service=tg&api_key=...   # purchasePrice per row
GET /simple?action=get_price&service=tg&country=6&api_key=...

GET /simple?action=list_rental_options&service=tg&api_key=...
GET /simple?action=get_rental_price&service=tg&country=6&hours=24&api_key=...
GET /simple?action=rent_number&service=tg&country=6&hours=1&api_key=...
→ SMSTWINS_OK:RENTAL:{id}:{phone}:{purchasePrice}

GET /simple?action=extend_rental&id={rentalId}&hours=24&api_key=...
→ SMSTWINS_OK:EXTENDED:{id}:{totalPaid}

GET /simple?action=cancel_rental&id={rentalId}&api_key=...
→ SMSTWINS_OK:CANCELLED:{refunded}

Error codes: MISSING_KEY, INVALID_KEY, BAD_REQUEST, NO_BALANCE, NO_NUMBERS, PRICE_TOO_HIGH, UNKNOWN_ACTION.

Errors

REST errors return JSON:

{
  "statusCode": 400,
  "message": "Insufficient balance",
  "error": "Bad Request"
}
  • 400 — invalid request, no stock, insufficient balance
  • 401 — missing or invalid API key
  • 404 — activation/rental not found
  • 429 — rate limit exceeded
GET /user/balance
→ { "balance": 25.50 }

API reference

Complete parameter and example reference for every integration endpoint. Use OpenAPI for an interactive explorer.

OpenAPI.

Health & catalog

GET/healthPublic

Health check

Response example

{
  "status": "ok",
  "timestamp": "2026-06-19T12:00:00.000Z",
  "service": "smstwins-api"
}
GET/countriesPublic

List countries

Parameters

NameInTypeRequiredExampleDescription
withStockquerystringNotrueSet to true or 1 for live stock counts

Response example

[
  {
    "id": 6,
    "code": "ID",
    "name": "Indonesia",
    "phoneCode": "+62",
    "flag": "🇮🇩",
    "availableCount": 42
  }
]
GET/servicesPublic

List verification services

Parameters

NameInTypeRequiredExampleDescription
withStockquerystringNotrueSet to true or 1 for availability counts

Response example

[
  {
    "id": "clxsvc001",
    "code": "tg",
    "name": "Telegram",
    "description": "Telegram messenger verification"
  }
]
GET/operatorsPublic

List mobile operators

Parameters

NameInTypeRequiredExampleDescription
countryquerynumberNo6Filter by country ID

Response example

[
  { "id": "clxop001", "code": "telkomsel", "name": "Telkomsel", "countryId": 6 }
]
GET/pricesPublic

Get prices and stock

Parameters

NameInTypeRequiredExampleDescription
servicequerystringNotgService code
countryquerynumberNo6Country ID
operatorquerystringNotelkomselOperator code

Response example

[
  {
    "service": { "code": "tg", "name": "Telegram" },
    "country": { "id": 6, "code": "ID", "name": "Indonesia", "flag": "🇮🇩" },
    "purchasePrice": 0.35,
    "price": 0.35,
    "availableCount": 18,
    "available": true
  }
]
GET/prices/quotePublic

Get activation price for one service and country

Parameters

NameInTypeRequiredExampleDescription
servicequerystringYestgService code
countryquerynumberYes6Country ID

Response example

{
  "service": { "code": "tg", "name": "Telegram" },
  "country": { "id": 6, "code": "ID", "name": "Indonesia", "flag": "🇮🇩" },
  "purchasePrice": 0.35,
  "price": 0.35
}
GET/rental-optionsPublic

Rental options per country for a service

Parameters

NameInTypeRequiredExampleDescription
servicequerystringYestgService code (required)

Response example

[
  {
    "service": { "code": "tg", "name": "Telegram" },
    "country": { "id": 43, "code": "DE", "name": "Germany", "phoneCode": "+49", "flag": "🇩🇪" },
    "purchasePricePerHour": 2,
    "durations": [
      { "hours": 1, "label": "1 hour", "purchasePrice": 2 },
      { "hours": 6, "label": "6 hours", "purchasePrice": 12 },
      { "hours": 24, "label": "24 hours", "purchasePrice": 30 }
    ],
    "availableCount": 5
  }
]
GET/rental-quotePublic

Get rental purchase price for service, country, and hours

Parameters

NameInTypeRequiredExampleDescription
servicequerystringYestgService code
countryquerynumberYes6Country ID
hoursquerynumberYes241, 6, 24, 168, or 720

Response example

{
  "service": { "code": "tg", "name": "Telegram" },
  "country": { "id": 43, "code": "DE", "name": "Germany", "flag": "🇩🇪" },
  "hours": 24,
  "purchasePricePerHour": 1.25,
  "purchasePrice": 30,
  "price": 30
}

Account

GET/user/balanceAPI key

Account balance

Response example

{ "balance": 25.5, "currency": "USD" }
GET/user/loyaltyAPI key

Loyalty rank, discount, and effective API tier

Response example

{
  "loyaltyRank": 2,
  "loyaltyDiscountPercent": 15,
  "tierName": "Silver",
  "apiTier": "STANDARD",
  "apiRateLimitPerMinute": 120,
  "rankReviewAt": "2026-06-15T12:00:00.000Z"
}

Activations

POST/activationsAPI key

Request a virtual number

Parameters

NameInTypeRequiredExampleDescription
servicebodystringYestgService code from GET /services
countrybodynumberYes6Country ID from GET /countries
operatorbodystringNotelkomselOperator code
maxPricebodynumberNo0.50Reject if price exceeds this USD amount

Request example

POST https://smstwins.com/v1/activations
Content-Type: application/json
Authorization: ApiKey sk_live_...

{
  "service": "tg",
  "country": 6,
  "operator": "telkomsel",
  "maxPrice": 0.50
}

Response example

{
  "id": "clxact001",
  "phone": "+6281234567890",
  "status": "WAITING_SMS",
  "purchasePrice": 0.35,
  "price": 0.35,
  "amountCharged": 0.35,
  "canCancel": false,
  "sms": null,
  "service": { "code": "tg", "name": "Telegram" },
  "country": { "code": "ID", "name": "Indonesia", "flag": "🇮🇩" },
  "expiresAt": "2026-06-19T12:30:00.000Z"
}
GET/activationsAPI key

List activations

Parameters

NameInTypeRequiredExampleDescription
statusquerystringNoWAITING_SMSWAITING_SMS | COMPLETED | CANCELLED | EXPIRED

Response example

[ /* array of activation objects */ ]
GET/activations/activeAPI key

Activations waiting for SMS

Response example

[ /* activations with status WAITING_SMS */ ]
GET/activations/{id}API key

Get activation by ID

Parameters

NameInTypeRequiredExampleDescription
idpathstringYesclxact001Activation ID

Response example

{
  "id": "clxact001",
  "status": "COMPLETED",
  "purchasePrice": 0.35,
  "sms": { "code": "12345", "text": "Your Telegram code is 12345" }
}
POST/activations/{id}/cancelAPI key

Cancel activation

Parameters

NameInTypeRequiredExampleDescription
idpathstringYesclxact001Activation ID

Response example

{
  "id": "clxact001",
  "status": "CANCELLED",
  "refundedAmount": 0.35
}
GET/activations/reactivatableAPI key

Numbers available for reactivation

Response example

[ { "id": "clxact001", "canReactivate": true, ... } ]
POST/activations/{id}/reactivateAPI key

Reactivate same number

Parameters

NameInTypeRequiredExampleDescription
idpathstringYesclxact001Previous activation ID

Response example

{ "id": "clxact002", "reactivatedFromId": "clxact001", ... }

Rentals

GET/rentals/durationsAPI key

Allowed rental duration presets in hours

Response example

{ "hours": [1, 6, 24, 168, 720] }
GET/rentalsAPI key

List rentals

Response example

[
  {
    "id": "clxrent001",
    "phone": "+6281234567890",
    "status": "ACTIVE",
    "service": { "code": "tg", "name": "Telegram" },
    "purchasePricePerHour": 2,
    "purchasePrice": 30,
    "durationHours": 24,
    "canCancel": true,
    "endsAt": "2026-06-20T12:00:00.000Z"
  }
]
POST/rentalsAPI key

Rent a dedicated number

Parameters

NameInTypeRequiredExampleDescription
servicebodystringYestgService code from GET /rental-options?service=
countrybodynumberYes6Country ID
hoursbodynumberYes241, 6, 24, 168, or 720

Request example

POST https://smstwins.com/v1/rentals
Content-Type: application/json
Authorization: ApiKey sk_live_...

{
  "service": "tg",
  "country": 6,
  "hours": 24
}

Response example

{
  "id": "clxrent001",
  "phone": "+6281234567890",
  "status": "ACTIVE",
  "purchasePrice": 30,
  "durationHours": 24,
  "canCancel": true,
  "endsAt": "2026-06-20T12:00:00.000Z"
}
POST/rentals/{id}/extendAPI key

Extend rental

Parameters

NameInTypeRequiredExampleDescription
idpathstringYesclxrent001Rental ID
hoursbodynumberYes241, 6, 24, 168, or 720

Request example

POST https://smstwins.com/v1/rentals/clxact001/extend
Content-Type: application/json
Authorization: ApiKey sk_live_...

{ "hours": 24 }

Response example

{
  "id": "clxrent001",
  "durationHours": 48,
  "purchasePrice": 55,
  "endsAt": "2026-06-21T12:00:00.000Z"
}
POST/rentals/{id}/cancelAPI key

Cancel rental (within 30 minutes, no OTP received)

Parameters

NameInTypeRequiredExampleDescription
idpathstringYesclxrent001Rental ID

Response example

{
  "id": "clxrent001",
  "status": "CANCELLED",
  "refundedAmount": 30,
  "canCancel": false
}

Webhooks

GET/webhooksAPI key

Get webhook config

Response example

{
  "id": "clxwh001",
  "url": "https://your-server.com/hook",
  "events": ["activation.assigned", "sms.received"],
  "active": true
}
PUT/webhooksAPI key

Create or update webhook

Parameters

NameInTypeRequiredExampleDescription
urlbodystringYeshttps://your-server.com/smstwins-hookHTTPS callback URL
eventsbodystring[]Yes["activation.assigned","sms.received"]Subscribed event names

Request example

PUT https://smstwins.com/v1/webhooks
Content-Type: application/json
Authorization: ApiKey sk_live_...

{
  "url": "https://your-server.com/smstwins-hook",
  "events": ["activation.assigned", "sms.received", "activation.completed"]
}

Response example

{
  "id": "clxwh001",
  "url": "https://your-server.com/smstwins-hook",
  "secret": "shown-once-on-create",
  "active": true
}
DELETE/webhooksAPI key

Disable webhooks

Response example

{ "success": true }

Simple API

GET/simpleAPI key

Simple API (plain text)

Parameters

NameInTypeRequiredExampleDescription
actionquerystringYesbalancebalance | request_number | check_status | cancel_number | list_countries | list_services | list_prices | get_price | list_rental_options | get_rental_price | rent_number | extend_rental | cancel_rental
api_keyquerystringYessk_live_...API key sk_live_...
servicequerystringNotgFor request_number, list_prices
countryquerystringNo6For request_number, list_prices
idquerystringNoclxact001Activation or rental ID for check_status, cancel_number, extend_rental, cancel_rental
max_pricequerystringNo0.50Max price for request_number
operatorquerystringNotelkomselOperator for request_number
hoursquerystringNo24Rental hours for get_rental_price, rent_number, extend_rental

Response example

SMSTWINS_OK:BALANCE:25.50
# or SMSTWINS_OK:RENTAL:clxrent001:+6281234567890:30.00

SDKs

npm install @smstwins/sdk
pip install smstwins-sdk

import { SMSTwinsClient } from '@smstwins/sdk';

const client = new SMSTwinsClient({
  apiKey: 'sk_live_...',
  baseUrl: 'https://smstwins.com/v1',
});

const balance = await client.getBalance();
const activation = await client.requestNumber('tg', 6, { maxPrice: 0.5 });
const code = await client.waitForCode(activation.id, 120000);
await client.cancelActivation(activation.id);

// Simple API from SDK
const text = await client.simple('balance');

Provider gateway (sell numbers)

Providers supply real SIM numbers to the marketplace. This uses a WebSocket gateway, not REST API keys.

  1. Apply as a provider and get approved
  2. Create a device token: POST /providers/devices (JWT login in provider portal)
  3. Download SMSTwins Gateway or integrate your own client
  4. Connect to WebSocket namespace and authenticate
WebSocket URL: wss://smstwins.com/gateway
Library: socket.io-client

// 1. Connect
const socket = io('https://smstwins.com/gateway', {
  path: '/socket.io',
  transports: ['websocket', 'polling'],
});

// 2. Authenticate with device token from dashboard
socket.emit('auth', { deviceToken: 'your-96-char-hex-token', version: '1.0.0' });
socket.on('auth_ok', (data) => console.log('Online', data));
socket.on('auth_error', (err) => console.error(err));

// 3. Heartbeat every 30s
setInterval(() => socket.emit('heartbeat', {}), 30000);

// 4. Sync SIM inventory
socket.emit('numbers_sync', {
  numbers: [
    { phone: '+6281234567890', countryId: 6, iccid: 'optional' },
  ],
});
socket.on('numbers_sync_ack', (result) => console.log(result));

// 5. Forward incoming SMS
socket.emit('sms_received', {
  phone: '+6281234567890',
  text: 'Your Telegram code is 12345',
});
socket.on('sms_ack', (result) => console.log(result));

Server → client events: auth_ok, auth_error, heartbeat_ack, numbers_sync_ack, sms_ack.

Earnings and payouts are managed in the provider portal. Numbers appear to customers when your device is online and stock > 0.