Referência da API

API para desenvolvedores

Uma única API REST para comprar números, ler códigos e gerenciar saldo — mais uma camada de compatibilidade direta para scripts handler_api legados.

URL basev1.4.0OpenAPI 3.1

01

Visão geral

Uma superfície REST, JSON na entrada e na saída, pré-pago. Leia esta seção uma vez e o resto da página é referência — tudo abaixo segue as mesmas convenções.

Base URL
https://virtualsmsnumbers.com/api/v1 — HTTPS only, HTTP is redirected.
Content type
application/json, UTF-8, in both directions. No form encoding on /api/v1.
Money
Integer euro cents, in fields ending _cents. The string forms (price, balance) are for display; do arithmetic on the integers.
Timestamps
ISO 8601 with milliseconds, always UTC: 2026-08-26T12:21:07.412Z.
Identifiers
Opaque strings. Activation and rental ids happen to be ten digits today — store them as text.
Lists
Wrapped in { "object": "list", "data": [ … ], "total": n }, with page and per_page where the endpoint pages.
Errors
Always { "error": { "code", "message" } } with the right HTTP status. Branch on code, never on message.
Versioning
The major version is in the path. Fields are added without notice, never removed or retyped inside a version; every change is on the changelog.

Sua primeira requisição

Crie uma chave no painel, exporte e chame /me. Ela não exige scope, então um 200 prova que a chave existe, não está revogada, não está vencida e está chamando de um endereço permitido.

first-request.sh
# Every call needs a key. Create one in the dashboard, then:
-kw">export VSN_KEY="vsn_live_xxxxxxxxxxxxxxxxxxxxxxxx"

# /me needs no scope, so it is the cheapest way to prove a key works.
-kw">curl https://virtualsmsnumbers.com/api/v1/me \
  -H "Authorization: Bearer $VSN_KEY"

# {"object":"account","email":"[email protected]","balance_cents":4187,
#  "can_purchase":true,"authenticated_with":"api_key"}

02

Autenticação

Envie sua chave como bearer token ou no cabeçalho X-Api-Key. As chaves têm escopos e podem ser travadas em uma faixa de IP.

Três formas de enviar uma chave

O bearer token e o cabeçalho X-Api-Key são equivalentes. O parâmetro de query existe para clientes handler_api e deixa a chave nos seus logs — use apenas nesse caso.

bearer.sh
# Preferred everywhere. The key never appears in a URL or an access log.
-kw">curl https://virtualsmsnumbers.com/api/v1/balance \
  -H "Authorization: Bearer $VSN_KEY"

Como as chaves se comportam

  • Keys look like vsn_live_ followed by 32 URL-safe characters. Only a SHA-256 hash is stored, which is why the full key is shown once and never again.
  • A revoked key stops working on the next request. There is no grace period and no propagation delay.
  • Expiry is chosen at creation — never, 30, 90 or 365 days — and cannot be extended afterwards. Issue a replacement and roll it in.
  • Scopes are fixed for the life of a key. Widening access means creating a second key, which is deliberate: a key's blast radius should never grow after it has been deployed.
  • Each successful call updates the key's last-used timestamp, last-used IP and request counter. The dashboard shows all three, so an unused key is easy to spot before you revoke it.

Scopes

Uma chave carrega os scopes com que foi criada. Um endpoint que precise de um que ela não tem responde 403 insufficient_scope e informa na mensagem qual scope está faltando.

Scopes
ScopePermiteUsado por
numbers:readRead activations and their messages.GET /activations, GET /activations/{id}
numbers:writeBuy, complete, cancel and resend. Spends the balance.POST /activations and the three /activations/{id}/… actions
rentals:readRead rentals and their messages.GET /rentals
rentals:writeRent numbers and set auto-renew. Spends the balance.POST /rentals
balance:readRead the prepaid balance and lifetime totals.GET /balance
webhooks:writeCreate and delete webhook endpoints.Dashboard and future webhook management routes

Lista de IPs permitidos

Opcional, por chave. Com uma lista definida, uma requisição de qualquer outro endereço é recusada antes da checagem de scope, o que deixa as duas falhas fáceis de distinguir.

  • One entry per line: a bare IPv4 or IPv6 address, or a CIDR range such as 203.0.113.0/24. An empty list means any address.
  • The address is read from X-Forwarded-For, falling back to X-Real-IP. Behind your own proxy, make sure it forwards the real client address.
  • A request from outside the list is refused with 403 ip_not_allowed before any scope check runs, so an allowlist miss and a scope miss are easy to tell apart.
  • On serverless platforms and NAT gateways the egress address moves. Allowlist the published range, or leave the list empty and rely on scopes plus rotation.
  • IPv6 entries are matched on their textual prefix rather than a full bitwise mask. Prefer whole /64 or /48 prefixes over exotic boundaries.

03

Início rápido

Compre um número, espere o código, libere-o. Todo o resto é opcional.

  1. 1

    Comprar

    POST /activations com um serviço e, opcionalmente, um país. Você recebe um id, um número de telefone e uma janela de vinte minutos.

  2. 2

    Esperar

    GET /activations/{id}?wait=180 mantém a conexão aberta e responde no instante em que o SMS chega. Sem laço de polling.

  3. 3

    Liberar

    POST /activations/{id}/complete encerra a venda. Se nada chegou, não faça nada — o reembolso é automático.

buy-and-wait.sh
# 1. Buy a Telegram number in Portugal
-kw">curl -X POST https://virtualsmsnumbers.com/api/v1/activations \
  -H "Authorization: Bearer $VSN_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"service":"telegram","country":"PT"}'

# {"id":"1043872915","phone_number":"351926114508","status":"waiting",
#  "price_cents":11,"expires_at":"2026-08-26T12:41:07Z"}

# 2. Long-poll until the code lands (blocks up to 120 s)
-kw">curl "https://virtualsmsnumbers.com/api/v1/activations/1043872915?wait=120" \
  -H "Authorization: Bearer $VSN_KEY"

# {"id":"1043872915","status":"code_received",
#  "messages":[{"code":"48219","text":"Telegram code: 48219"}]}

# 3. Release the number
-kw">curl -X POST https://virtualsmsnumbers.com/api/v1/activations/1043872915/complete \
  -H "Authorization: Bearer $VSN_KEY"

O reembolso não é algo que você precise pedir

Uma ativação que nunca recebe mensagem é encerrada e creditada integralmente pela varredura de expiração, em até um minuto após expires_at. Cancelar antes é conveniência, não obrigação.

04

Endpoints

Treze endpoints. Tudo sob /activations e /rentals exige uma chave com o scope correspondente; as leituras do catálogo não pedem credencial nenhuma.

POST/activationsnumbers:write

Buy a number

Reserves a number for one service and one verification. The number is held for twenty minutes; if no message arrives inside that window the activation is closed and refunded in full by the expiry sweep, without you calling anything.

Parâmetros

POST /activations
ParâmetroTipoEmDescrição
serviceobrigatóriostringbodyService slug or legacy short code — telegram and tg both resolve to the same service. Required unless you send service_id.
service_idintegerbodyNumeric service id from GET /services. Use it instead of service, never alongside it.
countrystringbodyISO 3166-1 alpha-2, upper or lower case. Omit it and the cheapest country that currently has stock is chosen for you.
country_idintegerbodyNumeric country id from GET /countries. Takes precedence over country when both are sent.
max_price_centsintegerbodyRefuse the purchase above this price, in euro cents. Between 1 and 100000. Offers over the ceiling are skipped, and 409 no_stock is returned if none are left.
allow_multiple_smsbooleanbodyKeep the number listening after the first message so POST /activations/{id}/resend can ask for another. Defaults to false.
Idempotency-KeystringheaderUp to 128 characters. Replaying the same key within 24 hours returns the original activation with status 200 and an Idempotent-Replay: true header, instead of buying a second number.

Notas

  • The account's first top-up has to settle before any purchase succeeds. Until it does, every call here returns 402 with code top_up_required.
  • Up to four candidate offers are tried in price order before the request gives up, so a single operator glitch does not surface as a failure.
  • price_cents is the final charge. It is debited when the number is reserved and refunded in full if nothing arrives.

Requisição

buy.sh
-kw">curl -X POST https://virtualsmsnumbers.com/api/v1/activations \
  -H "Authorization: Bearer $VSN_KEY" \
  -H "Idempotency-Key: 9f1c2a44-2f0e-4c8e-9a2b-6f9a2c7d3e10" \
  -H "Content-Type: application/json" \
  -d '{"service":"telegram","country":"PT","max_price_cents":25}'

Resposta201 Created

201.json
{
  "id": "1043872915",
  "object": "activation",
  "status": "waiting",
  "phone_number": "351926114508",
  "country": "PT",
  "country_id": 117,
  "country_name": "Portugal",
  "service": "telegram",
  "service_code": "tg",
  "service_name": "Telegram",
  "price_cents": 11,
  "price": "0.11",
  "currency": "EUR",
  "refunded_cents": 0,
  "allow_multiple_sms": false,
  "created_at": "2026-08-26T12:21:07.412Z",
  "expires_at": "2026-08-26T12:41:07.412Z",
  "completed_at": null,
  "messages": [],
  "code": null
}
GET/activationsnumbers:read

List activations

Pages through the activations owned by the key's account, newest first. Useful for reconciliation; for the state of one activation you are waiting on, long-poll it instead.

Parâmetros

GET /activations
ParâmetroTipoEmDescrição
statusstringqueryOne of active, waiting, completed, cancelled, expired. active is a shorthand for pending, waiting and code_received together. Omit for everything.
pageintegerquery1-based page number. Defaults to 1.
per_pageintegerqueryDefaults to 20 and is clamped to the range 5–100.

Notas

  • Ordering is by created_at descending and is stable — page through it without worrying about rows shifting between calls.
  • total counts the whole filtered set, not the page.

Requisição

list.sh
-kw">curl "https://virtualsmsnumbers.com/api/v1/activations?status=active&per_page=25" \
  -H "Authorization: Bearer $VSN_KEY"

Resposta200 OK

200.json
{
  "object": "list",
  "data": [
    {
      "id": "1043872915",
      "object": "activation",
      "status": "code_received",
      "phone_number": "351926114508",
      "country": "PT",
      "service": "telegram",
      "price_cents": 11,
      "code": "48219"
    }
  ],
  "total": 2148,
  "page": 1,
  "per_page": 25
}
GET/activations/{id}numbers:read

Read an activation, or long-poll for the code

Returns the current state of one activation. With wait it holds the connection open and answers the moment a message lands, which is the supported way to wait for a code.

Parâmetros

GET /activations/{id}
ParâmetroTipoEmDescrição
idobrigatóriostringpathThe id returned by POST /activations.
waitintegerquerySeconds to hold the connection open, 0–300. Defaults to 0, which returns immediately. The operator is re-checked every two seconds and the response is sent as soon as there is something to send.

Notas

  • A long poll is one request against your rate limit no matter how long it blocks. A polling loop at the same timeout is dozens. This is why the limits can stay where they are.
  • The connection returns early as soon as messages is non-empty, or as soon as the activation leaves pending/waiting — expired, cancelled and banned all end the wait.
  • The route is capped at 300 seconds server-side. Set your HTTP client timeout above the wait you ask for, not below it.
  • code is a convenience mirror of the last message's code. Read messages when a service sends more than one.

Requisição

wait.sh
-kw">curl "https://virtualsmsnumbers.com/api/v1/activations/1043872915?wait=180" \
  -H "Authorization: Bearer $VSN_KEY" \
  --max-time -num">200

Resposta200 OK

200.json
{
  "id": "1043872915",
  "object": "activation",
  "status": "code_received",
  "phone_number": "351926114508",
  "country": "PT",
  "service": "telegram",
  "price_cents": 11,
  "currency": "EUR",
  "refunded_cents": 0,
  "created_at": "2026-08-26T12:21:07.412Z",
  "expires_at": "2026-08-26T12:41:07.412Z",
  "messages": [
    {
      "id": "cmf3k9x0h0001s6a1r8b2q4tz",
      "sender": "Telegram",
      "text": "Telegram code: 48219",
      "code": "48219",
      "received_at": "2026-08-26T12:21:44.907Z"
    }
  ],
  "code": "48219"
}
POST/activations/{id}/completenumbers:write

Complete an activation

Closes the activation once the code has been used, releases the number back to the operator and finalises the charge. Call it as soon as you are done — it frees the number for the next customer and it is how the success rate on the pricing page is measured.

Parâmetros

POST /activations/{id}/complete
ParâmetroTipoEmDescrição
idobrigatóriostringpathThe activation id.

Notas

  • Safe to call twice. An activation that is already completed, cancelled, expired or refunded is returned unchanged rather than erroring.
  • Fires the activation.completed webhook and settles any referral commission on the purchase.

Requisição

complete.sh
-kw">curl -X POST https://virtualsmsnumbers.com/api/v1/activations/1043872915/complete \
  -H "Authorization: Bearer $VSN_KEY"

Resposta200 OK

200.json
{
  "id": "1043872915",
  "object": "activation",
  "status": "completed",
  "phone_number": "351926114508",
  "price_cents": 11,
  "refunded_cents": 0,
  "completed_at": "2026-08-26T12:22:03.118Z",
  "code": "48219"
}

Erros que ele pode retornar

POST/activations/{id}/cancelnumbers:write

Cancel and refund an activation

Releases a number that received nothing and refunds it in full, immediately. Use it when you decide to give up before the twenty-minute window closes.

Parâmetros

POST /activations/{id}/cancel
ParâmetroTipoEmDescrição
idobrigatóriostringpathThe activation id.

Notas

  • Refuses with 409 already_received once a message has been delivered. A delivered code is a completed sale — close it with /complete instead.
  • Refuses with 409 already_closed if the activation is already completed, cancelled, expired or refunded.
  • You are not required to call this. The expiry sweep runs every minute and refunds anything past expires_at that never received a message.

Requisição

cancel.sh
-kw">curl -X POST https://virtualsmsnumbers.com/api/v1/activations/1043872915/cancel \
  -H "Authorization: Bearer $VSN_KEY"

Resposta200 OK

200.json
{
  "id": "1043872915",
  "object": "activation",
  "status": "cancelled",
  "phone_number": "351926114508",
  "price_cents": 11,
  "refunded_cents": 11,
  "messages": [],
  "code": null
}
POST/activations/{id}/resendnumbers:write

Request another SMS

Asks the operator to send a second message to the same number — for services that split signup and login codes, or that re-send on a timer. Billed at 30 % of the original price and added to the activation's price_cents.

Parâmetros

POST /activations/{id}/resend
ParâmetroTipoEmDescrição
idobrigatóriostringpathThe activation id.

Notas

  • Not every operator can do this. When the one holding your number cannot, the call returns 409 not_supported and nothing is charged.
  • On success the activation returns to waiting and expires_at is pushed out by ten minutes from the moment of the call.
  • Buy with allow_multiple_sms: true when you already know you will need a second code — it steers the purchase towards operators that support the resend.

Requisição

resend.sh
-kw">curl -X POST https://virtualsmsnumbers.com/api/v1/activations/1043872915/resend \
  -H "Authorization: Bearer $VSN_KEY"

Resposta200 OK

200.json
{
  "id": "1043872915",
  "object": "activation",
  "status": "waiting",
  "price_cents": 14,
  "expires_at": "2026-08-26T12:32:11.640Z",
  "allow_multiple_sms": true,
  "code": null
}
GET/rentalsrentals:read

List rentals

Every rental on the account, active and finished, with the messages each number has received during its window.

Parâmetros

Este endpoint não recebe parâmetros.

Notas

  • Rentals are not paged. An account with hundreds of them gets hundreds of rows.
  • Message bodies are deleted 30 days after they arrive, so long-finished rentals come back with an empty messages array.

Requisição

rentals.sh
-kw">curl https://virtualsmsnumbers.com/api/v1/rentals \
  -H "Authorization: Bearer $VSN_KEY"

Resposta200 OK

200.json
{
  "object": "list",
  "data": [
    {
      "id": "1174320885",
      "object": "rental",
      "status": "active",
      "phone_number": "447700900142",
      "country": "GB",
      "service": "whatsapp",
      "duration_hours": 168,
      "price_cents": 1490,
      "currency": "EUR",
      "auto_renew": false,
      "starts_at": "2026-08-24T09:02:55.301Z",
      "ends_at": "2026-08-31T09:02:55.301Z",
      "messages": []
    }
  ],
  "total": 1
}

Erros que ele pode retornar

POST/rentalsrentals:write

Rent a number

Holds a number for a fixed window instead of a single verification. Every message that arrives during the window is yours, and the number keeps the same digits until it expires.

Parâmetros

POST /rentals
ParâmetroTipoEmDescrição
countryobrigatóriostringbodyISO 3166-1 alpha-2. Required unless you send country_id.
country_idintegerbodyNumeric country id. Used when country is absent.
servicestringbodyService slug or code to restrict the rental to. Omit it, or send any, for a number that accepts every service — that is the more expensive option.
duration_hoursobrigatóriointegerbodyOne of 4, 12, 24, 72, 168, 720 or 2160. Anything else returns 422 invalid_duration.
auto_renewbooleanbodyCharge the balance and extend for another identical window when this one ends. Defaults to false. A renewal that cannot be paid simply lets the rental lapse.

Notas

  • The full price of the window is debited up front. Rentals are not refundable once the number is issued — that is the trade for keeping the digits.
  • Long rentals (720 and 2160 hours) are not available in every country. Read GET /prices, or the rentals page, before assuming a combination exists.

Requisição

rent.sh
-kw">curl -X POST https://virtualsmsnumbers.com/api/v1/rentals \
  -H "Authorization: Bearer $VSN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"country":"GB","service":"whatsapp","duration_hours":168}'

Resposta201 Created

201.json
{
  "id": "1174320885",
  "object": "rental",
  "status": "active",
  "phone_number": "447700900142",
  "country": "GB",
  "service": "whatsapp",
  "duration_hours": 168,
  "price_cents": 1490,
  "currency": "EUR",
  "auto_renew": false,
  "starts_at": "2026-08-26T12:24:18.775Z",
  "ends_at": "2026-09-02T12:24:18.775Z",
  "messages": []
}
GET/pricesSem chave

Live prices and stock

The price matrix as the site sees it: what a code costs per country and service right now, how many numbers are in the pool, and the delivery rate we measured on the last thousand activations.

Parâmetros

GET /prices
ParâmetroTipoEmDescrição
servicestringqueryService slug or short code. Omit for every service.
countrystringqueryISO 3166-1 alpha-2 country code.
pageintegerquery1-based page number. Defaults to 1.
per_pageintegerqueryDefaults to 100 and is clamped to the range 10–200.

Notas

  • No key required — you can shop the catalogue before you have an account. Rate limited by IP rather than by key: 120 requests per minute.
  • An unknown service or country is ignored rather than rejected, so a typo returns the unfiltered matrix. Check what you sent if a result set looks too large.
  • Rows with no stock are not returned at all. An empty data array means the combination is unavailable, not that it does not exist.
  • success_rate is a fraction between 0 and 1. price_cents is what you will actually be charged, all fees included.

Requisição

prices.sh
-kw">curl "https://virtualsmsnumbers.com/api/v1/prices?service=telegram&per_page=3"

Resposta200 OK

200.json
{
  "object": "list",
  "updated_at": "2026-08-26T12:20:01.004Z",
  "data": [
    {
      "service": "telegram",
      "service_code": "tg",
      "service_name": "Telegram",
      "country": "PT",
      "country_name": "Portugal",
      "price_cents": 11,
      "price": "0.11",
      "currency": "EUR",
      "available": 2841,
      "success_rate": 0.94
    }
  ],
  "total": 168,
  "page": 1,
  "per_page": 3
}

Erros que ele pode retornar

GET/countriesSem chave

Countries

Every country in the catalogue with its numeric id, ISO code and dialling code. The numeric id is the one the legacy handler_api protocol expects.

Parâmetros

Este endpoint não recebe parâmetros.

Notas

  • Cached for five minutes. It is a small, slow-moving list — fetch it at boot and keep it in memory rather than per request.
  • Presence in this list is not stock. A country can be listed and have nothing available; GET /prices is the source of truth for that.

Requisição

countries.sh
-kw">curl https://virtualsmsnumbers.com/api/v1/countries

Resposta200 OK

200.json
{
  "object": "list",
  "data": [
    {
      "id": 117,
      "code": "PT",
      "name": "Portugal",
      "dial_code": "+351",
      "region": "Europe",
      "flag": "🇵🇹"
    }
  ],
  "total": 227
}
GET/servicesSem chave

Services

Every service the pool can receive for, with both identifiers: the readable slug used by this API and the two- or three-letter code used by handler_api clients.

Parâmetros

Este endpoint não recebe parâmetros.

Notas

  • Cached for five minutes.
  • POST /activations accepts either identifier in the service field, so you do not have to translate between them.

Requisição

services.sh
-kw">curl https://virtualsmsnumbers.com/api/v1/services

Resposta200 OK

200.json
{
  "object": "list",
  "data": [
    {
      "id": 42,
      "slug": "telegram",
      "code": "tg",
      "name": "Telegram",
      "category": "messaging"
    }
  ],
  "total": 517
}
GET/balancebalance:read

Balance

The prepaid balance the key draws on, plus lifetime totals. Check can_purchase before a worker starts buying: it is false until the account's first top-up has settled.

Parâmetros

Este endpoint não recebe parâmetros.

Notas

  • All amounts are integer euro cents. balance is the same figure as a two-decimal string, for display only — do arithmetic on balance_cents.
  • This is a live read, not a cached one. It reflects debits from purchases made a second ago.

Requisição

balance.sh
-kw">curl https://virtualsmsnumbers.com/api/v1/balance \
  -H "Authorization: Bearer $VSN_KEY"

Resposta200 OK

200.json
{
  "object": "balance",
  "currency": "EUR",
  "balance_cents": 4187,
  "balance": "41.87",
  "lifetime_spent_cents": 118240,
  "lifetime_topped_up_cents": 122427,
  "can_purchase": true
}

Erros que ele pode retornar

GET/meQualquer chave

Account

Who the credential belongs to. The cheapest way to prove a key works, and the only endpoint that requires no scope at all — any valid, unexpired, allowlisted key can call it.

Parâmetros

Este endpoint não recebe parâmetros.

Notas

  • Authentication is still required: this endpoint is scope-free, not public. Without a key or a dashboard session it returns 401 unauthenticated.
  • authenticated_with is api_key or session, which makes it useful as a health check from both a worker and the browser.
  • Use it in deployment smoke tests: a 200 here proves the key exists, is not revoked, is not expired and is calling from an allowed IP.

Requisição

me.sh
-kw">curl https://virtualsmsnumbers.com/api/v1/me \
  -H "X-Api-Key: $VSN_KEY"

Resposta200 OK

200.json
{
  "object": "account",
  "id": "cmf1a0q7t0000s6y3v1n8k2pd",
  "email": "[email protected]",
  "name": "Example Ops",
  "locale": "en",
  "balance_cents": 4187,
  "referral_code": "VL-4K2PD",
  "email_verified": true,
  "can_purchase": true,
  "created_at": "2023-11-02T08:14:29.006Z",
  "authenticated_with": "api_key"
}

05

Erros

Os erros usam códigos de status HTTP padrão e um campo code estável e legível por máquina. Nunca faça parsing da mensagem legível por humanos.

O objeto de erro

O mesmo formato em toda falha. Alguns códigos acrescentam campos — insufficient_funds traz os valores, rate_limited traz retry_after.

402.json
{
  "error": {
    "code": "insufficient_funds",
    "message": "Balance is too low for this purchase.",
    "required_cents": 24,
    "available_cents": 11
  }
}

Todos os códigos que a API retorna

Esta é a lista completa. Qualquer coisa fora dela é um bug do nosso lado, e gostaríamos de saber.

Erros
CódigoHTTPQuando aconteceO que fazer
missing_api_key401No Authorization header, X-Api-Key header or api_key parameter reached the server.Check that your proxy forwards Authorization. Some load balancers strip it by default.
invalid_api_key401The key does not exist, or it has been revoked.Create a new key in the dashboard. Revocation takes effect on the next request, with no grace period.
expired_api_key401The key is past the expiry date it was created with.Issue a replacement and roll it in. Expiry is set once, at creation, and cannot be extended.
insufficient_scope403The key is valid but was not granted a scope the endpoint requires.Scopes are fixed for the life of a key. Create a second key with the scope you need; the message names the missing one.
ip_not_allowed403The key has an IP allowlist and the request did not come from it.Add the egress IP or CIDR to the key. Behind a NAT gateway or a serverless platform, allowlist the range rather than one address.
unauthenticated401No credential of any kind: no key, and no dashboard session cookie.Send a key. This is the response the browser gets when a session has expired.
forbidden403The credential is valid but the resource belongs to another account.Confirm the id came from the same account as the key. Ids are never shared between accounts.
invalid_request422The body failed validation. The message quotes the first field that failed.Fix the field and retry. This is deterministic — retrying the same body returns the same error.
unknown_service404No service matches the slug, code or id you sent.Look it up in GET /services. Both slug and code are accepted in the service field.
unknown_country404No country matches the ISO code or id you sent.Use GET /countries. The country field wants alpha-2, not a dialling code or a name.
no_stock409No operator has a number for that country and service, or every offer was above your max_price_cents.Retry after a few seconds, drop the price ceiling, or omit country and let the cheapest country with stock be picked. A transient sourcing failure surfaces as 503 with the same code.
insufficient_funds402The balance is lower than the price of the number.Top up. The response carries required_cents and available_cents so a worker can log exactly how short it was.
top_up_required402The account has never completed a top-up. Nothing can be purchased before the first one settles.Complete the first top-up in the dashboard. can_purchase on GET /balance and GET /me tells you whether this is still pending.
account_suspended403The account is not in the ACTIVE state.Open a ticket. Keys keep authenticating so the failure is legible, but nothing that spends money will run.
already_closed409You cancelled an activation that is already completed, cancelled, expired or refunded.Read it first. /complete is deliberately idempotent; /cancel is not, because cancelling a settled sale should be loud.
already_received409You cancelled an activation that has already received a message.Call /complete instead. A delivered code is billable and will not be refunded.
not_supported409The operator holding the number cannot send another SMS to it.Buy a fresh activation. Nothing was charged for the failed resend.
invalid_duration422duration_hours is not one of the seven supported rental windows.Use 4, 12, 24, 72, 168, 720 or 2160. The message lists them.
rate_limited429The key's token bucket is empty.Back off for retry_after seconds, which is in the body and in X-RateLimit-Reset. Replace polling loops with ?wait= before asking for a higher limit.
not_found404No activation or rental with that id belongs to this account.Both the internal id and the public id are accepted, so a 404 here means the wrong account or a typo.
internal_error500Something failed on our side. It is logged with the request.Retry once with the same Idempotency-Key. If it repeats, send us the timestamp and the endpoint.

06

Idempotência

Envie um cabeçalho Idempotency-Key nas requisições POST. Repetir a mesma chave dentro de 24 horas retorna a resposta original em vez de comprar um segundo número.

idempotency.sh
# First call: buys a number, 201 Created.
-kw">curl -i -X POST https://virtualsmsnumbers.com/api/v1/activations \
  -H "Authorization: Bearer $VSN_KEY" \
  -H "Idempotency-Key: 9f1c2a44-2f0e-4c8e-9a2b-6f9a2c7d3e10" \
  -H "Content-Type: application/json" \
  -d '{"service":"telegram","country":"PT"}'

# HTTP/2 201
# {"id":"1043872915","status":"waiting","phone_number":"351926114508"}

# Same key again, any time in the next 24 hours: the original activation,
# 200 instead of 201, and nothing is charged a second time.
-kw">curl -i -X POST https://virtualsmsnumbers.com/api/v1/activations \
  -H "Authorization: Bearer $VSN_KEY" \
  -H "Idempotency-Key: 9f1c2a44-2f0e-4c8e-9a2b-6f9a2c7d3e10" \
  -H "Content-Type: application/json" \
  -d '{"service":"telegram","country":"PT"}'

# HTTP/2 200
# Idempotent-Replay: true
# {"id":"1043872915","status":"code_received","code":"48219"}

Regras

  • Only POST /activations honours the header today. The other POST routes are already safe to repeat: /complete returns the activation unchanged, /cancel and /resend refuse a second time with a 409.
  • Keys are scoped to your account, so two customers sending the same UUID never collide.
  • The replay window is 24 hours from the original purchase. After that the same key buys a new number.
  • A replay answers 200 with an Idempotent-Replay: true header, where the original answered 201. Treat both as success.
  • The stored response is the activation as it is *now*, not as it was: replaying after the code has landed returns the activation with the code in it.
  • The key is not a fingerprint of the body. Sending a different body under a used key returns the original activation and ignores the new body — generate one key per logical purchase.
  • Use a UUID, or something equally unique per attempt: uuidgen, crypto.randomUUID(), uuid.uuid4(). Up to 128 characters.

07

Limites de requisição

120 requisições por minuto por chave no padrão, com picos de até 20 por segundo. Os limites são retornados nos cabeçalhos X-RateLimit-*. Peça ao suporte para aumentá-los.

Limites de requisição
SuperfícieLimiteBurstNotas
REST API, per key120 / min30The default on a newly created key. Raised limits are attached to the key, not the account, so a worker key and a laptop key can differ.
Dashboard session300 / min75What the browser gets when it calls the same handlers with a session cookie instead of a key.
handler_api layersame as the key30Its own bucket, at the key's rate. Exhaustion comes back as ERROR_SQL because the protocol has no rate-limit token.
GET /prices, per IP120 / min30No key is involved, so the bucket is keyed on the caller's address. GET /countries and GET /services are cached and not metered.

Cabeçalhos de resposta

Toda resposta medida traz os três cabeçalhos abaixo. Leia-os em vez de contar requisições por conta própria.

Cabeçalhos de resposta
CabeçalhoSignificado
X-RateLimit-LimitRequests per minute this credential is allowed.
X-RateLimit-RemainingWhole tokens left in the bucket at the moment of the reply.
X-RateLimit-ResetSeconds until at least one token is back. Never zero.
rate-limit.http
HTTP/2 200
Content-Type: application/json
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
X-RateLimit-Reset: 1

HTTP/2 429
Content-Type: application/json
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2

{"error":{"code":"rate_limited","message":"Too many requests.","retry_after":2}}

Como ficar abaixo do limite

  • Replace polling loops with ?wait= on GET /activations/{id}. A 180-second long poll is one request; polling every two seconds for the same period is ninety.
  • Subscribe to sms.received instead of reading GET /activations on a timer. A webhook costs you nothing against the limit.
  • Cache GET /countries and GET /services at boot. They change a few times a month and are already served from a five-minute cache.
  • On 429, sleep for retry_after seconds rather than retrying immediately. The bucket refills continuously, so a short wait is usually enough.
  • The limit is per key. Splitting one worker's traffic across two keys to get more throughput works, but tell us instead — we would rather raise the limit than debug a fleet of keys.

08

Webhooks

Registre um endpoint HTTPS e receba os eventos conforme acontecem. A assinatura é um HMAC-SHA256 do corpo bruto, no cabeçalho X-VSN-Signature.

Eventos

A inscrição é por endpoint. Um endpoint recebe apenas os eventos para os quais foi cadastrado, e nada além disso.

Eventos
EventoQuando disparaChaves de data
sms.received

SMS recebido

Once per message, the moment it is pulled from the operator. A multi-SMS activation fires it repeatedly.activationId, phoneNumber, service, country, sender, text, code, receivedAt
activation.completed

Ativação concluída

When you close an activation with POST /activations/{id}/complete.activationId, phoneNumber, code
activation.expired

Ativação expirada

When the window closes with no message, or the number is rejected by the service. The refund is already booked.activationId, phoneNumber, refundedCents, reason
payment.paid

Pagamento confirmado

When a crypto top-up reaches the required confirmations and the balance has been credited.paymentId, orderId, amountCents, bonusCents, currency, txHash
balance.low

Saldo abaixo do limite

Once when the balance crosses the threshold set on the account — on the crossing, not on every purchase after it.balanceCents, thresholdCents

Uma entrega, na íntegra

O corpo é JSON com id, event, createdAt e data. Tudo que é específico do evento fica dentro de data.

delivery.http
POST /hooks/virtualsmsnumbers HTTP/1.1
Content-Type: application/json
User-Agent: VirtualSMSNumbers-Webhooks/1.0
X-VSN-Event: sms.received
X-VSN-Delivery: cmf3kb2rq0004s6a1w7d9m1yv
X-VSN-Timestamp: 1787747904
X-VSN-Signature: t=1787747904,v1=4d6f1c0b8a2e57d3f9b41ca8e6072d5518bb93f0a17c4e2d9068fa3c15be7742

{
  "id": "cmf3kb2rq0004s6a1w7d9m1yv",
  "event": "sms.received",
  "createdAt": "2026-08-26T12:21:44.931Z",
  "data": {
    "activationId": "1043872915",
    "phoneNumber": "351926114508",
    "service": "telegram",
    "country": "PT",
    "sender": "Telegram",
    "text": "Telegram code: 48219",
    "code": "48219",
    "receivedAt": "2026-08-26T12:21:44.907Z"
  }
}

Cabeçalhos de entrega

Cabeçalhos de entrega
CabeçalhoSignificado
X-VSN-EventEvent name, identical to the event field in the body.
X-VSN-DeliveryUnique id for this delivery attempt chain. Use it to make your handler idempotent.
X-VSN-TimestampUnix seconds at signing time. Also the t in the signature.
X-VSN-Signaturet=<unix>,v1=<hex> — HMAC-SHA256 of <t>.<raw body> with your endpoint secret.
User-AgentAlways VirtualSMSNumbers-Webhooks/1.0. Do not use it for authentication.

Verificando a assinatura

Calcule o HMAC-SHA256 sobre <timestamp>.<corpo bruto> com o segredo do endpoint e compare em tempo constante. Assine os bytes brutos, antes de qualquer parsing de JSON — um corpo reserializado não vai bater.

webhook.js
-kw">import crypto -kw">from "node:crypto";

// Signature: X-VSN-Signature: t=<unix>,v1=<hex>
// where hex = HMAC-SHA256("<t>.<raw body>", endpointSecret)
-kw">export -kw">function verify(rawBody, header, secret) {
  -kw">const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("=")),
  );
  -kw">const expected = crypto
    .createHmac("sha256", secret)
    .update(parts.t + "." + rawBody)
    .digest("hex");

  -kw">const a = Buffer.-kw">from(expected);
  -kw">const b = Buffer.-kw">from(parts.v1);
  -kw">if (a.length !== b.length) -kw">return -kw">false;
  // Reject anything older than five minutes to stop replays.
  -kw">if (Math.abs(Date.now() / -num">1000 - Number(parts.t)) > -num">300) -kw">return -kw">false;
  -kw">return crypto.timingSafeEqual(a, b);
}

Novas tentativas

Esperamos dez segundos por uma resposta 2xx. Qualquer outra coisa é repetida até seis vezes, com este backoff:

#230 s#32 min#410 min#51 h#66 h

Toda tentativa carrega o mesmo id X-VSN-Delivery, então usá-lo como chave já basta para deixar o seu handler idempotente. Um endpoint que passa um dia inteiro falhando é desativado automaticamente e precisa ser reativado pelo painel.

Gerenciar endpoints

09

Compatibilidade legada

Scripts escritos para o protocolo clássico handler_api.php funcionam sem alteração: aponte-os para o nosso endpoint e troque a api_key. As respostas usam o mesmo formato de tokens ACCESS_/STATUS_.

URL base
legacy-compat.sh
# Existing scripts keep working: same actions, same response tokens.
BASE="https://virtualsmsnumbers.com/stubs/handler_api.php"

-kw">curl "$BASE?api_key=$KEY&action=getNumber&service=tg&country=117"
# ACCESS_NUMBER:1043872915:351926114508

-kw">curl "$BASE?api_key=$KEY&action=getStatus&id=1043872915"
# STATUS_WAIT_CODE
# STATUS_OK:48219

-kw">curl "$BASE?api_key=$KEY&action=setStatus&id=1043872915&status=6"
# ACCESS_ACTIVATION

-kw">curl "$BASE?api_key=$KEY&action=getBalance"
# ACCESS_BALANCE:41.87

Mapeamento de ações

Todas as ações que implementamos, com o token exato ou o formato JSON com que respondem. Qualquer coisa fora desta tabela retorna BAD_ACTION.

Mapeamento de ações
AçãoParâmetrosSucessoFalhaNotas
getBalanceACCESS_BALANCE:41.87BAD_KEYEuros with two decimals, never cents.
getBalanceAndCashBackACCESS_BALANCE:41.87:0.00BAD_KEYThe cashback field is always 0.00. We do not run a cashback programme.
getNumberservice, country, maxPriceACCESS_NUMBER:1043872915:351926114508BAD_SERVICE · NO_NUMBERS · NO_BALANCEcountry is the numeric legacy country id, not an ISO code. maxPrice is in euros.
getNumberV2service, country, maxPriceJSON: activationId, phoneNumber, activationCost, countryCode, canGetAnotherSms, activationTime, activationOperatorBAD_SERVICE · NO_NUMBERS · NO_BALANCEphoneNumber carries a leading +, unlike every other surface we expose.
getStatusidSTATUS_WAIT_CODE · STATUS_OK:48219 · STATUS_CANCELNO_ACTIVATIONSTATUS_CANCEL covers cancelled, expired and refunded. Poll this at most once every five seconds.
setStatus · status=1id, statusACCESS_READYBAD_ACTIONAccepted and acknowledged, but a no-op here: the number is already listening when it is issued.
setStatus · status=3id, statusACCESS_RETRY_GETERROR_WRONG_STATUS · NO_ACTIVATIONRequests another SMS. Same 30 % charge as POST /activations/{id}/resend.
setStatus · status=6id, statusACCESS_ACTIVATIONERROR_WRONG_STATUS · NO_ACTIVATIONFinishes the activation. Equivalent to POST /activations/{id}/complete.
setStatus · status=8id, statusACCESS_CANCELERROR_WRONG_STATUS · NO_ACTIVATIONCancels and refunds. Equivalent to POST /activations/{id}/cancel.
getPricesservice, countryJSON: { countryId: { serviceCode: { cost, count } } }BAD_KEYcost is euros as a number, count is stock. Up to 200 rows per call.
getNumbersStatuscountryJSON: { "tg_0": "2841", "wa_0": "1190" }BAD_KEYThe _0 suffix is the operator slot from the original protocol. We always report slot 0.
getCountriesJSON: { id: { id, rus, eng, chn, visible, retry, rent, multiService } }BAD_KEYThe three language fields carry the same English name. We do not maintain translated country names for this endpoint.
getActiveActivationsJSON: { status: "success", activeActivations: [ … ] }NO_ACTIVATIONSactivationStatus is 3 once a code has arrived, 2 while waiting. Capped at 100 rows.

Tokens de erro

Tokens de erro
TokenSignificado
BAD_KEYMissing, unknown, revoked or expired key — the protocol has one token for all four.
BAD_ACTIONUnknown action, or a required parameter is missing.
BAD_SERVICENo service matches the code you sent.
BAD_STATUSsetStatus was called with something other than 1, 3, 6 or 8.
NO_NUMBERSNo stock for that country and service.
NO_BALANCEBalance too low, or the first top-up has not settled.
NO_ACTIVATIONNo activation with that id belongs to this key's account.
NO_ACTIVATIONSgetActiveActivations found nothing open. Not an error.
ERROR_WRONG_STATUSThe activation is already closed, or already has a code and cannot be cancelled.
ERROR_SQLRate limit exceeded, or an unexpected server fault. The old protocol has no separate token for either.

O que saber antes de depender disso

  • Every response is HTTP 200 with a text/plain body, including the failures. Parse the token, not the status code.
  • The key travels in the api_key query parameter. That is how the protocol works, and it means keys land in access logs and proxy history — use a key scoped to exactly what the script needs.
  • Country is a numeric id in this protocol. GET /countries returns the mapping, and the ids match the ones long-standing scripts already hardcode.
  • GET and POST both work. A POST with application/x-www-form-urlencoded is read from the body, with query parameters taking precedence.
  • Rate limiting is shared with the REST API — the same bucket, the same per-key limit — but exhaustion is reported as ERROR_SQL because the protocol has nothing better.
  • This layer exists so an existing bot keeps running while you migrate. New work should use /api/v1, which returns real status codes and stable error codes.

10

Feita para agentes de IA

A API traz um documento OpenAPI 3.1, um índice llms.txt e um servidor MCP hospedado, para que agentes possam descobri-la e chamá-la sem código de cola escrito à mão.

Apontar um agente para o servidor MCP

JSON-RPC 2.0 sobre HTTP em /api/mcp. initialize e tools/list são abertos; tools/call exige a chave no cabeçalho Authorization.

mcp.json
{
  "mcpServers": {
    "virtualsmsnumbers": {
      "type": "http",
      "url": "https://virtualsmsnumbers.com/api/mcp",
      "headers": { "Authorization": "Bearer vsn_live_xxxxxxxxxxxx" }
    }
  }
}

Ferramentas de agente disponíveis

Cinco ferramentas, de propósito. Ler e gastar são chamadas separadas, então uma chave com scopes restritos continua limitando o que um agente pode fazer com elas.

Ferramentas de agente disponíveis
FerramentaDescriçãoObrigatóriosOpcionais
vsn_list_pricesLive prices, stock and success rate for a service, optionally narrowed to one country. The agent is told to call this first so it buys somewhere that actually has numbers.servicecountry, limit (1–50, default 10)
vsn_buy_numberReserves a number and debits the balance. With no country, the cheapest country with stock is used.servicecountry, max_price_cents
vsn_wait_for_codeBlocks until the code arrives or the window closes. Returns the code, the message text and whether the activation was refunded.activation_idtimeout_seconds (5–280, default 120)
vsn_release_numberCloses the activation. complete after using the code, cancel to release and refund a number that received nothing.activation_idmode: "complete" | "cancel" (default "complete")
vsn_get_balanceRemaining prepaid balance in euro cents, plus whether the account is cleared to purchase.
tools/list.json
[
  {
    "name": "vsn_list_prices",
    "description": "List live prices, stock and success rate for a service, optionally filtered by country. Use this before buying to pick a country that is actually in stock.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "service": { "type": "string", "description": "Service slug or short code, e.g. 'telegram' or 'tg'." },
        "country": { "type": "string", "description": "ISO 3166-1 alpha-2 country code, e.g. 'PT'." },
        "limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 }
      },
      "required": ["service"]
    }
  },
  {
    "name": "vsn_buy_number",
    "description": "Rent a virtual number capable of receiving the verification SMS of a service. Debits the account balance. If no country is given, the cheapest country with stock is used.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "service": { "type": "string", "description": "Service slug or short code." },
        "country": { "type": "string", "description": "ISO 3166-1 alpha-2 country code. Optional." },
        "max_price_cents": { "type": "integer", "description": "Refuse the purchase above this price, in euro cents." }
      },
      "required": ["service"]
    }
  },
  {
    "name": "vsn_wait_for_code",
    "description": "Wait for the verification code on an activation. Blocks up to timeout_seconds. Returns the code when it arrives, or null if the window closed (in which case the activation is refunded automatically).",
    "inputSchema": {
      "type": "object",
      "properties": {
        "activation_id": { "type": "string" },
        "timeout_seconds": { "type": "integer", "minimum": 5, "maximum": 280, "default": 120 }
      },
      "required": ["activation_id"]
    }
  },
  {
    "name": "vsn_release_number",
    "description": "Close an activation. Use 'complete' once the code has been used, or 'cancel' to release and refund a number that never received anything.",
    "inputSchema": {
      "type": "object",
      "properties": {
        "activation_id": { "type": "string" },
        "mode": { "type": "string", "enum": ["complete", "cancel"], "default": "complete" }
      },
      "required": ["activation_id"]
    }
  },
  {
    "name": "vsn_get_balance",
    "description": "Read the remaining prepaid balance, in euro cents.",
    "inputSchema": { "type": "object", "properties": {} }
  }
]

Exemplo prático: um agente conclui um cadastro

O fluxo inteiro como ele trafega na rede, do handshake ao número liberado.

agent-signup.txt
# One agent, one signup, four tool calls. JSON-RPC 2.0 over HTTP,
# every request to https://virtualsmsnumbers.com/api/mcp with the key in the Authorization header.

# 1. Handshake. No key needed yet — initialize and tools/list are unauthenticated.
--> {"jsonrpc":"2.0","id":-num">1,"method":"initialize",
     "params":{"protocolVersion":"2025-06-18"}}
<-- {"jsonrpc":"2.0","id":-num">1,"result":{"protocolVersion":"2025-06-18",
     "serverInfo":{"name":"virtualsmsnumbers","version":"1.0.0"},
     "instructions":"Always call vsn_list_prices first to confirm stock..."}}

# 2. Where is Telegram in stock, and what does it cost?
--> {"jsonrpc":"2.0","id":-num">2,"method":"tools/call",
     "params":{"name":"vsn_list_prices",
               "arguments":{"service":"telegram","limit":-num">3}}}
<-- [{"country":"PT","country_name":"Portugal","price_cents":-num">11,
      "available":-num">2841,"success_rate":-num">0.94}, ...]

# 3. Buy in Portugal, with a ceiling so a price spike cannot surprise the agent.
--> {"jsonrpc":"2.0","id":-num">3,"method":"tools/call",
     "params":{"name":"vsn_buy_number",
               "arguments":{"service":"telegram","country":"PT","max_price_cents":-num">25}}}
<-- {"id":"1043872915","status":"waiting","phone_number":"351926114508",
     "price_cents":-num">11,"expires_at":"2026-08-26T12:41:07.412Z"}

# The agent types 351926114508 into the signup form here, then blocks.

# 4. Wait for the code. One call, up to 280 seconds, no polling loop.
--> {"jsonrpc":"2.0","id":-num">4,"method":"tools/call",
     "params":{"name":"vsn_wait_for_code",
               "arguments":{"activation_id":"1043872915","timeout_seconds":-num">180}}}
<-- {"activation_id":"1043872915","status":"code_received","code":"48219",
     "text":"Telegram code: 48219","refunded":false}

# 5. Code entered, signup done. Release the number.
--> {"jsonrpc":"2.0","id":-num">5,"method":"tools/call",
     "params":{"name":"vsn_release_number",
               "arguments":{"activation_id":"1043872915","mode":"complete"}}}
<-- {"id":"1043872915","status":"completed","refunded_cents":-num">0}

# If step 4 had returned code: null, the activation would already have been
# refunded — the agent should call vsn_list_prices again and retry in
# a different country rather than buying the same one twice.

Dê aos agentes uma chave própria

Limite a numbers:read e numbers:write, deixe rentals:write de fora a menos que o agente realmente precise, e defina um vencimento. Um agente com uma chave de escopo total pode alugar números por meses sem querer.

11

Bibliotecas cliente

Wrappers enxutos e sem dependências. Ou use fetch direto — a API tem seis endpoints.

virtualsmsnumbers.ts
// Whole client, no dependencies. Drop it in and delete what you do not use.
-kw">export -kw">type Activation = {
  id: string;
  status: "pending" | "waiting" | "code_received" | "completed" | "cancelled" | "expired" | "refunded" | "banned";
  phone_number: string;
  price_cents: number;
  code: string | -kw">null;
  messages: { code: string | -kw">null; text: string; received_at: string }[];
};

-kw">export -kw">class VirtualSMSNumbersError -kw">extends Error {
  constructor(readonly code: string, message: string, readonly status: number) {
    super(message);
  }
}

-kw">export -kw">class VirtualSMSNumbers {
  constructor(
    private readonly key: string,
    private readonly base = "https://virtualsmsnumbers.com/api/v1",
  ) {}

  private -kw">async call<T>(path: string, init: RequestInit = {}): Promise<T> {
    -kw">const response = -kw">await fetch(this.base + path, {
      ...init,
      headers: {
        Authorization: `Bearer ${this.key}`,
        "Content-Type": "application/json",
        ...init.headers,
      },
    });
    -kw">const body = -kw">await response.json();
    -kw">if (!response.ok) {
      -kw">throw -kw">new VirtualSMSNumbersError(body?.error?.code ?? "internal_error", body?.error?.message ?? "Request failed", response.status);
    }
    -kw">return body -kw">as T;
  }

  buy(service: string, country?: string, maxPriceCents?: number) {
    -kw">return this.call<Activation>("/activations", {
      method: "POST",
      headers: { "Idempotency-Key": crypto.randomUUID() },
      body: JSON.stringify({ service, country, max_price_cents: maxPriceCents }),
    });
  }

  /** Long-polls. Keep your client timeout above `seconds`. */
  waitForCode(id: string, seconds = -num">180) {
    -kw">return this.call<Activation>(`/activations/${id}?wait=${seconds}`);
  }

  complete(id: string) {
    -kw">return this.call<Activation>(`/activations/${id}/complete`, { method: "POST" });
  }

  cancel(id: string) {
    -kw">return this.call<Activation>(`/activations/${id}/cancel`, { method: "POST" });
  }

  balance() {
    -kw">return this.call<{ balance_cents: number; can_purchase: boolean }>("/balance");
  }
}

Falta alguma coisa nesta página?

Os tickets são lidos pelos mesmos engenheiros que operam o pool de operadoras. Envie o endpoint, o timestamp e o corpo da requisição — isso costuma bastar para responder já na primeira resposta.

Falar com o suporte