Talk to our live AI agent right now —Call our live AI —IN +91 79711 70843
NutaanNutaanVOICE AI
DEVELOPER PLATFORM

Voice AI Calling API

One REST API and a single key to put AI on the phone — create voice agents, place outbound calls, run batch campaigns, buy numbers and pick voices. Built for production, billed pay-as-you-go from ₹6/min.

N
Maintained by the Nutaan engineering team at Tecosys, who run this telephony + speech stack in production across retail, healthcare and real-estate deployments. Last updated June 2026.

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

text
https://nutaan.com/api

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.

Sign in and every example on this page fills in your own API key.
bash
curl https://nutaan.com/api/voice/agents \
  -H "X-API-Key: nut-XXXXXXXXXXXXXXXXXXXX"

The same key also works as a Bearer token:

bash
-H "Authorization: Bearer nut-XXXXXXXXXXXXXXXXXXXX"

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.

url
https://nutaan.com/api/mcp/YOUR_API_KEY

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):

bash
claude mcp add --transport http nutaan https://nutaan.com/api/mcp/YOUR_API_KEY
gemini mcp add --transport http nutaan https://nutaan.com/api/mcp/YOUR_API_KEY

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:

json
{
  "mcpServers": {
    "nutaan": { "url": "https://nutaan.com/api/mcp/YOUR_API_KEY" }
  }
}

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:

url
https://nutaan.com/api/mcp/auto/YOUR_API_KEY

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:

text
You:  Find 100 dental clinics in Pune with phone numbers.
      (your assistant researches them)

You:  Read smilecare.in and build an outbound agent that books
      consultations - warm female voice, Hindi.
      -> nutaan_discover_business, then nutaan_create_agent

You:  Call all 100 with that agent.
      -> nutaan_create_batch   (it confirms the list size first)

You:  How is it going?
      -> nutaan_batch_status   43/100 called, 18 answered

You:  Which ones are worth following up?
      -> nutaan_list_leads     number, what they wanted, next step

The 18 tools

text
nutaan_list_agents         your agents, with language, voice and number
nutaan_get_agent           one agent in full, including its prompt
nutaan_discover_business   read a website, report what an agent would know
nutaan_create_agent        build an agent, with a number and a link to test it
nutaan_update_agent        FIX an existing agent - prompt, language, voice, KB
nutaan_list_voices         voices for a language + this account's premium access
nutaan_prompt_guide        how to write a prompt that works on a phone call
nutaan_place_call          place one real call
nutaan_create_batch        create a campaign and dial a whole list
nutaan_list_batches        campaigns and their progress
nutaan_batch_status        one campaign, with per-call outcomes
nutaan_list_calls          recent calls with status and duration
nutaan_get_call            one call with its transcript
nutaan_list_leads          qualified leads - number, intent, next step
nutaan_analyze_calls       extract contacts and intent from new calls
nutaan_create_knowledge_base  crawl a site so agents can answer from it
nutaan_list_knowledge_bases   knowledge bases and their crawl status
nutaan_share_agent         create a public link anyone can talk to

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.

GET/voice/agents— list your agents
Request
bash
curl https://nutaan.com/api/voice/agents \
  -H "X-API-Key: nut-XXXX"
Response · 200 OK
json
{
  "agents": [
    {
      "_id": "6a2d751adf5ec7876a0099e7",
      "name": "Support Agent",
      "stt": { "provider": "deepgram", "model": "nova-3", "language": "en-GB" },
      "tts": { "provider": "deepgram", "voiceName": "aura-asteria-en", "speed": 1 },
      "llm": { "provider": "openai", "model": "gpt-4.1", "temperature": 0.7, "maxTokens": 300 }
    }
  ]
}
Errors
401API key missing or invalid
5xxTransient server or upstream provider error — safe to retry with backoff
POST/voice/agents— create an agent (azure-voicelive — English/Indian S2S)
Request
bash
curl -X POST https://nutaan.com/api/voice/agents \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{
    "name": "Support Agent",
    "systemPrompt": "You are a friendly support agent for Acme. Keep replies under 2 sentences.",
    "languageMode": "multilingual",
    "voiceEngine": "azure-voicelive",
    "realtimeModel": "gpt-realtime",
    "realtimeVoice": "en-IN-NeerjaNeural",
    "llm": { "provider": "openai", "model": "gpt-4.1-nano" }
  }'
Response · 200 OK
json
{
  "_id": "6a2d751adf5ec7876a0099e7",
  "name": "Support Agent",
  "voiceEngine": "azure-voicelive",
  "realtimeModel": "gpt-realtime",
  "realtimeVoice": "en-IN-NeerjaNeural",
  "llm": { "provider": "openai", "model": "gpt-4.1-nano" }
}
Errors
400Missing required fields (name, systemPrompt)
401API key missing or invalid
5xxTransient server or upstream provider error — safe to retry with backoff
⚠️ Match the fields to the engine — this is the #1 mistake.
  • azure-voicelive uses realtimeVoice (an Azure neural voice, e.g. hi-IN-SwaraNeural) and realtimeModel: "gpt-realtime". It does not read stt/tts.
  • elevenlabs-agent uses tts.voiceId (the voice), plus tts.model (eleven_multilingual_v2 · eleven_v3_conversational · eleven_turbo_v2_5 · eleven_flash_v2_5), tts.stability, tts.speed and tts.similarityBoost — all applied. Only style is ignored. Use eleven_v3_conversational to perform inline expression tags like [laughs]/[excited].
  • pipeline is the only engine that reads stt and tts (with provider + language).
An agent whose fields don't match its engine may connect but never speak. See Voice engines and Languages & voices below.
PATCH/voice/agents/:id— update an agent (same body as create; partial)
DELETE/voice/agents/:id— delete an agent

Agent 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.

GET/voice/agents/templates— list available templates
Request
bash
curl https://nutaan.com/api/voice/agents/templates -H "X-API-Key: nut-XXXX"
Response · 200 OK
json
{
  "categories": [ { "id": "real_estate", "label": "Real Estate" }, … ],
  "templates": [
    { "id": "realestate_sitevisit_in", "vertical": "real_estate",
      "label": "Real Estate — Site-Visit Booking (India)",
      "config": { "systemPrompt": "…", "welcomeMessage": "…",
                  "extractionFields": [ … ], "bookingEnabled": true } }
  ]
}
GET/voice/agents/templates/:id— full detail of one template (prompt, voice preset, fields)
POST/voice/agents— create an agent FROM a template
Request
bash
curl -X POST https://nutaan.com/api/voice/agents \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{ "template_id": "realestate_sitevisit_in",
        "name": "My Property Agent",
        "telephonyProvider": "plivo", "phoneNumberId": "+912269985921" }'
Response · 200 OK
json
{ "_id": "6a…", "name": "My Property Agent", "systemPrompt": "…(from template)…" }
POST/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.

interruptionModeinstant | words | sentence | off — when the bot yields to the caller (default words)
interruptionThresholdwords needed in "words" mode (default 3)
autoEndOnGoodbyebool — bot ends gracefully when the conversation is done (default true)
endCallGoodbyeTextoptional line spoken right before hang-up
endCallPhrasesstring[] — caller phrases that end the call instantly
autoEndOnRejectionbool — end politely when the caller declines (outcome: rejected)
autoEndAfterNoResponsenumber — end after N silent turns (0 = off); noResponseSeconds sets a turn
bookingEnabledbool — capture a meeting when the caller agrees a day & time (outcome: meeting_booked)
postCallAnalysisbool — AI summary, disposition, sentiment & extracted fields after the call
postCallWebhookUrlyour URL — receives {callId, outcome, transcript, analysis, …} when the call ends
knowledgeBaseUrlyour URL fetched fresh each call — the bot answers only from it (anti-hallucination)
voiceEnginepipeline | azure-voicelive | deepgram-agent — how speech is produced; controls latency & language. See "Voice engines" below.
transferEnabledbool — forward the live call to a human when the AI decides (pipeline engine, Plivo/Telnyx)
transferNumberfallback number to bridge to; transferMessage is the "connecting you" line
departments[{name, number, description}] — the AI routes the call to the right team from the conversation

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.

bash
curl -X PATCH https://nutaan.com/api/voice/agents/AGENT_ID \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{
    "interruptionMode": "words", "interruptionThreshold": 2,
    "autoEndOnGoodbye": true, "autoEndOnRejection": true,
    "bookingEnabled": true, "postCallAnalysis": true,
    "postCallWebhookUrl": "https://your-backend.com/api/call-ended"
  }'

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.

voiceEngineTypeFirst-response latencyLanguagesBest for
pipelineSTT → LLM → TTS (cascade)~1.5–2s · ≈0.5s first-audio with streaming TTS30+ incl. Hindi & IndianHindi/Indian & multilingual, full control, lowest cost
azure-voiceliveSpeech-to-speech (S2S)under ~500ms–1s (sub-second)English + Azure languagesLow-latency, expressive English
deepgram-agentSpeech-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 TTS smallest (streams over WebSocket, first audio ~500ms) or sarvam.
  • 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.

bash
# Fastest English agent (sub-300ms speech-to-speech)
curl -X PATCH https://nutaan.com/api/voice/agents/AGENT_ID \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{ "voiceEngine": "deepgram-agent", "dgAgentSpeakModel": "aura-2-thalia-en" }'

# Hindi agent (pipeline + streaming Indian TTS)
curl -X PATCH https://nutaan.com/api/voice/agents/AGENT_ID \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{ "voiceEngine": "pipeline",
        "stt": { "provider": "azure", "language": "hi-IN" },
        "tts": { "provider": "smallest", "voiceName": "niharika", "language": "hi" } }'

The engine (and its fields) come back on the agent object:

json
{
  "_id": "6a2d751adf5ec7876a0099e7",
  "name": "English Sales — Turbo",
  "voiceEngine": "deepgram-agent",
  "dgAgentSpeakModel": "aura-2-thalia-en",
  "dgAgentThinkModel": "gpt-4o-mini"
}

Voices

List the available voices and providers to pick a voiceId for your agent, or preview one.

GET/voice/voices— list voices grouped by provider
Request
bash
curl https://nutaan.com/api/voice/voices \
  -H "X-API-Key: nut-XXXX"
Response · 200 OK
json
{
  "tts": {
    "deepgram": {
      "note": "English-only voices.",
      "voices": [
        {
          "id": "aura-asteria-en",
          "name": "Asteria",
          "language": "en-US",
          "gender": "female",
          "recommended": true,
          "description": "Friendly, conversational"
        }
      ]
    }
  }
}
Errors
401API key missing or invalid
5xxTransient server or upstream provider error — safe to retry with backoff
GET/voice/voices/providers— list voice providers
GET/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.

GET/voice/numbers— list your numbers
Request
bash
curl https://nutaan.com/api/voice/numbers -H "X-API-Key: nut-XXXX"
Response · 200 OK
json
{ "numbers": [], "total": 0 }
Errors
401API key missing or invalid
5xxTransient server or upstream provider error — safe to retry with backoff
POST/voice/numbers/buy— buy a number (min ₹1000 balance)
Request
bash
curl -X POST https://nutaan.com/api/voice/numbers/buy \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{ "provider": "telnyx", "number": "+15551234567" }'
Response · 200 OK
json
{
  "success": true,
  "number": "+15551234567",
  "sid": "PNxxxxxxxx",
  "valid_till": "13 July 2026"
}
Errors
402Balance below ₹1000 (the response includes balance & required)
401API key missing or invalid
502Provider could not provision the number
GET/voice/numbers/search— search available numbers (query: country, type, areaCode)
DELETE/voice/numbers/:id— release a number

Languages & 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).

LanguageCodeBest engineExample voice
English (India)en-INazure-voiceliveen-IN-NeerjaNeural
English (US)en-USazure-voicelive / deepgram-agenten-US-AvaNeural / aura-2-thalia-en
Hindihi-INelevenlabs-agent (Pari) or pipelineElevenLabs voiceId / hi-IN-SwaraNeural
Bengalibn-INazure-voicelivebn-IN-TanishaaNeural
Tamilta-INazure-voiceliveta-IN-PallaviNeural
Telugute-INazure-voicelivete-IN-ShrutiNeural
Marathimr-INazure-voicelivemr-IN-AarohiNeural
Gujaratigu-INazure-voicelivegu-IN-DhwaniNeural
Kannadakn-INazure-voicelive / pipelinekn-IN-SapnaNeural
Malayalamml-INazure-voicelive / pipelineml-IN-SobhanaNeural
Punjabipa-INazure-voicelivepa-IN-VaaniNeural
Spanishes-ESazure-voicelivees-ES-ElviraNeural
Arabicar-SAazure-voicelivear-SA-ZariyahNeural
Notes. ElevenLabs (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.

PATCH/voice/agents/:id— turn inbound ON for this agent's number
Request
bash
curl -X PATCH https://nutaan.com/api/voice/agents/AGENT_ID \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{ "telephonyProvider": "plivo", "phoneNumberId": "+912269985921", "activate_inbound": true }'
Response · 200 OK
json
{
  "_id": "AGENT_ID",
  "inbound": { "provider": "plivo", "auto_configured": true, "number": "+912269985921" }
}
Plivo (Nutaan India)Auto-wired — we create a Plivo Application whose answer URL carries the agent id (so inbound routes to the right agent) and attach it. inbound.auto_configured = true.
Telnyx (Global)Auto-wired — the number is pinned to this agent and inbound resolves by the dialed number. inbound.auto_configured = true.
Vobiz (legacy India)Can't be set via API — the response returns inbound.answer_url; paste it as the number's Answer URL (HTTP POST) in the Vobiz portal once.

Send 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.

POST/voice/calls/outbound— place an outbound AI call
Request
bash
curl -X POST https://nutaan.com/api/voice/calls/outbound \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{
    "agentId": "6a2d751adf5ec7876a0099e7",
    "to": "+15557654321",
    "from": "+15551234567"
  }'
Response · 200 OK
json
{
  "callId": "43742d21-53b1-4599-9529-3cec00f5c2e1",
  "status": "queued",
  "agentId": "6a2d751adf5ec7876a0099e7",
  "to": "+15557654321"
}
Errors
402Insufficient credits (needs ≥ ₹6). Body: { error, balance }
404Agent not found / not owned by this key
401API key missing or invalid
5xxTransient server or upstream provider error — safe to retry with backoff

Poll the returned callId for status, transcript and outcome:

GET/voice/calls/:id— get one call with transcript
Request
bash
curl https://nutaan.com/api/voice/calls/43742d21-... \
  -H "X-API-Key: nut-XXXX"
Response · 200 OK
json
{
  "id": "6a2d87aedf5ec7876a009a22",
  "call_id": "43742d21-53b1-4599-9529-3cec00f5c2e1",
  "agent_id": "6a2d751adf5ec7876a0099e7",
  "direction": "outbound",
  "status": "completed",
  "from_number": "+15551234567",
  "to_number": "+15557654321",
  "duration": 170,
  "recording_url": "https://.../calls/43742d21-....wav",
  "has_recording": true,
  "booking": {
    "appointmentTime": "2026-07-31T20:00:00+05:30",
    "timezone": "Asia/Kolkata",
    "durationMinutes": 30,
    "notes": "Site visit — 2BHK"
  },
  "outcome": "booked",
  "analysis": { "sentiment": "positive", "disposition": "interested", "summary": "..." },
  "created_at": "2026-06-13T16:39:10.910Z",
  "started_at": "2026-06-13T16:39:10.910Z",
  "ended_at":   "2026-06-13T16:42:00.100Z",
  "transcript": [ { "role": "assistant", "text": "Hi! How can I help?" } ]
}
Errors
404Call not found
401API key missing or invalid
5xxTransient server or upstream provider error — safe to retry with backoff

Call object — every field (returned by GET /voice/calls and GET /voice/calls/:id):

FieldTypeMeaning
id / call_idstringUnique call id — use call_id to poll GET /voice/calls/:id.
agent_idstringThe agent that ran the call.
directionstring"inbound" or "outbound".
statusstringqueued · initiated · ringing · in-progress · completed · failed · no-answer · busy · canceled.
from_numberstring (E.164)The number the call was placed FROM (your caller ID).
to_numberstring (E.164)The number the call was placed TO (the customer).
durationnumberTalk time in seconds.
recording_urlstringHTTPS URL of the call recording (WAV). Empty until the recording is ready.
has_recordingbooleantrue when a recording exists.
bookingobject | nullPresent when the agent booked something: { appointmentTime (ISO), timezone, durationMinutes, notes }.
outcomestringBusiness outcome captured by the agent (e.g. "booked", "not_interested").
analysisobjectPost-call AI: { sentiment, disposition, summary }.
created_at / started_at / ended_atISO dateTimestamps for the call lifecycle.
transcriptarrayTurn-by-turn [{ role: "assistant" | "user", text }] — only on GET /voice/calls/:id.
Both camelCase (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.
GET/voice/calls— list calls (query: page, limit, direction)
POST/voice/calls/:id/hangup— hang up an in-progress call

Batch 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…).

POST/voice/batch— create & queue a batch campaign
Request
bash
curl -X POST https://nutaan.com/api/voice/batch \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{
    "name": "October reminders",
    "agentId": "6a2d751adf5ec7876a0099e7",
    "from": "+15551234567",
    "contacts": [
      { "name": "Asha", "number": "+15557654321", "variables": { "order": "A-1029" } },
      { "name": "Ravi", "number": "+15557654322", "variables": { "order": "A-1030" } }
    ]
  }'
Response · 200 OK
json
{
  "_id": "6a2e01...batch",
  "name": "October reminders",
  "status": "queued",
  "total": 2,
  "completed": 0
}
Errors
400Empty or malformed contacts array
402Insufficient credits for the campaign
401API key missing or invalid
5xxTransient server or upstream provider error — safe to retry with backoff
GET/voice/batch— list batches
GET/voice/batch/:id— get a batch (progress, stats)
GET/voice/batch/:id/calls— list the calls in a batch
POST/voice/batch/:id/start— start / resume a batch
POST/voice/batch/:id/pause— pause a batch
DELETE/voice/batch/:id— delete a batch

SMS & 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.

POST/voice/messages/dlt— register (or update) a DLT sender
Request
bash
curl -X POST https://nutaan.com/api/voice/messages/dlt \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{
    "number": "912269985921",
    "provider": "plivo",
    "entityId": "1101359117054321987",
    "senderId": "TRBNPK",
    "templates": [{ "templateId": "1107167762198765432", "name": "site-visit",
                    "text": "Thanks {#var#}, your visit is booked for {#var#}." }]
  }'
Response · 200 OK
json
{ "dlt": { "number": "912269985921", "status": "pending" },
  "note": "Saved. Call /messages/dlt/verify to confirm." }
GET/voice/messages/dlt— list your DLT senders (status: pending | verified | failed)
POST/voice/messages/dlt/verify— send a real test SMS & confirm the carrier accepts it — body: { number, to }
POST/voice/messages/send— send an SMS
Request
bash
curl -X POST https://nutaan.com/api/voice/messages/send \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{ "agentId": "6a51f...", "to": "919999999999", "text": "Hi from Nutaan!" }'
Response · 200 OK
json
{ "messageId": "...", "status": "sent", "from": "912269985921", "to": "919999999999" }
GET/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.

POST/kb— create a KB from a website
Request
bash
curl -X POST https://nutaan.com/api/kb \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{ "website_url": "https://yoursite.com" }'
Response · 200 OK
json
{
  "record_id": "6a55c45d161af636f39c0657",
  "namespace": "86714560-f4a1-4bd2-a6a2-d0e7c9b9e87f",
  "widget_key": "nutaan-09f51da3ca2f270e",
  "status": "processing"
}
Errors
402Balance below the minimum to create a KB
409A KB already exists for this domain (returns the existing one)
401API key missing or invalid
GET/kb— list your knowledge bases (status: processing | completed | failed)
PATCH/kb/:id— set priority_info — guidelines/links that always override the scraped content
POST/kb/test/query— test what the AI answers from a KB
Request
bash
curl -X POST https://nutaan.com/api/kb/test/query \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{ "kb_id": "6a55c45d...", "query": "What do you sell?" }'
Response · 200 OK
json
{ "answer": "…grounded answer…", "sources": [ { "url": "…", "score": 0.82 } ] }
Errors
401API key missing or invalid
5xxTransient server or upstream provider error — safe to retry with backoff
DELETE/kb/:id— delete a knowledge base

Chat 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:

html
<script src="https://cdn.nutaan.com/chatbot-widget.js"
        x-api-key="nut-XXXX"
        Authorization="Bearer nutaan-XXXX"></script>

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>.

POST/widget/conversation— start or resume a visitor thread
Request
bash
curl -X POST https://nutaan.com/api/widget/conversation \
  -H "X-API-Key: nut-XXXX" -H "Authorization: Bearer nutaan-XXXX" \
  -H "Content-Type: application/json" \
  -d '{ "visitor_id": "visitor-123" }'
Response · 200 OK
json
{ "thread_id": "97b65c22-2af2-46d7-8cb1-01e1e2ab2edc", "is_new": true }
Errors
401API key missing or invalid
5xxTransient server or upstream provider error — safe to retry with backoff
POST/widget/chat— send a message, get the AI reply
Request
bash
curl -X POST https://nutaan.com/api/widget/chat \
  -H "X-API-Key: nut-XXXX" -H "Authorization: Bearer nutaan-XXXX" \
  -H "Content-Type: application/json" \
  -d '{ "query": "Do you ship to Mumbai?", "visitor_id": "visitor-123" }'
Response · 200 OK
json
{
  "response": "Yes — we ship pan-India in 3–5 days.",
  "thread_id": "97b65c22-...",
  "context_used": 3,
  "intelligence": { "intent": "browse", "sentiment": "neutral" },
  "products": [],
  "needs_human": false
}
Errors
401API key missing or invalid
5xxTransient server or upstream provider error — safe to retry with backoff
GET/widget/config— the widget's live config (branding, texts, readiness, voice agent)
GET/widget/history/:thread_id— full transcript for a thread

Widget 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.

POST/preferences— set widget branding & texts
Request
bash
curl -X POST https://nutaan.com/api/preferences \
  -H "X-API-Key: nut-XXXX" -H "Content-Type: application/json" \
  -d '{
    "theme": { "primaryStart": "#111111", "primaryEnd": "#555555" },
    "texts": {
      "botName": "Keizer Assistant",
      "headerTitle": "How can we help?",
      "greetingText": "Hi! Ask us anything.",
      "profileLogoUrl": "https://yourcdn.com/logo.png",
      "showProfileLogo": true
    }
  }'
Response · 200 OK
json
{ "success": true }
Errors
400Invalid colour (must be #RRGGBB)
401API key missing or invalid
GET/preferences— read the current branding & texts
POST/preferences/kb/:record_id— per-KB branding overrides (uses the KB record_id)

WhatsApp

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.

POST/integrations/whatsapp/connect— start a session, get a QR to scan
Request
bash
curl -X POST https://nutaan.com/api/integrations/whatsapp/connect \
  -H "X-API-Key: nut-XXXX"
Response · 200 OK
json
{ "status": "qr", "qr": "data:image/png;base64,iVBORw0K…" }
Errors
401API key missing or invalid
5xxTransient server or upstream provider error — safe to retry with backoff

Poll status until the QR is scanned, then send a test:

GET/integrations/whatsapp/status— poll connection status
Request
bash
curl https://nutaan.com/api/integrations/whatsapp/status -H "X-API-Key: nut-XXXX"
Response · 200 OK
json
{ "connected": true, "status": "connected", "phone": "9198…", "connected_at": "2026-07-14T…" }
Errors
401API key missing or invalid
POST/integrations/whatsapp/test— send a test message — body: { testPhone?, type? }
GET/integrations/whatsapp/settings— read auto-send settings (follow-ups, booking confirmations, calendar)
PUT/integrations/whatsapp/settings— configure what auto-sends after calls
POST/integrations/whatsapp/disconnect— unlink & wipe the session

Credits & 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:

GET/payment/balance— check your account balance
Request
bash
curl https://nutaan.com/api/payment/balance \
  -H "X-API-Key: nut-XXXX"
Response · 200 OK
json
{
  "balance": 900,
  "currency": "INR",
  "has_subscription": false,
  "pricing": { "call_per_minute": 6, "chat_per_message": 2 }
}
Errors
401API key missing or invalid
5xxTransient server or upstream provider error — safe to retry with backoff

When the balance can’t cover an action, the API returns 402 with the current balance — top up and retry:

json
{ "error": "Insufficient credits. Calls cost ₹6/min — top up to continue.", "balance": 12 }

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:

200Success.
400Bad request — missing/invalid fields in the body or query.
401Missing or invalid API key.
402Insufficient credits. Body includes the current balance. Top up and retry.
404Resource not found or not owned by this key (agent, call, batch, number).
429Too many requests — back off and retry (rare; see performance below).
500 / 502Transient server or upstream provider error. Safe to retry with exponential backoff.

Example — placing a call with no balance:

json
{ "error": "Insufficient credits. Calls cost ₹6/min — top up to continue.", "balance": 0 }

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.

Frequently asked questions

Do I need a separate developer account?
No. Your existing Nutaan account and API key work for both the dashboard and the API — the same workspace, balance and agents.
Which providers power the voices and telephony?
Voice, language models and telephony are all managed by Nutaan. You pick the voice and behaviour per agent; we handle the orchestration end-to-end.
Can the AI use my knowledge base on a call?
Yes — attach a knowledge base to an agent and it answers from your real content (products, prices, policies) live during the call.
Is there a sandbox?
Use a small balance and call your own phone to test end-to-end. Every call returns a full transcript, sentiment and outcome for debugging.
Get your API key in 2 minutes
Create a free account, grab a key from Settings, and place your first AI call.
Start building free →