Skip to content

Reference

API documentation

Order numbers, poll for codes, and manage rentals over plain REST. Every endpoint accepts GET or POST and returns JSON.

Authentication

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"

Already using another provider?

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.

A complete verification

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

Order status codes

1pendingWaiting for the SMS to arrive.
2expiredThe window closed without a code. Refunded automatically.
3completedA code arrived and is available on the order.
4resendA resend was requested.
5cancelledYou cancelled the order.
6refundedThe order was refunded.
7processingThe order is being fulfilled.
8activatingThe number is being activated.

Endpoints

Account

POST/api/v1/request/balance

Retrieve your current balance in USD.

{ "balance": "48.06" }

Catalogue

GET/api/v1/country/retrieve_allpublic

Every supported country. No authentication required.

[
  { "ID": 1, "name": "United States", "short_name": "US", "region": "North America" }
]
GET/api/v1/service/retrieve_allpublic

Every supported service.

country
Limit to services available in this country.
[
  { "ID": 1, "name": "Telegram", "favourite": 1 }
]
POST/api/v1/request/price

Price and historical success rate for a service and country.

country*
Country ID, ISO code, or name.
service*
Service ID or name.
pool
Restrict to a specific supplier pool.
{ "pool": 2, "price": "1.94", "high_price": "1.94", "success_rate": 87 }

Ordering

POST/api/v1/purchase/sms

Order a number. Your balance is debited immediately and refunded automatically if no code arrives.

country*
Country ID, ISO code, or name.
service*
Service ID or name.
pool
Supplier pool ID.
max_price
Decline if the price exceeds this, in dollars.
pricing_option
0 for cheapest, 1 for highest success rate.
activation_type
SMS, VOICE or FLASH. Defaults to SMS.
areacode
JSON array of area codes, e.g. [415].
{
  "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
}
POST/api/v1/sms/check

Poll one order. Always returns HTTP 200 for a known order — branch on `status`, not the status code.

orderid*
The order_id from your purchase.
// waiting
{ "status": 1, "resend": 0, "time_left": 1188 }

// code arrived
{
  "status": 3,
  "sms": "140722",
  "full_sms": "140722 is your Telegram verification code."
}
POST/api/v1/request/active

Every 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
  }
]
POST/api/v1/sms/cancel

Cancel an order and refund it. Orders that already received a code cannot be refunded.

orderid*
The order to cancel.
{ "success": 1, "message": "The order has been cancelled, and you have been refunded 1.22 dollars." }
POST/api/v1/sms/resend

Request the SMS again. Returns a NEW order_id — poll that one, not the original.

orderid*
The order to resend.
{ "success": 1, "order_id": "QRXWQWZK", "charge": 0 }

Long-term rentals

GET/api/v1/rental/retrieve_all

Every country a number can be rented in. Rentals are identified by ISO country code.

{
  "success": 1,
  "data": [{ "id": "GB", "name": "United Kingdom" }]
}
GET/api/v1/rental/retrieve_pricing

Durations available for a country, with the price and quality band for each.

id*
Country code, e.g. GB.
{
  "success": 1,
  "pricing": { "7": "41.88", "30": "82.38" },
  "plans": [
    { "days": 7, "cost": "41.88", "quality": "good", "success_rate": 90, "in_stock": true }
  ]
}
POST/api/v1/purchase/rental

Rent a number for a fixed term. It receives unlimited messages for the whole period.

id*
Country code, e.g. GB.
days*
Term length, from retrieve_pricing.
max_price
Refuse the rental above this price.
{
  "success": 1,
  "rental_code": "SNBW9MPY",
  "phonenumber": "44839572864",
  "days": 7,
  "expiration_date": 1789600000,
  "can_extend": true,
  "can_reset": true
}
GET/api/v1/rental/retrieve

Every rental still inside its term.

[
  { "rental_code": "SNBW9MPY", "phonenumber": "44839572864", "expires_in": 601200 }
]
GET/api/v1/rental/retrieve_messages

Every message received on the number. Rentals carry full text, so the code is one we recovered from it.

rental_code*
The rental to read.
{
  "success": 1,
  "messages": [
    { "sender": "Telegram", "message": "Login code: 88213", "code": "88213" }
  ]
}
POST/api/v1/rental/extend

Buy more days on the same number. Check can_extend first — not every number allows it.

rental_code*
The rental to extend.
days*
Additional days to add.
{ "success": 1, "extension_cost": "41.88", "expiration_date": 1790204800 }
POST/api/v1/rental/reset

Reload the inbound route on a number that has stopped receiving. Check can_reset first.

rental_code*
The rental to reset.
{ "success": 1, "message": "The inbound route has been reset." }
POST/api/v1/rental/refund

Give the number back. A refund is only possible within a short window and before any message arrives — the response says what was actually credited.

rental_code*
The rental to cancel.
{ "success": 1, "refunded": "41.88", "refunded_in_cents": 4188 }

Email accounts

GET/api/v1/email/products

Every 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
    }
  ]
}
POST/api/v1/email/purchase

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

product*
Product id, e.g. outlook-trusted.
quantity
How many. Defaults to 1, max 500.
max_price
Refuse above this price per account.
{
  "success": 1,
  "order_id": "K2M4PQ7X",
  "requested": 10,
  "delivered": 7,
  "charged": "2.94",
  "refunded": "1.26",
  "accounts": [
    {
      "email": "…@outlook.com",
      "password": "…",
      "refresh_token": "…",
      "client_id": "…"
    }
  ]
}
GET/api/v1/email/accounts

Every account ever delivered to you, with its credentials. Pass order_id to narrow it to one order. These do not expire and stay readable.

order_id
Limit to one order.
{
  "success": 1,
  "accounts": [{ "id": "acct_…", "email": "…@outlook.com", "password": "…" }]
}
GET/api/v1/email/inbox

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

email*
The mailbox to read.
folder
inbox (default) or junk.
since
Unix seconds; ignore anything older.
{
  "success": 1,
  "message": {
    "from": "[email protected]",
    "subject": "Your verification code",
    "text": "Your code is 553311",
    "received_at": 1789600000000
  }
}

Webhooks

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.

Errors

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