Once a code is in flight, three lifecycle endpoints let you keep state coherent without rebuilding the request flow:
All three are scoped to your Developer App. An OTP issued under app A cannot be touched via app B.
Resend
Issue a new OTP after invalidating the previous one. Body shape and channel-specific fields are identical to /v1/otp/request; only the path changes.
POST https://karibu.briq.tz/v1/otp/resend
Common request fields
These fields are the same on every channel:
| Field | Type | Required | Default | Notes |
|---|
phone_number | string (E.164 digits) | yes | — | Digits only, no +. |
app_key | string | yes | — | From your Developer App. |
delivery_method | string | yes (recommended) | "sms" | One of "sms", "call", "whatsapp". |
otp_length | int | no | 6 | Length of the generated code. |
minutes_to_expire | int | no | 10 | TTL in minutes. |
sender_id and message_template are SMS-only — they appear in the SMS tab below and are silently ignored on "call" and "whatsapp".
Success response
{
"success": true,
"message": "OTP resent successfully.",
"data": { "expires_at": "2026-05-08T12:45:01.000000" },
"status_code": 200
}
Errors mirror /request exactly. See Error scenarios.
request vs resend — what’s the difference? Functionally both invalidate any prior active OTP and dispatch a new one. Use /resend when the end user explicitly clicks “Resend code”; this lets you treat it differently in your analytics, rate-limits, or UI without changing payloads.
Switching channels on resend is allowed — e.g. user clicks “I didn’t get the SMS, send via WhatsApp instead.” Just send the new delivery_method. The previous active OTP is invalidated regardless of which channel issued it.
Channel-specific payload & code samples
Customisable via sender_id and message_template.Channel-specific fields:| Field | Type | Required | Default | Notes |
|---|
sender_id | string | no | OTP_DEFAULT_SENDER_ID | The SMS sender ID shown to the recipient. Must be approved for your account. |
message_template | string | no | server default | Must contain {code}. {expiry} is also substituted. Falls back if {code} missing. |
curl -X POST https://karibu.briq.tz/v1/otp/resend \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "255712345678",
"app_key": "YOUR_APP_KEY",
"delivery_method": "sms",
"sender_id": "BRIQ OTP",
"message_template": "Your verification code is {code}. It expires in {expiry} minutes."
}'
The platform calls the recipient and reads the new code via TTS. No sender_id or message_template.Channel-specific fields: none.curl -X POST https://karibu.briq.tz/v1/otp/resend \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "255712345678",
"app_key": "YOUR_APP_KEY",
"delivery_method": "call"
}'
Routes through the platform-managed briq_otp template. Sender resolution is automatic — see Requesting OTP codes → WhatsApp for the full resolution order.Channel-specific fields: none.curl -X POST https://karibu.briq.tz/v1/otp/resend \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "255712345678",
"app_key": "YOUR_APP_KEY",
"delivery_method": "whatsapp"
}'
WhatsApp-specific 400s. If the dispatcher cannot resolve a sender + APPROVED briq_otp template, the response is success: false with NO_DEFAULT_SENDER or TEMPLATE_NOT_APPROVED in the message. Fall back to SMS in your client.
Invalidate
Force-expire any active OTP for a phone scoped to your Developer App. Useful on logout, security events (suspicious activity, password change), or when the user changes their phone number.
POST https://karibu.briq.tz/v1/otp/invalidate
Request body
| Field | Type | Required |
|---|
phone_number | string (E.164 digits) | yes |
app_key | string | yes |
Success response
{
"success": true,
"message": "OTP invalidated successfully.",
"data": null,
"status_code": 200
}
Idempotent. Calling this when there is no active OTP also returns 200 — it simply has nothing to invalidate. Safe to fire from a logout handler without checking state first.
Code samples
curl -X POST https://karibu.briq.tz/v1/otp/invalidate \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "255712345678",
"app_key": "YOUR_APP_KEY"
}'
Status
Inspect the currently active OTP for a phone, scoped to your Developer App, without triggering a send. Useful for UI countdowns, diagnostics, and idempotent UX (e.g. “an OTP was already sent — please check your phone”).
GET https://karibu.briq.tz/v1/otp/status
Query parameters
| Param | Required |
|---|
phone_number | yes |
app_key | yes |
Success response
{
"success": true,
"message": "OTP status retrieved.",
"data": {
"is_valid": true,
"expires_at": "2026-05-08T12:45:01.000000",
"remaining_attempts": 3
},
"status_code": 200
}
No active OTP
{
"success": false,
"message": "No active OTP found.",
"data": null,
"status_code": 404
}
The HTTP response body wraps status_code: 404, but the underlying HTTP response itself is 200 OK. Inspect body.success and body.status_code, not the raw HTTP status, when handling this endpoint.
Code samples
curl -G https://karibu.briq.tz/v1/otp/status \
-H "X-API-Key: YOUR_API_KEY" \
--data-urlencode "phone_number=255712345678" \
--data-urlencode "app_key=YOUR_APP_KEY"
Best practices
- Use
/status to drive countdown UI, not speculative resends. Polling is cheap; firing duplicate /request calls invalidates the in-flight code and confuses the user.
- Always invalidate on logout. It’s idempotent — no need to check state first.
- Treat
is_valid: true, remaining_attempts: 0 as a locked OTP. The user has burned all 3 attempts and must resend.
For full payload tables, error matrices, and the canonical reference, see the Karibu OTP API reference.