Home / Blog / API
APIDevelopersAutomation

Fetching OTPs Programmatically: A Developer's Guide to the OTP Bolt API

Authenticate, rent a temporary number, poll for the code, handle refunds and errors — with copy-paste examples in curl, Python and Node.js.

OTP Bolt Team · · 9 min read

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 (when can_request_another is 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 id with 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.

Ready when you are

Your next OTP is one click away.

Create a free account, top up from $5 and receive your first code in under a minute.