Skip to main content
Every OTP.ID API response uses a consistent JSON envelope. When a request fails, the 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.
A wrong OTP code submitted to POST /v3/verify is not an error response. The API returns HTTP 200 with success: true and data.verified: false. Only check for error codes when success is false.

Quick reference


Authentication errors

UNAUTHORIZED — HTTP 401

Your request did not include a valid API key. Causes:
  • The Authorization header 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
Example response:
How to resolve: Verify that every request includes the header 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_length outside the allowed range)
Example response:
How to resolve: Check the 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 channel string does not match any supported value (whatsapp, sms, email, missed_call, whatsapp_inbound)
  • You passed whatsapp_inbound to POST /v3/send — this channel is only valid with POST /v3/request because it requires the user to initiate the WhatsApp message
Example response:
How to resolve: Use one of the supported channel values listed in the Channels guide. If you need WhatsApp Inbound, switch to the 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., 081234567890 instead of 6281234567890)
  • The phone number contains non-numeric characters
  • The email address is syntactically invalid
Example response:
How to resolve: Format phone numbers in E.164 without a leading + (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
Example response:
How to resolve: Top up your balance from the OTP.ID dashboard. Review the Billing guide for per-channel pricing details. Consider setting up a low-balance alert to avoid disruption in production.

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_id was typed or stored incorrectly
  • The OTP was created with a different API key (cross-merchant access is not permitted)
Example response:
How to resolve: Confirm that you are storing and forwarding the 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_id but changed the channel, number, or other parameters between retries
  • Two concurrent processes generated the same external_id with different payloads
Example response:
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.
How to resolve: Read 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 same otp_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_at on the send response)
Example response:
How to resolve: Create a new OTP transaction by calling 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:
How to resolve: Create a new OTP transaction. Consider adding client-side attempt tracking to disable the submit button after several failures before reaching the server-side limit.

ALREADY_USED — HTTP 422

The OTP has already been successfully verified and cannot be used again. Causes:
  • Your application called POST /v3/verify a second time after receiving data.verified: true
  • A race condition caused two concurrent verify calls to be sent for the same otp_id
Example response:
How to resolve: After a successful verification (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:
How to resolve: Implement exponential backoff — wait a short period before retrying, and increase the wait time on subsequent failures. See the Rate Limits reference for full details and backoff recommendations. If your use case requires a higher limit, contact OTP.ID support.

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
Example response:
How to resolve: Wait until the current window expires before sending another OTP to this destination. Track the number of OTPs you send per destination on your side to surface a friendly waiting message to users instead of showing them an error. See the Rate Limits reference for the full per-destination policy.

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
Example response:
How to resolve: Retry the request after a short delay using exponential backoff. If the channel remains unavailable for an extended period, consider falling back to an alternative channel (e.g., SMS instead of WhatsApp) for time-sensitive use cases. Monitor the OTP.ID status page for ongoing incidents.

INTERNAL_ERROR — HTTP 500

An unexpected error occurred on the OTP.ID server. Example response:
How to resolve: Retry the request with exponential backoff. If the error persists, contact OTP.ID support with the timestamp and any otp_id or external_id values from the affected requests to help with investigation.
Log all INTERNAL_ERROR responses in your application alongside request identifiers and timestamps. This trace information significantly speeds up diagnosis if you need to open a support ticket.