OTP Verifications
4 endpoints · Core API
The OTP Verifications API provides a secure, two-step one-time password delivery and verification service for both mobile numbers and email addresses. It enables your application to send a 6-digit OTP to an end user's mobile (via SMS) or email inbox, receive a reference_id, and then verify the OTP the user enters — all through a simple REST interface.
This API is ideal for use cases such as user identity confirmation, transaction authorization, account recovery, and step-up authentication flows — without requiring a login or session.
How it works
1. Send OTP — Call the mobile or email send endpoint with the target number / email. The API generates a cryptographically random 6-digit OTP, delivers it, and returns a reference_id valid for 10 minutes.
2. Verify OTP — Call the corresponding verify endpoint with the reference_id and the OTP your user entered. The API confirms the OTP is correct, unexpired, and has not exceeded the 5-attempt limit.
Send a 6-digit OTP to a mobile number via SMS.
Generates a cryptographically random 6-digit OTP, delivers it via SMS to the supplied mobile
number, and returns a referenceId that must be passed to mobile/verify along
with the OTP entered by the end user.
The OTP expires after 10 minutes. Each company can send at most 10 mobile OTPs per hour; further requests return 429.
Request body: mobileNumber (required) is a 10-digit Indian mobile number starting with
6, 7, 8 or 9, optionally prefixed with +91. requestId (optional) is your own correlation id.
The deliveryTarget in the response is a masked representation of the mobile number
(e.g. XXXXXX9178) — the raw number is never echoed back.
Use the optional requestId field to supply your own correlation identifier; it is
echoed back in the response for end-to-end audit tracing across your system.
Headers
- X-API-KEYstringrequiredYour secret API key for the environment you are calling (Stage and Live use different keys).
- X-API-APP-IDstringrequiredThe App ID issued together with the key.
- Content-Typestringrequiredapplication/json
Body parameters
application/json- mobileNumberstringrequiredRequired. Indian mobile number to send the OTP to: 10 digits starting with 6, 7, 8 or 9, optionally prefixed with +91 (for example 9876543210 or +919876543210).minLength 1pattern ^(\+91)?[6-9]\d{9}$default 9876543210
- requestIdstringoptionalOptional. Your own correlation id for this request. Echoed back as requestId in the response; a UUID is generated when omitted.nullable
Sample request body
Responses
200OK
- successbooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 200
- messagestringoptionalNo description.nullabledefault XXXXX
- dataobjectoptionalResponse body for OTP Send.
3 fields
- referenceIdstringoptionalUnique token the caller must pass back in the Verify call. Backed by Redis (10-minute TTL) and the Feature.OtpVerification table (durable fallback).nullable
- deliveryTargetstringoptionalMasked delivery target for display (e.g. "XXXXXX9178" or "r***@gmail.com"). Never exposes the raw value.nullable
- expiresAtstring (date-time)optionalUTC timestamp when this OTP will expire.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- isBillablebooleanoptionalWhether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
400Bad Request
- dataobjectoptionalNo description.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- successbooleanoptionalNo description.default false
- messagestringoptionalNo description.nullabledefault XXXXX
- isBillablebooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 400
401Unauthorized
- dataobjectoptionalNo description.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- successbooleanoptionalNo description.default false
- messagestringoptionalNo description.nullabledefault XXXXX
- isBillablebooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 401
402Payment Required
- successbooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 200
- messagestringoptionalNo description.nullabledefault XXXXX
- dataanyoptionalNo description.nullable
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- isBillablebooleanoptionalWhether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
429Too Many Requestssame body as 402
500Internal Server Error
- dataobjectoptionalNo description.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- successbooleanoptionalNo description.default false
- messagestringoptionalNo description.nullabledefault XXXXX
- isBillablebooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 500
Code samplescurl · Node.js · Python
Verify the OTP sent to a mobile number.
Request body: referenceId (required) and otp (required, the 6-digit code as a
string); requestId is optional.
Pass the referenceId returned by mobile/send together with the OTP your end
user entered. The API confirms the OTP is correct, has not expired (10-minute window), and
has not exceeded the 5-attempt limit.
On success, isVerified: true and a verifiedAt UTC timestamp are returned.
On failure, isVerified: false is returned with a descriptive message — invalid OTP,
expired OTP, or max attempts exceeded.
Each failed attempt increments the attempt counter. Once 5 incorrect attempts are made, the
referenceId is permanently invalidated and a new send must be initiated.
Headers
- X-API-KEYstringrequiredYour secret API key for the environment you are calling (Stage and Live use different keys).
- X-API-APP-IDstringrequiredThe App ID issued together with the key.
- Content-Typestringrequiredapplication/json
Body parameters
application/json- referenceIdstringrequiredRequired. The referenceId returned by the matching send call.nullable
- otpstringrequiredRequired. The 6-digit OTP the end user entered. Send it as a string so leading zeros are kept.nullabledefault 123456
- requestIdstringoptionalOptional. Your own correlation id for this request. Echoed back as requestId in the response; a UUID is generated when omitted.nullable
Sample request body
Responses
200OK
- successbooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 200
- messagestringoptionalNo description.nullabledefault XXXXX
- dataobjectoptionalResponse body for OTP Verify.
3 fields
- isVerifiedbooleanoptionalTrue if the OTP matched and was within the validity window.
- referenceIdstringoptionalThe reference_id that was verified.nullable
- verifiedAtstring (date-time)optionalUTC timestamp of successful verification. Null on failure.nullable
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- isBillablebooleanoptionalWhether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
400Bad Request
- dataobjectoptionalNo description.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- successbooleanoptionalNo description.default false
- messagestringoptionalNo description.nullabledefault XXXXX
- isBillablebooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 400
401Unauthorized
- dataobjectoptionalNo description.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- successbooleanoptionalNo description.default false
- messagestringoptionalNo description.nullabledefault XXXXX
- isBillablebooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 401
404Not Found
- successbooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 200
- messagestringoptionalNo description.nullabledefault XXXXX
- dataanyoptionalNo description.nullable
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- isBillablebooleanoptionalWhether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
422Unprocessable Contentsame body as 404
500Internal Server Error
- dataobjectoptionalNo description.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- successbooleanoptionalNo description.default false
- messagestringoptionalNo description.nullabledefault XXXXX
- isBillablebooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 500
Code samplescurl · Node.js · Python
Send a 6-digit OTP to an email address.
Generates a cryptographically random 6-digit OTP, delivers it to the supplied email address,
and returns a referenceId that must be passed to email/verify along with the
OTP entered by the end user.
The email is sent using the company's configured email template. If a custom
OTPVerification template has been configured in Console → Email Templates, that
template is used; otherwise the default SignCare OTP email is sent.
The OTP expires after 10 minutes. Each company can send at most 10 email OTPs per hour; further requests return 429.
Request body: email (required) is a valid email address. requestId (optional) is
your own correlation id.
The deliveryTarget in the response is a masked representation of the email address
(e.g. r***@example.com) — the raw address is never echoed back.
Headers
- X-API-KEYstringrequiredYour secret API key for the environment you are calling (Stage and Live use different keys).
- X-API-APP-IDstringrequiredThe App ID issued together with the key.
- Content-Typestringrequiredapplication/json
Body parameters
application/json- emailstringrequiredRequired. Email address to send the OTP to, for example user@example.com.minLength 1pattern ^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-…default user@example.com
- requestIdstringoptionalOptional. Your own correlation id for this request. Echoed back as requestId in the response; a UUID is generated when omitted.nullable
Sample request body
Responses
200OK
- successbooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 200
- messagestringoptionalNo description.nullabledefault XXXXX
- dataobjectoptionalResponse body for OTP Send.
3 fields
- referenceIdstringoptionalUnique token the caller must pass back in the Verify call. Backed by Redis (10-minute TTL) and the Feature.OtpVerification table (durable fallback).nullable
- deliveryTargetstringoptionalMasked delivery target for display (e.g. "XXXXXX9178" or "r***@gmail.com"). Never exposes the raw value.nullable
- expiresAtstring (date-time)optionalUTC timestamp when this OTP will expire.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- isBillablebooleanoptionalWhether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
400Bad Request
- dataobjectoptionalNo description.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- successbooleanoptionalNo description.default false
- messagestringoptionalNo description.nullabledefault XXXXX
- isBillablebooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 400
401Unauthorized
- dataobjectoptionalNo description.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- successbooleanoptionalNo description.default false
- messagestringoptionalNo description.nullabledefault XXXXX
- isBillablebooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 401
402Payment Required
- successbooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 200
- messagestringoptionalNo description.nullabledefault XXXXX
- dataanyoptionalNo description.nullable
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- isBillablebooleanoptionalWhether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
429Too Many Requestssame body as 402
500Internal Server Error
- dataobjectoptionalNo description.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- successbooleanoptionalNo description.default false
- messagestringoptionalNo description.nullabledefault XXXXX
- isBillablebooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 500
Code samplescurl · Node.js · Python
Verify the OTP sent to an email address.
Request body: referenceId (required) and otp (required, the 6-digit code as a
string); requestId is optional.
Pass the referenceId returned by email/send together with the OTP your end
user entered. The API confirms the OTP is correct, has not expired (10-minute window), and
has not exceeded the 5-attempt limit.
On success, isVerified: true and a verifiedAt UTC timestamp are returned.
On failure, isVerified: false is returned with a descriptive message — invalid OTP,
expired OTP, or max attempts exceeded.
Each failed attempt increments the attempt counter. Once 5 incorrect attempts are made, the
referenceId is permanently invalidated and a new send must be initiated.
Headers
- X-API-KEYstringrequiredYour secret API key for the environment you are calling (Stage and Live use different keys).
- X-API-APP-IDstringrequiredThe App ID issued together with the key.
- Content-Typestringrequiredapplication/json
Body parameters
application/json- referenceIdstringrequiredRequired. The referenceId returned by the matching send call.nullable
- otpstringrequiredRequired. The 6-digit OTP the end user entered. Send it as a string so leading zeros are kept.nullabledefault 123456
- requestIdstringoptionalOptional. Your own correlation id for this request. Echoed back as requestId in the response; a UUID is generated when omitted.nullable
Sample request body
Responses
200OK
- successbooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 200
- messagestringoptionalNo description.nullabledefault XXXXX
- dataobjectoptionalResponse body for OTP Verify.
3 fields
- isVerifiedbooleanoptionalTrue if the OTP matched and was within the validity window.
- referenceIdstringoptionalThe reference_id that was verified.nullable
- verifiedAtstring (date-time)optionalUTC timestamp of successful verification. Null on failure.nullable
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- isBillablebooleanoptionalWhether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
400Bad Request
- dataobjectoptionalNo description.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- successbooleanoptionalNo description.default false
- messagestringoptionalNo description.nullabledefault XXXXX
- isBillablebooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 400
401Unauthorized
- dataobjectoptionalNo description.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- successbooleanoptionalNo description.default false
- messagestringoptionalNo description.nullabledefault XXXXX
- isBillablebooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 401
404Not Found
- successbooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 200
- messagestringoptionalNo description.nullabledefault XXXXX
- dataanyoptionalNo description.nullable
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- isBillablebooleanoptionalWhether this request was charged: true if it was, false if not. Errors always show false. Flows that finish later (ITR Fetch, SHCIL eStamp, DigiLocker, eSign) show false here, because the charge is made when the result is ready. eSign charges each signer when they complete signing. DigiLocker deducts the credit when the link is created and refunds it automatically if the link expires before the user completes it.
422Unprocessable Contentsame body as 404
500Internal Server Error
- dataobjectoptionalNo description.
- messagesarray<string>optionalNo description.nullable
- request_idstringoptionalNo description.nullable
- successbooleanoptionalNo description.default false
- messagestringoptionalNo description.nullabledefault XXXXX
- isBillablebooleanoptionalNo description.
- statusCodeinteger (int32)optionalNo description.default 500