Voice OTP
POSThttps://api.9bits.net/voice/v1/voice_otp
Place a single voice call that speaks a 1-8 digit OTP code to one recipient.
The caller ID is one of your approved numbers; poll the returned status_url
to find out whether the call connected.
Request
Section titled “Request”| Field | Type | Required | Description |
|---|---|---|---|
to | string | ✅ | Destination in international format. Nigerian mobile: 13 digits, 234XXXXXXXXXX. |
code | string | ✅ | The OTP to speak. 1-8 digits, digit-only — spoken via Asterisk SayDigits. |
from | string | — | Caller ID to present. If omitted, mistyped, or not approved for your account, one of your approved caller IDs is auto-picked. |
Examples
Section titled “Examples”curl -X POST https://api.9bits.net/voice/v1/voice_otp \ -H "Authorization: Bearer $NINEBITS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "to": "2348020000000", "code": "482913", "from": "2349000000000" }'const res = await fetch('https://api.9bits.net/voice/v1/voice_otp', { method: 'POST', headers: { Authorization: `Bearer ${process.env.NINEBITS_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ to: '2348020000000', code: '482913', }),});
const job = await res.json();console.log(job.reference_id); // save this to poll the call statusimport os, requests
res = requests.post( "https://api.9bits.net/voice/v1/voice_otp", headers={"Authorization": f"Bearer {os.environ['NINEBITS_TOKEN']}"}, json={ "to": "2348020000000", "code": "482913", }, timeout=10,)res.raise_for_status()print(res.json()["reference_id"])Response
Section titled “Response”{ "state": "queued", "description": "Your OTP request is being processed", "reference_id": "fc4c032a-2b56-44f2-828d-d6773a0ebc79", "to": "2348020000000", "status_url": "/voice/v1/calls/fc4c032a-2b56-44f2-828d-d6773a0ebc79"}| Field | Meaning |
|---|---|
state | queued while the call is pending, failed if the pipeline rejected it up front. |
description | Human-readable message. On queued: “Your OTP request is being processed”; on failed, the specific reason (e.g. Insufficient Balance). |
reference_id | Per-call handle. Save it to poll status or to correlate with your own records. |
to | The normalized destination we accepted (in case your input was reformatted). |
status_url | Path to GET for the call’s current status — see below. |
Poll for the call outcome
Section titled “Poll for the call outcome”Unlike sendvoice, voice_otp has a polling endpoint. GET the status_url
you got back — auth is the same as the POST, and the call must belong to the
same account.
curl https://api.9bits.net/voice/v1/calls/fc4c032a-2b56-44f2-828d-d6773a0ebc79 \ -H "Authorization: Bearer $NINEBITS_TOKEN"{ "call_id": "fc4c032a-2b56-44f2-828d-d6773a0ebc79", "status": "answered", "service": "votp", "destination": "2348020000000", "caller_id": "2349000000000", "duration": 12, "total_duration": 18, "fee": 24.5, "currency": "NGN", "dialed_at": "2026-07-30T11:24:40+01:00", "answered_at": "2026-07-30T11:24:46+01:00", "ended_at": "2026-07-30T11:24:58+01:00", "created_at": "2026-07-30T11:24:40+01:00"}status is one of pending, answered, no_answer, busy, failed.
duration is billed (answered) seconds; total_duration includes ring time.
Errors
Section titled “Errors”| HTTP | Body | Meaning |
|---|---|---|
400 | {"error":"recipient phone number is required"} | to missing. |
400 | {"error":"recipient phone number must be a valid 13-digit Nigerian mobile number (234XXXXXXXXXX)"} | to isn’t a valid Nigerian mobile. |
400 | {"error":"OTP code is required and must be 1-8 digits"} | code missing or not 1-8 digits. |
401 | {"error":"missing authorization header"} | No Authorization header. |
401 | {"error":"unauthorized"} | Token signature invalid or malformed. |
401 | {"error":"no account is associated with this token"} | Token is signature-valid but its accountId doesn’t map to a real account. |
403 | {"error":"insufficient role"} | Your role can’t place calls (viewer/child). |
500 | {"error":"call processing failed"} | Pipeline failed for a reason other than a bad token — retry with backoff. |
Same as every /voice/v1 endpoint — a platform API key (Bearer 9bk_...)
or your messaging JWT. See Authentication for how
to mint keys.