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.
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 integration — buy temporary numbers, receive OTP codes, rent long-term numbers, and receive webhook events. Authenticate with an API key.
Provider integration — connect 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 SIMStatuses: 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 purchaseWebhooks
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 # disableEvents:
activation.assigned— number allocatedsms.received— OTP text arrivedactivation.completed— session finishedactivation.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 balance401— missing or invalid API key404— activation/rental not found429— 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.
Health & catalog
/healthPublicHealth check
Response example
{
"status": "ok",
"timestamp": "2026-06-19T12:00:00.000Z",
"service": "smstwins-api"
}/countriesPublicList countries
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| withStock | query | string | No | true | Set to true or 1 for live stock counts |
Response example
[
{
"id": 6,
"code": "ID",
"name": "Indonesia",
"phoneCode": "+62",
"flag": "🇮🇩",
"availableCount": 42
}
]/servicesPublicList verification services
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| withStock | query | string | No | true | Set to true or 1 for availability counts |
Response example
[
{
"id": "clxsvc001",
"code": "tg",
"name": "Telegram",
"description": "Telegram messenger verification"
}
]/operatorsPublicList mobile operators
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| country | query | number | No | 6 | Filter by country ID |
Response example
[
{ "id": "clxop001", "code": "telkomsel", "name": "Telkomsel", "countryId": 6 }
]/pricesPublicGet prices and stock
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| service | query | string | No | tg | Service code |
| country | query | number | No | 6 | Country ID |
| operator | query | string | No | telkomsel | Operator 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
}
]/prices/quotePublicGet activation price for one service and country
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| service | query | string | Yes | tg | Service code |
| country | query | number | Yes | 6 | Country ID |
Response example
{
"service": { "code": "tg", "name": "Telegram" },
"country": { "id": 6, "code": "ID", "name": "Indonesia", "flag": "🇮🇩" },
"purchasePrice": 0.35,
"price": 0.35
}/rental-optionsPublicRental options per country for a service
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| service | query | string | Yes | tg | Service 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
}
]/rental-quotePublicGet rental purchase price for service, country, and hours
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| service | query | string | Yes | tg | Service code |
| country | query | number | Yes | 6 | Country ID |
| hours | query | number | Yes | 24 | 1, 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
/user/balanceAPI keyAccount balance
Response example
{ "balance": 25.5, "currency": "USD" }/user/loyaltyAPI keyLoyalty 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
/activationsAPI keyRequest a virtual number
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| service | body | string | Yes | tg | Service code from GET /services |
| country | body | number | Yes | 6 | Country ID from GET /countries |
| operator | body | string | No | telkomsel | Operator code |
| maxPrice | body | number | No | 0.50 | Reject 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"
}/activationsAPI keyList activations
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| status | query | string | No | WAITING_SMS | WAITING_SMS | COMPLETED | CANCELLED | EXPIRED |
Response example
[ /* array of activation objects */ ]
/activations/activeAPI keyActivations waiting for SMS
Response example
[ /* activations with status WAITING_SMS */ ]
/activations/{id}API keyGet activation by ID
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| id | path | string | Yes | clxact001 | Activation ID |
Response example
{
"id": "clxact001",
"status": "COMPLETED",
"purchasePrice": 0.35,
"sms": { "code": "12345", "text": "Your Telegram code is 12345" }
}/activations/{id}/cancelAPI keyCancel activation
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| id | path | string | Yes | clxact001 | Activation ID |
Response example
{
"id": "clxact001",
"status": "CANCELLED",
"refundedAmount": 0.35
}/activations/reactivatableAPI keyNumbers available for reactivation
Response example
[ { "id": "clxact001", "canReactivate": true, ... } ]/activations/{id}/reactivateAPI keyReactivate same number
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| id | path | string | Yes | clxact001 | Previous activation ID |
Response example
{ "id": "clxact002", "reactivatedFromId": "clxact001", ... }Rentals
/rentals/durationsAPI keyAllowed rental duration presets in hours
Response example
{ "hours": [1, 6, 24, 168, 720] }/rentalsAPI keyList 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"
}
]/rentalsAPI keyRent a dedicated number
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| service | body | string | Yes | tg | Service code from GET /rental-options?service= |
| country | body | number | Yes | 6 | Country ID |
| hours | body | number | Yes | 24 | 1, 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"
}/rentals/{id}/extendAPI keyExtend rental
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| id | path | string | Yes | clxrent001 | Rental ID |
| hours | body | number | Yes | 24 | 1, 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"
}/rentals/{id}/cancelAPI keyCancel rental (within 30 minutes, no OTP received)
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| id | path | string | Yes | clxrent001 | Rental ID |
Response example
{
"id": "clxrent001",
"status": "CANCELLED",
"refundedAmount": 30,
"canCancel": false
}Webhooks
/webhooksAPI keyGet webhook config
Response example
{
"id": "clxwh001",
"url": "https://your-server.com/hook",
"events": ["activation.assigned", "sms.received"],
"active": true
}/webhooksAPI keyCreate or update webhook
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| url | body | string | Yes | https://your-server.com/smstwins-hook | HTTPS callback URL |
| events | body | string[] | 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
}/webhooksAPI keyDisable webhooks
Response example
{ "success": true }Simple API
/simpleAPI keySimple API (plain text)
Parameters
| Name | In | Type | Required | Example | Description |
|---|---|---|---|---|---|
| action | query | string | Yes | balance | balance | 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_key | query | string | Yes | sk_live_... | API key sk_live_... |
| service | query | string | No | tg | For request_number, list_prices |
| country | query | string | No | 6 | For request_number, list_prices |
| id | query | string | No | clxact001 | Activation or rental ID for check_status, cancel_number, extend_rental, cancel_rental |
| max_price | query | string | No | 0.50 | Max price for request_number |
| operator | query | string | No | telkomsel | Operator for request_number |
| hours | query | string | No | 24 | Rental 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.
- Apply as a provider and get approved
- Create a device token:
POST /providers/devices(JWT login in provider portal) - Download SMSTwins Gateway or integrate your own client
- 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.