API reference
Programmatic access to your NumberHub wallet, numbers, rentals, eSIMs, and email OTPs.
Developer resources
Getting started
All endpoints are rooted here; every response is JSON.
Send your secret as a Bearer token on every request — the header only (a key in the URL would leak into logs). A key acts as your account and spends your wallet; keep it private. Manage keys in Account.
curl https://api.numberhub.io/v1/balance \
-H "Authorization: Bearer nh_live_..."SMS & Email are charge-on-receive: buying only holdsthe price; you’re billed once a code arrives — no code, no charge. Rent & eSIM are charged upfront. All amounts are USD; available = balance − held.
Versioning and asynchronous delivery
The major version is part of the base URL. Backward-compatible fields and endpoints may be added to v1; removals or incompatible semantic changes require a new major version and a migration notice in the changelog.
A unique Idempotency-Key is required on every purchase request. Keep the same key when retrying a timeout. The first result is retained for 24 hours and safely replayed for identical retries; reusing the key with a different route or body returns 409. This protects numbers, rentals, eSIMs, and email orders from duplicate charges after a timeout.
For push delivery, create a webhook and verify X-NumberHub-Signature as HMAC-SHA256 over timestamp.raw_body, using the exact raw request body and the X-NumberHub-Timestamp value. Compare the result as v1=<hex digest> and reject stale timestamps. Events contain the order id and status, not OTP contents; fetch the order with your API key after notification. Delivery is at least once, so deduplicate using the event delivery id. Failed attempts retry for seven days, with backoff capped at 24 hours. Use the delivery-history endpoint to diagnose failures and rotate a signing secret immediately if it may have leaked.
Objects
Every order response — SMS numbers, rentals, eSIMs, and emails — returns this same object (wrapped as number / rental / esim / email). The per-endpoint examples below highlight the fields that change for that call.
idintegerOrder id.kindstring"sms" · "rent" · "esim" · "email".servicestringService code (SMS/rent) or package code (eSIM).service_namestringHuman name of the service / package.countrystringCountry id / region code.country_namestringHuman country / region name.phonestring | nullNumber (SMS/rent), ICCID (eSIM), or inbox address (email).statusstringpending · waiting · received · completed · canceled · expired.status_labelstringHuman-readable status.pricestringAmount held/charged, e.g. "0.43" — pair with currency USD.codestring | nullLatest received code (null until one arrives).codesarrayEvery code received so far.created_atstringISO-8601 timestamp.expires_atstringISO-8601 expiry.esim { ready, qr, ac, smdp, matchingId, iccid, short_url }email { address, code }Account
Wallet/balanceWallet balance for the key’s owner. available = balance − held; held is reserved by open orders.
curl https://api.numberhub.io/v1/balance \
-H "Authorization: Bearer nh_live_..."{
"balance": "12.40",
"available": "9.15",
"held": "3.25",
"currency": "USD"
}/orders?limit=40Your most recent orders across all products (sms, rent, esim, email). limit is 1–100 (default 40).
curl "https://api.numberhub.io/v1/orders?limit=40" \
-H "Authorization: Bearer nh_live_..."{
"orders": [
{ "id": 10482, "kind": "sms", "service": "tg",
"country": "0", "status": "received",
"price": "0.43", "code": "1234", "codes": ["1234"] }
]
}SMS numbers
One-time codes/servicesSMS services you can buy a number for. code is what you pass to /numbers and /countries.
curl https://api.numberhub.io/v1/services \
-H "Authorization: Bearer nh_live_..."{
"services": [
{ "code": "tg", "name": "Messaging app" },
{ "code": "wa", "name": "Messaging app" },
{ "code": "go", "name": "Email provider" }
]
}/countries?service=tgCountries for a service with live sell price + stock. Out-of-stock entries queue when bought. Unknown service → 404.
curl "https://api.numberhub.io/v1/countries?service=tg" \
-H "Authorization: Bearer nh_live_..."{
"service": "tg",
"countries": [
{ "country": "0", "name": "Russia", "flag": "RU",
"price": "0.43", "in_stock": true },
{ "country": "187", "name": "United States", "flag": "US",
"price": "1.10", "in_stock": false }
]
}/top-countriesSmart Top 10 using 30-day demand, live stock, starting price, and delivery quality (refreshed ~10 min). Raw order volumes are private.
curl https://api.numberhub.io/v1/top-countries \
-H "Authorization: Bearer nh_live_..."{
"countries": [
{ "country": "151", "name": "Chile", "iso": "CL",
"from_price": "0.14", "in_stock_services": 8,
"delivery_rate": 96, "delivery_sample": 184 }
]
}/numbersBuy a number. The price is HELD, not charged — you’re billed only when a code arrives. Returns 201. Optional max_price (USD) is a safety cap — if the number’s price is above it, nothing is bought and you get 409 price_exceeded with the actual price. Optional queue (boolean, default true): false = synchronous request/response mode — one sourcing attempt, and if nothing is available right now you get 409 sold_out immediately with nothing created or held (retry whenever you like); true = out-of-stock buys become a background search that keeps hunting for up to 4 h. Up to 50 concurrent open orders per service+country (409 duplicate_order beyond).
Body: service, country, max_price?, queue?
curl -X POST https://api.numberhub.io/v1/numbers \
-H "Authorization: Bearer nh_live_..." \
-H "Idempotency-Key: order-550e8400-e29b-41d4-a716" \
-H "Content-Type: application/json" \
-d '{"service":"tg","country":"0","max_price":"1.00"}'{
"number": {
"id": 10482, "kind": "sms",
"service": "tg", "service_name": "Messaging app",
"country": "0", "country_name": "Russia",
"phone": "79991234567",
"status": "waiting", "status_label": "Waiting for code",
"price": "0.43", "code": null, "codes": [],
"created_at": "2026-06-16T12:00:00+00:00",
"expires_at": "2026-06-16T12:20:00+00:00"
}
}/numbers/{id}Poll one order. When a code lands, status → "received" and code / codes fill in (and you’re charged once).
curl https://api.numberhub.io/v1/numbers/10482 \
-H "Authorization: Bearer nh_live_..."{
"number": {
"id": 10482, "status": "received",
"status_label": "Code received",
"phone": "79991234567", "price": "0.43",
"code": "1234", "codes": ["1234"]
}
}/numbers/{id}Cancel + release the hold (no charge). Activation cancels are only accepted ~2 min after issue (else 409 cancel_locked).
curl -X DELETE https://api.numberhub.io/v1/numbers/10482 \
-H "Authorization: Bearer nh_live_..."{
"ok": true,
"number": { "id": 10482, "status": "canceled",
"status_label": "Cancelled (not charged)" }
}/numbers/{id}/anotherRequest another code on the same number. Places a fresh hold; 402 if available balance is too low.
curl -X POST https://api.numberhub.io/v1/numbers/10482/another \
-H "Authorization: Bearer nh_live_..."{ "ok": true, "number": { "id": 10482, "status": "waiting" } }/numbers/{id}/reactivateReuse a completed/expired number to receive a fresh code (billable, same charge-on-receive flow).
curl -X POST https://api.numberhub.io/v1/numbers/10482/reactivate \
-H "Authorization: Bearer nh_live_..."{ "ok": true, "number": { "id": 10482, "status": "waiting" } }Rent
Long-lived numbers/rent/durationsAvailable rental durations. h (hours) is the value used in the routes below.
curl https://api.numberhub.io/v1/rent/durations \
-H "Authorization: Bearer nh_live_..."{
"durations": [
{ "h": 4, "label": "4 hours" },
{ "h": 24, "label": "1 day" },
{ "h": 168, "label": "1 week" }
]
}/rent/{h}/countriesCountries that have rentals in stock for a duration. Unknown h → 404.
curl https://api.numberhub.io/v1/rent/24/countries \
-H "Authorization: Bearer nh_live_..."{
"h": 24,
"countries": [ { "country": "0", "name": "Russia", "flag": "RU" } ]
}/rent/{h}/{country}/servicesRentable services + price for a duration + country.
curl https://api.numberhub.io/v1/rent/24/0/services \
-H "Authorization: Bearer nh_live_..."{
"h": 24, "country": "0",
"services": [ { "code": "tg", "name": "Messaging app", "price": "3.20" } ]
}/rent/{h}/{country}/{code}/quoteExact price + affordability before buying a rental.
curl https://api.numberhub.io/v1/rent/24/0/tg/quote \
-H "Authorization: Bearer nh_live_..."{
"h": 24, "country": "0", "code": "tg",
"service_name": "Messaging app", "country_name": "Russia", "flag": "RU",
"duration_label": "1 day", "price": "3.20",
"available": "9.15", "can_afford": true
}/rentalsRent a number for the whole period (charged UPFRONT). Returns 201 with the rental order.
Body: h, country, code
curl -X POST https://api.numberhub.io/v1/rentals \
-H "Authorization: Bearer nh_live_..." \
-H "Idempotency-Key: rent-550e8400-e29b-41d4-a716" \
-H "Content-Type: application/json" \
-d '{"h":24,"country":"0","code":"tg"}'{
"rental": {
"id": 10590, "kind": "rent", "service": "tg",
"country": "0", "phone": "79990001122",
"status": "waiting", "price": "3.20"
}
}/rentals/{id}Poll a rental for the codes it has received (a rental collects many codes for the period).
curl https://api.numberhub.io/v1/rentals/10590 \
-H "Authorization: Bearer nh_live_..."{
"rental": { "id": 10590, "status": "received",
"phone": "79990001122", "codes": ["1234", "5678"] }
}/rentals/{id}Cancel + refund — only inside the 2–20 min window (else 409 cancel_window_closed).
curl -X DELETE https://api.numberhub.io/v1/rentals/10590 \
-H "Authorization: Bearer nh_live_..."{ "ok": true, "rental": { "id": 10590, "status": "canceled" } }/rentals/{id}/finishEnd a rental early without a refund (frees the number).
curl -X POST https://api.numberhub.io/v1/rentals/10590/finish \
-H "Authorization: Bearer nh_live_..."{ "ok": true, "rental": { "id": 10590, "status": "completed" } }eSIM
Travel data/esim/regionsDestinations (countries + regional bundles) you can buy a data eSIM for.
curl https://api.numberhub.io/v1/esim/regions \
-H "Authorization: Bearer nh_live_..."{
"regions": [
{ "code": "US", "name": "United States", "flag": "US" },
{ "code": "EU", "name": "Europe", "flag": "EU" }
]
}/esim/regions/{region}/packagesData plans for a destination — pkg.code is what you pass to POST /esims.
curl https://api.numberhub.io/v1/esim/regions/US/packages \
-H "Authorization: Bearer nh_live_..."{
"region": "US", "region_name": "United States",
"packages": [
{ "code": "US_1GB_7D", "name": "USA 1GB 7Days",
"gb": "1 GB", "duration": 7, "unit": "day",
"price": "3.50", "scope": "Local",
"is_local": true, "countries": 1 }
]
}/esimsBuy an eSIM (charged upfront). The QR profile is usually ready within seconds — poll GET /esims/{id}.
Body: region, pkg
curl -X POST https://api.numberhub.io/v1/esims \
-H "Authorization: Bearer nh_live_..." \
-H "Idempotency-Key: esim-550e8400-e29b-41d4-a716" \
-H "Content-Type: application/json" \
-d '{"region":"US","pkg":"US_1GB_7D"}'{
"esim": {
"id": 10610, "kind": "esim", "status": "waiting",
"price": "3.50",
"esim": { "ready": false }
}
}/esims/{id}Poll for the installable profile: QR image, the LPA activation string, and the manual SM-DP+ / activation code.
curl https://api.numberhub.io/v1/esims/10610 \
-H "Authorization: Bearer nh_live_..."{
"esim": {
"id": 10610, "status": "received",
"esim": {
"ready": true,
"qr": "https://.../qr.png",
"ac": "LPA:1$rsp.example.com$ABC123",
"smdp": "rsp.example.com",
"matchingId": "ABC123",
"iccid": "8943...",
"short_url": "https://.../p/abc"
}
}
}Email OTP
Disposable inbox/email/sitesSites you can receive a sign-up OTP email for.
curl https://api.numberhub.io/v1/email/sites \
-H "Authorization: Bearer nh_live_..."{ "sites": [ { "code": "shop", "name": "Shopping site" } ] }/email/sites/{site}/domainsAvailable inbox domains + price for a site. domain is optional on buy (cheapest in stock is used).
curl https://api.numberhub.io/v1/email/sites/shop/domains \
-H "Authorization: Bearer nh_live_..."{
"site": "shop",
"site_name": "Shopping site",
"domains": [ { "domain": "inboxes.com", "price": "0.20", "in_stock": true } ]
}/emailsBuy a disposable inbox (charge-on-receive). Returns 201 with the order + mailbox address.
Body: site, domain?
curl -X POST https://api.numberhub.io/v1/emails \
-H "Authorization: Bearer nh_live_..." \
-H "Idempotency-Key: email-550e8400-e29b-41d4-a716" \
-H "Content-Type: application/json" \
-d '{"site":"amazon"}'{
"email": {
"id": 10700, "kind": "email", "status": "waiting",
"price": "0.20",
"email": { "address": "[email protected]", "code": null }
}
}/emails/{id}Poll the inbox. When the OTP email lands, code fills in and you’re charged once.
curl https://api.numberhub.io/v1/emails/10700 \
-H "Authorization: Bearer nh_live_..."{
"email": {
"id": 10700, "status": "received",
"email": { "address": "[email protected]", "code": "549182" }
}
}Webhooks
Signed events/webhooksList active webhook endpoints and delivery health. Signing secrets are never returned after creation.
curl https://api.numberhub.io/v1/webhooks \
-H "Authorization: Bearer nh_live_..."{
"webhooks": [
{ "id": 12, "url": "https://example.com/numberhub-events",
"events": ["order.*"], "active": true,
"failure_count": 0, "last_success_at": null,
"secret_hint": "whsec_abc…" }
]
}/webhooksRegister a public HTTPS endpoint. events defaults to ["order.*"]. The whsec_ signing secret is returned once; save it securely. Up to five active endpoints per account.
Body: url, events?
curl -X POST https://api.numberhub.io/v1/webhooks \
-H "Authorization: Bearer nh_live_..." \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/numberhub-events","events":["order.*"]}'{
"webhook": {
"id": 12, "url": "https://example.com/numberhub-events",
"events": ["order.*"],
"secret": "whsec_save_this_once"
}
}/webhooks/{id}/testQueue a webhook.test event so you can validate networking and signature verification.
curl -X POST https://api.numberhub.io/v1/webhooks/12/test \
-H "Authorization: Bearer nh_live_..."{ "queued": true }/webhooks/{id}/rotateReplace the signing secret immediately. The new secret is returned once; update your receiver before sending more events.
curl -X POST https://api.numberhub.io/v1/webhooks/12/rotate \
-H "Authorization: Bearer nh_live_..."{
"webhook": { "id": 12, "url": "https://example.com/numberhub-events" },
"secret": "whsec_save_this_once"
}/webhooks/{id}/deliveriesInspect recent attempts, delivery state, and the last sanitized error for one endpoint.
curl https://api.numberhub.io/v1/webhooks/12/deliveries \
-H "Authorization: Bearer nh_live_..."{
"deliveries": [
{ "id": "c17f4c58f0c54d77910d4ef0e93363f7", "event": "order.received", "order_id": 10700,
"attempts": 1, "pending": false, "delivered_at": "2026-08-01T10:30:00Z",
"last_error": null }
]
}/webhooks/{id}Deactivate an endpoint immediately. New deliveries will no longer be queued.
curl -X DELETE https://api.numberhub.io/v1/webhooks/12 \
-H "Authorization: Bearer nh_live_..."{ "ok": true }Rate limits & errors
60 requests / 10s per key (sliding window — bursts to 60, ~6 req/s sustained), and 180 requests / 10s aggregated per account across all your keys. Sustained use at the limit is fine indefinitely; need more for a high-volume integration? Contact us via the bot. Over the limit returns 429 with a Retry-After header — back off and retry.
{ "error": "rate_limited" }Errors use the HTTP status plus a stable machine code:
{ "error": "insufficient_funds" }401invalid_api_key429rate_limited400missing_params / missing_service / bad_request404unknown_service / not_found400invalid_duration409sold_out409duplicate_order400idempotency_key_required400invalid_idempotency_key409idempotency_conflict / idempotency_in_progress409cancel_locked / cancel_window_closed402insufficient_funds503esim_disabled / email_disabled502purchase_failed