Referensi API

API untuk developer

Satu REST API untuk membeli nomor, membaca kode, dan mengelola saldo — plus lapisan kompatibilitas siap pakai untuk skrip handler_api lama.

Base URLv1.4.0OpenAPI 3.1

01

Ikhtisar

Satu permukaan REST, JSON masuk dan keluar, prabayar. Baca bagian ini sekali dan sisa halaman ini tinggal jadi referensi — semua di bawah mengikuti konvensi yang sama.

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.

Permintaan pertama Anda

Buat kunci di dasbor, ekspor, lalu panggil /me. Endpoint ini tidak butuh scope, jadi respons 200 membuktikan kunci ada, belum dicabut, belum kedaluwarsa, dan dipanggil dari alamat yang diizinkan.

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

Autentikasi

Kirim kunci Anda sebagai bearer token, atau lewat header X-Api-Key. Kunci punya cakupan dan bisa dikunci ke rentang IP tertentu.

Tiga cara mengirim kunci

Bearer token dan header X-Api-Key setara. Parameter query ada untuk klien handler_api dan menaruh kunci di log Anda — pakai hanya di sana.

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"

Cara kunci bekerja

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

Scope

Kunci membawa scope yang diberikan saat dibuat. Endpoint yang membutuhkan scope yang tidak dimilikinya menjawab 403 insufficient_scope, dan menyebutkan scope yang kurang di dalam pesan.

Scope
ScopeMemberi aksesDipakai oleh
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

Daftar izin IP

Opsional, per kunci. Jika daftar diatur, permintaan dari alamat lain ditolak sebelum pemeriksaan scope berjalan, sehingga kedua jenis kegagalan mudah dibedakan.

  • 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

Mulai cepat

Beli nomor, tunggu kodenya, lepaskan. Selebihnya opsional.

  1. 1

    Beli

    POST /activations dengan sebuah layanan dan, opsional, sebuah negara. Anda mendapat id, nomor telepon, dan periode dua puluh menit.

  2. 2

    Tunggu

    GET /activations/{id}?wait=180 menahan koneksi tetap terbuka dan menjawab begitu SMS masuk. Tanpa loop polling.

  3. 3

    Lepas

    POST /activations/{id}/complete menutup transaksi. Jika tidak ada yang masuk, biarkan saja — pengembalian dana berjalan otomatis.

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"

Pengembalian dana tidak perlu Anda minta

Aktivasi yang tidak pernah menerima pesan akan ditutup dan dikreditkan kembali penuh oleh proses sapu kedaluwarsa, dalam satu menit setelah expires_at. Membatalkan lebih awal hanyalah kemudahan, bukan keharusan.

04

Endpoint

Tiga belas endpoint. Semua yang ada di bawah /activations dan /rentals butuh kunci dengan scope yang sesuai; pembacaan katalog tidak butuh kredensial sama sekali.

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.

Parameter

POST /activations
ParameterTipeDiDeskripsi
servicewajibstringbodyService 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.

Catatan

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

Request

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

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

Parameter

GET /activations
ParameterTipeDiDeskripsi
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.

Catatan

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

Request

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

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

Parameter

GET /activations/{id}
ParameterTipeDiDeskripsi
idwajibstringpathThe 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.

Catatan

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

Request

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

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

Parameter

POST /activations/{id}/complete
ParameterTipeDiDeskripsi
idwajibstringpathThe activation id.

Catatan

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

Request

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

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

Error yang bisa dikembalikan

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.

Parameter

POST /activations/{id}/cancel
ParameterTipeDiDeskripsi
idwajibstringpathThe activation id.

Catatan

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

Request

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

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

Parameter

POST /activations/{id}/resend
ParameterTipeDiDeskripsi
idwajibstringpathThe activation id.

Catatan

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

Request

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

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

Parameter

Endpoint ini tidak menerima parameter.

Catatan

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

Request

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

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

Error yang bisa dikembalikan

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.

Parameter

POST /rentals
ParameterTipeDiDeskripsi
countrywajibstringbodyISO 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_hourswajibintegerbodyOne 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.

Catatan

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

Request

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

Respons201 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/pricesTanpa kunci

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.

Parameter

GET /prices
ParameterTipeDiDeskripsi
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.

Catatan

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

Request

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

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

Error yang bisa dikembalikan

GET/countriesTanpa kunci

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.

Parameter

Endpoint ini tidak menerima parameter.

Catatan

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

Request

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

Respons200 OK

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

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.

Parameter

Endpoint ini tidak menerima parameter.

Catatan

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

Request

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

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

Parameter

Endpoint ini tidak menerima parameter.

Catatan

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

Request

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

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

Error yang bisa dikembalikan

GET/meKunci apa pun

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.

Parameter

Endpoint ini tidak menerima parameter.

Catatan

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

Request

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

Respons200 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

Error

Error memakai kode status HTTP standar dan field code yang stabil serta terbaca mesin. Jangan pernah mengurai pesan yang ditujukan untuk manusia.

Objek error

Bentuknya sama pada setiap kegagalan. Beberapa kode menambah field — insufficient_funds membawa nominalnya, rate_limited membawa retry_after.

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

Semua kode yang dikembalikan API

Ini daftar lengkapnya. Apa pun di luar daftar ini adalah bug di sisi kami, dan kami ingin mendengarnya.

Error
KodeHTTPKapan terjadiTindakan
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

Idempotensi

Kirim header Idempotency-Key pada request POST. Mengulang kunci yang sama dalam 24 jam mengembalikan respons asli alih-alih membeli nomor kedua.

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

Aturan

  • 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

Batas laju

Secara default 120 request per menit per kunci, burst hingga 20 per detik. Batas dikembalikan pada header X-RateLimit-*. Minta dukungan untuk menaikkannya.

Batas laju
PermukaanBatasBurstCatatan
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.

Header respons

Setiap respons yang dihitung kuotanya membawa tiga header di bawah ini. Baca header tersebut alih-alih menghitung permintaan sendiri.

Header respons
HeaderArti
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}}

Cara tetap di bawah batas

  • 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

Webhook

Daftarkan endpoint HTTPS dan terima event saat terjadi. Tanda tangannya adalah HMAC-SHA256 dari body mentah, pada header X-VSN-Signature.

Peristiwa

Berlangganan per endpoint. Sebuah endpoint hanya menerima peristiwa yang didaftarkan untuknya, tidak lebih.

Peristiwa
PeristiwaKapan dipicukunci data
sms.received

SMS diterima

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

Aktivasi selesai

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

Aktivasi kedaluwarsa

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

Pembayaran dikonfirmasi

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

Saldo di bawah ambang

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

Satu pengiriman, lengkap

Body-nya berupa JSON dengan id, event, createdAt, dan data. Semua yang spesifik per peristiwa berada di dalam 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"
  }
}

Header pengiriman

Header pengiriman
HeaderArti
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.

Memverifikasi tanda tangan

Hitung HMAC-SHA256 atas <timestamp>.<raw body> dengan secret endpoint lalu bandingkan dalam waktu konstan. Tandatangani byte mentahnya, sebelum parsing JSON apa pun — body yang diserialisasi ulang tidak akan cocok.

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

Percobaan ulang

Kami menunggu sepuluh detik untuk respons 2xx. Selain itu diulang hingga enam kali, dengan jeda berikut:

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

Setiap percobaan membawa id X-VSN-Delivery yang sama, jadi cukup memakainya sebagai kunci untuk membuat handler Anda idempoten. Endpoint yang gagal selama satu hari penuh dinonaktifkan otomatis dan harus diaktifkan lagi dari dasbor.

Kelola endpoint

09

Kompatibilitas lawas

Skrip yang ditulis untuk protokol handler_api.php klasik berjalan tanpa perubahan: arahkan ke endpoint kami dan ganti api_key. Respons memakai format token ACCESS_/STATUS_ yang sama.

Base 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

Pemetaan action

Setiap action yang kami terapkan, beserta token atau bentuk JSON persis yang dijawabnya. Apa pun di luar tabel ini mengembalikan BAD_ACTION.

Pemetaan action
ActionParameterBerhasilGagalCatatan
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.

Token error

Token error
TokenArti
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.

Yang perlu diketahui sebelum mengandalkannya

  • 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

Dibangun untuk agen AI

API ini menyediakan dokumen OpenAPI 3.1, indeks llms.txt, dan server MCP terkelola sehingga agen dapat menemukan dan memanggilnya tanpa kode perekat buatan tangan.

Arahkan agen ke server MCP

JSON-RPC 2.0 melalui HTTP di /api/mcp. initialize dan tools/list terbuka; tools/call butuh kunci di header Authorization.

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

Alat agen yang tersedia

Lima tool, dan itu disengaja. Membaca dan membelanjakan adalah panggilan terpisah, jadi kunci ber-scope tetap membatasi apa yang bisa dilakukan agen dengannya.

Alat agen yang tersedia
ToolDeskripsiWajibOpsional
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": {} }
  }
]

Contoh lengkap: agen menyelesaikan sebuah pendaftaran

Seluruh alurnya sebagaimana melintas di jaringan, dari handshake sampai nomor dilepas.

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.

Beri agen kunci tersendiri

Batasi ke numbers:read dan numbers:write, biarkan rentals:write mati kecuali agen benar-benar membutuhkannya, dan tetapkan masa berlaku. Agen yang memegang kunci dengan scope penuh bisa menyewa nomor berbulan-bulan karena kekeliruan.

11

Pustaka klien

Pembungkus tipis tanpa dependensi. Atau cukup pakai fetch — API-nya hanya enam endpoint.

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

Ada yang kurang di halaman ini?

Tiket dibaca oleh insinyur yang sama yang mengelola kumpulan operator. Kirim endpoint, stempel waktu, dan body permintaan — biasanya itu cukup untuk dijawab pada balasan pertama.

Hubungi dukungan