Clicking through a web inbox is fine for the occasional signup. When you are testing ten flows a day — or running verification steps inside a CI pipeline — you want a clean API. This guide covers everything you need to integrate the OTP Bolt REST API in an afternoon.
Authentication
Generate a key from Dashboard → Developers. Keys start with ob_live_ and are shown once; store them in your secrets manager. Every request sends the key as a bearer token:
Authorization: Bearer ob_live_xxxxxxxxxxxxxxxxxxxxxxxx
Your plan sets the rate limit — 30 requests per minute on pay-as-you-go Spark, up to 600 on Surge. A 429 response includes the limit in the message.
The lifecycle of an activation
An activation is one rented number for one service. It moves through a small set of states:
| Status | Meaning | Money |
|---|---|---|
pending |
Number rented, waiting for an SMS | Debited |
received |
A code arrived — read code and sms |
Kept |
finished |
You (or the timeout) closed a successful rental | Kept |
cancelled |
You cancelled before any SMS | Refunded |
expired |
Window closed with no SMS | Refunded |
Refunds are automatic — you never need to file a claim for a code that did not come.
Step 1 — find a service and price
curl -s "https://otpbolt.com/api/v1/services?country=us" \
-H "Authorization: Bearer $OTPBOLT_KEY"
Each row has an id (for example telegram, whatsapp, openai), a display name, your price_cents for that country and an in_stock flag. Prices already include your plan's discount.
Step 2 — rent a number
curl -s -X POST https://otpbolt.com/api/v1/activations \
-H "Authorization: Bearer $OTPBOLT_KEY" \
-H "Content-Type: application/json" \
-d '{"service":"telegram","country":"us"}'
{
"id": "5f0c2a9e4b1d7c3a9e2f6b10",
"status": "pending",
"phone": "+12015550123",
"price_cents": 34,
"expires_at": "2026-10-16T10:32:00+00:00"
}
If your wallet cannot cover the price you get 402 with shortfall_c. If no number is free for that pair you get 409 and nothing is charged.
Step 3 — poll for the code
Poll every three to five seconds. The API refreshes the carrier state on each call, so you do not need webhooks.
Python
import os, time, requests
API = "https://otpbolt.com/api/v1"
H = {"Authorization": f"Bearer {os.environ['OTPBOLT_KEY']}"}
act = requests.post(f"{API}/activations", json={"service": "telegram", "country": "us"}, headers=H).json()
print("Use this number:", act["phone"])
deadline = time.time() + 300
while time.time() < deadline:
a = requests.get(f"{API}/activations/{act['id']}", headers=H).json()
if a["status"] == "received":
print("Code:", a["code"])
requests.post(f"{API}/activations/{act['id']}/finish", headers=H)
break
if a["status"] in ("cancelled", "expired"):
print("No SMS — refunded")
break
time.sleep(4)
Node.js
const API = "https://otpbolt.com/api/v1";
const H = { Authorization: `Bearer ${process.env.OTPBOLT_KEY}`, "Content-Type": "application/json" };
export async function getOtp(service, country, timeoutMs = 300_000) {
const act = await (await fetch(`${API}/activations`, {
method: "POST", headers: H, body: JSON.stringify({ service, country }),
})).json();
const until = Date.now() + timeoutMs;
while (Date.now() < until) {
const a = await (await fetch(`${API}/activations/${act.id}`, { headers: H })).json();
if (a.status === "received") return { phone: act.phone, code: a.code, id: act.id };
if (["cancelled", "expired"].includes(a.status)) throw new Error("no SMS (refunded)");
await new Promise(r => setTimeout(r, 4000));
}
await fetch(`${API}/activations/${act.id}/cancel`, { method: "POST", headers: H });
throw new Error("timed out");
}
Step 4 — close, retry or cancel
POST /activations/{id}/finish— you used the code; close the rental.POST /activations/{id}/retry— ask for another SMS on the same number while the window is open (whencan_request_anotheris true).POST /activations/{id}/cancel— give the number back before any SMS arrives and get refunded. Some networks require a short wait before cancelling; the error tells you how many seconds remain.
Error handling
Errors are JSON with a stable code you can branch on:
| HTTP | code |
What to do |
|---|---|---|
| 401 | invalid_api_key |
Check the key or rotate it |
| 402 | insufficient_funds |
Top up; shortfall_c tells you how much |
| 409 | out_of_stock / no_retry / too_early |
Try another country, or wait |
| 423 | account_locked |
Contact support |
| 429 | rate_limited |
Back off; upgrade plan for more headroom |
Dedicated numbers over the API
If you own a dedicated number, read its inbox with GET /api/v1/numbers/{id}/messages?since_id=0. Pass the last id you saw as since_id to fetch only new messages — a simple, reliable way to collect codes for long-lived test accounts.
Production tips
- Store the activation
idwith your test run so you can audit costs later. - Never share one key across unrelated projects; rotate keys when people leave the team.
- Keep polling intervals at three seconds or more — faster polling does not make SMS arrive sooner.
- Put the poll loop behind a timeout and always cancel on timeout so unused rentals refund immediately.
The full reference lives at otpbolt.com/developers.