/api/v1/request/balanceRetrieve your current balance in USD.
{ "balance": "48.06" }Reference
Order numbers, poll for codes, and manage rentals over plain REST. Every endpoint accepts GET or POST and returns JSON.
Send your API key as a bearer token, an X-API-Key header, or a key parameter — whichever your client already does. Find your key in settings.
curl https://numera.app/api/v1/request/balance \
-H "Authorization: Bearer $NUMERA_KEY"
# equivalently
curl https://numera.app/api/v1/request/balance -d "key=$NUMERA_KEY"This API deliberately follows the conventions most verification APIs share — the same parameter names, the same response shapes, the same status codes. Migrating is usually just pointing your base URL at numera.app/api/v1 and swapping the key.
Order a number, poll until the code lands, and you are done.
const key = process.env.NUMERA_KEY;
const api = "https://numera.app/api/v1";
// 1. Order a number
const order = await fetch(`${api}/purchase/sms`, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body: new URLSearchParams({ country: "US", service: "telegram" }),
}).then((r) => r.json());
console.log(order.phonenumber); // use this to sign up
// 2. Poll until the code arrives
while (true) {
const check = await fetch(`${api}/sms/check`, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body: new URLSearchParams({ orderid: order.order_id }),
}).then((r) => r.json());
if (check.status === 3) {
console.log("code:", check.sms);
break;
}
if ([2, 5, 6].includes(check.status)) {
throw new Error("Order expired or was refunded");
}
await new Promise((r) => setTimeout(r, 3000));
}| 1 | pending | Waiting for the SMS to arrive. |
| 2 | expired | The window closed without a code. Refunded automatically. |
| 3 | completed | A code arrived and is available on the order. |
| 4 | resend | A resend was requested. |
| 5 | cancelled | You cancelled the order. |
| 6 | refunded | The order was refunded. |
| 7 | processing | The order is being fulfilled. |
| 8 | activating | The number is being activated. |
/api/v1/request/balanceRetrieve your current balance in USD.
{ "balance": "48.06" }/api/v1/country/retrieve_allpublicEvery supported country. No authentication required.
[
{ "ID": 1, "name": "United States", "short_name": "US", "region": "North America" }
]/api/v1/service/retrieve_allpublicEvery supported service.
[
{ "ID": 1, "name": "Telegram", "favourite": 1 }
]/api/v1/request/pricePrice and historical success rate for a service and country.
{ "pool": 2, "price": "1.94", "high_price": "1.94", "success_rate": 87 }/api/v1/purchase/smsOrder a number. Your balance is debited immediately and refunded automatically if no code arrives.
{
"success": 1,
"number": 19550925198,
"cc": "1",
"phonenumber": "9550925198",
"order_id": "JRJ5UEXS",
"country": "United States",
"service": "Telegram",
"expires_in": 1198,
"cost": "1.94",
"cost_in_cents": 194
}/api/v1/sms/checkPoll one order. Always returns HTTP 200 for a known order — branch on `status`, not the status code.
// waiting
{ "status": 1, "resend": 0, "time_left": 1188 }
// code arrived
{
"status": 3,
"sms": "140722",
"full_sms": "140722 is your Telegram verification code."
}/api/v1/request/activeEvery outstanding order in a single call. Prefer this over /sms/check when polling more than one order.
[
{
"order_code": "JRJ5UEXS",
"phonenumber": "19550925198",
"code": "140722",
"status": "completed",
"time_left": 1102
}
]/api/v1/sms/cancelCancel an order and refund it. Orders that already received a code cannot be refunded.
{ "success": 1, "message": "The order has been cancelled, and you have been refunded 1.22 dollars." }/api/v1/sms/resendRequest the SMS again. Returns a NEW order_id — poll that one, not the original.
{ "success": 1, "order_id": "QRXWQWZK", "charge": 0 }/api/v1/rental/retrieve_allEvery country a number can be rented in. Rentals are identified by ISO country code.
{
"success": 1,
"data": [{ "id": "GB", "name": "United Kingdom" }]
}/api/v1/rental/retrieve_pricingDurations available for a country, with the price and quality band for each.
{
"success": 1,
"pricing": { "7": "41.88", "30": "82.38" },
"plans": [
{ "days": 7, "cost": "41.88", "quality": "good", "success_rate": 90, "in_stock": true }
]
}/api/v1/purchase/rentalRent a number for a fixed term. It receives unlimited messages for the whole period.
{
"success": 1,
"rental_code": "SNBW9MPY",
"phonenumber": "44839572864",
"days": 7,
"expiration_date": 1789600000,
"can_extend": true,
"can_reset": true
}/api/v1/rental/retrieveEvery rental still inside its term.
[
{ "rental_code": "SNBW9MPY", "phonenumber": "44839572864", "expires_in": 601200 }
]/api/v1/rental/retrieve_messagesEvery message received on the number. Rentals carry full text, so the code is one we recovered from it.
{
"success": 1,
"messages": [
{ "sender": "Telegram", "message": "Login code: 88213", "code": "88213" }
]
}/api/v1/rental/extendBuy more days on the same number. Check can_extend first — not every number allows it.
{ "success": 1, "extension_cost": "41.88", "expiration_date": 1790204800 }/api/v1/rental/resetReload the inbound route on a number that has stopped receiving. Check can_reset first.
{ "success": 1, "message": "The inbound route has been reset." }/api/v1/rental/refundGive the number back. A refund is only possible within a short window and before any message arrives — the response says what was actually credited.
{ "success": 1, "refunded": "41.88", "refunded_in_cents": 4188 }/api/v1/email/productsEvery mailbox type on sale, priced per account.
{
"success": 1,
"data": [
{
"id": "outlook-trusted",
"name": "Outlook — Trusted",
"price": "0.42",
"in_stock": true,
"stock_level": "plenty",
"api_access": false
}
]
}/api/v1/email/purchaseBuy accounts in bulk. If fewer are available than you asked for you get what there is, and the difference is refunded before this responds — always check delivered against requested.
{
"success": 1,
"order_id": "K2M4PQ7X",
"requested": 10,
"delivered": 7,
"charged": "2.94",
"refunded": "1.26",
"accounts": [
{
"email": "…@outlook.com",
"password": "…",
"refresh_token": "…",
"client_id": "…"
}
]
}/api/v1/email/accountsEvery account ever delivered to you, with its credentials. Pass order_id to narrow it to one order. These do not expire and stay readable.
{
"success": 1,
"accounts": [{ "id": "acct_…", "email": "…@outlook.com", "password": "…" }]
}/api/v1/email/inboxThe newest message in a mailbox you own. Poll it exactly as you would poll for an SMS code. An empty mailbox returns message: null, which is not an error.
{
"success": 1,
"message": {
"from": "[email protected]",
"subject": "Your verification code",
"text": "Your code is 553311",
"received_at": 1789600000000
}
}Rather than polling, set a webhook URL in settings and we will POST the code the moment it arrives. HTTPS only.
POST https://your-app.com/hooks/numera
X-Numera-Event: sms.received
X-Numera-Signature: 9f2c1a… // HMAC-SHA256 of the raw body, keyed with your API key
{
"orderid": "JRJ5UEXS",
"sms": "140722",
"full_sms": "140722 is your Telegram verification code.",
"timestamp": "2026-09-14 07:42:11pm"
}Rented numbers fire the same way for every message they receive — which is the point of renting one, since nobody should have to sit watching it for days. Switch on X-Numera-Event to tell them apart.
X-Numera-Event: rental.received
{
"rental_code": "SNBW9MPY",
"phonenumber": "44839572864",
"full_sms": "Login code: 88213",
"timestamp": "2026-09-14 07:42:11pm"
}Verify the signature before trusting the payload:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, signature, apiKey) {
const expected = createHmac("sha256", apiKey).update(rawBody).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(signature, "hex");
// Length check first: timingSafeEqual throws on a length mismatch.
return a.length === b.length && timingSafeEqual(a, b);
}Respond with any 2xx to acknowledge. After 50 consecutive failures delivery is disabled; saving the URL again re-enables it.
Validation failures name the offending parameter. Business failures carry a message, and ordering failures add a machine-readable type.
// 400 — missing or invalid parameter
{
"success": 0,
"errors": [
{ "message": "You are missing a required parameter for this request.", "param": "country" }
]
}
// 402 — not enough balance
{ "success": 0, "message": "Insufficient balance…", "type": "BALANCE_ERROR" }
// 409 — nothing in stock for that pairing
{ "success": 0, "message": "No numbers are available…", "type": "OUT_OF_STOCK" }
// 429 — over the rate limit (32 requests/second)
{ "success": 0, "errors": [{ "message": "Rate limit exceeded. Slow down and try again." }] }