Skip to content

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.

FieldTypeRequiredDescription
tostringDestination in international format. Nigerian mobile: 13 digits, 234XXXXXXXXXX.
codestringThe OTP to speak. 1-8 digits, digit-only — spoken via Asterisk SayDigits.
fromstringCaller ID to present. If omitted, mistyped, or not approved for your account, one of your approved caller IDs is auto-picked.
Terminal window
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"
}'
{
"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"
}
FieldMeaning
statequeued while the call is pending, failed if the pipeline rejected it up front.
descriptionHuman-readable message. On queued: “Your OTP request is being processed”; on failed, the specific reason (e.g. Insufficient Balance).
reference_idPer-call handle. Save it to poll status or to correlate with your own records.
toThe normalized destination we accepted (in case your input was reformatted).
status_urlPath to GET for the call’s current status — see below.

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.

Terminal window
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.

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