For sending messages, the base url is:
@bulk_base/api/v1
| Method | URI |
|---|---|
| POST | /send-otp |
| Param | Type | Description |
|---|---|---|
| sender | string (required) |
The Sender ID to send the message with. |
| phone | string (required) |
Phone Number to receive the OTP (same format rules as SMS). |
| expiry_time | integer (optional) |
OTP validity in seconds, 60 to 3600. Defaults to 300 (5 minutes). |
| character_type | string (optional) |
numeric (default), alpha or mixed. |
| length | integer (optional) |
OTP length, 4 to 10. Defaults to 6. |
| correlator | string (optional) |
Unique identifier generated by your system for tracking. |
| endpoint | string (optional) |
Your delivery receipt endpoint. |
Content-Type: application/json
Accept: application/json
Authorization: Bearer YOUR_TOKEN
{warning}
Accept: application/jsonis required. Without it, an authentication failure returns a302redirect to the HTML login page instead of a JSON401.{info} The message text is generated by us and cannot be customised. It reads:
Your OTP is: 123456. It will expire in 5 minutes.Anymessagefield you send is ignored.{warning} An OTP is issued to one number, so this endpoint takes a single message. A batch body is rejected with
422. Values outside the ranges above are also rejected with422and nothing is sent.{info}
alphaandmixedcodes leave out characters that are easily misread off a screen (I,O,L,0,1), andmixedis upper case only. Verification is not case sensitive, so a recipient typing lower case still succeeds.{info} Asking again for the same number while a code is still comfortably alive resends the same code rather than issuing a new one, so a retry does not invalidate the message the recipient is holding. A newer code always retires an older one, so only one code per number is ever valid.
{
"sender": "BONGATECH",
"phone": "2547XXXXXXXX",
"expiry_time": 300,
"character_type": "numeric",
"length": 6,
"correlator": "otp-789"
}
StatusCode 200
Content
{
"status": true,
"message": "Message successfully queued!",
"data": {
"correlator": 2,
"uniqueId": "unique-string",
"phone": "254XXXXXXXXX",
"sms_units": 1,
"expires_at": "2026-01-01 09:20:02",
"expiry_time": 300
}
}
The Verify OTP endpoint allows you to confirm whether a one-time password (OTP) sent to a user’s phone is valid.
This step is typically used during login, registration, or sensitive transactions to ensure that the user has access
to the phone number they provided. The OTP must match the most recent, unexpired code issued to that phone number.
| Method | URI |
|---|---|
| POST | /verify-otp |
| Param | Type | Description |
|---|---|---|
| phone | string (required) |
Phone number the OTP was sent to, in the same format as when requesting. |
| otp | string (required) |
The OTP entered by the user. |
{
"phone": "07XXXXXXXX",
"otp": "482195"
}
StatusCode 200
Content
{
"status": true,
"message": "OTP verified successfully"
}
Each outcome carries the HTTP status that matches it. A successful verification is the
only 2xx, so you can rely on the status code as well as the status field.
| Status | message |
Meaning |
|---|---|---|
404 |
OTP not found |
No live OTP exists for that number on your account. |
410 |
OTP expired |
The OTP was issued but is past its expiry time. |
422 |
Invalid OTP |
A live OTP exists but the code does not match. |
429 |
Too many incorrect attempts |
The allowed wrong guesses have been used up. |
Content
{
"status": false,
"message": "Invalid OTP",
"attempts_remaining": 3
}
{info} A code allows 5 wrong guesses.
attempts_remainingtells you how many are left; at zero the code is retired and a new one must be requested. Expiry, exhaustion and a successful verification all retire the code, so it can never be presented twice.{info} The number may be sent in any accepted format.
0712345678and254712345678are the same recipient, so a code requested with one can be verified with the other.
All Requested delivery receipts will be delivered via a POST method with the below structure.
{
"phone": "254XXXXXXXXX",
"correlator": 2,
"uniqueId": "unique-string",
"deliveryStatus": "DeliveredToTerminal",
"deliveryTime": "2020-01-01 00:00:00"
}