Referencia de la API

API para desarrolladores

Una única API REST para comprar números, leer códigos y gestionar el saldo, más una capa de compatibilidad directa para los scripts handler_api antiguos.

URL basev1.4.0OpenAPI 3.1

01

Resumen

Una sola superficie REST, JSON de entrada y de salida, de prepago. Lee esta sección una vez y el resto de la página es pura referencia: todo lo de abajo sigue las mismas convenciones.

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.

Tu primera petición

Crea una clave en el panel, expórtala y llama a /me. No necesita ningún scope, así que un 200 demuestra que la clave existe, que no está revocada, que no ha caducado y que llama desde una dirección permitida.

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

Autenticación

Envía tu clave como bearer token o en la cabecera X-Api-Key. Las claves tienen permisos y pueden restringirse a un rango de IP.

Tres formas de enviar una clave

El bearer token y la cabecera X-Api-Key son equivalentes. El parámetro de consulta existe para los clientes de handler_api y deja la clave en tus logs: úsalo solo ahí.

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"

Cómo se comportan las claves

  • 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

Una clave lleva los scopes con los que se creó. Un endpoint que necesite uno que no tiene responde 403 insufficient_scope e indica en el mensaje el scope que falta.

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 IP permitidas

Opcional, por clave. Con una lista definida, una petición desde cualquier otra dirección se rechaza antes de comprobar los scopes, lo que hace fácil distinguir los dos fallos.

  • 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

Inicio rápido

Compra un número, espera el código, libéralo. Todo lo demás es opcional.

  1. 1

    Comprar

    POST /activations con un servicio y, opcionalmente, un país. Recibes un id, un número de teléfono y una ventana de veinte minutos.

  2. 2

    Esperar

    GET /activations/{id}?wait=180 mantiene la conexión abierta y responde en cuanto llega el SMS. Sin bucle de sondeo.

  3. 3

    Liberar

    POST /activations/{id}/complete cierra la venta. Si no ha llegado nada, no hagas nada: el reembolso es 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"

El reembolso no hay que pedirlo

Una activación que no recibe ningún mensaje se cierra y se abona íntegra en la pasada de caducidad, en menos de un minuto desde expires_at. Cancelar antes es una comodidad, no un requisito.

04

Endpoints

Trece endpoints. Todo lo que hay bajo /activations y /rentals necesita una clave con el scope correspondiente; las lecturas del catálogo no requieren ninguna credencial.

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ámetroTipoEnDescripción
serviceobligatoriostringbodyService 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.

Petición

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}'

Respuesta201 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ámetroTipoEnDescripción
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.

Petición

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

Respuesta200 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ámetroTipoEnDescripción
idobligatoriostringpathThe 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.

Petición

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

Respuesta200 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ámetroTipoEnDescripción
idobligatoriostringpathThe 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.

Petición

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

Respuesta200 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"
}

Errores que puede devolver

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ámetroTipoEnDescripción
idobligatoriostringpathThe 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.

Petición

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

Respuesta200 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ámetroTipoEnDescripción
idobligatoriostringpathThe 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.

Petición

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

Respuesta200 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 no admite 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.

Petición

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

Respuesta200 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
}

Errores que puede devolver

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ámetroTipoEnDescripción
countryobligatoriostringbodyISO 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_hoursobligatoriointegerbodyOne 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.

Petición

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}'

Respuesta201 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/pricesSin clave

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ámetroTipoEnDescripción
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.

Petición

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

Respuesta200 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
}

Errores que puede devolver

GET/countriesSin clave

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 no admite 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.

Petición

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

Respuesta200 OK

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

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 no admite 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.

Petición

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

Respuesta200 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 no admite 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.

Petición

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

Respuesta200 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
}

Errores que puede devolver

GET/meCualquier clave

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 no admite 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.

Petición

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

Respuesta200 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

Errores

Los errores usan códigos de estado HTTP estándar y un campo code estable y legible por máquina. Nunca analices el mensaje legible por humanos.

El objeto de error

La misma forma en todos los fallos. Algunos códigos añaden campos: insufficient_funds lleva los importes y rate_limited lleva retry_after.

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

Todos los códigos que devuelve la API

Esta es la lista completa. Cualquier cosa fuera de ella es un error nuestro, y nos gustaría que nos lo contaras.

Errores
CódigoHTTPCuándo ocurreQué hacer
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

Idempotencia

Envía una cabecera Idempotency-Key en las peticiones POST. Repetir la misma clave dentro de 24 horas devuelve la respuesta original en lugar de comprar un 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"}

Reglas

  • 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

Límites de peticiones

120 peticiones por minuto y clave por defecto, con picos de hasta 20 por segundo. Los límites se devuelven en las cabeceras X-RateLimit-*. Pide a soporte que los amplíe.

Límites de peticiones
SuperficieLímiteRáfagaNotas
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.

Cabeceras de respuesta

Toda respuesta medida lleva las tres cabeceras siguientes. Léelas en lugar de contar peticiones por tu cuenta.

Cabeceras de respuesta
CabeceraSignificado
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}}

Cómo no pasarte

  • 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

Registra un endpoint HTTPS y recibe los eventos según ocurren. La firma es un HMAC-SHA256 del cuerpo en bruto, en la cabecera X-VSN-Signature.

Eventos

La suscripción es por endpoint. Un endpoint recibe únicamente los eventos para los que se registró, y nada más.

Eventos
EventoCuándo se disparaClaves de data
sms.received

SMS recibido

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

Activación completada

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

Activación caducada

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

Pago 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 por debajo del umbral

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

Una entrega, al completo

El cuerpo es JSON con id, event, createdAt y data. Todo lo específico de cada evento vive 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"
  }
}

Cabeceras de entrega

Cabeceras de entrega
CabeceraSignificado
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.

Verificar la firma

Calcula el HMAC-SHA256 sobre <timestamp>.<cuerpo sin procesar> con el secreto del endpoint y compáralo en tiempo constante. Firma los bytes sin procesar, antes de cualquier parseo de JSON: un cuerpo reserializado no coincidirá.

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);
}

Reintentos

Esperamos diez segundos a una respuesta 2xx. Cualquier otra cosa se reintenta hasta seis veces, con este backoff:

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

Todos los intentos llevan el mismo id X-VSN-Delivery, así que basta con usarlo como clave para que tu handler sea idempotente. Un endpoint que lleve un día entero fallando se desactiva automáticamente y hay que reactivarlo desde el panel.

Gestionar endpoints

09

Compatibilidad heredada

Los scripts escritos para el protocolo clásico handler_api.php funcionan sin cambios: apúntalos a nuestro endpoint y cambia la api_key. Las respuestas usan el mismo 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

Correspondencia de acciones

Todas las acciones que implementamos, con el token exacto o la forma JSON con la que responden. Cualquier cosa fuera de esta tabla devuelve BAD_ACTION.

Correspondencia de acciones
AcciónParámetrosÉxitoErrorNotas
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 error

Tokens de error
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.

Qué conviene saber antes de depender de ello

  • 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

Pensada para agentes de IA

La API incluye un documento OpenAPI 3.1, un índice llms.txt y un servidor MCP alojado, para que los agentes puedan descubrirla y llamarla sin código de pegamento escrito a mano.

Apuntar un agente al servidor MCP

JSON-RPC 2.0 sobre HTTP en /api/mcp. initialize y tools/list son abiertos; tools/call necesita la clave en la cabecera Authorization.

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

Herramientas de agente disponibles

Cinco herramientas, deliberadamente. Leer y gastar son llamadas distintas, así que una clave con scopes limitados sigue acotando lo que un agente puede hacer con ellas.

Herramientas de agente disponibles
HerramientaDescripciónObligatoriosOpcionales
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": {} }
  }
]

Ejemplo completo: un agente termina un registro

Todo el flujo tal como viaja por la red, del handshake al 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.

Da a los agentes su propia clave

Limítala a numbers:read y numbers:write, deja rentals:write fuera salvo que el agente lo necesite de verdad, y ponle una caducidad. Un agente con una clave sin restricciones puede alquilar números durante meses por error.

11

Bibliotecas cliente

Envoltorios ligeros y sin dependencias. O usa fetch directamente: la API tiene 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 algo en esta página?

Los tickets los leen los mismos ingenieros que gestionan el pool de operadores. Envía el endpoint, la marca de tiempo y el cuerpo de la petición: normalmente basta para responderte a la primera.

Contactar con soporte