Visão geral
A API para desenvolvedores vende os mesmos números do app, do mesmo saldo, por meio de uma API REST e de um endpoint compatível.
- Base URL
https://smsgrab.com/api/dev/v1- sms-activate compatible
https://smsgrab.com/api/stubs/handler_api.php- Format
- JSON over HTTPS,
snake_casefields, UTF-8. Money is an integer in minor units ofcurrency(USD cents). Timestamps are ISO 8601 in UTC with milliseconds. - Authentication
- An API key (
sgk_…) inAuthorization: BearerorX-Api-Key. - Money
- Every charge goes through the same double-entry ledger as the app and the website. Numbers without a code are refunded automatically.
- Request ids
- Every answer carries
X-Request-Id. Include it when you contact support.
Início rápido
Três chamadas levam você de uma chave nova a um código recebido.
- Crie uma conta com um e-mail confirmado e abra a área de desenvolvedor.
- Crie uma chave com acesso “Leitura e compra” e copie o segredo dela.
- Confirme que seu saldo cobre o número desejado e rode as chamadas abaixo.
export SMSGRAB_API_KEY="sgk_your_key"
curl -s https://smsgrab.com/api/dev/v1/balance \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
curl -s -X POST https://smsgrab.com/api/dev/v1/activations \
-H "Authorization: Bearer $SMSGRAB_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"service":"telegram","country":"indonesia","max_price_minor":30}'
curl -s "https://smsgrab.com/api/dev/v1/activations/100000123?wait=25" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
curl -s -X POST https://smsgrab.com/api/dev/v1/activations/100000123/finish \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
The number is yours for 20 minutes. Poll GET /activations/{id}?wait=25 (the request is held until something changes) or subscribe to webhooks. When a code has arrived, finish the activation; if none arrives, it expires and the charge is refunded automatically. Complete programs in five languages are in Exemplos completos.
Autenticação
Cada requisição carrega uma das suas chaves de API.
GET /api/dev/v1/balance HTTP/1.1
Host: smsgrab.com
Authorization: Bearer sgk_your_key
X-Api-Key: sgk_… works as well. A key in a query string is accepted only by the compatible endpoint. There is no CORS: keys belong on servers, never in browsers or apps you ship to users.
| Situation | Answer |
|---|---|
| No key, malformed or unknown key | 401 UNAUTHORIZED · API_KEY_INVALID |
| Revoked key | 401 UNAUTHORIZED · API_KEY_REVOKED |
| Expired key, or an old secret after its rotation grace | 401 UNAUTHORIZED · API_KEY_EXPIRED |
| Address outside the key's allowlist | 403 FORBIDDEN · API_KEY_IP_NOT_ALLOWED |
| The key lacks the needed scope | 403 FORBIDDEN · API_KEY_SCOPE |
| E-mail no longer verified, account suspended, or developer access blocked | 403 FORBIDDEN · EMAIL_NOT_VERIFIED, ACCOUNT_BLOCKED, DEVELOPER_ACCESS_BLOCKED |
| Too many rejected keys from one network | 429 RATE_LIMITED |
Chaves de API
As chaves são criadas, limitadas e rotacionadas na sua conta.
| Format | sgk_ followed by 40 letters and digits. Shown once, when it is created or rotated. We store only a keyed hash. |
|---|---|
| Scopes | read (balance, catalogue, prices, activations, usage) or read + purchase (buy, cancel, finish). |
| IP allowlist | Up to 50 IPv4/IPv6 addresses or CIDR ranges. Empty means any address. |
| Daily spend cap | Optional, per UTC day, counted over the key's net charges. Purchases above it answer 403 DAILY_SPEND_CAP_REACHED and nothing is charged. |
| Expiry | Optional, at most two years ahead. |
| Rotation | A new secret for the same key, settings, usage and webhooks. The old secret keeps working for the grace period you choose (up to 72 hours). |
| Revocation | Immediate and final. The key's webhooks are switched off. |
| Per account | Up to 10 active keys. |
Creating or rotating a key, adding a webhook, rolling its secret and widening a key's access (more scopes, a wider allowlist, a higher cap or a later expiry) ask for your password again, and your two-step code when it is on. Revoking and tightening never do.
Preços
Seu preço é formado pelo custo de atacado de cada oferta e uma margem que cai com o volume.
developer_price = max(ceil(wholesale × (100 + markup) / 100), minimum)
developer_price = min(developer_price, app_price)
| Gasto de desenvolvedor em 30 dias | Margem sobre o preço de atacado | Preço de exemplo |
|---|---|---|
| Abaixo de US$ 100,00 | +80% | US$ 0,18 28% mais barato que no app |
| A partir de US$ 100,00 | +70% | US$ 0,17 32% mais barato que no app |
| A partir de US$ 500,00 | +60% | US$ 0,16 36% mais barato que no app |
| A partir de US$ 2.000,00 | +50% | US$ 0,15 40% mais barato que no app |
The tier follows your trailing 30-day developer spend (charges minus refunds of numbers bought through the API or the compatible endpoint) and applies within five minutes of crossing a threshold. The minimum price is US$ 0,10. A developer price is never above the price of the same number in the app. The price is fixed at purchase time; send max_price_minor to protect yourself from a change between reading prices and buying. Your current markup and tier are in GET /me.
Limites de taxa
Os limites mantêm a plataforma rápida para todos. A maioria das integrações nunca chega a eles.
| Limit | Default | When exceeded |
|---|---|---|
| Requests per key | 600 / minute | 429 RATE_LIMITED with Retry-After |
| Purchases per key | 60 / minute | 429 RATE_LIMITED |
| Requests per account (all keys) | 3.000 / minute | 429 RATE_LIMITED |
| Numbers waiting at once | 100 | 422 VALIDATION · ACTIVATION_LIMIT_REACHED |
| Held long-poll requests per key | 20 | 429 RATE_LIMITED |
| Webhooks per key | 3 | 422 VALIDATION · WEBHOOK_LIMIT_REACHED |
Every answer carries RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy for the tightest limit that applies.
Erros
Chamadas com falha respondem com um código estável e, quando útil, um motivo mais preciso.
{
"code": "NO_NUMBERS_AVAILABLE",
"message": "No numbers available for this service and country",
"reason": "OUT_OF_STOCK"
}
| HTTP | code | reason | Meaning |
|---|---|---|---|
400 / 422 | VALIDATION | ACTIVATION_LIMIT_REACHED, ACTIVATION_CODE_RECEIVED, ACTIVATION_FINISHED, ACTIVATION_NO_CODE_YET, ACTIVATION_CANCEL_NOT_YET | A field is malformed or the activation is in the wrong state. The message names the field. |
401 | UNAUTHORIZED | API_KEY_INVALID, API_KEY_REVOKED, API_KEY_EXPIRED | No key, an unknown key, a revoked key, or an old secret after its rotation grace. |
402 | INSUFFICIENT_BALANCE | – | The balance does not cover the price. Nothing is charged. |
403 | FORBIDDEN | API_KEY_IP_NOT_ALLOWED, API_KEY_SCOPE, EMAIL_NOT_VERIFIED, ACCOUNT_BLOCKED, DEVELOPER_ACCESS_BLOCKED, DAILY_SPEND_CAP_REACHED | The key or the account may not do this right now. |
404 | NOT_FOUND, SERVICE_NOT_FOUND, COUNTRY_NOT_FOUND | ACTIVATION_NOT_FOUND | Unknown id, service or country. |
409 | NO_NUMBERS_AVAILABLE | OUT_OF_STOCK, PROVIDER_OUT_OF_NUMBERS | No number could be delivered. Any charge was refunded in the same request. |
409 | PRICE_CHANGED | – | The price is above max_price_minor. Nothing is charged. |
429 | RATE_LIMITED | – | Slow down and retry after the Retry-After header. |
503 | PROVIDER_UNAVAILABLE | PURCHASES_PAUSED, PROVIDER_PAUSED, PROVIDER_CIRCUIT_OPEN, SERVER_DRAINING | Purchases are paused for a moment. Nothing is charged. |
503 | FEATURE_UNAVAILABLE | – | The developer API is switched off for maintenance. |
5xx | INTERNAL | – | Retry idempotent requests with exponential backoff. |
New reason values may be added at any time; treat unknown ones like their code.
Idempotência
Repetir uma compra com segurança nunca compra duas vezes.
Send an Idempotency-Key header (any unique string up to 64 characters, a UUID is ideal) with POST /activations. If the network drops and you retry with the same key, you get the first answer again with Idempotent-Replayed: true instead of a second number. Cancel and finish are idempotent by nature: repeating them when the activation already has that status succeeds.
Endpoints
Todos os endpoints ficam sob o endereço base e respondem em JSON.
- GET
/meA chave que faz a chamada, seu preço e seus limites. - GET
/balanceO saldo da sua conta. - GET
/countriesPaíses com números em estoque. - GET
/servicesServiços, opcionalmente só os disponíveis em um país. - GET
/pricesSeus preços de desenvolvedor e o estoque por oferta. - POST
/activationsComprar um número. - GET
/activationsListar suas ativações. - GET
/activations/{id}Uma ativação, com long polling opcional. - POST
/activations/{id}/cancelCancelar antes de chegar um código e receber o reembolso. - POST
/activations/{id}/finishConcluir uma ativação depois do código. - GET
/usageRequisições, compras e gastos ao longo do tempo.
GET
/me
A chave que faz a chamada, seu preço e seus limites. permissão: read
curl -s https://smsgrab.com/api/dev/v1/me \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{
"key": { "id": "0b8e2a41-7c1d-4e5f-9a3b-6c7d8e9f0a1b", "name": "production bot", "display_prefix": "sgk_4hT9wQ", "last4": "Xa7Q",
"scopes": ["read", "purchase"], "ip_allowlist": ["203.0.113.0/24"], "daily_spend_cap_minor": 5000,
"rate_limit_per_minute": 600, "purchase_limit_per_minute": 60, "expires_at": null },
"account": { "user_id": "2b4b9c1d-3e5f-4a6b-8c7d-9e0f1a2b3c4d", "email": "dev@example.org" },
"currency": "USD",
"pricing": { "markup_percent": 70, "source": "tier", "min_price_minor": 10, "cap_at_retail": true,
"trailing_30d_spend_minor": 18250,
"tier": { "min_spend_minor": 10000, "markup_percent": 70 },
"next_tier": { "min_spend_minor": 50000, "markup_percent": 60, "remaining_minor": 31750 } },
"limits": { "max_active_activations": 100, "active_activations": 3, "spend_today_minor": 420 }
}
GET
/balance
O saldo da sua conta. permissão: read
curl -s https://smsgrab.com/api/dev/v1/balance \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "balance_minor": 12345, "currency": "USD", "updated_at": "2026-09-27T10:02:11.482Z" }
GET
/countries
Países com números em estoque. permissão: read
curl -s https://smsgrab.com/api/dev/v1/countries \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "items": [ { "id": "indonesia", "name": "Indonesia", "iso_code": "ID", "dial_code": "62", "flag_emoji": "🇮🇩",
"available_numbers": 812334, "service_count": 402, "compat_id": 6 } ] }
GET
/services
Serviços, opcionalmente só os disponíveis em um país. permissão: read
| Parameter | In | Type | Description |
|---|---|---|---|
country | query | string | Only services in stock in this country. With it, min_price_minor is your price there and country_count is omitted. |
curl -s "https://smsgrab.com/api/dev/v1/services?country=indonesia" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "items": [ { "id": "whatsapp", "name": "WhatsApp", "category": "MESSAGING",
"icon_url": "https://smsgrab.com/api/v1/icons/whatsapp?v=1fceb4feb3", "compat_code": "wa",
"min_price_minor": 17 } ] }
GET
/prices
Seus preços de desenvolvedor e o estoque por oferta. permissão: read
| Parameter | In | Type | Description |
|---|---|---|---|
service | query | string | A service id. Leave it out for every service. |
country | query | string | A country id. Leave it out for every country. |
curl -s --compressed "https://smsgrab.com/api/dev/v1/prices?service=whatsapp&country=indonesia" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "currency": "USD", "markup_percent": 70,
"items": [ { "service_id": "whatsapp", "country_id": "indonesia", "price_minor": 17, "retail_price_minor": 25,
"available_numbers": 30412 } ] }
POST
/activations
Comprar um número. permissão: purchase
| Parameter | In | Type | Description |
|---|---|---|---|
service required | body | string | A service id (whatsapp) or its compatible code (wa). |
country required | body | string | A country id (indonesia), an ISO code (ID) or a compatible id (6). |
max_price_minor | body | integer | The most you want to pay, in cents. A higher price answers 409 PRICE_CHANGED and nothing is charged. |
Idempotency-Key | header | string | Any unique string up to 64 characters. Repeating it replays the first success instead of buying twice. |
curl -s -X POST https://smsgrab.com/api/dev/v1/activations \
-H "Authorization: Bearer $SMSGRAB_API_KEY" \
-H "Idempotency-Key: order-7f3c2a" \
-H "Content-Type: application/json" \
-d '{"service":"whatsapp","country":"indonesia","max_price_minor":20}'
{ "id": "5c1d0c2e-9f3a-4b7e-8a61-2f0c9d4e7b10", "compat_id": 100000123, "status": "WAITING_SMS",
"service_id": "whatsapp", "service_name": "WhatsApp", "country_id": "indonesia", "country_name": "Indonesia",
"country_iso_code": "ID", "phone_number": "+6281234567890", "price_minor": 17, "currency": "USD",
"created_at": "2026-09-27T10:05:00.000Z", "expires_at": "2026-09-27T10:25:00.000Z", "finished_at": null,
"refunded": false, "refund_minor": 0, "sms": [], "api_key_id": "0b8e2a41-7c1d-4e5f-9a3b-6c7d8e9f0a1b" }
GET
/activations
Listar suas ativações. permissão: read
| Parameter | In | Type | Description |
|---|---|---|---|
status | query | string | active (waiting or code received) or finished. |
service | query | string | Only this service id. |
country | query | string | Only this country id. |
from, to | query | date-time | Only activations bought in this time window (ISO 8601). |
source | query | string | api (default, bought through the developer API or the compatible endpoint) or all (the whole account). |
limit | query | integer | 1 to 100, default 50. |
cursor | query | string | next_cursor of the previous page. |
curl -s "https://smsgrab.com/api/dev/v1/activations?status=active&limit=20" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "items": [ { "id": "5c1d0c2e-9f3a-4b7e-8a61-2f0c9d4e7b10", "status": "CODE_RECEIVED", "phone_number": "+6281234567890" } ], "next_cursor": "MTc5NDI1" }
GET
/activations/{id}
Uma ativação, com long polling opcional. permissão: read
| Parameter | In | Type | Description |
|---|---|---|---|
id required | path | string | The activation UUID or its numeric compat_id. |
wait | query | integer | 0 to 30 seconds. The request is held until the activation changes (a new SMS or status) or the time runs out. At most 20 held requests per key. |
curl -s "https://smsgrab.com/api/dev/v1/activations/100000123?wait=25" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "id": "5c1d0c2e-9f3a-4b7e-8a61-2f0c9d4e7b10", "compat_id": 100000123, "status": "CODE_RECEIVED",
"phone_number": "+6281234567890", "price_minor": 17, "currency": "USD",
"sms": [ { "code": "482913", "text": "Your WhatsApp code is 482-913", "sender": "WhatsApp",
"received_at": "2026-09-27T10:06:11.870Z" } ] }
POST
/activations/{id}/cancel
Cancelar antes de chegar um código e receber o reembolso. permissão: purchase
| Parameter | In | Type | Description |
|---|---|---|---|
id required | path | string | The activation UUID or compat_id. Accepted only while waiting for the first SMS; the charge is refunded. |
curl -s -X POST https://smsgrab.com/api/dev/v1/activations/100000123/cancel \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "id": "5c1d0c2e-9f3a-4b7e-8a61-2f0c9d4e7b10", "status": "CANCELLED", "refunded": true, "refund_minor": 17 }
POST
/activations/{id}/finish
Concluir uma ativação depois do código. permissão: purchase
| Parameter | In | Type | Description |
|---|---|---|---|
id required | path | string | The activation UUID or compat_id. Accepted once a code has arrived. |
curl -s -X POST https://smsgrab.com/api/dev/v1/activations/100000123/finish \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "id": "5c1d0c2e-9f3a-4b7e-8a61-2f0c9d4e7b10", "status": "COMPLETED", "finished_at": "2026-09-27T10:07:02.113Z" }
GET
/usage
Requisições, compras e gastos ao longo do tempo. permissão: read
| Parameter | In | Type | Description |
|---|---|---|---|
from, to | query | date | YYYY-MM-DD, at most 366 days apart. Default: the last 30 days. |
granularity | query | string | day (default) or month. |
key | query | string | current (default, the calling key) or all (every key of the account). |
curl -s "https://smsgrab.com/api/dev/v1/usage?granularity=day&key=all" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "currency": "USD", "from": "2026-09-01", "to": "2026-09-27", "granularity": "day",
"totals": { "requests": 48211, "errors": 120, "rate_limited": 3, "purchases": 1804, "spend_minor": 31211,
"refunds_minor": 2410, "net_minor": 28801 },
"series": [ { "date": "2026-09-27", "requests": 2103, "errors": 4, "rate_limited": 0, "purchases": 88,
"spend_minor": 1496, "refunds_minor": 102, "net_minor": 1394 } ],
"by_key": [ { "key_id": "0b8e2a41-7c1d-4e5f-9a3b-6c7d8e9f0a1b", "name": "production bot", "requests": 48211, "net_minor": 28801 } ] }
Ciclo de vida de uma ativação
Uma ativação passa por alguns estados, da compra até o código.
WAITING_SMSCODE_RECEIVEDCOMPLETED
WAITING_SMS: the number is ready and waiting for the first message. It can be cancelled (refunded).CODE_RECEIVED: at least one SMS arrived;smsholds every message withcode,text,senderandreceived_at. More messages may follow.COMPLETED: you finished it, or the code window ended after a code.CANCELLEDandEXPIRED: no code arrived. The charge is refunded andrefunded/refund_minorare set.
A number lives 20 minutes (expires_at). Cancel is accepted only while waiting (422 ACTIVATION_CODE_RECEIVED after a code; 422 ACTIVATION_CANCEL_NOT_YET when an early cancel is refused, retry after a minute). Finish is accepted once a code has arrived.
Long polling. GET /activations/{id}?wait=25 holds the request until the activation changes or the time runs out, then answers the current object. It replaces tight polling loops and costs one request per change.
Webhooks
Os webhooks enviam os eventos de ativação ao seu servidor no momento em que acontecem.
| Event | data |
|---|---|
activation.created | activation |
activation.code_received | activation (with every SMS so far) |
activation.completed | activation |
activation.cancelled | activation |
activation.expired | activation |
activation.refunded | activation |
balance.low | { balance_minor, currency, threshold_minor } |
webhook.test | { message } |
POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: SmsGrab-Webhooks/1.0
SmsGrab-Event: activation.code_received
SmsGrab-Delivery: 9f7d6c1e-2b4a-4f0e-9c3d-1a2b3c4d5e6f
SmsGrab-Signature: t=1790503200,v1=3b9a0c5e7d2f41a8b6c9e0d1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5
{
"id": "evt_4e1f9a0b2c3d",
"type": "activation.code_received",
"created_at": "2026-09-27T10:06:12.004Z",
"api_version": "2026-09-27",
"data": {
"activation": {
"id": "5c1d0c2e-9f3a-4b7e-8a61-2f0c9d4e7b10",
"status": "CODE_RECEIVED",
"sms": [{ "code": "482913", "text": "Your WhatsApp code is 482-913", "sender": "WhatsApp", "received_at": "2026-09-27T10:06:11.870Z" }]
}
}
}
- Success is any 2xx answer within 10 seconds. Redirects are never followed.
- Retries after 1 minute, 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours, 12 hours and 24 hours (±10 % jitter), then the delivery is marked
dead. A retry repeats the same eventid, so de-duplicate by it. - Order: events of one activation are delivered in order.
- 410 Gone switches the webhook off at once. After 50 failed attempts spanning at least 24 hours it is switched off and you get a notification.
- URLs must be
https://on port 443 or 1024–65535, use a host name, and resolve only to public addresses. Private, loopback, link-local and reserved ranges are refused. - Deliveries are kept 30 days. Send a test event, see every attempt and redeliver from your account.
Verificar assinaturas
Verifique cada entrega antes de confiar nela.
SmsGrab-Signature: t=<unix seconds>,v1=<hex>. v1 is the lowercase hex HMAC-SHA256 of "<t>.<raw body>" with your webhook secret (whsec_…, shown once). Verify against the raw bytes before parsing JSON, reject a t more than 300 seconds from your clock, and compare in constant time.
PHP
<?php
function verifySmsGrabSignature(string $header, string $body, string $secret): bool
{
$parts = [];
foreach (explode(',', $header) as $part) {
[$name, $value] = array_pad(explode('=', $part, 2), 2, '');
$parts[trim($name)] = trim($value);
}
$timestamp = (int) ($parts['t'] ?? 0);
$expected = hash_hmac('sha256', $timestamp.'.'.$body, $secret);
return abs(time() - $timestamp) <= 300 && hash_equals($expected, $parts['v1'] ?? '');
}
$body = file_get_contents('php://input');
if (! verifySmsGrabSignature($_SERVER['HTTP_SMSGRAB_SIGNATURE'] ?? '', $body, getenv('SMSGRAB_WEBHOOK_SECRET'))) {
http_response_code(400);
exit;
}
$event = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
http_response_code(200);
Python
import hashlib
import hmac
import time
def verify(header: str, body: bytes, secret: str) -> bool:
parts = dict(part.split("=", 1) for part in header.split(",") if "=" in part)
timestamp = int(parts.get("t", "0"))
expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
return abs(time.time() - timestamp) <= 300 and hmac.compare_digest(expected, parts.get("v1", ""))
Node.js
import crypto from 'node:crypto';
export function verify(header, rawBody, secret) {
const parts = Object.fromEntries(header.split(',').map((part) => part.split('=', 2)));
const timestamp = Number(parts.t ?? 0);
const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest('hex');
const given = Buffer.from(parts.v1 ?? '', 'utf8');
return Math.abs(Date.now() / 1000 - timestamp) <= 300
&& given.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(expected, 'utf8'), given);
}
Go
package webhooks
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
"time"
)
func Verify(header string, body []byte, secret string) bool {
var timestamp int64
var signature string
for _, part := range strings.Split(header, ",") {
name, value, found := strings.Cut(strings.TrimSpace(part), "=")
if !found {
continue
}
switch name {
case "t":
timestamp, _ = strconv.ParseInt(value, 10, 64)
case "v1":
signature = value
}
}
age := time.Now().Unix() - timestamp
if age > 300 || age < -300 {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(strconv.FormatInt(timestamp, 10) + "."))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
Compatibilidade com sms-activate
Ferramentas escritas para o protocolo do sms-activate funcionam trocando um único endereço.
https://smsgrab.com/api/stubs/handler_api.php
GET or POST (form encoded) with api_key, action and the action's parameters. The key may also be sent as Authorization: Bearer. Answers are plain text (or JSON where shown) with HTTP 200 for every protocol answer, exactly like the original protocol; rate limiting answers HTTP 429 with TOO_MANY_REQUESTS. Amounts are decimal USD with two decimals (your developer price). A wrong, revoked or expired key answers BAD_KEY; an unknown action answers BAD_ACTION.
| action | Parameters | Success | Errors |
|---|---|---|---|
getBalance | – | ACCESS_BALANCE:12.34 | BAD_KEY |
getNumber | service, country, maxPrice? | ACCESS_NUMBER:100000123:6281234567890 | BAD_SERVICE, BAD_COUNTRY, NO_NUMBERS, NO_BALANCE, WRONG_MAX_PRICE:0.17, CHANNELS_LIMIT, BANNED:…, NO_ACCESS |
getNumberV2 | service, country, maxPrice? | {"activationId":"100000123","phoneNumber":"6281234567890","activationCost":"0.17",…} | same as getNumber |
setStatus | id, status (1, 3, 6, 8) | ACCESS_READY, ACCESS_RETRY_GET, ACCESS_ACTIVATION, ACCESS_CANCEL | NO_ACTIVATION, BAD_STATUS, EARLY_CANCEL_DENIED |
getStatus | id | STATUS_WAIT_CODE, STATUS_WAIT_RETRY:…, STATUS_OK:482913, STATUS_CANCEL | NO_ACTIVATION |
getStatusV2 | id | {"verificationType":0,"sms":{"code":"482913",…},"call":null} | NO_ACTIVATION |
getActiveActivations | – | {"status":"success","activeActivations":[…]} | {"status":"error","error":"NO_ACTIVATIONS"} |
getPrices | service?, country? | {"6":{"wa":{"cost":0.17,"count":30412}}} | BAD_SERVICE, BAD_COUNTRY |
getCountries | – | {"6":{"id":6,"eng":"Indonesia",…}} | – |
getServicesList | country? | {"status":"success","services":[{"code":"wa","name":"WhatsApp"}]} | – |
getNumbersStatus | country? | {"wa_0":"30412","tg_0":"1822"} | BAD_COUNTRY |
getTopCountriesByService | service | {"0":{"country":6,"count":30412,"price":0.17}} | BAD_SERVICE |
setStatus: 1 ready, 3 wait for another SMS, 6 finish, 8 cancel and refund. getActiveActivations reports 4 while waiting and 1 after a code. Times are UTC, yyyy-MM-dd HH:mm:ss. The activation id is the numeric compat_id, which the REST API accepts too.
Service codes
waWhatsApptgTelegramviViberigInstagramfbFacebookgoGoogletwX / TwitterdsDiscordlfTikTokamAmazonmmMicrosoftmbYahoooiTinderubUbertsPayPalnfNetflixwbWeChatvkVKontakteokOdnoklassnikidheBaykaShopeefuSnapchatmtSteambwSignaldrOpenAImeLINEktKakaoTalkotAny other
Country ids
0Russia6Indonesia4Philippines10Vietnam16United Kingdom22India36Canada43Germany73Brazil187USA
Our catalogue ids and ISO country codes are accepted as well, so every product is reachable. getCountries and getServicesList are the authoritative lists. Keys, scopes, allowlists, spend caps, limits, prices, refunds, webhooks and metering are the same as for the REST API.
curl
BASE="https://smsgrab.com/api/stubs/handler_api.php?api_key=$SMSGRAB_API_KEY"
curl -s "$BASE&action=getBalance"
curl -s "$BASE&action=getNumber&service=tg&country=6"
curl -s "$BASE&action=getStatus&id=100000123"
curl -s "$BASE&action=setStatus&id=100000123&status=6"
Python
import os
import requests
BASE = "https://smsgrab.com/api/stubs/handler_api.php"
KEY = os.environ["SMSGRAB_API_KEY"]
def action(name: str, **params) -> str:
response = requests.get(BASE, params={"api_key": KEY, "action": name, **params}, timeout=30)
response.raise_for_status()
return response.text
print(action("getBalance"))
answer = action("getNumber", service="tg", country=6)
if answer.startswith("ACCESS_NUMBER:"):
_, activation_id, number = answer.split(":")
print(activation_id, number)
Exemplos completos
Compre um número, aguarde o código e conclua a ativação, em cinco linguagens.
curl
export SMSGRAB_API_KEY="sgk_your_key"
curl -s https://smsgrab.com/api/dev/v1/balance \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
curl -s -X POST https://smsgrab.com/api/dev/v1/activations \
-H "Authorization: Bearer $SMSGRAB_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"service":"telegram","country":"indonesia","max_price_minor":30}'
curl -s "https://smsgrab.com/api/dev/v1/activations/100000123?wait=25" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
curl -s -X POST https://smsgrab.com/api/dev/v1/activations/100000123/finish \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
Python
import os
import uuid
import requests
API = "https://smsgrab.com/api/dev/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SMSGRAB_API_KEY']}"
def buy(service: str, country: str, max_price_minor: int) -> dict:
response = session.post(
f"{API}/activations",
json={"service": service, "country": country, "max_price_minor": max_price_minor},
headers={"Idempotency-Key": str(uuid.uuid4())},
timeout=30,
)
response.raise_for_status()
return response.json()
def wait_for_code(activation_id: str) -> str | None:
while True:
response = session.get(f"{API}/activations/{activation_id}", params={"wait": 25}, timeout=40)
response.raise_for_status()
activation = response.json()
if activation["sms"]:
return activation["sms"][-1]["code"]
if activation["status"] != "WAITING_SMS":
return None
activation = buy("telegram", "indonesia", 30)
print("Number:", activation["phone_number"])
code = wait_for_code(activation["id"])
if code:
print("Code:", code)
session.post(f"{API}/activations/{activation['id']}/finish", timeout=30).raise_for_status()
else:
print("No code arrived, the charge was refunded")
Node.js
import { randomUUID } from 'node:crypto';
const API = 'https://smsgrab.com/api/dev/v1';
async function call(path, { method = 'GET', body, idempotencyKey } = {}) {
const response = await fetch(`${API}${path}`, {
method,
headers: {
Authorization: `Bearer ${process.env.SMSGRAB_API_KEY}`,
...(body ? { 'Content-Type': 'application/json' } : {}),
...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const data = response.status === 204 ? null : await response.json();
if (!response.ok) {
throw new Error(`${data?.code ?? response.status}: ${data?.message ?? ''}`);
}
return data;
}
let activation = await call('/activations', {
method: 'POST',
body: { service: 'telegram', country: 'indonesia', max_price_minor: 30 },
idempotencyKey: randomUUID(),
});
console.log('Number:', activation.phone_number);
while (activation.sms.length === 0 && activation.status === 'WAITING_SMS') {
activation = await call(`/activations/${activation.id}?wait=25`);
}
if (activation.sms.length > 0) {
console.log('Code:', activation.sms.at(-1).code);
await call(`/activations/${activation.id}/finish`, { method: 'POST' });
} else {
console.log('No code arrived, the charge was refunded');
}
PHP
<?php
require __DIR__.'/vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://smsgrab.com/api/dev/v1/',
'headers' => ['Authorization' => 'Bearer '.getenv('SMSGRAB_API_KEY')],
'timeout' => 40,
]);
$read = static fn ($response): array => json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);
$activation = $read($client->post('activations', [
'json' => ['service' => 'telegram', 'country' => 'indonesia', 'max_price_minor' => 30],
'headers' => ['Idempotency-Key' => bin2hex(random_bytes(16))],
]));
echo 'Number: '.$activation['phone_number'].PHP_EOL;
while ($activation['sms'] === [] && $activation['status'] === 'WAITING_SMS') {
$activation = $read($client->get('activations/'.$activation['id'], ['query' => ['wait' => 25]]));
}
if ($activation['sms'] !== []) {
echo 'Code: '.$activation['sms'][array_key_last($activation['sms'])]['code'].PHP_EOL;
$client->post('activations/'.$activation['id'].'/finish');
} else {
echo 'No code arrived, the charge was refunded'.PHP_EOL;
}
Go
package main
import (
"bytes"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
const api = "https://smsgrab.com/api/dev/v1"
type Activation struct {
ID string `json:"id"`
Status string `json:"status"`
PhoneNumber string `json:"phone_number"`
SMS []struct {
Code string `json:"code"`
} `json:"sms"`
}
var client = &http.Client{Timeout: 40 * time.Second}
func call(method, path string, body any, idempotencyKey string, out any) error {
var payload bytes.Buffer
if body != nil {
if err := json.NewEncoder(&payload).Encode(body); err != nil {
return err
}
}
request, err := http.NewRequest(method, api+path, &payload)
if err != nil {
return err
}
request.Header.Set("Authorization", "Bearer "+os.Getenv("SMSGRAB_API_KEY"))
if body != nil {
request.Header.Set("Content-Type", "application/json")
}
if idempotencyKey != "" {
request.Header.Set("Idempotency-Key", idempotencyKey)
}
response, err := client.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode >= 300 {
var failure struct {
Code string `json:"code"`
Message string `json:"message"`
}
_ = json.NewDecoder(response.Body).Decode(&failure)
return fmt.Errorf("%s: %s", failure.Code, failure.Message)
}
if out == nil {
return nil
}
return json.NewDecoder(response.Body).Decode(out)
}
func idempotencyKey() string {
buffer := make([]byte, 16)
_, _ = rand.Read(buffer)
return hex.EncodeToString(buffer)
}
func main() {
var activation Activation
order := map[string]any{"service": "telegram", "country": "indonesia", "max_price_minor": 30}
if err := call("POST", "/activations", order, idempotencyKey(), &activation); err != nil {
panic(err)
}
fmt.Println("Number:", activation.PhoneNumber)
for len(activation.SMS) == 0 && activation.Status == "WAITING_SMS" {
if err := call("GET", "/activations/"+activation.ID+"?wait=25", nil, "", &activation); err != nil {
panic(err)
}
}
if len(activation.SMS) == 0 {
fmt.Println("No code arrived, the charge was refunded")
return
}
fmt.Println("Code:", activation.SMS[len(activation.SMS)-1].Code)
if err := call("POST", "/activations/"+activation.ID+"/finish", nil, "", nil); err != nil {
panic(err)
}
}
Versões e mudanças
Mantemos a API estável e avisamos o que muda.
- The current version is
2026-09-27; webhook payloads carry it inapi_version. - We add fields, events and error reasons without notice. Ignore fields and reasons you do not know.
- Breaking changes get a new path (
/dev/v2) and a long overlap. - The machine-readable description is part of the OpenAPI document at
https://smsgrab.com/api/v1/openapi.json.