Quickstart
- Get a keyLog in, open Dashboard → API and click Generate API key. Copy it: it is shown only once.
- Add fundsThe API spends your normal balance. Top up by card or USDC / SOL in the dashboard.
- Buy, read, doneBuy a number, put it in the app you are verifying, poll for the code (or use a webhook), then finish or cancel.
curl -X POST https://noirsms.com/api/v1/numbers \
-H "Authorization: Bearer nsk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"service": "wa", "country": "us", "max_price": 1.50}'Authentication
Every request needs your API key, either as a bearer token or in the X-API-Key header. Keys start with nsk_live_. One key per account; regenerating it in the dashboard disables the old one at once. Never put the key in a browser or mobile app: call the API from your server.
Authorization: Bearer nsk_live_YOUR_KEY
# or
X-API-Key: nsk_live_YOUR_KEYBase URL: https://noirsms.com/api/v1. All bodies are JSON, all prices are in US dollars, all times are ISO 8601 in UTC.
Machine-readable spec: /api/openapi.json (OpenAPI 3.1). Import it into Postman or Insomnia, or generate a client in your language.
How a purchase works
- waiting: the number is yours for 20 minutes (
expires_at). Enter it in the app and wait for the SMS. - received: at least one SMS arrived; the code is in
sms[].codeand the full text insms[].text. You can ask for another SMS on the same number with/resendwhile it is open. - canceled: you canceled it (allowed from
cancelable_at, 2 minutes after buying) and the price went back to your balance. - expired: no SMS in 20 minutes; the price went back to your balance automatically.
The price is calculated on our side at the moment of purchase; send max_price to refuse the purchase if the current price is higher than you expect.
Endpoints
GET/balance
Your current balance.
curl https://noirsms.com/api/v1/balance -H "Authorization: Bearer nsk_live_YOUR_KEY"{ "balance": 24.37, "currency": "USD", "status": "active" }GET/countries
Countries with numbers in stock right now and how many services each has.
curl https://noirsms.com/api/v1/countries -H "Authorization: Bearer nsk_live_YOUR_KEY"{ "server": 1, "countries": [ { "country": "gb", "services": 382 }, { "country": "us", "services": 264 } ] }GET/services?country=us
Services in stock for one country, with the price you pay per number and the stock.
Use the service code (for example wa = WhatsApp, tg = Telegram, go = Google) when you buy.
| Parameter | Type | Description |
|---|---|---|
country | string | 2-letter ISO code, default us. |
curl "https://noirsms.com/api/v1/services?country=us" -H "Authorization: Bearer nsk_live_YOUR_KEY"{
"server": 1, "country": "us", "currency": "USD",
"services": [
{ "service": "wa", "name": "Whatsapp", "price": 0.73, "stock": 91562 },
{ "service": "tg", "name": "Telegram", "price": 0.65, "stock": 40211 }
]
}POST/numbers
Buy a number.
The price is debited from your balance and refunded automatically if no SMS arrives. Responds 201 with the number.
| Parameter | Type | Description |
|---|---|---|
service | string | Service code from /services. Required. |
country | string | 2-letter ISO code. Required. |
max_price | number | Optional. Refuse (HTTP 409 price_above_max) if the current price is higher. |
curl -X POST https://noirsms.com/api/v1/numbers \
-H "Authorization: Bearer nsk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"service": "wa", "country": "us", "max_price": 1.50}'{
"id": "6026e15f-088d-45b5-bf22-70ed0b417afa",
"phone": "12025550147",
"service": "wa",
"country": "us",
"server": 1,
"price": 0.73,
"currency": "USD",
"status": "waiting",
"created_at": "2026-10-14T07:05:10Z",
"expires_at": "2026-10-14T07:25:10Z",
"cancelable_at": "2026-10-14T07:07:10Z",
"sms": []
}GET/numbers/{id}
Status of one number and every SMS it received.
Checks the carrier in real time while the number is open. Poll every 3 to 5 seconds until status is received.
curl https://noirsms.com/api/v1/numbers/6026e15f-088d-45b5-bf22-70ed0b417afa \
-H "Authorization: Bearer nsk_live_YOUR_KEY"{
"id": "6026e15f-088d-45b5-bf22-70ed0b417afa",
"phone": "12025550147", "service": "wa", "country": "us",
"status": "received", "price": 0.73, "currency": "USD",
"expires_at": "2026-10-14T07:25:10Z",
"sms": [ { "code": "482915", "text": "Your WhatsApp code: 482-915", "received_at": "2026-10-14T07:06:02Z" } ]
}GET/numbers
Numbers you bought through the API, newest first.
| Parameter | Type | Description |
|---|---|---|
status | string | active (default: open numbers), waiting, received, canceled, expired or all. |
limit | integer | 1 to 100, default 50. |
before | timestamp | Pagination: numbers created before this time (use the last created_at). |
curl "https://noirsms.com/api/v1/numbers?status=active&limit=20" -H "Authorization: Bearer nsk_live_YOUR_KEY"{ "data": [ { "id": "…", "status": "waiting", "sms": [] } ], "has_more": false }POST/numbers/{id}/cancel
Cancel a number that has not received an SMS and get the price back.
Allowed from cancelable_at (2 minutes after buying). If the SMS arrived in the meantime, nothing is refunded and the code is returned.
curl -X POST https://noirsms.com/api/v1/numbers/{id}/cancel -H "Authorization: Bearer nsk_live_YOUR_KEY"{ "id": "…", "status": "canceled", "refunded": true, "sms": [] }POST/numbers/{id}/resend
Ask for another SMS on the same number, free, while it is open.
Only after the first SMS arrived. Some carriers refuse it; you then get resend_refused.
curl -X POST https://noirsms.com/api/v1/numbers/{id}/resend -H "Authorization: Bearer nsk_live_YOUR_KEY"{ "id": "…", "status": "received", "sms": [ … ] }POST/numbers/{id}/finish
Tell us you are done with a number that received its code.
Optional, but it releases the number sooner.
curl -X POST https://noirsms.com/api/v1/numbers/{id}/finish -H "Authorization: Bearer nsk_live_YOUR_KEY"{ "id": "…", "status": "received", "sms": [ … ] }Webhooks
Set an HTTPS URL on the API page of your dashboard and we POST sms.received as soon as we detect an SMS on a number you bought through the API (we retry once after 3 seconds if your server fails). Respond with any 2xx status.
{
"type": "sms.received",
"created_at": "2026-10-14T07:06:03Z",
"data": {
"number_id": "6026e15f-088d-45b5-bf22-70ed0b417afa",
"phone": "12025550147", "service": "wa", "country": "us",
"status": "received", "expires_at": "2026-10-14T07:25:10Z",
"sms": { "code": "482915", "text": "Your WhatsApp code: 482-915", "received_at": "2026-10-14T07:06:02Z" }
}
}Every webhook carries X-NoirSMS-Signature: t=<unix time>,v1=<signature>, where the signature is the hex HMAC-SHA256 of "<t>.<raw body>" with your webhook secret (shown in the dashboard). Check it, and reject timestamps older than 5 minutes.
import crypto from 'node:crypto';
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}import hmac, hashlib, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])Errors
Errors use the HTTP status code and a JSON body with a stable code you can switch on:
{ "error": { "code": "insufficient_balance", "message": "Insufficient balance. You need $0.73.", "needed": 0.73 } }| HTTP | code | Meaning |
|---|---|---|
| 401 | invalid_api_key | Missing, wrong or revoked key. |
| 402 | insufficient_balance | Not enough balance; needed has the price. |
| 409 | price_above_max | Current price is above your max_price. |
| 400 | no_numbers | No stock for this service and country right now. You were not charged. |
| 400 | too_early | Cancel is allowed 2 minutes after buying; see waitSeconds. |
| 400 | already_closed | The number was already canceled or expired. |
| 400 | invalid_request / invalid_country | Check the service and country codes. |
| 403 | account_suspended | The account is suspended. Contact support. |
| 429 | rate_limited | More than 120 requests in a minute. |
| 502 / 503 | provider_error / restocking | Temporary carrier problem. You were not charged; retry shortly. |
Limits & rules
- 120 requests per minute per key. Poll a number every 3 to 5 seconds, not faster.
- Each number is for one verification and stays reserved for 20 minutes. SMS only: no calls, no outgoing messages.
- Prices follow supply and demand and can change during the day; always read them from
/servicesor usemax_price. - Use the API only for your own legitimate accounts or, as a reseller, for customers who follow the same Acceptable Use Policy. Bulk or automated account creation, spam, ban evasion and bypassing bank, fintech or identity (KYC) checks are prohibited and lead to account closure.
High volume
Buying numbers every day for your platform, panel or bot? Open a ticket in the Support area of your dashboard with your approximate monthly volume, the services and countries you use and what your platform does. We review each request and can offer special conditions for medium and high volume.
API FAQ
How much does the API cost?
Nothing extra. Each number costs the same as in the dashboard and is paid from the same balance. If no SMS arrives within 20 minutes, the charge goes back to your balance automatically.
Can I resell numbers with the API?
Yes. You can offer SMS verification inside your own platform or panel and set your own prices. Your customers must follow the same Acceptable Use Policy, and you are responsible for abuse on your platform.
Is there a sandbox?
There is no separate sandbox. Use GET endpoints (balance, services, countries) freely; a purchase uses real balance but is refunded automatically if no SMS arrives, and you can cancel it 2 minutes after buying.
How fast do I get the code?
Usually within seconds of the app sending it. Poll GET /numbers/{id} every 3 to 5 seconds, or set a webhook in the dashboard to receive sms.received as soon as we detect the SMS.
What is the rate limit?
120 requests per minute per API key. Over the limit you get HTTP 429 with the code rate_limited; wait a few seconds and retry.
Are there special conditions for high volume?
Yes, on request. If you buy medium or high volume, open a ticket in the Support area of your dashboard with your monthly volume, services and countries, and we will review special conditions for your account.
Is there an OpenAPI spec?
Yes, at https://noirsms.com/api/openapi.json (OpenAPI 3.1). Import it into Postman, Insomnia or a code generator to get a ready client.