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:
The message varies by cause: "Sertakan header Authorization: Bearer <api_key>" (header missing/malformed), "API key kosong" (empty token), or "API key tidak dikenal" (unknown key). How to resolve: Verify that every request includes the header Authorization: Bearer <api_key> using your key from the OTP.ID dashboard (Integration page → API Key). If the key was recently rotated, update it in your application configuration.

IP_NOT_ALLOWED — HTTP 403

The request came from an IP address that is not in the merchant’s API V3 IP whitelist. IP allowlisting is independent of API-key authentication and is evaluated after the API key is validated. How to resolve: Ask OTP.ID to configure the whitelist for your merchant, then provide the exact public IPv4 or IPv6 addresses of the servers that call the API. Multiple addresses are comma-separated, for example 203.0.113.10,198.51.100.7,2001:db8::7. Whitespace around commas is ignored; CIDR ranges are not accepted. An empty whitelist allows requests from any resolved IP. OTP.ID resolves the client IP automatically in this order: CF-Connecting-IP, X-Real-IP, the first address in X-Forwarded-For, then the direct connection address. If Cloudflare is bypassed, the resolver automatically falls back to the next available value. Example response:
See the API V3 IP allowlist guide for proxy setup and operational details.

Request validation errors

VALIDATION_ERROR — HTTP 400

The request body is missing required fields or contains values that fail validation. Causes:
  • On POST /v3/send: the otp field is missing, contains non-digit characters, or its length is outside 4–8
  • On POST /v3/verify: otp_id or otp is missing from the request body
Note that out-of-range ttl and otp_length values do not trigger this error — they are silently clamped to the valid range. Example response:
(POST /v3/verify uses the message "otp_id dan otp wajib diisi".) 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, misscall, whatsapp_inbound)
  • You passed misscall or whatsapp_inbound to POST /v3/send — these channels are only valid with POST /v3/request
Example response:
How to resolve: Use one of the supported channel values listed in the Channels guide. If you need Missed Call or WhatsApp Inbound, switch to the POST /v3/request endpoint. See the Missed Call guide and WhatsApp Inbound guide for the full flows.

INVALID_NUMBER — HTTP 400

The destination phone number or email address could not be parsed or validated. Causes:
  • The phone number contains non-digit characters (e.g. a leading +, spaces, or dashes)
  • The phone number is shorter than 10 digits
  • The email address is syntactically invalid
  • The destination field is missing on a channel that requires it
Example response:
How to resolve: Send phone numbers as digits only, in international format without a leading + (e.g., 6281234567890 for an Indonesian number). For email, ensure the address conforms to standard format (user@example.com).

Credit errors

INSUFFICIENT_BALANCE — HTTP 402

Your merchant account does not have enough prepaid credits to cover the cost of this transaction. Causes:
  • Your credit 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 credits from the OTP.ID dashboard or create a payment link with POST /v3/topups. Review the Billing guide for per-channel pricing details. Consider setting up a low-credit 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, destination, 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 destination), 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 the maximum number of incorrect codes for this OTP transaction. The default limit is 5 failed attempts; Missed Call transactions have a stricter limit of 2 because their caller-ID OTP is four digits. Further verification attempts are blocked. Example response:
How to resolve: Create a new OTP transaction. For Missed Call, the second wrong submission locks the transaction immediately; the mismatch response for that submission is still HTTP 200, while subsequent verification calls return this 422 error. 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 for standard channels:
  • Maximum 5 OTPs per 10 minutes to the same destination
  • Maximum 10 OTPs per hour to the same destination
Limits for misscall:
  • Maximum 2 OTPs per 10 minutes to the same destination
  • Maximum 4 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.

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.