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.

POST

/api/v1/otp/mobile/send

#

Send a 6-digit OTP to a mobile number via SMS.

docs.signcare.io/core/api/otp/otp-verifications/#post-api-v1-otp-mobile-send

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-KEY
    stringrequired
    Your secret API key for the environment you are calling (Stage and Live use different keys).
  • X-API-APP-ID
    stringrequired
    The App ID issued together with the key.
  • Content-Type
    stringrequired
    application/json

Body parameters

application/json
  • mobileNumber
    stringrequired
    Required. 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
  • requestId
    stringoptional
    Optional. Your own correlation id for this request. Echoed back as requestId in the response; a UUID is generated when omitted.
    nullable
Sample request body
{ "mobileNumber": "9876543210" }

Responses

200OK
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    objectoptional
    Response body for OTP Send.
    3 fields
    • referenceId
      stringoptional
      Unique token the caller must pass back in the Verify call. Backed by Redis (10-minute TTL) and the Feature.OtpVerification table (durable fallback).
      nullable
    • deliveryTarget
      stringoptional
      Masked delivery target for display (e.g. "XXXXXX9178" or "r***@gmail.com"). Never exposes the raw value.
      nullable
    • expiresAt
      string (date-time)optional
      UTC timestamp when this OTP will expire.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether 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.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": { "referenceId": "string", "deliveryTarget": "string", "expiresAt": "2026-01-01T00:00:00Z" }, "messages": [ "string" ], "request_id": "string", "isBillable": false }
400Bad Request
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 400
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 400 }
401Unauthorized
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 401
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 401 }
402Payment Required
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    anyoptional
    No description.
    nullable
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether 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.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": null, "messages": [ "string" ], "request_id": "string", "isBillable": false }
429Too Many Requestssame body as 402
500Internal Server Error
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 500
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 500 }
Code samplescurl · Node.js · Python
Stage: uat-ext.signcare.io
curl -X POST 'https://uat-ext.signcare.io/api/v1/otp/mobile/send' \ -H 'X-API-KEY: YOUR_API_KEY' \ -H 'X-API-APP-ID: YOUR_APP_ID' \ -H 'Content-Type: application/json' \ -d '{ "mobileNumber": "9876543210" }'
POST

/api/v1/otp/mobile/verify

#

Verify the OTP sent to a mobile number.

docs.signcare.io/core/api/otp/otp-verifications/#post-api-v1-otp-mobile-verify

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-KEY
    stringrequired
    Your secret API key for the environment you are calling (Stage and Live use different keys).
  • X-API-APP-ID
    stringrequired
    The App ID issued together with the key.
  • Content-Type
    stringrequired
    application/json

Body parameters

application/json
  • referenceId
    stringrequired
    Required. The referenceId returned by the matching send call.
    nullable
  • otp
    stringrequired
    Required. The 6-digit OTP the end user entered. Send it as a string so leading zeros are kept.
    nullabledefault 123456
  • requestId
    stringoptional
    Optional. Your own correlation id for this request. Echoed back as requestId in the response; a UUID is generated when omitted.
    nullable
Sample request body
{ "referenceId": "string", "otp": "123456", "requestId": "string" }

Responses

200OK
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    objectoptional
    Response body for OTP Verify.
    3 fields
    • isVerified
      booleanoptional
      True if the OTP matched and was within the validity window.
    • referenceId
      stringoptional
      The reference_id that was verified.
      nullable
    • verifiedAt
      string (date-time)optional
      UTC timestamp of successful verification. Null on failure.
      nullable
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether 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.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": { "isVerified": false, "referenceId": "string", "verifiedAt": "2026-01-01T00:00:00Z" }, "messages": [ "string" ], "request_id": "string", "isBillable": false }
400Bad Request
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 400
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 400 }
401Unauthorized
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 401
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 401 }
404Not Found
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    anyoptional
    No description.
    nullable
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether 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.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": null, "messages": [ "string" ], "request_id": "string", "isBillable": false }
422Unprocessable Contentsame body as 404
500Internal Server Error
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 500
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 500 }
Code samplescurl · Node.js · Python
Stage: uat-ext.signcare.io
curl -X POST 'https://uat-ext.signcare.io/api/v1/otp/mobile/verify' \ -H 'X-API-KEY: YOUR_API_KEY' \ -H 'X-API-APP-ID: YOUR_APP_ID' \ -H 'Content-Type: application/json' \ -d '{ "referenceId": "string", "otp": "123456", "requestId": "string" }'
POST

/api/v1/otp/email/send

#

Send a 6-digit OTP to an email address.

docs.signcare.io/core/api/otp/otp-verifications/#post-api-v1-otp-email-send

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-KEY
    stringrequired
    Your secret API key for the environment you are calling (Stage and Live use different keys).
  • X-API-APP-ID
    stringrequired
    The App ID issued together with the key.
  • Content-Type
    stringrequired
    application/json

Body parameters

application/json
  • email
    stringrequired
    Required. 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
  • requestId
    stringoptional
    Optional. Your own correlation id for this request. Echoed back as requestId in the response; a UUID is generated when omitted.
    nullable
Sample request body
{ "email": "user@example.com" }

Responses

200OK
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    objectoptional
    Response body for OTP Send.
    3 fields
    • referenceId
      stringoptional
      Unique token the caller must pass back in the Verify call. Backed by Redis (10-minute TTL) and the Feature.OtpVerification table (durable fallback).
      nullable
    • deliveryTarget
      stringoptional
      Masked delivery target for display (e.g. "XXXXXX9178" or "r***@gmail.com"). Never exposes the raw value.
      nullable
    • expiresAt
      string (date-time)optional
      UTC timestamp when this OTP will expire.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether 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.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": { "referenceId": "string", "deliveryTarget": "string", "expiresAt": "2026-01-01T00:00:00Z" }, "messages": [ "string" ], "request_id": "string", "isBillable": false }
400Bad Request
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 400
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 400 }
401Unauthorized
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 401
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 401 }
402Payment Required
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    anyoptional
    No description.
    nullable
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether 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.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": null, "messages": [ "string" ], "request_id": "string", "isBillable": false }
429Too Many Requestssame body as 402
500Internal Server Error
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 500
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 500 }
Code samplescurl · Node.js · Python
Stage: uat-ext.signcare.io
curl -X POST 'https://uat-ext.signcare.io/api/v1/otp/email/send' \ -H 'X-API-KEY: YOUR_API_KEY' \ -H 'X-API-APP-ID: YOUR_APP_ID' \ -H 'Content-Type: application/json' \ -d '{ "email": "user@example.com" }'
POST

/api/v1/otp/email/verify

#

Verify the OTP sent to an email address.

docs.signcare.io/core/api/otp/otp-verifications/#post-api-v1-otp-email-verify

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-KEY
    stringrequired
    Your secret API key for the environment you are calling (Stage and Live use different keys).
  • X-API-APP-ID
    stringrequired
    The App ID issued together with the key.
  • Content-Type
    stringrequired
    application/json

Body parameters

application/json
  • referenceId
    stringrequired
    Required. The referenceId returned by the matching send call.
    nullable
  • otp
    stringrequired
    Required. The 6-digit OTP the end user entered. Send it as a string so leading zeros are kept.
    nullabledefault 123456
  • requestId
    stringoptional
    Optional. Your own correlation id for this request. Echoed back as requestId in the response; a UUID is generated when omitted.
    nullable
Sample request body
{ "referenceId": "string", "otp": "123456", "requestId": "string" }

Responses

200OK
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    objectoptional
    Response body for OTP Verify.
    3 fields
    • isVerified
      booleanoptional
      True if the OTP matched and was within the validity window.
    • referenceId
      stringoptional
      The reference_id that was verified.
      nullable
    • verifiedAt
      string (date-time)optional
      UTC timestamp of successful verification. Null on failure.
      nullable
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether 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.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": { "isVerified": false, "referenceId": "string", "verifiedAt": "2026-01-01T00:00:00Z" }, "messages": [ "string" ], "request_id": "string", "isBillable": false }
400Bad Request
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 400
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 400 }
401Unauthorized
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 401
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 401 }
404Not Found
  • success
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 200
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • data
    anyoptional
    No description.
    nullable
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • isBillable
    booleanoptional
    Whether 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.
Example
{ "success": false, "statusCode": 200, "message": "XXXXX", "data": null, "messages": [ "string" ], "request_id": "string", "isBillable": false }
422Unprocessable Contentsame body as 404
500Internal Server Error
  • data
    objectoptional
    No description.
  • messages
    array<string>optional
    No description.
    nullable
  • request_id
    stringoptional
    No description.
    nullable
  • success
    booleanoptional
    No description.
    default false
  • message
    stringoptional
    No description.
    nullabledefault XXXXX
  • isBillable
    booleanoptional
    No description.
  • statusCode
    integer (int32)optional
    No description.
    default 500
Example
{ "data": {}, "messages": [ "string" ], "request_id": "string", "success": false, "message": "XXXXX", "isBillable": false, "statusCode": 500 }
Code samplescurl · Node.js · Python
Stage: uat-ext.signcare.io
curl -X POST 'https://uat-ext.signcare.io/api/v1/otp/email/verify' \ -H 'X-API-KEY: YOUR_API_KEY' \ -H 'X-API-APP-ID: YOUR_APP_ID' \ -H 'Content-Type: application/json' \ -d '{ "referenceId": "string", "otp": "123456", "requestId": "string" }'