NaCaller API Reference
Build with NaCaller using an AI coding tool
This page is for language models and for the developers working with them. It says which NaCaller integration fits which job, gives you prompts to paste into Cursor, Lovable, Bolt, Replit, v0 or Claude Code, and lists what the API does not do, so nothing gets invented.
Which integration for which job
| The job | Use | How it is secured |
|---|---|---|
| Prove a user owns the Indian mobile number they typed | Miss OTP (missed call), with Voice OTP as the fallback | API key, server side |
| Read a code to a user who cannot give a missed call | Voice OTP | Same API key |
| Get every finished call (outcome, captured fields, recording, transcript) into a CRM or database | Call results webhook | Signing secret, verify each request |
| Get every finished call into a spreadsheet with no code | Google Sheets connector | Share the sheet, no keys |
| Send a new lead (form, ad, Zapier) so an agent calls them within seconds | Instant Leads hook | A token in the URL |
| Learn that a number just verified without polling | Verification callback | Signing secret |
Machine-readable files
Give an assistant one of these instead of asking it to browse:
llms.txt: The index: what NaCaller is, plans, every page, and every docs page, in the standard llmstxt.org format.llms-full.txt: The whole developer API in plain text (endpoints, fields, limits, errors), plus pricing, comparisons and FAQs. Best single file to give an assistant.This documentation: Human-readable reference with copy-paste curl, Node and Python samples.
Ground rules for an assistant
- The key is a server secret. Verification keys look like
nck_live_…and live inNACALLER_KEY. Never place one in client code, aVITE_orNEXT_PUBLIC_variable, a mobile app or a public repository. Every call goes through your backend. - One base URL, one header.
https://api.nacaller.com/api/verifywithAuthorization: Bearer <key>. Errors are JSON,{ "error": "…" }, with an ordinary HTTP status. - Verify webhooks on the raw body with a constant-time compare. The two webhooks use different headers:
X-MissOTP-Signaturefor verification andX-NaCaller-Signaturefor call results. - De-duplicate. A delivery can be retried, so the same event can arrive twice. Key on
requestIdorcall.id. - Indian mobiles only for verification: ten digits starting 6 to 9.
- Do not invent endpoints or fields. If something is not in this documentation or in
llms-full.txt, it does not exist.
What the API does not do
- There is no public API to create or edit agents, campaigns or contact lists, or to start an outbound call. These are done in the dashboard. Contacts can also be imported from a CSV, and leads can be posted in with the Instant Leads hook.
- The only API-key endpoints are the verification ones (Miss OTP and Voice OTP). The lead and WhatsApp hooks use a token in the URL.
- NaCaller sends exactly two kinds of outbound event: the verification callback and the call results webhook.
- There is no sandbox: the free credits run on the live line, so test with your own phone.
- There is no SDK package and no OpenAPI file yet. Every endpoint is plain HTTPS with JSON.
Prompts to paste
Each prompt is complete: it names the endpoints, where the secret lives, what to build and what not to invent.
Add phone verification
Prompt: phone verification (Miss OTP and Voice OTP)
Add mobile-number verification to this app using the NaCaller API.
Miss OTP (missed call) is the main method; Voice OTP (we call the user and read a code) is the fallback.
Full reference: https://nacaller.com/docs — plain-text summary: https://nacaller.com/llms-full.txt
SECRETS — read these carefully
- The API key is in the environment variable NACALLER_KEY (it looks like nck_live_…).
- It is a SERVER secret. Never put it in client code, a VITE_/NEXT_PUBLIC_ variable, or the browser bundle.
Every NaCaller call must go through my backend (an API route, server action, or edge/serverless function —
on Supabase use an Edge Function and store the key with `supabase secrets set`).
BASE URL: https://api.nacaller.com/api/verify
AUTH HEADER on every call: Authorization: Bearer <NACALLER_KEY>
Errors are JSON: { "error": "human-readable message" } with a normal HTTP status.
BUILD THESE SERVER ENDPOINTS (names are yours to choose):
1. POST /verify/start { phone }
- Validate phone: keep the last 10 digits; must match /^[6-9]\d{9}$/ (Indian mobile).
- Call POST https://api.nacaller.com/api/verify/start with JSON { "phone": "<10 digits>", "reference": "<my user id>" }.
- Response: { requestId, phone, callNumber, status: "pending", expiresAt, expiresIn, instruction }.
- Save requestId against the signed-in user/session on the server. Return callNumber, instruction, expiresAt, requestId.
2. GET /verify/status/:requestId
- Only for a requestId saved for this user. Call GET https://api.nacaller.com/api/verify/status/<requestId>.
- Response: { requestId, phone, reference, status: "pending" | "verified" | "expired", verified, verifiedAt, expiresAt }.
- When verified is true, mark this user's phone as verified IN MY DATABASE (server side) and return { verified: true }.
3. POST /verify/voice { phone } (the fallback)
- Call POST https://api.nacaller.com/api/verify/start with { "phone", "reference", "method": "voice", "language": "en" } ("hi" for Hindi, "te" for Telugu).
- Response: { id, method: "voice", status: "calling", expiresAt }. Save id against the user.
- We ring the number and read a 6-digit code twice. The verification is valid for 10 minutes from /start. Until the user answers, /check returns 400 "not answered yet" (no attempt used) and /status says "pending".
4. POST /verify/check { id, code }
- Call POST https://api.nacaller.com/api/verify/check with { "id", "code" }.
- 200 { verified: true, id, phone, reference } → mark the phone verified server side.
- 400 { verified: false, error, attemptsLeft } → show error and attemptsLeft. 5 attempts max.
- 410 → code expired; offer to call again.
UI (one screen, mobile first)
- Step 1: a phone input ("Your mobile number", +91 prefix shown, 10 digits) and a "Verify" button.
- Step 2 (missed call): show the instruction text, e.g. "Give a missed call to <callNumber> from <phone>. You can cut the call as soon as it rings."
Show callNumber large, as a tel: link ("Tap to call") on phones. Show a countdown to expiresAt (5 minutes).
Poll my status endpoint every 2–3 seconds; stop on verified, expired, or when the user leaves the screen.
On verified: a success state and continue. On expired: "Time ran out" + a "Try again" button.
- After ~20 seconds on step 2, show a secondary button: "Can't give a missed call? Get a call instead".
It calls my /verify/voice endpoint, then shows a 6-digit code input (inputmode="numeric",
autocomplete="one-time-code") with the text "We're calling <phone> now. Enter the 6-digit code you hear."
- Show the API's error message as-is for 400, 402 (out of credits — also log it for me), 429 (too many attempts; ask them to wait).
- Disable buttons while a request is in flight. Never show or log the API key.
OPTIONAL: instead of polling, receive a webhook. NaCaller POSTs JSON
{ event: "verification.verified", requestId, phone, reference, verifiedAt, timestamp } (voice ones also carry method) to my HTTPS callback URL
with header X-MissOTP-Signature = hex HMAC-SHA256 of the RAW request body, keyed with the signing secret in NACALLER_WEBHOOK_SECRET.
Verify it with a constant-time compare against the raw bytes (not re-serialised JSON), reject if timestamp
(milliseconds) is more than 5 minutes old, then reply 2xx. Keep polling as a backup.
Do not invent other NaCaller endpoints or fields. Write the code, then list the env vars I need to set.Receive finished-call results
Prompt: call results webhook
Add an endpoint to this app that receives finished-call results from NaCaller.
Full reference: https://nacaller.com/docs/call-results — plain-text summary: https://nacaller.com/llms-full.txt
WHAT NACALLER DOES
- When a call ends, NaCaller waits about 45 seconds (so the transcript and outcome are ready) and then POSTs JSON to my HTTPS URL.
- Body: { event: "call.completed", call: { id, direction, status, outcome, outcomeReason, startedAt, completedAt, durationSeconds, to, from, recordingUrl, transcript, captured, tags }, contact: { id, name, phone } | null, campaign: { id, name } | null, agent: { id, name } | null }.
- "captured" is an object of the fields my campaign asked the agent to find out (for example { course: "B.Tech CSE" }); it can be empty.
- Header X-NaCaller-Signature: "sha256=" + hex HMAC-SHA256 of the RAW request body, keyed with the signing secret from NaCaller → Settings. Store that secret in NACALLER_CALL_SECRET.
BUILD
1. A POST route at a public HTTPS URL that reads the RAW body (not parsed JSON).
2. Verify the signature with a constant-time compare. Reject with 401 if it does not match.
3. De-duplicate on call.id (a retry after a failure can deliver the same call twice).
4. Save what I need (outcome, captured, recordingUrl, transcript), then reply with any 2xx within 8 seconds. Anything else is retried 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours later, then given up.
RULES
- The URL must be https and a public host. Redirects are not followed. Localhost does not work: use a tunnel while developing.
- Never log the signing secret. Do not invent other NaCaller endpoints or fields.
Write the code, then list the env vars and the URL I must paste into NaCaller → Settings → "Send call results".Related
- Developer docs overview
- Call results: webhook and Google Sheets
- Integrations for non-developers: forms, Zapier, WhatsApp
Stuck on the NaCaller API? Ask us.
A developer reads every message and replies by email, usually within one business day.