مرجع الـ API

واجهة المطوّرين البرمجية

واجهة REST واحدة لشراء الأرقام وقراءة الرموز وإدارة الرصيد — إضافة إلى طبقة توافق جاهزة لبرامج handler_api القديمة.

العنوان الأساسيv1.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 token أو في ترويسة 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 مع خدمة، ودولة اختياريًا. تحصل على معرّف ورقم هاتف ومهلة عشرين دقيقة.

  2. 2

    الانتظار

    GET /activations/{id}?wait=180 يُبقي الاتصال مفتوحًا ويجيب لحظة وصول الرسالة. بلا حلقة استعلام.

  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

أرسل ترويسة 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

Webhooks

سجّل نقطة نهاية HTTPS واستقبل الأحداث فور وقوعها. التوقيع هو HMAC-SHA256 للجسم الخام، في ترويسة X-VSN-Signature.

الأحداث

الاشتراك يتم لكل نقطة نهاية على حدة. ولا تستقبل نقطة النهاية سوى الأحداث المسجَّلة لها، ولا شيء غيرها.

الأحداث
الحدثمتى يُطلقمفاتيح data
sms.received

وصلت رسالة

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

تحمل كل محاولة معرّف X-VSN-Delivery نفسه، لذا يكفي الاعتماد عليه لجعل المعالج لديك مُتماثل الاستدعاء. وأي نقطة نهاية ظلت تُخفق يومًا كاملًا تُعطَّل تلقائيًا ويجب إعادة تفعيلها من لوحة التحكم.

إدارة نقاط النهاية

09

التوافق مع الإصدارات القديمة

تعمل البرامج المكتوبة لبروتوكول handler_api.php الكلاسيكي دون تعديل: وجّهها إلى نقطة النهاية لدينا وبدّل قيمة api_key. وتستخدم الاستجابات صيغة الرموز ACCESS_/STATUS_ نفسها.

العنوان الأساسي
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

مبنية لوكلاء الذكاء الاصطناعي

توفّر الواجهة مستند 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 مباشرة — فالواجهة ست نقاط نهاية لا أكثر.

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

هل ينقص هذه الصفحة شيء؟

يقرأ التذاكرَ المهندسون أنفسهم الذين يديرون مجموعة المشغّلين. أرسل نقطة النهاية والطابع الزمني وجسم الطلب — عادةً ما يكفي ذلك للإجابة من أول رد.

التواصل مع الدعم