success field is false, the data field is null, and the error object contains a machine-readable code and a human-readable message. Always branch your error-handling logic on error.code — the code string is stable across API versions, while the message text may change without notice.
Quick reference
Authentication errors
UNAUTHORIZED — HTTP 401
Your request did not include a valid API key.
Causes:
- The
Authorizationheader is missing entirely - The header is present but empty or malformed (e.g., not using
Bearer <api_key>) - The API key has been revoked or does not exist
Authorization: Bearer <api_key> using a valid key from the OTP.ID dashboard. If the key was recently rotated, update it in your application configuration.
Request validation errors
VALIDATION_ERROR — HTTP 400
The request body is missing required fields or contains values that fail schema validation.
Causes:
- A required field (e.g.,
channel,number) is absent from the request body - A field value is the wrong type (e.g., a number where a string is expected)
- A field value violates a constraint (e.g., a
pin_lengthoutside the allowed range)
error.message text for the specific field that failed validation, then correct your request body. Consult the API Reference for each endpoint’s required and optional fields.
INVALID_CHANNEL — HTTP 400
The channel value you provided is not recognised, or you used a channel that is not permitted on the specified endpoint.
Causes:
- The
channelstring does not match any supported value (whatsapp,sms,email,missed_call,whatsapp_inbound) - You passed
whatsapp_inboundtoPOST /v3/send— this channel is only valid withPOST /v3/requestbecause it requires the user to initiate the WhatsApp message
POST /v3/request endpoint. See the WhatsApp Inbound guide for the full flow.
INVALID_NUMBER — HTTP 400
The destination phone number or email address could not be parsed or validated.
Causes:
- A phone number is not in E.164 format (e.g.,
081234567890instead of6281234567890) - The phone number contains non-numeric characters
- The email address is syntactically invalid
+ (e.g., 6281234567890 for an Indonesian number). For email, ensure the address conforms to standard format (user@example.com).
Balance errors
INSUFFICIENT_BALANCE — HTTP 402
Your merchant account does not have enough prepaid balance to cover the cost of this transaction.
Causes:
- Your balance has fallen below the per-transaction price for the requested channel
- A recent top-up has not yet been applied to your account
Not found errors
OTP_NOT_FOUND — HTTP 404
The otp_id you referenced does not exist or is not owned by the API key making the request.
Causes:
- The
otp_idwas typed or stored incorrectly - The OTP was created with a different API key (cross-merchant access is not permitted)
otp_id value exactly as returned by POST /v3/request or POST /v3/send. Ensure you are using the same API key that created the OTP transaction.
Conflict errors
DUPLICATE_EXTERNAL_ID — HTTP 409
You submitted an external_id that has already been used for a different OTP transaction with different parameters.
Causes:
- Your system retried a request with the same
external_idbut changed thechannel,number, or other parameters between retries - Two concurrent processes generated the same
external_idwith different payloads
This is the only error code that includes an
error.details object. The existing_otp_id field gives you the otp_id of the original transaction. Use GET /v3/otp/{otp_id} to retrieve its current status and continue the verification flow without creating a new OTP.error.details.existing_otp_id and look up the original transaction with GET /v3/otp/{otp_id}. If the parameters genuinely need to change (different channel, different number), generate a new unique external_id. See the Idempotency guide for best practices on external_id generation.
Terminal state errors (422)
These three errors indicate that the OTP transaction has reached a point from which it cannot recover. You must create a new OTP transaction — calling verify again on the sameotp_id will continue to fail.
OTP_EXPIRED — HTTP 422
The OTP’s time-to-live (TTL) has elapsed and the code is no longer valid.
Causes:
- The user took too long to enter the code
- Your application delayed the verify call beyond the OTP’s expiry time (visible in
data.expires_aton the send response)
POST /v3/request or POST /v3/send again. Display a “resend OTP” option in your UI before the expiry time so users can request a new code proactively.
TOO_MANY_ATTEMPTS — HTTP 422
The user has submitted 5 incorrect codes for this OTP transaction. Further verification attempts are blocked.
Example response:
ALREADY_USED — HTTP 422
The OTP has already been successfully verified and cannot be used again.
Causes:
- Your application called
POST /v3/verifya second time after receivingdata.verified: true - A race condition caused two concurrent verify calls to be sent for the same
otp_id
data.verified: true), store the result and do not call verify again for the same otp_id. If your architecture has concurrent processes, add a local lock or idempotency check before calling the verify endpoint.
Rate limit errors
RATE_LIMITED — HTTP 429
Your API key has exceeded the global rate limit of 20 requests per second across all /v3/* endpoints.
Example response:
DESTINATION_RATE_LIMITED — HTTP 429
You have sent too many OTPs to the same destination phone number or email address in a short window.
Limits:
- Maximum 5 OTPs per 10 minutes to the same destination
- Maximum 10 OTPs per hour to the same destination
Infrastructure errors
CHANNEL_UNAVAILABLE — HTTP 503
The delivery channel or underlying vendor is temporarily unavailable.
Causes:
- The WhatsApp, SMS, email, or missed call provider is experiencing an outage or maintenance window
- A transient network issue between OTP.ID and the delivery vendor
INTERNAL_ERROR — HTTP 500
An unexpected error occurred on the OTP.ID server.
Example response:
otp_id or external_id values from the affected requests to help with investigation.

