Skip to main content
Every response from the OTP.ID v3 API — whether the request succeeded or failed — uses the same top-level JSON envelope. Understanding this format lets you write a single, consistent response-handling layer in your application rather than special-casing each endpoint.

Envelope structure

The two rules that always hold:
  • When success is true, data contains the endpoint-specific response payload and error is null.
  • When success is false, data is null and error contains a machine-readable code and a human-readable message.
boolean
required
Whether the request was processed successfully. Use this as your primary branch condition — check success before reading data or error.
object | null
The response payload when success is true. The shape of this object varies by endpoint. It is always null when success is false.
object | null
The error details when success is false. Always null when success is true.
Never branch on error.message text. Always use error.code. The message is a localised, human-readable string that may change without notice; the code is a stable contract.

Success response

A successful response sets success: true, populates data with the endpoint result, and sets error to null.
The data fields shown above are typical of a POST /v3/request or POST /v3/send response. Each endpoint documents its own data shape in the API Reference.

Error response

A failed response sets success: false, sets data to null, and populates error with a code and message.

Error response with details

A small number of error codes include an error.details object with additional structured data. Currently, only DUPLICATE_EXTERNAL_ID returns details.
When you receive DUPLICATE_EXTERNAL_ID, read error.details.existing_otp_id and call GET /v3/otp/{otp_id} with that value to retrieve the status of the original transaction. See the Idempotency guide for the full retry pattern.

Parsing the envelope

Structure your response-handling code around the success flag first, then delegate to error-specific logic based on error.code.

Special cases

Wrong OTP code is not an error

When a user submits an incorrect OTP code to POST /v3/verify, the API does not return an error response. Instead, it returns HTTP 200 with success: true and data.verified: false. Check data.verified to determine whether verification passed.
Do not treat data.verified: false as an error. It is a normal, successful API response telling you the submitted code was wrong. Only look for success: false to detect actual API errors.

Verify success response

A successful verification returns data.verified: true along with a verified_at timestamp.

Data formats

Timestamps

All timestamp fields (expires_at, verified_at) use the format YYYY-MM-DD HH:MM:SS in WIB (Western Indonesian Time, UTC+7). When storing or comparing timestamps in your own system, convert to UTC or your local timezone as needed.
WIB is UTC+7. To convert a WIB timestamp to UTC, subtract 7 hours.

OTP ID format

Every OTP transaction is assigned a unique otp_id with the following structure:
Example: OTP20260807ABCD000001
  • OTP — fixed prefix
  • 20260807 — date the OTP was created (YYYYMMDD)
  • ABCD — 4 random uppercase letters
  • 000001 — zero-padded daily counter, resets each day
Store the full otp_id string. Use it as the path parameter for GET /v3/otp/{otp_id} and as the body parameter for POST /v3/verify.