API reference · Server-side

OTP verification

Send a one-time code to a phone number and check it, entirely from your backend. If you use one of our SDKs in your app, the SDK does the sending and checking — your backend only needs Confirm a verification.

How a verification works

  1. Send — POST /v1/auth/initiate with the phone number. You get a requestId.
  2. Verify — the user types the code; POST /v1/auth/verify with the requestId and the code.
  3. Sign in — when verified is true, log the user in. If the code was checked anywhere other than your backend, confirm it first with /v1/auth/status or /v1/auth/verify-code.

Verify or Confirm — which one do I call?

/v1/auth/verify checks the code the user typed. /v1/auth/verify-code asks QuickAuth who verified — it never checks a code and never uses up an attempt.

Verify an OTPConfirm a verification
You sendrequestId + the code the user typedrequestId or idToken — no code
ItCompares the code, counts the attempt, marks the login verifiedOnly reads the result; changes nothing
You getverified true/false and an idTokensuccess and the verified phoneNumber, countryCode, nationalPhoneNumber
Use it whenYour backend shows the code screen and checks the code itselfOur SDK checked the code in your app, and your backend needs proof before creating a session

Backend only (no SDK): initiate → verify → if verified is true, sign the user in. You don’t need Confirm.

With our SDK: the SDK sends and checks the code. Your app passes the requestId to your backend, and your backend calls Confirm with its secret and signs in the phoneNumber QuickAuth returns — never a number your app sends, which could be forged.

Defaults you can change in the dashboard

SettingDefaultAllowed
Code length6 digits4–8
Code expires after5 minutes30 seconds – 15 minutes
Wrong attempts allowed31–10
New codes per number10 per hourAsk us to change it
Fallback to second channelafter 60 seconds undelivered30 seconds minimum

Resending a code

Calling Send again for the same number while a code is still valid re-sends the same code with the same requestId and a fresh expiry. Each resend is a new message and is billed. Resends do not count toward the 10-per-hour limit and do not reset the wrong-attempt counter.

Automatic fallback

If you set a fallback channel in the dashboard (for example WhatsApp first, SMS second), QuickAuth sends the same code on the fallback channel once — immediately if the first channel reports a failure, or if it is not delivered within the fallback window. You do not need to do anything; the user can enter the code from either message. Both attempts appear in deliveries.

Keep these calls on your server

These endpoints use your client secret. Never call them from a browser or mobile app — use an SDK with a publishable key instead (see SDK & sessions).

Send an OTP

POST/v1/auth/initiate

Creates a verification and sends a one-time code to the user over SMS or WhatsApp. Returns a requestId you keep and use to verify the code.

A 200 means the code was queued. Delivery happens a moment later; if it fails, QuickAuth retries on your fallback channel automatically. Use Get verification status to see what was delivered.

AuthServer keyX-Client-Id + X-Client-Secret

Request body

  • identitystringrequired
    The user’s phone number (or email when identityType is EMAIL). Digits only, with country code — for India 919876543210. A leading +, spaces and dashes are stripped. A bare 10-digit number is treated as Indian. Indian numbers must be mobiles (start with 6, 7, 8 or 9). Send the number the same way every time: rate limits and resend matching compare the string exactly, so +919876543210 and 919876543210 count as two different users.
  • identityTypeenumrequired
    What identity is. Upper case.
    MOBILEEMAIL
  • modeenumrequired
    Upper case.
    OTP — send a numeric code (almost always what you want)OAUTH — send a one-tap sign-in link over WhatsApp or Truecaller
  • channelenumoptional
    Where to send the code. Leave it out to use the primary channel set in your dashboard — recommended, so you can change channels without a deploy. Required for OAUTH mode (WHATSAPP or TRUECALLER). Upper case; do not send "auto" here.
    SMSWHATSAPPEMAILRCSTRUECALLER
  • orderIdstringoptional
    Your own reference (an order or signup id). You can verify and look up by orderId instead of requestId.
  • otpLengthintegeroptionaldefault 6
    Number of digits, 4 to 8. Defaults to your dashboard setting.
  • templateIdstringoptional
    SMS only. The 19-digit DLT template id to use instead of your default OTP template.
  • headerstringoptional
    SMS only. The 6-letter DLT sender id (e.g. ACMEIN) to use instead of your default.
  • wabaIdstringoptional
    WhatsApp only. Send from this specific WhatsApp number instead of letting QuickAuth pick a healthy one.
  • deviceTokenstringoptional
    The token returned by a previous successful login on this device. If the device is still trusted, no OTP is sent and the verification is approved at once — see OneTap.
  • deviceInfoobjectoptional
    Free-form device details for your audit trail (model, OS, app version). Max 4 KB.

Response · 200

  • requestIdstring
    Keep this. You need it to verify the code and to check status.
  • messagestring
    OTP sent successfully, or Authenticated via OneTap. No OTP sent.
  • oneTapAppliedboolean
    When true the device was trusted: the user is already verified and no code was sent. Skip the OTP screen.
  • deviceTokenstring | null
    Store this on the device and send it on the next login to enable OneTap.

Errors

Request
curl -X POST "https://api.quickauth.in/v1/auth/initiate" \
  -H "X-Client-Id: YOUR_CLIENT_ID" \
  -H "X-Client-Secret: YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
  "identity": "919876543210",
  "identityType": "MOBILE",
  "mode": "OTP",
  "orderId": "signup-1029"
}'
Response200
{
  "requestId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c",
  "message": "OTP sent successfully",
  "oneTapApplied": false,
  "deviceToken": "a41c0f9e2b7d4c6a8e1f3b5d7c9a0e2f4b6d8c1a3e5f7b9d0c2e4a6f8b1d3c5e"
}

Verify an OTP

POST/v1/auth/verify

Checks the code the user typed. Send either the requestId or your orderId.

A wrong code is not an error. It returns 200 with verified: false and how many tries are left. Errors only come back once the code has expired or the user has run out of attempts — then send a new OTP.

AuthServer keyX-Client-Id + X-Client-Secret

Request body

  • requestIdstringoptional
    From Send an OTP. Required unless you send orderId.
  • orderIdstringoptional
    Your reference from Send an OTP. Matches the most recent verification with that orderId.
  • otpstringrequired
    The code the user entered, exactly — trim spaces first.
  • deviceTokenstringoptional
    The deviceToken from Send an OTP. When OneTap is on, a successful verify marks this device as trusted for this number.
  • deviceInfoobjectoptional
    Free-form device details for your audit trail. Max 4 KB.

Response · 200

  • requestIdstring
    The verification this result is for.
  • verifiedboolean
    true if the code matched (or the verification was already approved).
  • messagestring
    Verified successfully, Already verified, or Invalid OTP. 2 attempt(s) remaining.
  • idTokenstring | null
    A short-lived (5 minute) signed token proving the verification. Pass it to Confirm a verification from your backend. null when verified is false.

Errors

Request
curl -X POST "https://api.quickauth.in/v1/auth/verify" \
  -H "X-Client-Id: YOUR_CLIENT_ID" \
  -H "X-Client-Secret: YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
  "requestId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c",
  "otp": "482913"
}'
Response200
{
  "requestId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c",
  "verified": true,
  "message": "Verified successfully",
  "idToken": "eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJxdWlja2F1dGguaW4i…"
}

Get verification status

GET/v1/auth/status

Returns where a verification stands and every delivery attempt (primary channel and fallback). Call it from your backend before you sign the user in, and check that authStatus is VERIFIED and identity is the number you expect.

AuthServer keyX-Client-Id + X-Client-Secret

Query parameters

  • requestIdstringoptional
    Required unless you send orderId. Wins if you send both.
  • orderIdstringoptional
    Your reference. Matches the most recent verification with that orderId.

Response · 200

  • requestIdstring
  • identitystring
    The phone number or email exactly as you sent it.
  • identityTypeenum
    MOBILEEMAIL
  • modeenum
    OTPOAUTH
  • authStatusenum
    A verification nobody tries again stays PENDING after it expires — treat “PENDING and older than your expiry” as expired.
    PENDING — code sent, not verified yetVERIFIED — the user entered the right codeEXPIRED — a verify was attempted after expiryFAILED — a verify was attempted after too many wrong codes
  • verifiedAtstring | null
    ISO-8601 time of verification. Null until verified.
  • orderIdstring | null
  • codestring | null
    OAUTH mode only: the authorisation code. Always null for OTP.
  • deliveriesarray
    One entry per send. Empty when OneTap approved the login without a code.

Errors

Request
curl -X GET "https://api.quickauth.in/v1/auth/status?requestId=3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c" \
  -H "X-Client-Id: YOUR_CLIENT_ID" \
  -H "X-Client-Secret: YOUR_CLIENT_SECRET"
Response200
{
  "requestId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c",
  "identity": "919876543210",
  "verifiedAt": "2026-10-16T07:31:12.481Z",
  "orderId": "signup-1029",
  "mode": "OTP",
  "identityType": "MOBILE",
  "authStatus": "VERIFIED",
  "code": null,
  "deliveries": [
    {
      "channel": "WHATSAPP",
      "type": "OTP",
      "deliveryStatus": "FAILED",
      "attemptNumber": 1
    },
    {
      "channel": "SMS",
      "type": "OTP",
      "deliveryStatus": "DELIVERED",
      "attemptNumber": 1
    }
  ]
}

Confirm a verification

POST/v1/auth/verify-code

The safest way to finish a login that happened in your app or website through the SDK. Your frontend sends the requestId our SDKs give you (or the idToken, if you call the API directly) to your backend; your backend calls this endpoint with its secret and gets back the verified phone number. Never trust a phone number your frontend sends you directly.

AuthServer keyX-Client-Id + X-Client-Secret

Request body

  • idTokenstringoptional
    From the verify response. Valid for 5 minutes. Required unless you send requestId.
  • requestIdstringoptional
    The verification id (the SDK calls it sessionId). Used when both are sent.

Response · 200

  • successboolean
    true only when the verification is VERIFIED. When false, every field except requestId is null — do not sign the user in.
  • phoneNumberstring | null
    The verified number as it was submitted (an email for EMAIL verifications).
  • countryCodestring | null
    "91" for Indian numbers sent with the country code; null otherwise.
  • nationalPhoneNumberstring | null
    The 10-digit Indian number without 91; null for other countries.
  • identityTypeenum | null
    MOBILEEMAIL
  • authTimeinteger | null
    When the user verified, in Unix seconds.
  • requestIdstring

Errors

Request
curl -X POST "https://api.quickauth.in/v1/auth/verify-code" \
  -H "X-Client-Id: YOUR_CLIENT_ID" \
  -H "X-Client-Secret: YOUR_CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
  "idToken": "eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJxdWlja2F1dGguaW4i…"
}'
Response200
{
  "success": true,
  "phoneNumber": "919876543210",
  "countryCode": "91",
  "nationalPhoneNumber": "9876543210",
  "identityType": "MOBILE",
  "authTime": 1792143072,
  "requestId": "3f9c1a2b7d4e4f0a9b8c6d5e4f3a2b1c"
}
Chat with us