Справочник API

API для разработчиков

Единый REST API для покупки номеров, чтения кодов и управления балансом — плюс слой совместимости для старых скриптов handler_api.

Базовый URLv1.4.0OpenAPI 3.1

01

Обзор

Один REST-интерфейс, JSON на входе и на выходе, предоплата. Прочитайте этот раздел один раз — всё, что ниже, является справочником и следует тем же соглашениям.

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.

Первый запрос

Создайте ключ в панели, экспортируйте его и вызовите /me. Этому эндпоинту не нужны права, поэтому ответ 200 доказывает, что ключ существует, не отозван, не просрочен и вызывается с разрешённого адреса.

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

Аутентификация

Передавайте ключ как bearer-токен или в заголовке X-Api-Key. Ключи имеют области действия и могут быть привязаны к диапазону IP.

Три способа передать ключ

Bearer-токен и заголовок X-Api-Key равнозначны. Параметр в строке запроса существует для клиентов handler_api и оставляет ключ в ваших логах — используйте его только там.

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"

Как ведут себя ключи

  • 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.

Права доступа

Ключ несёт те права, с которыми он был создан. Эндпоинт, которому нужно право, отсутствующее у ключа, отвечает 403 insufficient_scope и называет недостающее право в сообщении.

Права доступа
ПравоЧто даётГде нужно
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

Белый список IP

Необязательный, отдельный для каждого ключа. Если список задан, запрос с любого другого адреса отклоняется ещё до проверки прав, так что эти два отказа легко различить.

  • 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

Быстрый старт

Купить номер, дождаться кода, освободить его. Всё остальное — по желанию.

  1. 1

    Купить

    POST /activations с сервисом и, при желании, страной. В ответ приходят id, номер телефона и двадцатиминутное окно.

  2. 2

    Ждать

    GET /activations/{id}?wait=180 держит соединение открытым и отвечает в тот момент, когда приходит SMS. Цикл опроса не нужен.

  3. 3

    Освободить

    POST /activations/{id}/complete закрывает сделку. Если ничего не пришло, не делайте ничего — возврат произойдёт автоматически.

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"

Возврат не нужно запрашивать

Активация, на которую так и не пришло сообщение, закрывается и возвращается в полном объёме процедурой очистки, в течение минуты после expires_at. Досрочная отмена — это удобство, а не обязанность.

04

Эндпоинты

Тринадцать эндпоинтов. Всему, что находится под /activations и /rentals, нужен ключ с соответствующим правом; чтение каталога не требует учётных данных вовсе.

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.

Параметры

POST /activations
ПараметрТипГдеОписание
serviceобязательноstringbodyService 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.

Примечания

  • 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.

Запрос

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

Ответ201 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.

Параметры

GET /activations
ПараметрТипГдеОписание
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.

Примечания

  • 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.

Запрос

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

Ответ200 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.

Параметры

GET /activations/{id}
ПараметрТипГдеОписание
idобязательноstringpathThe 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.

Примечания

  • 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.

Запрос

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

Ответ200 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.

Параметры

POST /activations/{id}/complete
ПараметрТипГдеОписание
idобязательноstringpathThe activation id.

Примечания

  • 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.

Запрос

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

Ответ200 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"
}

Возможные ошибки

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.

Параметры

POST /activations/{id}/cancel
ПараметрТипГдеОписание
idобязательноstringpathThe activation id.

Примечания

  • 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.

Запрос

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

Ответ200 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.

Параметры

POST /activations/{id}/resend
ПараметрТипГдеОписание
idобязательноstringpathThe activation id.

Примечания

  • 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.

Запрос

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

Ответ200 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.

Параметры

Этот эндпоинт не принимает параметров.

Примечания

  • 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.

Запрос

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

Ответ200 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
}

Возможные ошибки

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.

Параметры

POST /rentals
ПараметрТипГдеОписание
countryобязательноstringbodyISO 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_hoursобязательноintegerbodyOne 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.

Примечания

  • 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.

Запрос

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

Ответ201 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/pricesБез ключа

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.

Параметры

GET /prices
ПараметрТипГдеОписание
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.

Примечания

  • 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.

Запрос

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

Ответ200 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
}

Возможные ошибки

GET/countriesБез ключа

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.

Параметры

Этот эндпоинт не принимает параметров.

Примечания

  • 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.

Запрос

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

Ответ200 OK

200.json
{
  "object": "list",
  "data": [
    {
      "id": 117,
      "code": "PT",
      "name": "Portugal",
      "dial_code": "+351",
      "region": "Europe",
      "flag": "🇵🇹"
    }
  ],
  "total": 227
}
GET/servicesБез ключа

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.

Параметры

Этот эндпоинт не принимает параметров.

Примечания

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

Запрос

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

Ответ200 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.

Параметры

Этот эндпоинт не принимает параметров.

Примечания

  • 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.

Запрос

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

Ответ200 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
}

Возможные ошибки

GET/meЛюбой ключ

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.

Параметры

Этот эндпоинт не принимает параметров.

Примечания

  • 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.

Запрос

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

Ответ200 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

Ошибки

Ошибки используют стандартные коды состояния HTTP и стабильное машиночитаемое поле code. Никогда не разбирайте текст сообщения для людей.

Объект ошибки

Одна и та же структура при любом сбое. Несколько кодов добавляют поля: insufficient_funds несёт суммы, rate_limited несёт retry_after.

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

Все коды, которые возвращает API

Это полный список. Всё, что вне его, — ошибка на нашей стороне, и мы хотели бы о ней узнать.

Ошибки
КодHTTPКогда возникаетЧто делать
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

Идемпотентность

Передавайте заголовок Idempotency-Key в POST-запросах. Повтор с тем же ключом в течение 24 часов вернёт исходный ответ вместо покупки второго номера.

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

Правила

  • 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

Лимиты запросов

По умолчанию 120 запросов в минуту на ключ, всплеск до 20 в секунду. Лимиты возвращаются в заголовках X-RateLimit-*. Поддержка может их повысить.

Лимиты запросов
ОбластьЛимитВсплескПримечания
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.

Заголовки ответа

Каждый учитываемый ответ несёт три заголовка ниже. Читайте их вместо того, чтобы считать запросы самостоятельно.

Заголовки ответа
ЗаголовокЗначение
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}}

Как не упереться в лимит

  • 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

Вебхуки

Зарегистрируйте HTTPS-эндпоинт и получайте события по мере их наступления. Подпись — HMAC-SHA256 от сырого тела запроса, в заголовке X-VSN-Signature.

События

Подписка задаётся для каждого эндпоинта отдельно. Эндпоинт получает только те события, на которые он зарегистрирован, и ничего больше.

События
СобытиеКогда срабатываетПоля data
sms.received

SMS получено

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

Активация завершена

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

Активация истекла

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

Платёж подтверждён

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

Баланс ниже порога

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

Доставка целиком

Тело — JSON с id, event, createdAt и data. Всё, что зависит от события, лежит внутри 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"
  }
}

Заголовки доставки

Заголовки доставки
ЗаголовокЗначение
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.

Проверка подписи

Вычислите HMAC-SHA256 от <timestamp>.<raw body> с секретом эндпоинта и сравните за постоянное время. Подписывайте сырые байты, до разбора JSON — повторно сериализованное тело не совпадёт.

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

Повторы

Мы ждём ответ 2xx десять секунд. Всё остальное повторяется до шести раз, с такими задержками:

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

Каждая попытка несёт один и тот же id X-VSN-Delivery, поэтому опоры на него достаточно, чтобы ваш обработчик стал идемпотентным. Эндпоинт, который отдаёт ошибки целые сутки, отключается автоматически, и включить его заново нужно из панели.

Управление эндпоинтами

09

Совместимость со старым API

Скрипты под классический протокол handler_api.php работают без изменений: направьте их на наш эндпоинт и замените api_key. Ответы используют тот же формат токенов ACCESS_/STATUS_.

Базовый URL
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

Сопоставление действий

Все действия, которые мы поддерживаем, с точным токеном или структурой JSON в ответе. Всё, чего нет в этой таблице, возвращает BAD_ACTION.

Сопоставление действий
ДействиеПараметрыУспехОшибкаПримечания
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.

Токены ошибок

Токены ошибок
ТокенЗначение
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.

Что стоит знать, прежде чем на это полагаться

  • 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

Готово для ИИ-агентов

API поставляется с документом OpenAPI 3.1, индексом llms.txt и хостируемым сервером MCP, чтобы агенты находили и вызывали методы без рукописных обвязок.

Подключите агента к MCP-серверу

JSON-RPC 2.0 поверх HTTP по адресу /api/mcp. initialize и tools/list открыты; tools/call требует ключ в заголовке Authorization.

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

Доступные инструменты для агентов

Пять инструментов, и это сделано намеренно. Чтение и трата — разные вызовы, поэтому ключ с ограниченными правами по-прежнему ограничивает то, что может сделать агент.

Доступные инструменты для агентов
ИнструментОписаниеОбязательныеНеобязательные
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": {} }
  }
]

Разбор примера: агент проходит регистрацию

Весь поток так, как он идёт по сети, от рукопожатия до освобождённого номера.

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.

Выдайте агентам отдельный ключ

Ограничьте его правами numbers:read и numbers:write, не включайте rentals:write, если агенту это действительно не нужно, и задайте срок действия. Агент с ключом на все права может по ошибке арендовать номера на месяцы.

11

Клиентские библиотеки

Тонкие обёртки без зависимостей. Или просто используйте fetch — в API всего шесть эндпоинтов.

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

Чего-то не хватает на этой странице?

Тикеты читают те же инженеры, которые ведут пул операторов. Пришлите эндпоинт, отметку времени и тело запроса — обычно этого достаточно, чтобы ответить с первого раза.

Написать в поддержку