NaCaller API Reference New

NaCaller Voice OTP API

We call the number and read a 6-digit code aloud, twice. Your user types it into your app and you check it. It is the second method on the same API as Miss OTP— same key, same base URL, same webhook — for the users who can’t or won’t give a missed call.

  1. Your server calls POST /start with method: "voice".
  2. The user answers, hears the code, and types it in.
  3. Your server calls POST /check with the id and the code.

The recommended pattern

Missed call first — it is cheaper and there is nothing to type. Then, a few seconds in, a secondary button: “Can’t give a missed call? Get a call instead”. That button starts a voice verification and swaps the screen for a code input.

POST /start (method: voice)

Endpoint
POST https://api.nacaller.com/api/verify/start
Body fieldNotes
phoneRequired. A 10-digit Indian mobile, as for Miss OTP.
method"voice". Leave it out (or send "missed_call") for Miss OTP.
languageOptional. "en" (English), "hi" (Hindi) or "te" (Telugu) — the language the code is read in.
referenceOptional. Your own id for this attempt, echoed back.

Response 200 — we are ringing the number now:

Response
{
  "id": "cm0x9a1b40007",
  "method": "voice",
  "status": "calling",
  "expiresAt": "2026-09-11T06:22:04.000Z"
}

The verification is valid for 10 minutes from /start. While the phone is ringing, /status reports pending; the code only exists once the user picks up, so a /check before then returns 400 “not answered yet” without using an attempt. Show the user something like:

Suggested copy
We're calling <phone> now.
Enter the 6-digit code you hear.

Give the input inputmode="numeric" and autocomplete="one-time-code".

POST /check

Endpoint
POST https://api.nacaller.com/api/verify/check

{ "id": "<id from /start>", "code": "123456" }
StatusBody
200{ "verified": true, "id": "cm0x9a1b40007", "phone": "9876543210", "reference": "user_123" }
400Wrong code: { "verified": false, "error": "…", "attemptsLeft": 3 }. 5 attempts per verification.
410The code has expired. Offer to call again.

Checking is idempotent once verified: sending the right code again returns the same 200, so a double-submitted form does no harm.

GET /status/:id

The same endpoint as Miss OTP, and the response now includes method ("voice" or "missed_call"). Voice verifications also fire your webhook, with method in the payload.

Limits and errors

StatusWhen
400Invalid number, unknown method or language, a code that isn’t 6 digits, a wrong code on /check, or a /check before the call was answered.
401Missing or invalid key.
402Out of voice credits.
403This number can’t be verified.
404Unknown verification id (ids are scoped to your key).
410Code expired.
429More than 3 voice calls to one number in an hour, or 10 in a day.
502The call could not be placed. Start again.
503Voice OTP is briefly unavailable. Fall back to a missed call.

Billing

One credit per answeredcall. If the user doesn’t pick up, it costs you nothing. You get 5 free calls per account to start; after that, voice credits come in prepaid packs — see Voice OTP pricing.

Compliance

  • Only place a call when the user has asked for one — the fallback button is the request.
  • The call says the code and nothing else — there is no message of yours to add.
  • Not for regulated lenders, banks or insurers yet.If you are regulated by the RBI, SEBI or IRDAI, TRAI rules require your OTP calls to come from a 1600-series number, so Voice OTP isn’t for you yet.

Code

curl
# 1 — start: we ring the number and read the code
curl -X POST https://api.nacaller.com/api/verify/start \
  -H "Authorization: Bearer $NACALLER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"phone":"9876543210","method":"voice","language":"en","reference":"user_123"}'

# 2 — check the code the user typed
curl -X POST https://api.nacaller.com/api/verify/check \
  -H "Authorization: Bearer $NACALLER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"id":"VERIFICATION_ID","code":"123456"}'
Node (fetch, server-side)
// Server-side only — NACALLER_KEY must never reach the browser.
const BASE = "https://api.nacaller.com/api/verify";
const headers = {
  Authorization: `Bearer ${process.env.NACALLER_KEY}`,
  "Content-Type": "application/json",
};

export async function startVoiceCall(phone, userId, language = "en") {
  const res = await fetch(`${BASE}/start`, {
    method: "POST", headers,
    body: JSON.stringify({ phone, method: "voice", language, reference: userId }),
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error);   // 400, 401, 402, 429
  return data; // { id, method: "voice", status: "calling", expiresAt }
}

export async function checkCode(id, code) {
  const res = await fetch(`${BASE}/check`, {
    method: "POST", headers, body: JSON.stringify({ id, code }),
  });
  const data = await res.json();
  if (res.ok) return data;                           // { verified: true, id, phone, reference }
  if (res.status === 410) return { verified: false, expired: true, error: data.error };
  return data;                                       // 400 → { verified: false, error, attemptsLeft }
}
Python (requests)
# Server-side only — NACALLER_KEY must never reach the browser.
import os, requests

BASE = "https://api.nacaller.com/api/verify"
AUTH = {"Authorization": f"Bearer {os.environ['NACALLER_KEY']}"}

def start_voice_call(phone: str, user_id: str, language: str = "en") -> dict:
    r = requests.post(f"{BASE}/start", headers=AUTH, timeout=10, json={
        "phone": phone, "method": "voice", "language": language, "reference": user_id,
    })
    data = r.json()
    if not r.ok:
        raise RuntimeError(data["error"])   # 400, 401, 402, 429
    return data  # id, method="voice", status="calling", expiresAt

def check_code(verification_id: str, code: str) -> dict:
    r = requests.post(f"{BASE}/check", headers=AUTH, timeout=10,
                      json={"id": verification_id, "code": code})
    data = r.json()
    if r.status_code == 410:
        return {"verified": False, "expired": True, "error": data.get("error")}
    return data  # 200 → verified True; 400 → verified False, error, attemptsLeft

Stuck on the NaCaller API? Ask us.

A developer reads every message and replies by email, usually within one business day.

Call meStart free