Overview
The Nutaan Calling API lets you build conversational AI phone agents that speak naturally, understand your business, and handle real calls — outbound and inbound. Everything you can do in the dashboard (agents, calls, batch campaigns, numbers, voices) is available over a clean REST API, authenticated with the same API key.
Base URL
All requests use JSON. Send Content-Type: application/json on every POST/PATCH.
Authentication
Authenticate with your Nutaan API key. Generate one in the dashboard under Settings → API keys. Pass it in the X-API-Key header (or as a Bearer token). Keep it server-side — never expose it in a browser.
The same key also works as a Bearer token:
MCP — drive Nutaan from Claude, ChatGPT or Cursor
Nutaan hosts an MCP server, so an AI assistant can work the account directly: build an agent from a website, dial a whole list of contacts, then read the transcripts and the leads that come out. Nothing to install — you paste one URL.
Take the key from Settings → API keys (it starts with nut-) and paste that whole URL wherever your client asks for a custom connector or a remote MCP server:
Claude Code, Gemini CLI — one command in your terminal (the dashboard's Developers → MCP tab gives it to you with your key already in it):
Claude (web or desktop) — Settings → Connectors → Add custom connector. Name it Nutaan, paste the URL into the address field, leave the OAuth boxes empty, press Add.
ChatGPT — Settings → Connectors → Add, same URL.
Cursor, Windsurf, VS Code — put it in the MCP config:
The key rides in the URL because those dialogs take a URL and nothing else — there is no field for a header. So treat the URL like a password: anyone holding it can act on your account, and you rotate it by rotating the key. Clients that can send headers should use the bare https://nutaan.com/api/mcp endpoint with Authorization: Bearer nut-… or X-API-Key instead.
Approvals, and turning them off
By default the assistant checks with you before it spends credit or rings anyone. If those confirmations get in the way — you are running campaigns and being asked to approve each one — use the autonomous URL instead:
On that URL the tools drop their "ask first" instructions entirely rather than being told to both ask and not ask, and the assistant is told it is pre-authorised, so it carries out the work and reports afterwards — and can run independent steps at the same time instead of one approval at a time. It still stops if a request is ambiguous, if a contact list looks wrong, or if something failed partway.
Which mode you are in is fixed by the URL you pasted, so nothing the assistant later reads — a web page, a document, a tool result — can switch autonomy on. To change it, paste the other URL. Connect Tools has a checkbox that produces both.
It speaks Streamable HTTP over JSON-RPC, so anything MCP-compatible works. For a client that can only launch a local process, the repo also ships mcp-server/ — a stdio bridge onto this same endpoint, holding no tools of its own so it cannot drift.
Where this gets useful
A whole outbound campaign becomes one conversation. Research the list in your assistant, have it build the right agent, and let it dial — without opening the dashboard:
The 18 tools
Two of these exist to stop an assistant guessing. nutaan_list_voicesreports which voices cover a language and whether this account may use the premium ones — ask for a premium voice without access, or one that does not cover the language, and you get an agent nobody can hear. nutaan_prompt_guidecarries the rules for phone prompts: a hand-written one is nearly always too short, and a short prompt makes the agent improvise prices and opening hours. Better still, let nutaan_create_agent write it from the website and add your own rules on top with appendToPrompt.
nutaan_update_agent matters more than it looks: without it an assistant that finds a fault in an agent can only build a second one, leaving the broken agent live and stranding its number and call history. Correcting an agent in place is the normal path — creating is for agents that do not exist yet.
Five of them change something real — creating an agent, placing a call, starting a campaign, sharing an agent publicly, and running analysis. Each says so in its own description, so a well-behaved client asks you first. Nothing in the set deletes anything, and every tool runs through this same REST API under your key's scopes, so MCP can never reach further than your own integrations can.
Voice agents
A voice agent is the AI persona that answers and speaks on a call — its instructions, language, LLM, and voice. Create one, then reference its _id when you place calls.
/voice/agents— list your agents/voice/agents— create an agent (azure-voicelive — English/Indian S2S)azure-voiceliveusesrealtimeVoice(an Azure neural voice, e.g.hi-IN-SwaraNeural) andrealtimeModel: "gpt-realtime". It does not readstt/tts.elevenlabs-agentusestts.voiceId(the voice), plustts.model(eleven_multilingual_v2·eleven_v3_conversational·eleven_turbo_v2_5·eleven_flash_v2_5),tts.stability,tts.speedandtts.similarityBoost— all applied. Onlystyleis ignored. Useeleven_v3_conversationalto perform inline expression tags like[laughs]/[excited].pipelineis the only engine that readssttandtts(withprovider+language).
/voice/agents/:id— update an agent (same body as create; partial)/voice/agents/:id— delete an agentAgent templates
Ready-made, vertical-specific agent presets (real estate, healthcare, lending, e-commerce…). List them, inspect one, or create an agent from one by passing template_id — the template fills the prompt, welcome message, extraction fields, dispositions and voice preset for any value you leave blank (your own values always win). You can also generate a brand-new custom template from a plain-English brief.
/voice/agents/templates— list available templates/voice/agents/templates/:id— full detail of one template (prompt, voice preset, fields)/voice/agents— create an agent FROM a template/voice/agents/templates/generate— generate a custom template from a brief — body: { businessName, industry, goal, language }Call behaviour
These per-agent fields control how a call sounds, when the bot lets the caller interrupt, how it ends, and what happens afterwards. Set them on POST /voice/agents or PATCH /voice/agents/:id. They apply to every transport — phone calls and the browser widget.
When a call ends, your postCallWebhookUrl receives the transcript plus an outcome(completed | rejected | meeting_booked | no_response | natural_end) and, if postCallAnalysisis on, an analysis object (summary, disposition, sentiment,extractedData, suggestedFollowUp). Every call is also recorded two-sided (caller + AI) and the WAV appears as recordingUrl — nothing to configure.
Voice engines
The voiceEngine field on an agent decides how speech is produced — the single biggest lever on latency and language support. There are two families: a flexible pipeline(speech-to-text → LLM → text-to-speech, fully mix-and-match) and native speech-to-speech (S2S)engines that skip the middle steps for much lower latency.
| voiceEngine | Type | First-response latency | Languages | Best for |
|---|---|---|---|---|
pipeline | STT → LLM → TTS (cascade) | ~1.5–2s · ≈0.5s first-audio with streaming TTS | 30+ incl. Hindi & Indian | Hindi/Indian & multilingual, full control, lowest cost |
azure-voicelive | Speech-to-speech (S2S) | under ~500ms–1s (sub-second) | English + Azure languages | Low-latency, expressive English |
deepgram-agent | Speech-to-speech (S2S) | under 300ms (server-side) | English only (Aura-2) | Fastest, most natural English |
Which should I use?
- Hindi, Hinglish or any Indian language →
pipeline. It is the only family that handles them well. Pair it with TTSsmallest(streams over WebSocket, first audio ~500ms) orsarvam. - English, lowest latency & most natural →
deepgram-agent(a.k.a. Turbo) — under 300ms speech-to-speech. - English, cheapest low-latency →
azure-voicelive(Voice Live) — sub-second speech-to-speech.
How to enable. Set voiceEngine on the agent (create or PATCH). A pipelineagent also takes stt and tts; the S2S engines take optional engine-specific fields (dgAgentSpeakModel/dgAgentThinkModel for Turbo, realtimeVoice/realtimeModelfor Voice Live) and otherwise use sensible defaults.
The engine (and its fields) come back on the agent object:
Voices
List the available voices and providers to pick a voiceId for your agent, or preview one.
/voice/voices— list voices grouped by provider/voice/voices/providers— list voice providers/voice/voices/preview— preview a voice (query: provider, voiceId, text)Phone numbers
Search and buy a dedicated number so your AI can make and receive calls. Numbers are valid one month from purchase.
/voice/numbers— list your numbers/voice/numbers/buy— buy a number (min ₹1000 balance)/voice/numbers/search— search available numbers (query: country, type, areaCode)/voice/numbers/:id— release a numberLanguages & voices
Every agent speaks one primary language, set differently depending on the engine (see the table above). Match the field to the engine: azure-voicelive takes a realtimeVoice(an Azure neural voice whose name starts with the language code, e.g. hi-IN-SwaraNeural);pipeline takes stt.language + tts.language; elevenlabs-agenttakes tts.voiceId (Hindi & English only).
| Language | Code | Best engine | Example voice |
|---|---|---|---|
| English (India) | en-IN | azure-voicelive | en-IN-NeerjaNeural |
| English (US) | en-US | azure-voicelive / deepgram-agent | en-US-AvaNeural / aura-2-thalia-en |
| Hindi | hi-IN | elevenlabs-agent (Pari) or pipeline | ElevenLabs voiceId / hi-IN-SwaraNeural |
| Bengali | bn-IN | azure-voicelive | bn-IN-TanishaaNeural |
| Tamil | ta-IN | azure-voicelive | ta-IN-PallaviNeural |
| Telugu | te-IN | azure-voicelive | te-IN-ShrutiNeural |
| Marathi | mr-IN | azure-voicelive | mr-IN-AarohiNeural |
| Gujarati | gu-IN | azure-voicelive | gu-IN-DhwaniNeural |
| Kannada | kn-IN | azure-voicelive / pipeline | kn-IN-SapnaNeural |
| Malayalam | ml-IN | azure-voicelive / pipeline | ml-IN-SobhanaNeural |
| Punjabi | pa-IN | azure-voicelive | pa-IN-VaaniNeural |
| Spanish | es-ES | azure-voicelive | es-ES-ElviraNeural |
| Arabic | ar-SA | azure-voicelive | ar-SA-ZariyahNeural |
elevenlabs-agent) supports Hindi & English only — for Bengali/Tamil/Telugu/Marathi/Gujarati/Spanish/Arabic use azure-voicelive. On azure-voicelive the agent can mirror any language mid-call when languageMode: "multilingual", but it opens in the language of its realtimeVoice and welcomeMessage. Full Azure neural-voice list: Azure language support.Inbound calls (activate)
To have callers reach an agent when they dial its number, create or update the agent with activate_inbound: true and set phoneNumberId to a number you own. The rule is simple: the dialled number must equal the agent's phoneNumberId → that agent answers.
/voice/agents/:id— turn inbound ON for this agent's numberSend activate_inbound: false to turn it off — we unpin the number from the agent (so it stops answering there). Omit the field to leave inbound unchanged. Inbound isn't available on the free trial number (outbound-only). India transfers/bridging need a Plivo number.
Outbound calls
Place a single AI call. Give it an agentId and the destination number; the agent dials and converses.
/voice/calls/outbound— place an outbound AI callPoll the returned callId for status, transcript and outcome:
/voice/calls/:id— get one call with transcriptCall object — every field (returned by GET /voice/calls and GET /voice/calls/:id):
| Field | Type | Meaning |
|---|---|---|
| id / call_id | string | Unique call id — use call_id to poll GET /voice/calls/:id. |
| agent_id | string | The agent that ran the call. |
| direction | string | "inbound" or "outbound". |
| status | string | queued · initiated · ringing · in-progress · completed · failed · no-answer · busy · canceled. |
| from_number | string (E.164) | The number the call was placed FROM (your caller ID). |
| to_number | string (E.164) | The number the call was placed TO (the customer). |
| duration | number | Talk time in seconds. |
| recording_url | string | HTTPS URL of the call recording (WAV). Empty until the recording is ready. |
| has_recording | boolean | true when a recording exists. |
| booking | object | null | Present when the agent booked something: { appointmentTime (ISO), timezone, durationMinutes, notes }. |
| outcome | string | Business outcome captured by the agent (e.g. "booked", "not_interested"). |
| analysis | object | Post-call AI: { sentiment, disposition, summary }. |
| created_at / started_at / ended_at | ISO date | Timestamps for the call lifecycle. |
| transcript | array | Turn-by-turn [{ role: "assistant" | "user", text }] — only on GET /voice/calls/:id. |
from, to, recordingUrl, createdAt) and snake_case (from_number, to_number, recording_url, created_at) keys are returned, so existing integrations keep working. Prefer the snake_case names above for new code./voice/calls— list calls (query: page, limit, direction)/voice/calls/:id/hangup— hang up an in-progress callBatch calling
Run a campaign: one agent calls a list of contacts in parallel. Pass per-contact variables to personalise each conversation (name, order id, appointment time…).
/voice/batch— create & queue a batch campaign/voice/batch— list batches/voice/batch/:id— get a batch (progress, stats)/voice/batch/:id/calls— list the calls in a batch/voice/batch/:id/start— start / resume a batch/voice/batch/:id/pause— pause a batch/voice/batch/:id— delete a batchSMS & DLT
Send SMS from your numbers and read message history. India (A2P) requires a DLT-registeredsender — register your Entity ID, Sender ID and Template once, verify it with a real test message, then send. The sender number must be SMS-enabled on the provider (voice-only numbers can’t send SMS). All endpoints are scoped to numbers/agents your key owns.
/voice/messages/dlt— register (or update) a DLT sender/voice/messages/dlt— list your DLT senders (status: pending | verified | failed)/voice/messages/dlt/verify— send a real test SMS & confirm the carrier accepts it — body: { number, to }/voice/messages/send— send an SMS/voice/messages— message history (query: agentId, direction, provider, limit)India destinations automatically attach the sender’s stored dlt_entity_id / dlt_template_id; pass dltTemplateId to override. Inbound SMS is stored and forwarded to the agent’s post-call webhook as { type: "sms.received", … }.
Knowledge base
Give your AI your business content. Create a knowledge base from a website — it crawls and indexes automatically — and your agents and chat widget answer only from it, never inventing facts. Creating a KB also mints a widget_key you use to embed the chat widget.
/kb— create a KB from a website/kb— list your knowledge bases (status: processing | completed | failed)/kb/:id— set priority_info — guidelines/links that always override the scraped content/kb/test/query— test what the AI answers from a KB/kb/:id— delete a knowledge baseChat widget
Embed the same Nutaan widget on any website with one script tag from our CDN — it is fully white-label: your branding, your knowledge, your keys. Or drive the entire live chat through the API and build your own UI. The CDN script is identical for every customer; your x-api-key and widget_keymake it yours.
Drop-in embed — paste before </body> on your site:
Or build your own chat UI with the widget API. Every widget endpoint takes two headers — your X-API-Key and Authorization: Bearer <widget_key>.
/widget/conversation— start or resume a visitor thread/widget/chat— send a message, get the AI reply/widget/config— the widget's live config (branding, texts, readiness, voice agent)/widget/history/:thread_id— full transcript for a threadWidget branding & config
Manage the widget’s look and copy over the API — logo, colours, greeting, header title — so a white-label reseller can brand each client from code. Set it globally, or per knowledge base.
/preferences— set widget branding & texts/preferences— read the current branding & texts/preferences/kb/:record_id— per-KB branding overrides (uses the KB record_id)Link a WhatsApp number by scanning a QR once. After that, post-call follow-ups, booking confirmations and reminders are delivered on WhatsApp automatically — the same built-in flow, no per-message calls. Configure exactly what sends via settings.
/integrations/whatsapp/connect— start a session, get a QR to scanPoll status until the QR is scanned, then send a test:
/integrations/whatsapp/status— poll connection status/integrations/whatsapp/test— send a test message — body: { testPhone?, type? }/integrations/whatsapp/settings— read auto-send settings (follow-ups, booking confirmations, calendar)/integrations/whatsapp/settings— configure what auto-sends after calls/integrations/whatsapp/disconnect— unlink & wipe the sessionCredits & balance
Your account runs on a single prepaid INR balance shared by the dashboard and the API. Every call minute and chat message draws it down. Check it any time with your API key:
/payment/balance— check your account balanceWhen the balance can’t cover an action, the API returns 402 with the current balance — top up and retry:
Low-balance alert. When your balance drops below ₹20, Nutaan automatically emails the account owner a top-up reminder — so a running integration never silently stops mid-campaign. Add credits from the dashboard (Payment); paid credits never expire. Poll /payment/balance before large batches, or watch for 402 and back off.
Errors, billing & performance
Every error returns a JSON body of the shape { "error": "message" } (some add context like balance). Use the HTTP status to branch:
Example — placing a call with no balance:
Billing is per account in INR: calls from ₹6/min, dropping to ₹4/minabove 1,000 minutes and as low as ₹3/min (~$0.036) above 5,000 minutes a month. A paid key with balance and a free key are billed identically — the only difference is the available balance. See pricing.
Performance & concurrency. Read endpoints (agents, voices, calls, batches) typically respond in ~0.3–0.9s. The API is concurrency-safe — parallel requests on the same key all succeed; under heavy fan-out, individual latency can rise to ~2s as calls proxy to the telephony layer. Call placement is asynchronous: /voice/calls/outbound returns immediately with a callId you poll, rather than blocking for the call. For campaigns, prefer a single /voice/batch over many individual calls — it parallelises and rate-paces for you.
