API for developers
Sell temporary numbers in your website or app and receive verification codes through the API.
Explore the reseller program and sell at your own price →
1. What it does and how to get access
The API lets you resell temporary SMS numbers through your own website or app. Your customers do not need to visit Ativa SMS.
Create an account and request access through Resell numbers in the dashboard profile menu. Acceptance of the Reseller terms is required; the Terms of use also apply. Once approved, create keys in Dashboard → API.
2. Base URL and data format
Base URL: https://www.ativasms.store/api/v1. Send UTF-8 JSON with Content-Type: application/json for request bodies. Responses are JSON and include Cache-Control: no-store.
Amounts are integer cents: moeda is BRL or USD depending on your account; 83 BRL cents means R$ 0.83. Test mode always uses BRL. Dates are ISO 8601 UTC. Phone numbers use international +country-code format, or null while a reservation is still in progress.
Examples are illustrative and follow the actual response formats; purchase examples use fictional test data. Use service codes and options returned by the API; live prices vary. The curl examples read ATIVA_API_KEY from an environment variable on your server.
OpenAPI 3.1 specification for tools and AI agents: /openapi.json.
3. Authentication
Send Authorization: Bearer <key> on every endpoint. Keys start with ativa_live_ for production or ativa_test_ for testing. Keys are not accepted in query strings or bodies.
A key is displayed once, when created. Keep it on your server, never in a customer's browser or app. An account can have up to 5 active keys, counting live and test keys together. Revoke and replace lost or exposed keys in the dashboard. If an IP restriction is configured, requests must also satisfy it.
Authentication failures return the same HTTP 401 response without exposing the reason:
{
"erro": "nao_autorizado",
"mensagem": "Chave de API ausente, inválida ou revogada."
}
4. Usage limits
Default limits can be adjusted for each reseller. Check your account's current limits in the dashboard:
| Limit | Default |
|---|---|
| Requests per minute | 120 |
| Purchases/replacements per minute | 20 |
| Numbers waiting for SMS at once | 20 |
| Attempts without a code per account in 24 h | 100 |
| Attempts without a code per cliente_ref in 24 h | 15 |
Request and purchase rates combine all account keys within the same mode. Test and live counts are separate, using the same configured limits. Cancellations, replacements and expiries without SMS consume attempts; each number with a code restores 5 attempts, up to the limit. Delivery failures do not count. Live account limits for open numbers and attempts also cover purchases made on the website.
Exceeding request or purchase rates returns HTTP 429 muitas_requisicoes and Retry-After: 60 (seconds). Other limits return 429 with their specific code and no Retry-After: wait for open numbers or attempts to become available as appropriate.
5. Idempotency and maximum price
Idempotency-Key is required for purchases and replacements: 1–64 letters, digits or . _ : -. Use a new key for each intended operation and the same key for retries. Keys are scoped to account and mode; purchases and replacements share that scope. Do not reuse a key for a different customer or order.
Repeating a recorded key returns HTTP 200 with the same number and its current state, even if the body has changed; a new purchase returns 201. Live lookups do not stop after 24 h: the key refers to that number for as long as its record exists. Test history is retained for 30 days. Resetting the test balance preserves history and idempotency.
preco_maximo_centavos is the highest charge you accept in your account's currency. If the current price exceeds it, the API returns preco_acima_do_maximo with preco_centavos and opcoes, without purchasing. preco_centavos is deducted from your balance; preco_site_centavos is the website reference price. The price is checked again at purchase time.
6. Endpoints and examples
GET /saldo
No parameters. Returns the balance in the account currency (BRL or USD). Test mode always uses BRL and fictional funds.
curl "https://www.ativasms.store/api/v1/saldo" \
-H "Authorization: Bearer $ATIVA_API_KEY"
HTTP 200
{
"saldo_centavos": 10000,
"moeda": "BRL"
}
GET /servicos
No parameters. Returns all available services, without duplicates or blocked services. The example shows one entry; test mode also includes teste_falha (Teste sem código). API field names and service labels stay in Portuguese regardless of the documentation language.
curl "https://www.ativasms.store/api/v1/servicos" \
-H "Authorization: Bearer $ATIVA_API_KEY"
HTTP 200
[
{
"codigo": "wa",
"nome": "WhatsApp"
}
]
GET /paises
No parameters. Returns all countries. Use codigo in queries and purchases; iso is uppercase and ddi is the dialing code. The example shows one entry.
curl "https://www.ativasms.store/api/v1/paises" \
-H "Authorization: Bearer $ATIVA_API_KEY"
HTTP 200
[
{
"codigo": 73,
"iso": "BR",
"nome": "Brasil",
"ddi": "+55"
}
]
GET /opcoes
Required query parameters: servico (a code from the services list) and pais (a numeric country code). Returns up to 6 options in ascending preco_centavos order; the result may be []. opcao is an opaque identifier: op_ followed by 12 hexadecimal characters. Pass back the returned value without interpreting it. preco_centavos is the reseller's charge; preco_site_centavos is the website reference price, both in the stated currency. prazo_min is the time limit; taxa_sucesso may be null; estoque reports availability and recomendada marks a suggested option. This example uses the fictional test option.
curl "https://www.ativasms.store/api/v1/opcoes?servico=wa&pais=73" \
-H "Authorization: Bearer $ATIVA_API_KEY"
HTTP 200
[
{
"opcao": "op_000000000001",
"preco_centavos": 83,
"preco_site_centavos": 120,
"moeda": "BRL",
"prazo_min": 20,
"taxa_sucesso": null,
"estoque": 100,
"recomendada": false
}
]
POST /numeros
Required JSON fields: servico (1–20 lowercase letters, digits or _; an allowed service), pais (a valid integer country code), preco_maximo_centavos (a positive integer) and cliente_ref (an anonymous identifier, 1–64 letters, digits or . _ : -; never an email address or personal identifier). opcao is optional and may be null; omitting it selects the cheapest option. Send Idempotency-Key. A new purchase returns 201; a repeat returns 200 with the same id and current state, without another charge. This is a test purchase example.
curl -X POST "https://www.ativasms.store/api/v1/numeros" \
-H "Authorization: Bearer $ATIVA_API_KEY" \
-H "Idempotency-Key: compra_abc123" \
-H "Content-Type: application/json" \
-d '{"servico":"wa","pais":73,"opcao":"op_000000000001","preco_maximo_centavos":83,"cliente_ref":"cliente_abc123"}'
HTTP 201
{
"id": "5f0c0000-0000-4000-8000-000000000001",
"servico": "wa",
"servico_nome": "WhatsApp",
"pais": 73,
"telefone": "+12025550123",
"status": "aguardando_sms",
"motivo": null,
"preco_centavos": 83,
"preco_site_centavos": 120,
"moeda": "BRL",
"criado_em": "2026-10-08T12:00:00.000Z",
"cancelavel_em": "2026-10-08T12:02:00.000Z",
"expira_em": "2026-10-08T12:20:00.000Z",
"encerrado_em": null,
"substitui_id": null,
"cliente_ref": "cliente_abc123",
"sms": []
}
GET /numeros/{id}
Path: /numeros/{id}, where id is the number's UUID. Only API purchases belonging to this account and mode are visible; another owner's number, a website purchase or an invalid UUID returns nao_encontrado. Open numbers are refreshed before responding, up to 60 refreshes/min per live key; further requests return the latest available state (200, without an additional synchronization 429). This example shows a received test code.
curl "https://www.ativasms.store/api/v1/numeros/5f0c0000-0000-4000-8000-000000000001" \
-H "Authorization: Bearer $ATIVA_API_KEY"
HTTP 200
{
"id": "5f0c0000-0000-4000-8000-000000000001",
"servico": "wa",
"servico_nome": "WhatsApp",
"pais": 73,
"telefone": "+12025550123",
"status": "recebido",
"motivo": null,
"preco_centavos": 83,
"preco_site_centavos": 120,
"moeda": "BRL",
"criado_em": "2026-10-08T12:00:00.000Z",
"cancelavel_em": "2026-10-08T12:02:00.000Z",
"expira_em": "2026-10-08T12:20:00.000Z",
"encerrado_em": null,
"substitui_id": null,
"cliente_ref": "cliente_abc123",
"sms": [
{
"codigo": "123456",
"texto": "Seu código de teste: 123456",
"recebido_em": "2026-10-08T12:00:20.000Z"
}
]
}
GET /numeros
Optional query parameters: status (a documented number state), cliente_ref (same format as on purchase), pagina (1–1000, default 1). Returns up to 50 API purchases per page for this account and mode, newest first. tem_mais indicates another page. Listing live numbers does not force a refresh; query an individual number to refresh it.
curl "https://www.ativasms.store/api/v1/numeros?status=aguardando_sms&cliente_ref=cliente_abc123&pagina=1" \
-H "Authorization: Bearer $ATIVA_API_KEY"
HTTP 200
{
"numeros": [
{
"id": "5f0c0000-0000-4000-8000-000000000001",
"servico": "wa",
"servico_nome": "WhatsApp",
"pais": 73,
"telefone": "+12025550123",
"status": "aguardando_sms",
"motivo": null,
"preco_centavos": 83,
"preco_site_centavos": 120,
"moeda": "BRL",
"criado_em": "2026-10-08T12:00:00.000Z",
"cancelavel_em": "2026-10-08T12:02:00.000Z",
"expira_em": "2026-10-08T12:20:00.000Z",
"encerrado_em": null,
"substitui_id": null,
"cliente_ref": "cliente_abc123",
"sms": []
}
],
"pagina": 1,
"tem_mais": false
}
POST /numeros/{id}/cancelar
Path: /numeros/{id}/cancelar. Optional body field: motivo (desistiu, banido, ja_tem_dono, pede_outra_coisa or outro; default outro). With no code and after cancelavel_em, closes the number and refunds the balance. Before that time: 409 cedo with segundos. An already received number returns nao_cancelavel; codigo_recebido can occur if a code arrives during cancellation. Returns 200 with the closed number. This example uses teste_falha after two minutes.
curl -X POST "https://www.ativasms.store/api/v1/numeros/5f0c0000-0000-4000-8000-000000000001/cancelar" \
-H "Authorization: Bearer $ATIVA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"motivo":"desistiu"}'
HTTP 200
{
"id": "5f0c0000-0000-4000-8000-000000000001",
"servico": "teste_falha",
"servico_nome": "Teste sem código",
"pais": 73,
"telefone": "+12025550123",
"status": "cancelado",
"motivo": "desistiu",
"preco_centavos": 83,
"preco_site_centavos": 120,
"moeda": "BRL",
"criado_em": "2026-10-08T12:00:00.000Z",
"cancelavel_em": "2026-10-08T12:02:00.000Z",
"expira_em": "2026-10-08T12:20:00.000Z",
"encerrado_em": "2026-10-08T12:02:01.000Z",
"substitui_id": null,
"cliente_ref": "cliente_abc123",
"sms": []
}
POST /numeros/{id}/trocar
Path: /numeros/{id}/trocar. Required body field: preco_maximo_centavos (positive integer); opcao and motivo are optional, using the formats above. Service, country and cliente_ref are inherited from the original number. Requires Idempotency-Key and an aguardando_sms number with a phone, no code and an elapsed cancellation wait. The price is checked before closing the old number. The old number becomes trocado and is refunded; a new purchase returns 201 (a repeat returns 200). If the new purchase fails after the refund, the old number remains trocado: query it and handle the error. This example replaces a teste_falha number after two minutes.
curl -X POST "https://www.ativasms.store/api/v1/numeros/5f0c0000-0000-4000-8000-000000000001/trocar" \
-H "Authorization: Bearer $ATIVA_API_KEY" \
-H "Idempotency-Key: troca_abc123" \
-H "Content-Type: application/json" \
-d '{"preco_maximo_centavos":83,"motivo":"banido"}'
HTTP 201
{
"id": "5f0c0000-0000-4000-8000-000000000002",
"servico": "teste_falha",
"servico_nome": "Teste sem código",
"pais": 73,
"telefone": "+12025550124",
"status": "aguardando_sms",
"motivo": null,
"preco_centavos": 83,
"preco_site_centavos": 120,
"moeda": "BRL",
"criado_em": "2026-10-08T12:02:01.000Z",
"cancelavel_em": "2026-10-08T12:04:01.000Z",
"expira_em": "2026-10-08T12:22:01.000Z",
"encerrado_em": null,
"substitui_id": "5f0c0000-0000-4000-8000-000000000001",
"cliente_ref": "cliente_abc123",
"sms": []
}
POST /numeros/{id}/concluir
Path: /numeros/{id}/concluir. No body. Confirms the code has been used and closes a recebido number without a refund. Returns 200 with the concluido number; repeating completion or completing without a code returns 409 nao_concluivel.
curl -X POST "https://www.ativasms.store/api/v1/numeros/5f0c0000-0000-4000-8000-000000000001/concluir" \
-H "Authorization: Bearer $ATIVA_API_KEY"
HTTP 200
{
"id": "5f0c0000-0000-4000-8000-000000000001",
"servico": "wa",
"servico_nome": "WhatsApp",
"pais": 73,
"telefone": "+12025550123",
"status": "concluido",
"motivo": null,
"preco_centavos": 83,
"preco_site_centavos": 120,
"moeda": "BRL",
"criado_em": "2026-10-08T12:00:00.000Z",
"cancelavel_em": "2026-10-08T12:02:00.000Z",
"expira_em": "2026-10-08T12:20:00.000Z",
"encerrado_em": "2026-10-08T12:01:00.000Z",
"substitui_id": null,
"cliente_ref": "cliente_abc123",
"sms": [
{
"codigo": "123456",
"texto": "Seu código de teste: 123456",
"recebido_em": "2026-10-08T12:00:20.000Z"
}
]
}
GET /extrato
Optional query parameter: pagina (1–1000, default 1). Returns up to 50 account transactions, newest first (purchase, refund, top-up, adjustment), including website transactions in live mode. valor_centavos is signed: negative for debits, positive for credits; saldo_apos_centavos is the balance afterward. numero_id is null when the transaction is unrelated to a number; descricao may be null. The statement does not include moeda: use the currency from GET /saldo. Test statements are separate and always use BRL.
curl "https://www.ativasms.store/api/v1/extrato?pagina=1" \
-H "Authorization: Bearer $ATIVA_API_KEY"
HTTP 200
{
"movimentacoes": [
{
"id": 42,
"tipo": "compra",
"valor_centavos": -83,
"saldo_apos_centavos": 9917,
"descricao": "Número WhatsApp",
"numero_id": "5f0c0000-0000-4000-8000-000000000001",
"criado_em": "2026-10-08T12:00:00.000Z"
}
],
"pagina": 1,
"tem_mais": false
}
7. Number lifecycle
After purchase, show the phone number to your customer and ask them to request the code in the destination app. Buying a number does not automatically request an SMS. Test mode simulates delivery.
| Status | Meaning |
|---|---|
| aguardando_sms | Waiting; a null phone may mean the reservation is still in progress. |
| recebido | At least one SMS arrived; cancellation and replacement are unavailable. |
| concluido | Closed after receiving a code, manually or as the time limit ends. |
| cancelado | Cancelled without a code and refunded. |
| trocado | The previous number was closed and refunded during replacement. |
| expirado | Time limit ended without a code; automatic refund. |
| falhou | Purchase/delivery failed; funds returned. |
Number objects include servico_nome, motivo, moeda, preco_site_centavos (may be null), encerrado_em, substitui_id (the previous number's id on replacement) and sms. Cancellation, expiry and closure dates may be null. Each SMS includes codigo and texto (either may be null) and recebido_em.
Respect cancelavel_em: some options allow immediate cancellation; others require 2 min. A cedo error specifies the remaining wait:
{
"erro": "cedo",
"mensagem": "Este número ainda não pode ser cancelado. Tente de novo após `segundos`.",
"segundos": 45
}
Without an SMS by the time limit, funds return automatically to the balance. Live numbers may have a 5 min grace period for late SMS before closure. Follow expira_em and the returned state. Completing a number after a code arrives closes it without a refund; received numbers cannot be cancelled or replaced. During replacement, failure to buy the new number may leave the old one trocado and refunded.
8. Errors
Errors include erro and mensagem (in Portuguese). Handle the code rather than matching the message. Extra fields may include campos, segundos, preco_centavos and opcoes.
| Code | HTTP | When it happens |
|---|---|---|
nao_autorizado |
401 | Missing, invalid or revoked key, disallowed IP or suspended access; the same generic response hides the cause. |
parametro_invalido |
400 | Invalid field, query or body; may include campos listing fields to correct. |
servico_bloqueado |
400 | A blocked service was requested. |
idempotencia_obrigatoria |
400 | Missing or malformed Idempotency-Key on purchase or replacement. |
saldo_insuficiente |
402 | Insufficient funds for the purchase. |
cliente_bloqueado |
403 | cliente_ref is blocked for this account. |
nao_encontrado |
404 | Invalid UUID or a number unavailable to this account and mode. |
sem_numeros |
409 | No numbers available for that service and country; no charge. |
opcao_indisponivel |
409 | The selected option is no longer available; includes current opcoes. |
preco_acima_do_maximo |
409 | Current price exceeds the maximum; includes preco_centavos and opcoes, without a charge. |
preco_subiu |
409 | Price changed during purchase; the amount was refunded. |
cedo |
409 | Too early to cancel or replace; includes the wait in segundos. |
codigo_recebido |
409 | A received code prevents replacement, or arrived during cancellation. |
nao_cancelavel |
409 | The state does not allow cancellation, including an already received number. |
nao_trocavel |
409 | No phone number, a closed number or a state incompatible with replacement. |
nao_concluivel |
409 | The number is not recebido, including a second completion request. |
muitas_requisicoes |
429 | Request or purchase rate exceeded; Retry-After: 60. |
limite_abertas |
429 | Too many numbers waiting for SMS; no Retry-After. |
limite_tentativas |
429 | Account attempts without a code exhausted within 24 h; no Retry-After. |
limite_tentativas_cliente |
429 | Attempts without a code exhausted for this cliente_ref within 24 h; no Retry-After. |
erro_fornecedor |
502 | Number delivery failed; the amount was refunded. |
sem_confirmacao |
502 | Purchase unconfirmed; retry with the same Idempotency-Key. If no number is delivered within 10 min, the amount is refunded. |
erro_interno |
500 | Unexpected failure; retry purchases and replacements with the same idempotency key. |
9. Webhooks and signatures
Configure a public HTTPS URL in Dashboard → API. Internal addresses, URL credentials and fragments are rejected; redirects are not followed. The secret is displayed once when created or rotated: keep it on your server. Changing only the URL preserves the secret; removing the webhook deletes its configuration. Every delivery attempt uses the current URL and secret.
Events are sent as JSON POST requests:
numero.codigo_recebido: one notification per new SMS;dados.smsis an object with codigo, texto and recebido_em, rather than an SMS array. A number can receive multiple notifications, including late SMS after closure.numero.encerrado: concluido, cancelado, trocado, expirado or falhou; includes motivo (may be null), encerrado_em and estornado_centavos (zero without a refund), in the stated currency.teste.ping: a delivery test requested in the dashboard, without a number or charge.
The envelope contains id (the notification UUID, different from dados.id), evento, criado_em and dados. Number events include id, cliente_ref, servico, pais, telefone (or null), status and moeda in dados. Events for test numbers add modo: "test" to the envelope; live events and the dashboard ping omit that field. Examples:
{
"id": "a7100000-0000-4000-8000-000000000001",
"evento": "numero.codigo_recebido",
"criado_em": "2026-10-08T12:00:20.000Z",
"modo": "test",
"dados": {
"id": "5f0c0000-0000-4000-8000-000000000001",
"cliente_ref": "cliente_abc123",
"servico": "wa",
"pais": 73,
"telefone": "+12025550123",
"status": "recebido",
"moeda": "BRL",
"sms": {
"codigo": "123456",
"texto": "Seu código de teste: 123456",
"recebido_em": "2026-10-08T12:00:20.000Z"
}
}
}
{
"id": "a7100000-0000-4000-8000-000000000002",
"evento": "numero.encerrado",
"criado_em": "2026-10-08T12:20:00.000Z",
"modo": "test",
"dados": {
"id": "5f0c0000-0000-4000-8000-000000000001",
"cliente_ref": "cliente_abc123",
"servico": "teste_falha",
"pais": 73,
"telefone": "+12025550123",
"status": "expirado",
"moeda": "BRL",
"motivo": "prazo",
"encerrado_em": "2026-10-08T12:20:00.000Z",
"estornado_centavos": 83
}
}
{
"id": "a7100000-0000-4000-8000-000000000003",
"evento": "teste.ping",
"criado_em": "2026-10-08T12:00:00.000Z",
"dados": {
"mensagem": "Teste de webhook do Ativa SMS"
}
}
Headers:
X-Ativa-Evento: event name.X-Ativa-Timestamp: Unix timestamp in seconds, refreshed on each attempt.X-Ativa-Assinatura: lowercase hexadecimal HMAC-SHA256 over the UTF-8 bytes oftimestamp + "." + body, using the secret as text, without hex-decoding it.
Verify the raw body before parsing JSON; reserializing can change the signature. Reject timestamps more than 5 minutes away from the current time, in either direction. Compare with crypto.timingSafeEqual after checking lengths. This Node.js example uses Request/Headers and a Buffer containing the original body:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verificarWebhook(headers, corpoBruto, segredo) {
const timestamp = headers.get("x-ativa-timestamp") ?? "";
const assinatura = headers.get("x-ativa-assinatura") ?? "";
if (!/^\d{1,12}$/.test(timestamp)) return false;
const agora = Math.floor(Date.now() / 1000);
if (Math.abs(agora - Number(timestamp)) > 300) return false;
if (!/^[0-9a-f]{64}$/.test(assinatura)) return false;
const esperada = createHmac("sha256", segredo)
.update(timestamp + ".", "utf8")
.update(corpoBruto)
.digest();
const recebida = Buffer.from(assinatura, "hex");
return recebida.length === esperada.length
&& timingSafeEqual(recebida, esperada);
}
export async function lerAviso(request, segredo) {
const corpoBruto = Buffer.from(await request.arrayBuffer());
if (!verificarWebhook(request.headers, corpoBruto, segredo)) {
throw new Error("assinatura_invalida");
}
return JSON.parse(corpoBruto.toString("utf8"));
}
After verification, deduplicate by the notification's id in persistent storage and record receipt before processing asynchronously. Return 2xx promptly, including for duplicates already accepted. The delivery timeout is 5 s. Exactly-once delivery is not guaranteed: a lost response can cause a repeat.
Up to 8 total attempts. After failures 1–7, wait times are 1, 2, 4, 8, 16, 32 and 64 min, measured from attempt completion; deliveries are checked every minute. Only HTTP 2xx confirms delivery; 3xx, other errors and timeouts trigger retries. After the eighth failure there is no automatic resend. The id and body remain unchanged between attempts; the timestamp and signature are refreshed. Query GET /numeros/{id} if a notification is missing.
10. Test mode
Create an ativa_test_ key in the dashboard and use the same endpoints and base URL. No real funds are charged. The initial fictional balance is R$ 100.00 (10000 BRL cents), regardless of your live account currency. The option is op_000000000001: fictional price R$ 0.83, website reference R$ 1.20, a 20 min lifetime and cancellation after 2 min.
Phones are fictional numbers in the +1 202 555-01xx range, returned without spaces, such as +12025550123; the country you chose remains in pais. Code 123456 becomes available after 20 s; querying the number afterward updates its state, while the webhook may arrive in the next minute.
Use servico=teste_falha to receive no code and test cancellation, replacement (after 2 min) or expiry (after 20 min), with a fictional refund. Other services receive the code before the cancellation wait ends. Test numbers with a code become concluido on expiry.
Reset the balance to R$ 100.00 in the dashboard: open numbers without a code are cancelled; those with a code are completed. Resetting preserves history, attempts, idempotency and keys; it does not reset per-minute limits. Use a new Idempotency-Key for your next purchase. Test history is cleaned up after 30 days.
Live and test keys do not mix: numbers, balances, statements, usage counts and idempotency are separate. Configured limits and cliente_ref blocks apply in both modes. The dashboard ping only tests delivery; buy a number with a test key to exercise modo: "test".
11. Integration tips
- Reuse Idempotency-Key after network failures; check the outcome before choosing a new key.
- Query GET /numeros/{id} if a webhook is missing, and respect usage limits.
- Handle preco_acima_do_maximo: show the updated price and obtain your customer's decision before another purchase.
- Keep your acquisition price (preco_centavos) private; protect your keys and webhook secret too.
- Send only an anonymous cliente_ref; keep the link to your customer in your own system.
Read the Reseller terms. Contact: contato@ativasms.store.