KaiCalls API Reference

KaiCalls API

Give your AI agent a phone. Make outbound calls, manage agents, and integrate AI phone assistants into your applications.

Base URL:https://www.kaicalls.com/api/v1

Send Your AI Agent to KaiCalls📞

New accounts can sign up, get a phone number, and start taking calls through the API.

1

Send this to your agent

Copy the instruction below

2

Sign up via the API

Gets API key + phone number

3

Agent starts making calls

30-day free trial, no card

Click to copy — for existing accounts, use the Claude connector section below

Quick Start

1. Get your API key

Go to Dashboard → Settings → API Keys and generate a key.

2. Make your first call

curl -X POST https://www.kaicalls.com/api/v1/calls \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "your-agent-id",
    "to": "+15125551234",
    "name": "John Smith",
    "context": "Confirm appointment for tomorrow at 2pm"
  }'

3. Check the result

curl "https://www.kaicalls.com/api/v1/calls?id=CALL_ID" \
  -H "Authorization: Bearer kc_live_xxxxx"

Authentication

All API requests require a Bearer token in the Authorization header.

Authorization: Bearer kc_live_xxxxx

Key Prefixes

  • kc_live_ — Production keys (only prefix issued today; all calls hit live infrastructure and are billed)

A separate kc_test_ sandbox prefix is on the roadmap but not yet available — do not assume one exists when integrating.

Scopes

Keys are created with scopes that control access. Default scopes are granted automatically.

ParameterTypeDescription
agents:readscopeList and view agents
agents:writescopeCreate and update agents
numbers:readscopeList phone numbers
calls:readscopeList and view call records
calls:writescopeMake outbound calls
leads:readscopeList and view leads
leads:writescopeCreate and update leads
sms:readscopeList and view SMS messages
sms:writescopeSend SMS messages
emails:readscopeList and view email queue
emails:writescopeSend and auto-approve emails
webhooks:readscopeView webhook configuration
webhooks:writescopeConfigure or remove webhooks
evals:readscopeList eval scenarios and read run results
evals:writescopeCreate, update, delete, and run eval scenarios
*scopeFull access (all scopes)

Claude Connector / MCP

Claude users can connect their Kai Calls account through the remote MCP connector. Once approved, Claude can list agents, review recent calls, read transcripts, and start outbound calls through accessible Kai Calls agents.

Connector URL

https://www.kaicalls.com/api/mcp

Connect with OAuth

  1. Add a custom connector in Claude using the connector URL above.
  2. Claude discovers the Kai Calls OAuth metadata automatically.
  3. Sign in with your Kai Calls account and approve access.
  4. Claude receives a short-lived bearer token scoped to your approved access.
GET/.well-known/oauth-protected-resourceMCP protected-resource metadata
GET/.well-known/oauth-authorization-serverOAuth authorization server metadata
GET/.well-known/openid-configurationOpenID-compatible OAuth metadata
https://www.kaicalls.com/.well-known/oauth-protected-resource
https://www.kaicalls.com/.well-known/oauth-authorization-server
https://www.kaicalls.com/.well-known/openid-configuration

Permissions and tools

Access is scoped to the authenticated Kai Calls user and the businesses linked to that account. Claude cannot access API keys, dashboard settings, private environment values, or businesses outside the approved account.

ParameterTypeDescription
list_agentsagents:readList Kai Calls agents available to the authenticated account
get_business_infoagents:readShow business details, agent count, and recent call volume
list_recent_callscalls:readList recent calls, optionally filtered by agent or status
check_call_statuscalls:readCheck one call by Kai Calls call ID
get_transcriptcalls:readRetrieve a completed call transcript and summary
make_callcalls:writeStart an outbound call with agent_id, to, and optional context or lead_id

Direct MCP usage

API-key auth still works for developers and non-Claude MCP clients. Pass a Kai Calls API key as a bearer token to the JSON-RPC endpoint or the REST-style helper endpoints.

POST/api/mcpJSON-RPC MCP endpoint
curl -X POST https://www.kaicalls.com/api/mcp \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
POST/api/mcp/initializeMCP handshake
POST/api/mcp/tools/listList MCP tools
POST/api/mcp/tools/callCall one MCP tool
curl -X POST https://www.kaicalls.com/api/mcp/tools/call \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "list_recent_calls",
    "arguments": { "limit": 5 }
  }'

Signup

Create a full KaiCalls account via the API — no UI session required. This endpoint provisions a business, generates an API key, creates an AI agent, and assigns a phone number in a single call.

POST/api/v1/signupCreate a new account

No authentication required. Provisions a complete account: auth user, business, API key, AI intake agent, and phone number from the available pool. Returns everything needed to start making API calls immediately.

Rate limit: 5 requests/hour per IP

Parameters

ParameterTypeDescription
business_namerequiredstringName of the business
emailrequiredstringAccount email address (must be unique)
business_typestringCategory of business (e.g. "Legal Services", "Healthcare")
websitestringBusiness website URL
phone_forward_tostringPhone number to forward calls to (sets up ring-first routing — AI answers if no pickup after 20s)

Example

curl -X POST https://www.kaicalls.com/api/v1/signup \
  -H "Content-Type: application/json" \
  -d '{
    "business_name": "Smith Law Firm",
    "email": "contact@smithlaw.com",
    "business_type": "Legal Services",
    "website": "https://smithlaw.com",
    "phone_forward_to": "+15125551234"
  }'

Response

Response200
{
  "api_key": "kc_live_abc123def456...",
  "business_id": "uuid-biz456",
  "agent_id": "uuid-agent789",
  "phone_number": "+15125559876",
  "dashboard_url": "https://www.kaicalls.com/dashboard",
  "trial_ends_at": "2026-04-14T00:00:00.000Z"
}

Error Responses

CodeStatusDescription
validation_error400Missing or invalid business_name or email
email_exists409An account with this email already exists
rate_limited429Too many signups from this IP (5/hour limit)
payment_required402x402 payment required (when enabled)

What Gets Created

  • Auth user with auto-confirmed email
  • Business with 30-day free trial
  • API key with full default scopes
  • AI intake agent (ready to take calls)
  • Phone number from pool (with Vapi + Twilio)
  • Call forwarding (if phone_forward_to provided)

Agents

Editing agents from Claude Code or another agent CLI?

Drop in our official kaicalls-agent-api skill — it teaches your agent the full /api/v1/agents contract, the snake_case body shape, the vapi_config pass-through, and the common mistakes to avoid. Works with Claude Code, Codex, and any agent runtime that supports skill bundles.

Download skill (.skill)
GET/api/v1/agentsList all agents

Returns all agents assigned to your businesses. Requires agents:read scope.

Response200
{
  "agents": [
    {
      "id": "uuid-abc123",
      "name": "Kai",
      "business_id": "uuid-biz456",
      "created_at": "2026-01-15T10:00:00Z"
    }
  ]
}
GET/api/v1/agents?id=AGENT_IDGet agent details

Returns detailed info including prompts (inbound/outbound/SMS), voice, model, Vapi assistant ID, and phone number. The inbound prompt is fetched live from Vapi.

Use the ?id= query param, not a path segment. GET /api/v1/agents/<id> returns a 404 — there is no dynamic route. Always pass the agent UUID as ?id=.
Response200
{
  "id": "uuid-abc123",
  "name": "Kai",
  "business_id": "uuid-biz456",
  "vapi_assistant_id": "vapi-asst-789",
  "phone_number": "+15125551234",
  "created_at": "2026-01-15T10:00:00Z",
  "prompts": {
    "inbound": {
      "prompt": "You are a friendly receptionist for Smith Law...",
      "llm": "gpt-4.1"
    },
    "outbound": {
      "prompt": "You are calling {{name}} to follow up...",
      "llm": "gpt-4.1",
      "first_message": "Hey {{name}}, this is Kai from Smith Law."
    },
    "sms": {
      "prompt": "Respond helpfully to text messages...",
      "llm": "gpt-4.1"
    }
  },
  "voice": { "provider": "elevenlabs", "voiceId": "voice-id-123" },
  "model": { "provider": "openai", "model": "gpt-4.1", "temperature": 0.7 },
  "first_message": "Hello! How can I help you today?"
}
POST/api/v1/agentsCreate a new voice agent

Creates a Vapi assistant with lead collection capabilities and registers the agent in the KaiCalls database. If database insertion fails, the Vapi assistant is automatically cleaned up.

Required scope: agents:write

Parameters

ParameterTypeDescription
business_idrequiredstringBusiness to assign the agent to (must be accessible by your API key)
namerequiredstringAgent display name
system_promptrequiredstringThe agent's system prompt / instructions
first_messagestringGreeting message (default: "Hello! How can I help you today?")
voiceobjectVoice configuration: { provider, voiceId } (defaults to Vapi default)
modelobjectModel configuration: { provider, model, temperature } (defaults to OpenAI gpt-4.1 at 0.7)
metadataobjectExtra metadata to store with the agent (e.g. description, custom fields)

Example

curl -X POST https://www.kaicalls.com/api/v1/agents \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "business_id": "uuid-biz456",
    "name": "Kai Receptionist",
    "system_prompt": "You are a friendly receptionist for Smith Law. Answer questions about services and collect caller information.",
    "first_message": "Hello! Thanks for calling Smith Law. How can I help you today?",
    "voice": { "provider": "elevenlabs", "voiceId": "voice-id-123" },
    "model": { "provider": "openai", "model": "gpt-4.1", "temperature": 0.7 }
  }'
Response201
{
  "id": "uuid-agent789",
  "name": "Kai Receptionist",
  "business_id": "uuid-biz456",
  "vapi_assistant_id": "vapi-asst-xyz",
  "created_at": "2026-03-06T10:00:00Z"
}

Automatic Lead Collection

Every agent created via the API automatically includes lead collection capabilities. The agent will extract caller name, email, phone, and intent during conversations without any additional configuration.

PATCH/api/v1/agentsUpdate an existing agent

Partial updates to agent configuration. Only include the fields you want to change. Changes are synced to both the Vapi assistant and the KaiCalls database. Metadata updates are merged with existing values (not replaced).

Agents have separate prompts per channel: inbound (incoming calls, synced to Vapi), outbound (AI-initiated calls, applied at call time), and SMS (text responses).

Required scope: agents:write

Parameters

ParameterTypeDescription
idrequiredstringKaiCalls agent ID to update
namestringNew agent display name
inbound_promptstringSystem prompt for incoming calls (synced to Vapi + metadata)
outbound_promptstringSystem prompt for outbound calls (metadata only, applied at call time)
sms_promptstringSystem prompt for SMS responses (metadata only)
system_promptstringLegacy alias for inbound_prompt (still supported)
first_messagestringNew greeting message for inbound calls
outbound_first_messagestringCustom opening message for outbound calls
outbound_llmstringOverride model for outbound calls (e.g. gpt-4.1)
sms_llmstringOverride model for SMS (e.g. gpt-4.1)
voiceobjectNew voice configuration: { provider, voiceId }
modelobjectNew model configuration: { provider, model, temperature }
metadataobjectMetadata to merge with existing values
vapi_configobjectDirect Vapi assistant config passthrough. Any valid Vapi field is accepted: stopSpeakingPlan, startSpeakingPlan, endCallPhrases, endCallMessage, transcriber, analysisPlan, artifactPlan, backgroundSpeechDenoisingPlan, monitorPlan, silenceTimeoutSeconds, maxDurationSeconds, etc. Sanitized and forwarded to Vapi.

Example (update inbound prompt)

curl -X PATCH https://www.kaicalls.com/api/v1/agents \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "uuid-agent789",
    "inbound_prompt": "You are an intake specialist. Collect case details and schedule consultations."
  }'

Example (update outbound prompt)

curl -X PATCH https://www.kaicalls.com/api/v1/agents \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "uuid-agent789",
    "outbound_prompt": "You are calling {{name}} about their inquiry. Qualify and book a consultation.",
    "outbound_first_message": "Hey {{name}}, this is Kai from Smith Law — do you have a minute?"
  }'

Example (Vapi config — interruption handling)

curl -X PATCH https://www.kaicalls.com/api/v1/agents \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "uuid-agent789",
    "vapi_config": {
      "stopSpeakingPlan": {
        "numWords": 2,
        "voiceSeconds": 0.3,
        "backoffSeconds": 1,
        "interruptionPhrases": ["stop", "wait", "hold on", "shut up", "enough", "no", "actually"],
        "acknowledgementPhrases": ["okay", "yeah", "right", "uh-huh", "mm-hmm", "got it"]
      },
      "endCallPhrases": ["goodbye", "talk to you later", "have a nice day"],
      "endCallMessage": "Thanks for calling! Goodbye."
    }
  }'
Response200
{
  "id": "uuid-agent789",
  "name": "Kai Receptionist",
  "business_id": "uuid-biz456",
  "vapi_assistant_id": "vapi-asst-xyz",
  "updated": true,
  "updated_fields": ["inbound_prompt"]
}

Common mistakes

  • Path segment for GET single agent. Use ?id=<agent_id>, not /agents/<id>.
  • camelCase fields. The body is snake_case: inbound_prompt, first_message, vapi_config. camelCase keys are silently ignored.
  • Wrong agent ID. Pass the KaiCalls agent UUID (from GET /api/v1/agents), not the Vapi assistant ID. The endpoint resolves Vapi for you.
  • Round-tripping the full prompt unchanged. Every PATCH that touches the prompt writes an audit-history row and queues an owner-notification email. Send only the fields you actually changed.
  • Patching Vapi directly. Updating the Vapi assistant outside this endpoint leaves the KaiCalls DB stale (dashboard shows wrong data, no audit trail, no owner notification, lead-collection evaluation plans get wiped). Always go through /api/v1/agents.

Phone Numbers

GET/api/v1/numbersList phone numbers

Returns all phone numbers registered to your businesses. Requires numbers:read scope.

Response200
{
  "numbers": [
    {
      "id": "uuid-num789",
      "phone_number": "+15125551234",
      "agent_id": "uuid-abc123",
      "business_id": "uuid-biz456",
      "created_at": "2026-01-15T10:00:00Z"
    }
  ]
}

Calls

POST/api/v1/callsMake an outbound call

Initiates an outbound call using your AI agent. The system automatically enriches the call with context from your CRM — the agent will know the caller's name, lead score, interaction history, and business details.

Required scope: calls:write

Parameters

ParameterTypeDescription
agent_idrequiredstringThe agent to use for this call
torequiredstringPhone number to call (E.164 format, e.g. +15125551234)
namestringCustomer name. The agent greets them by name. Also accepts customer_name.
contextstringFreeform context the agent can reference. Use for task instructions, appointment details, or notes.
firstMessagestringOverride the agent's opening line for this call. Also accepts first_message. If omitted, uses the agent's configured outbound greeting.
lead_idstringLink this call to a lead in your CRM. Enables automatic enrichment with lead score, history, and source.
webhook_urlstringURL to receive a webhook when the call completes
max_durationintegerMaximum call duration in seconds (default: 600)

Automatic Context Enrichment

When you provide a lead_id or the to number matches an existing lead, the agent automatically receives these variables in its prompt:

VariableDescription
nameLead's first name (from name param or CRM)
lead_scoreAI-assigned quality score (0-100)
lead_statusCurrent lead status in your pipeline
interaction_countNumber of previous calls with this lead
days_since_first_contactDays since the lead was first created
lead_sourceWhere the lead came from (web form, referral, etc.)
business_nameYour business name
business_phoneYour office phone number
time_of_day"morning", "afternoon", or "evening"

These are injected automatically — you don't need to pass them.

Minimal Example

curl -X POST https://www.kaicalls.com/api/v1/calls \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{"agent_id": "uuid-abc123", "to": "+15125559876"}'

Full Example (with context and lead)

curl -X POST https://www.kaicalls.com/api/v1/calls \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "uuid-abc123",
    "to": "+15125559876",
    "name": "John Smith",
    "lead_id": "uuid-lead123",
    "context": "Following up on car accident case",
    "firstMessage": "Hey John, this is Kai from Smith Law — do you have a minute?"
  }'
Response201
{
  "id": "uuid-call789",
  "conversation_id": "vapi-conv-xyz",
  "status": "queued",
  "agent_id": "uuid-abc123",
  "to": "+15125559876",
  "name": "John Smith",
  "created_at": "2026-02-15T14:30:00Z"
}
GET/api/v1/callsList calls

Returns calls across all your businesses, ordered by most recent. Requires calls:read scope.

Query Parameters

ParameterTypeDescription
limitintegerMax results per page (default: 50, max: 100)
agent_idstringFilter by agent ID
statusstringFilter by call status
afterstringCursor-based pagination. Pass the created_at of the last result to get the next page.
Response200
{
  "calls": [
    {
      "id": "uuid-call789",
      "conversation_id": "vapi-conv-xyz",
      "status": "completed",
      "duration": 127,
      "agent_id": "uuid-abc123",
      "agent_name": "Kai",
      "created_at": "2026-02-15T14:30:00Z"
    }
  ],
  "has_more": true
}
GET/api/v1/calls?id=CALL_IDGet call details

Returns full details for a single call including summary, recording, and quality analysis.

Response200
{
  "id": "uuid-call789",
  "conversation_id": "vapi-conv-xyz",
  "status": "completed",
  "direction": "inbound",
  "duration": 127,
  "agent_id": "uuid-abc123",
  "agent_name": "Kai",
  "business_id": "uuid-biz456",
  "lead_id": "uuid-lead123",
  "summary": "Confirmed appointment with John for tomorrow at 2pm.",
  "recording_url": "https://storage.example.com/recordings/call_xyz.mp3",
  "quality_dimensions": {
    "professionalism": 92,
    "empathy": 88,
    "information_gathering": 95,
    "resolution": 90
  },
  "created_at": "2026-02-15T14:30:00Z"
}

Leads

GET/api/v1/leadsList leads

Returns leads across all your businesses, ordered by most recent. Requires leads:read scope.

Query Parameters

ParameterTypeDescription
statusstringFilter by status: new, contacted, qualified, converted, lost
sourcestringFilter by lead source
agent_idstringFilter by agent ID
phonestringFilter by exact phone number
emailstringFilter by exact email address
limitintegerMax results per page (default: 50, max: 100)
afterstringCursor-based pagination. Pass the created_at of the last result to get the next page.

Example

curl "https://www.kaicalls.com/api/v1/leads?status=new&limit=10" \
  -H "Authorization: Bearer kc_live_xxxxx"
Response200
{
  "leads": [
    {
      "id": "uuid-lead123",
      "name": "Jane Doe",
      "first_name": "Jane",
      "last_name": "Doe",
      "email": "jane@example.com",
      "phone": "+15551234567",
      "status": "new",
      "source": "inbound_call",
      "agent_id": "uuid-abc123",
      "agent_name": "Reception AI",
      "business_id": "uuid-biz456",
      "created_at": "2026-03-17T12:00:00Z",
      "updated_at": "2026-03-17T12:00:00Z"
    }
  ],
  "has_more": false
}
GET/api/v1/leads?id=LEAD_IDGet lead details

Returns full details for a single lead including address, notes, and event info.

Response200
{
  "id": "uuid-lead123",
  "name": "Jane Doe",
  "first_name": "Jane",
  "last_name": "Doe",
  "email": "jane@example.com",
  "phone": "+15551234567",
  "zip": "90210",
  "address": "123 Main St",
  "city": "Beverly Hills",
  "state": "CA",
  "status": "qualified",
  "source": "inbound_call",
  "source_url": null,
  "source_page": null,
  "agent_id": "uuid-abc123",
  "agent_name": "Reception AI",
  "business_id": "uuid-biz456",
  "notes": "Interested in premium plan",
  "message": null,
  "event_date": null,
  "event_type": null,
  "created_at": "2026-03-17T12:00:00Z",
  "updated_at": "2026-03-17T14:30:00Z"
}
PATCH/api/v1/leadsUpdate a lead

Update a lead's information. Only provided fields are modified. Requires leads:write scope.

Body Parameters

ParameterTypeDescription
idrequiredstringLead ID to update
namestringFull name
first_namestringFirst name
last_namestringLast name
emailstringEmail address
phonestringPhone number
statusstringnew, contacted, qualified, converted, or lost
sourcestringLead source
notesstringInternal notes
addressstringStreet address
citystringCity
statestringState
zipstringZIP code

Example

curl -X PATCH https://www.kaicalls.com/api/v1/leads \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "uuid-lead123",
    "status": "qualified",
    "notes": "Ready for follow-up call"
  }'
Response200
{
  "id": "uuid-lead123",
  "name": "Jane Doe",
  "first_name": "Jane",
  "last_name": "Doe",
  "email": "jane@example.com",
  "phone": "+15551234567",
  "zip": "90210",
  "address": "123 Main St",
  "city": "Beverly Hills",
  "state": "CA",
  "status": "qualified",
  "source": "inbound_call",
  "agent_id": "uuid-abc123",
  "agent_name": "Reception AI",
  "business_id": "uuid-biz456",
  "notes": "Ready for follow-up call",
  "created_at": "2026-03-17T12:00:00Z",
  "updated_at": "2026-03-17T15:45:00Z"
}

SDR Pipeline

Automated sales development pipeline. Segments leads, decides the next-best action (call/email/SMS/skip/escalate), enforces cadence, and records every touch in sdr_activity_log. All SDR routes accept a Bearer API key or a browser session. API keys need sdr:read for GET routes and sdr:write for POST / PUT routes; browser sessions bypass the scope check. The caller is always bound to the businessId they pass.

Segments: speed_to_lead, fresh_demo_caller, warm_demo_caller, cooling_demo, new_trial_stuck, trial_going_dark, trial_ending, contacted_no_reply, exhausted, churned_recent, cold.

GET/api/sdr/pipelineList leads in the SDR pipeline

Query Parameters

ParameterTypeDescription
businessIdrequiredstringBusiness to review
segmentstringFilter to a single segment
limitintegerMax results (default 100)

Example

curl "https://www.kaicalls.com/api/sdr/pipeline?businessId=uuid-biz&segment=speed_to_lead" \
  -H "Authorization: Bearer kc_live_xxxxx"
Response200
{
  "businessId": "uuid-biz",
  "enabled": true,
  "summary": { "total_leads": 42, "actionable": 30, "needs_manual": 2 },
  "leads": [
    {
      "id": "uuid-lead",
      "name": "Jane Doe",
      "email": "jane@example.com",
      "phone": "+15551234567",
      "status": "new",
      "source": "inbound_call",
      "segment": "speed_to_lead",
      "score": 78,
      "touch_count": 0,
      "last_touch": null,
      "next_action": "call",
      "paused": false,
      "created_at": "2026-04-14T12:00:00Z",
      "updated_at": "2026-04-14T12:00:00Z"
    }
  ]
}
POST/api/sdr/actionTrigger an action on a single lead

Enforces cadence (min_touch_gap_hours, max_touches). Returns 429 with nextAllowedAt if rate-limited.

Body Parameters

ParameterTypeDescription
businessIdrequiredstringBusiness the lead belongs to
leadIdrequiredstringLead to act on
actionrequiredstringOne of: call, email, sms, skip, pause, resume

Example

curl -X POST https://www.kaicalls.com/api/sdr/action \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "businessId": "uuid-biz",
    "leadId": "uuid-lead",
    "action": "call"
  }'
Response200
{
  "success": true,
  "action": "call",
  "leadId": "uuid-lead",
  "detail": "Call queued"
}
GET/api/sdr/configRead SDR configuration

Returns the current config; missing rows fall back to safe defaults.

curl "https://www.kaicalls.com/api/sdr/config?businessId=uuid-biz" \
  -H "Authorization: Bearer kc_live_xxxxx"
PUT/api/sdr/configUpsert SDR configuration

Body Parameters

ParameterTypeDescription
businessIdrequiredstringBusiness to configure
enabledbooleanMaster toggle for SDR automation
min_touch_gap_hoursintegerMinimum hours between touches on the same lead
max_touchesintegerHard cap; leads past this become exhausted
auto_callbooleanAllow dispatcher to place outbound calls
auto_emailbooleanAllow dispatcher to queue / auto-approve emails
auto_smsbooleanAllow dispatcher to send SMS
sdr_agent_idstringVapi agent used for outbound SDR calls
business_hours_startinteger0–23, caller-local time
business_hours_endinteger0–23, caller-local time
email_from_account_idstringconnected_email_accounts.id to send from
POST/api/sdr/runManually trigger a pipeline run

Reviews the full pipeline and dispatches actions. Pass { "dryRun": true } to preview without dispatching. Returns 400 if SDR is not enabled on the business's config.

curl -X POST https://www.kaicalls.com/api/sdr/run \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "businessId": "uuid-biz", "dryRun": true }'
Response200
{
  "success": true,
  "summary": {
    "calls_queued": 3,
    "emails_queued": 5,
    "sms_sent": 2,
    "escalated": 1,
    "failed": 0
  },
  "results": [
    { "success": true, "action": "call", "leadId": "uuid-lead", "detail": "Call queued" }
  ],
  "skipped": 0,
  "errors": []
}
GET/api/sdr/reportAggregated SDR pipeline report

Query Parameters

ParameterTypeDescription
businessIdrequiredstringBusiness to report on
periodstringtoday | week | month (default: week)

Returns action counts, outcome counts, new leads, hot leads (speed_to_lead / fresh_demo_caller / new_trial_stuck), and stale leads (on manual_review).

GET/api/sdr/activityRecent SDR activity log

Query Parameters

ParameterTypeDescription
businessIdrequiredstringBusiness to list activity for
leadIdstringFilter to a single lead
limitintegerMax results (default 50, max 200)

Scheduled runner

/api/cron/sdr-pipeline runs every 2 hours (0 */2 * * *), iterates every business with sdr_config.enabled = true, and dispatches. Cron-secret protected; not callable by API key or session.


SMS

POST/api/v1/sms/sendSend an outbound SMS

Sends an SMS via Twilio using the agent's assigned phone number. Includes DNC and consent compliance checks before sending. Messages are logged to the conversation thread.

Required scope: sms:write

Parameters

ParameterTypeDescription
torequiredstringRecipient phone number (E.164 format, e.g. +15125551234)
from_agent_idrequiredstringAgent UUID — the agent's Twilio number is used as the sender
messagerequiredstringText message content
lead_idstringOptional lead UUID to link the message to a conversation thread

Example

curl -X POST https://www.kaicalls.com/api/v1/sms/send \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+15125559876",
    "from_agent_id": "uuid-agent789",
    "message": "Hi John, just following up on our call. Let me know if you have any questions!",
    "lead_id": "uuid-lead123"
  }'
Response200
{
  "success": true,
  "message_sid": "SM1234567890abcdef",
  "to": "+15125559876",
  "from": "+15125551234"
}
Error CodeStatusDescription
number_on_dnc400Recipient is on the Do-Not-Call list
number_opted_out400Recipient has not opted in to SMS
not_found404Agent not found or has no phone number
sms_send_failed500Twilio failed to deliver the message
POST/api/v1/sms/update-promptUpdate an agent's SMS prompt

Updates the SMS auto-reply prompt for an agent. Useful for AI agents that need to adjust their own follow-up messaging programmatically. Changes are logged to the audit trail.

Required scope: agents:write

Parameters

ParameterTypeDescription
agent_idrequiredstringAgent UUID to update
sms_promptrequiredstringNew SMS system prompt text
reasonstringOptional audit reason for the change

Example

curl -X POST https://www.kaicalls.com/api/v1/sms/update-prompt \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "uuid-agent789",
    "sms_prompt": "You are a helpful legal assistant. Respond to text messages professionally and help schedule consultations.",
    "reason": "Switching to consultation-focused messaging"
  }'
Response200
{
  "success": true,
  "agent_id": "uuid-agent789",
  "message": "SMS prompt updated"
}

Evals

Eval scenarios are scripted mock conversations that run against an agent and grade each assistant turn with an AI judge (default: GPT-4.1). Use them to gate regressions on prompt changes — every scenario tests one behavior (greeting language, intake step, refusal, recall of a fact). Destructive tools (send_sms, send_link, bookings, config writes) are auto-stubbed during runs, so evals never trigger real SMS, emails, or bookings.

Each scenario is a sequence of turns. User turns are { "role": "user", "content": "..." }. Assistant turns include a judgePlan with one of three judge types: exact (string match), regex (regex match), or ai (LLM rubric).

POST/api/v1/evalsCreate an eval scenario

Requires evals:write. Creates the scenario in KaiCalls and mirrors it to Vapi.

Body Parameters

ParameterTypeDescription
agent_idrequiredstringAgent UUID — scenario runs against this agent's Vapi assistant.
business_idrequiredstringBusiness that owns the agent. Must be accessible to the API key.
namerequiredstringHuman-readable scenario name (e.g. greeting.identifies_firm).
descriptionstringWhat the scenario tests.
messagesrequiredVapiEvalMessage[]Ordered turns. Alternate user / assistant; assistant turns carry a judgePlan.

Example

curl -X POST https://www.kaicalls.com/api/v1/evals \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "uuid-agent789",
    "business_id": "uuid-biz456",
    "name": "greeting.identifies_firm",
    "description": "Agent confirms the firm name when asked.",
    "messages": [
      { "role": "user", "content": "Hi, is this the law office?" },
      {
        "role": "assistant",
        "judgePlan": {
          "type": "ai",
          "model": {
            "provider": "openai",
            "model": "gpt-4.1",
            "messages": [
              {
                "role": "system",
                "content": "Evaluate: {{messages[-1]}}\nContext: {{messages}}\n\nPass if the assistant confirms the firm name AND offers to help. Fail if it names a different firm or refuses.\n\nOutput exactly: pass or fail"
              }
            ]
          }
        }
      }
    ]
  }'
Response201
{
  "id": "uuid-eval-001",
  "agent_id": "uuid-agent789",
  "business_id": "uuid-biz456",
  "vapi_eval_id": "vapi-eval-abc",
  "name": "greeting.identifies_firm",
  "description": "Agent confirms the firm name when asked.",
  "messages": [/* ... */],
  "created_at": "2026-05-01T10:00:00Z"
}
GET/api/v1/evalsList evals (filter by agent_id) or fetch one (?id=)

Requires evals:read. List omits messages for brevity; fetch by id to get the full scenario.

Query Parameters

ParameterTypeDescription
idstringFetch a single eval by ID (returns full messages).
agent_idstringFilter list to one agent.
# List all evals for an agent
curl "https://www.kaicalls.com/api/v1/evals?agent_id=uuid-agent789" \
  -H "Authorization: Bearer kc_live_xxxxx"

# Fetch one eval
curl "https://www.kaicalls.com/api/v1/evals?id=uuid-eval-001" \
  -H "Authorization: Bearer kc_live_xxxxx"
PATCH/api/v1/evalsUpdate an eval

Requires evals:write. Send only the fields you want to change. Updates are mirrored to Vapi.

ParameterTypeDescription
idrequiredstringEval ID.
namestringRename the scenario.
descriptionstringReplace the description.
messagesVapiEvalMessage[]Replace the full turn list.
DELETE/api/v1/evals?id=EVAL_IDDelete an eval

Requires evals:write. Deletes the local row and the Vapi mirror.

POST/api/v1/evals/runRun an eval (or every eval for an agent)

Requires evals:write (runs bill Vapi compute). Pass eval_id to run one scenario, or agent_id to fan out every scenario for the agent in parallel.

Body Parameters

ParameterTypeDescription
eval_idstringRun one scenario by ID. (Provide eval_id OR agent_id.)
agent_idstringRun every scenario for this agent in parallel.
waitbooleantrue (default) blocks until the run ends; false returns immediately for polling.
max_wait_msintegerPer-run wait cap when wait=true. Default 60000, max 110000.

Single-run response

Response200
{
  "run_id": "uuid-run-001",
  "vapi_run_id": "vapi-run-xyz",
  "status": "ended",
  "passed": true,
  "results": [
    {
      "status": "pass",
      "messages": [
        { "role": "user", "content": "Hi, is this the law office?" },
        { "role": "assistant", "content": "Yes, this is Johnson Law — how can I help?" }
      ]
    }
  ]
}

Fan-out response

Response200
{
  "total": 11,
  "completed": 11,
  "passed": 10,
  "failed": 1,
  "results": [
    { "eval_id": "uuid-eval-001", "name": "greeting.identifies_firm", "run_id": "uuid-run-001", "vapi_run_id": "vapi-run-xyz", "status": "ended", "passed": true },
    { "eval_id": "uuid-eval-008", "name": "price.no_quote", "run_id": "uuid-run-008", "vapi_run_id": "vapi-run-...", "status": "ended", "passed": false }
  ]
}
GET/api/v1/evals/runPoll a run or list recent runs

Requires evals:read. Polling refreshes from Vapi if the run is still in flight and persists the final result. Once status is "ended", results and passed are stable.

Query Parameters

ParameterTypeDescription
run_idstringKaiCalls run ID (from the POST response).
vapi_run_idstringUnderlying Vapi run ID.
agent_idstringList the 50 most recent runs for an agent.

Run statuses

ParameterTypeDescription
queuedstatusSubmitted to Vapi, not yet started.
in-progressstatusMock conversation is executing.
endedstatusFinished — check passed and results.

Notes

  • During eval runs, the assistant runs with metadata.test_mode = true. Tool handlers short-circuit destructive actions (no real SMS, no real bookings, no config writes).
  • One scenario tests one behavior. Keep judge prompts narrow and explicit: Pass if X. Fail if Y, Z, or W.
  • Vapi rate-limits per org. Don't fan out 50+ evals back-to-back across multiple agents — space them out.

API Keys

Key management uses session authentication (dashboard login), not API key auth.

GET/api/v1/keysList your API keys
Response200
{
  "keys": [
    {
      "id": "uuid-key123",
      "prefix": "kc_live_abc1",
      "name": "Production Key",
      "scopes": ["agents:read", "agents:write", "numbers:read", "calls:read", "calls:write", "sms:read", "sms:write", "emails:read", "emails:write"],
      "business_id": null,
      "created_at": "2026-01-15T10:00:00Z",
      "last_used_at": "2026-02-15T14:30:00Z",
      "expires_at": null,
      "is_active": true
    }
  ]
}
POST/api/v1/keysCreate a new key

The full key is returned only once — store it securely.

ParameterTypeDescription
namestringHuman-readable name (default: "Untitled Key")
business_idstringScope key to a single business. If omitted, key accesses all your businesses.
scopesstring[]Permission scopes. Defaults to all standard scopes (agents, numbers, calls, sms, emails).
Response201
{
  "key": "kc_live_abc123def456...",
  "id": "uuid-key123",
  "prefix": "kc_live_abc1",
  "name": "My Integration Key",
  "scopes": ["agents:read", "agents:write", "numbers:read", "calls:read", "calls:write", "sms:read", "sms:write", "emails:read", "emails:write"],
  "business_id": "uuid-biz456",
  "created_at": "2026-02-15T14:30:00Z"
}
DELETE/api/v1/keys?id=KEY_IDRevoke a key

Revokes an API key. The key immediately stops working.

Response200
{ "success": true }

Analytics

Aggregate analytics endpoints for dashboards, reports, and monitoring. All endpoints auto-scope to the API key's business(es).

GET/api/v1/analytics/dashboardDashboard summary

Comprehensive snapshot: leads, calls, conversion rates, and top agents.

ParameterTypeDescription
daysintegerLook-back period in days (default: 30, max: 90)
Response200
{
  "period": { "days": 30, "since": "2026-02-16T00:00:00Z" },
  "leads": {
    "total": 142,
    "by_status": { "new": 45, "contacted": 38, "qualified": 32, "converted": 18, "lost": 9 },
    "conversion_rate": 35.21,
    "recent": [{ "id": "uuid-1", "name": "Jane Doe", "status": "new", "source": "website", "created_at": "..." }]
  },
  "calls": {
    "total": 387,
    "completed": 351,
    "total_duration_seconds": 48200,
    "avg_duration_seconds": 125
  },
  "agents": {
    "total": 3,
    "top_by_calls": [{ "agent_id": "uuid-a1", "name": "Kai", "count": 215, "total_duration": 28000, "avg_duration": 130 }]
  }
}
GET/api/v1/analytics/callsCall analytics

Call volume, outcomes, and avg duration grouped by day or week.

ParameterTypeDescription
daysintegerLook-back period (default: 30, max: 90)
group_bystring"day" (default) or "week"
agent_idstringFilter by agent
Response200
{
  "period": { "days": 7, "since": "...", "group_by": "day" },
  "summary": { "total_calls": 87, "total_duration_seconds": 11200, "avg_duration_seconds": 129 },
  "timeline": [
    { "date": "2026-03-12", "total": 14, "completed": 12, "total_duration": 1800, "avg_duration_seconds": 129, "statuses": { "ended": 12, "no-answer": 2 } }
  ]
}
GET/api/v1/analytics/funnelConversion funnel

Lead conversion funnel: new → contacted → qualified → converted. Requires leads:read scope.

ParameterTypeDescription
daysintegerLook-back period (default: 30, max: 90)
sourcestringFilter by lead source
Response200
{
  "period": { "days": 30, "since": "..." },
  "total_leads": 142,
  "lost": 9,
  "funnel": [
    { "stage": "new", "count": 133, "rate_from_top": 93.66, "dropoff_rate": 0 },
    { "stage": "contacted", "count": 88, "rate_from_top": 61.97, "dropoff_rate": 33.83 },
    { "stage": "qualified", "count": 50, "rate_from_top": 35.21, "dropoff_rate": 43.18 },
    { "stage": "converted", "count": 18, "rate_from_top": 12.68, "dropoff_rate": 64 }
  ]
}
GET/api/v1/analytics/agentsAgent performance

Per-agent stats: call count, avg duration, lead conversion.

ParameterTypeDescription
daysintegerLook-back period (default: 30, max: 90)
agent_idstringFilter to a single agent
Response200
{
  "period": { "days": 30, "since": "..." },
  "agents": [
    {
      "agent_id": "uuid-a1",
      "agent_name": "Kai",
      "calls": { "total": 215, "completed": 198, "avg_duration_seconds": 130, "total_duration_seconds": 28000 },
      "leads": { "total": 89, "converted": 18, "conversion_rate": 20.22 }
    }
  ]
}
GET/api/v1/analytics/weeklyWeekly report

This week vs. last week comparison with percent changes.

Response200
{
  "period": {
    "this_week": { "start": "2026-03-11T...", "end": "2026-03-18T..." },
    "last_week": { "start": "2026-03-04T...", "end": "2026-03-11T..." }
  },
  "calls": {
    "this_week": { "total": 87, "completed": 79, "total_duration_seconds": 11200, "avg_duration_seconds": 129 },
    "last_week": { "total": 72, "completed": 65, "total_duration_seconds": 9100, "avg_duration_seconds": 126 },
    "change": { "total": 20.83, "completed": 21.54, "avg_duration": 2.38 }
  },
  "leads": {
    "this_week": { "total": 34, "converted": 6, "conversion_rate": 17.65 },
    "last_week": { "total": 28, "converted": 4, "conversion_rate": 14.29 },
    "change": { "total": 21.43, "converted": 50 }
  },
  "top_agents": [{ "agent_id": "uuid-a1", "agent_name": "Kai", "total_calls": 52, "total_duration_seconds": 6800 }]
}
GET/api/v1/analytics/businessesBusiness performance

Per-business breakdown for multi-tenant accounts.

ParameterTypeDescription
daysintegerLook-back period (default: 30, max: 90)
Response200
{
  "period": { "days": 30, "since": "..." },
  "businesses": [
    {
      "business_id": "uuid-biz1",
      "business_name": "Smith Law Firm",
      "calls": { "total": 215, "completed": 198, "avg_duration_seconds": 130 },
      "leads": { "total": 89, "converted": 18, "conversion_rate": 20.22 }
    }
  ]
}

Transcripts

GET/api/v1/transcriptsGet call transcripts

Recent call transcripts with summaries. Only returns calls that have a transcript.

ParameterTypeDescription
limitintegerMax results (default: 20, max: 50)
agent_idstringFilter by agent
afterstringCursor pagination — created_at of last item
daysintegerLook-back period (default: 30, max: 90)
Response200
{
  "transcripts": [
    {
      "call_id": "uuid-call1",
      "conversation_id": "vapi-conv-123",
      "agent_id": "uuid-a1",
      "agent_name": "Kai",
      "status": "ended",
      "duration": 185,
      "summary": "Caller inquired about pricing for estate planning services...",
      "transcript": "Agent: Hello! How can I help you today?\nCaller: Hi, I'm looking for...",
      "created_at": "2026-03-17T14:30:00Z"
    }
  ],
  "has_more": true
}

Account

GET/api/v1/balanceGet subscription info

Returns usage and subscription status for each of your businesses.

Response200
{
  "businesses": [
    {
      "business_id": "uuid-biz456",
      "minutes_used": 412,
      "phone_numbers": 2,
      "subscription_status": "active"
    }
  ]
}
GET/api/v1/usageGet API usage log

Returns your API call history.

ParameterTypeDescription
startstringISO 8601 start date filter
endstringISO 8601 end date filter
limitintegerMax results (default: 100, max: 500)
Response200
{
  "usage": [
    {
      "id": "uuid-log123",
      "endpoint": "/v1/calls",
      "method": "POST",
      "status_code": 201,
      "call_id": "uuid-call789",
      "cost_cents": 15,
      "created_at": "2026-02-15T14:30:00Z"
    }
  ],
  "has_more": false
}

Webhooks

Two ways to receive events from KaiCalls:

  1. Per-call webhooks — pass a webhook_url when calling POST /v1/calls. Fires call.completed / call.failed for that single call only.
  2. Global webhooks — register one URL per business via POST /api/v1/webhooks to receive lead.created, lead.scored, and call.completed for every event in that business. Recommended for partners.

Global webhook configuration

POST/api/v1/webhooksRegister a global webhook URL

Required scope: webhooks:write. The signing secret is returned once in the response — store it immediately, it cannot be retrieved later.

Request

curl -X POST https://www.kaicalls.com/api/v1/webhooks \
  -H "Authorization: Bearer kc_live_xxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "business_id": "uuid-biz456",
    "webhook_url": "https://partner.example.com/webhooks/kaicalls"
  }'
Response201
{
  "business_id": "uuid-biz456",
  "webhook_url": "https://partner.example.com/webhooks/kaicalls",
  "webhook_secret": "abc123...full-secret",
  "webhook_secret_masked": "...a1b2c3d4",
  "events": ["lead.created", "lead.scored", "call.completed"],
  "message": "Webhook configured. Save the secret now — it will not be shown again."
}
GET/api/v1/webhooksView current webhook config

Required scope: webhooks:read. When the API key spans multiple businesses, pass ?business_id=<uuid>. Returns the URL plus a masked secret only.

curl "https://www.kaicalls.com/api/v1/webhooks?business_id=uuid-biz456" \
  -H "Authorization: Bearer kc_live_xxxxx"
Response200
{
  "business_id": "uuid-biz456",
  "webhook_url": "https://partner.example.com/webhooks/kaicalls",
  "webhook_secret_masked": "...a1b2c3d4",
  "configured": true,
  "events": ["lead.created", "lead.scored", "call.completed"]
}
DELETE/api/v1/webhooks?business_id=<uuid>Remove the configured webhook

Required scope: webhooks:write.

Verifying signatures

Every outbound webhook (other than Discord URLs) is signed with HMAC-SHA256. Each request carries:

X-KaiCalls-Signature: t=<unix_timestamp>,v1=<hex_signature>
X-KaiCalls-Event: lead.created

The signature is computed as HMAC_SHA256(secret, "<timestamp>." + raw_body). Example verification in Python:

import hmac, hashlib

def verify(secret, signature_header, body):
    parts = dict(p.split('=', 1) for p in signature_header.split(','))
    timestamp, signature = parts['t'], parts['v1']
    expected = hmac.new(
        secret.encode(),
        f"{timestamp}.{body}".encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Event payloads

lead.created

{
  "event": "lead.created",
  "timestamp": "2026-04-27T14:32:12Z",
  "business_id": "uuid-biz456",
  "data": {
    "id": "uuid-lead123",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+15125550123",
    "source": "inbound_call"
  }
}

lead.scored

{
  "event": "lead.scored",
  "timestamp": "2026-04-27T14:33:45Z",
  "business_id": "uuid-biz456",
  "data": {
    "lead_id": "uuid-lead123",
    "score": 82,
    "confidence": "high",
    "explanation": "Strong intent signals: requested pricing, asked about timeline.",
    "factors": ["pricing_question", "timeline_question", "decision_maker"]
  }
}

call.completed

{
  "event": "call.completed",
  "timestamp": "2026-04-27T14:32:12Z",
  "business_id": "uuid-biz456",
  "data": {
    "call_id": "uuid-call789",
    "status": "completed",
    "duration": 127,
    "agent_name": "Kai",
    "ended_reason": "customer-ended-call",
    "recording_url": "https://...",
    "summary": "Customer asked about pricing for 10-person team..."
  }
}

call.failed (per-call webhook only)

{
  "event": "call.failed",
  "call_id": "uuid-call789",
  "status": "failed",
  "error": "no_answer",
  "timestamp": "2026-04-27T14:31:00Z"
}

v1 limitations

  • One webhook URL per business — use your own dispatcher for fan-out.
  • Single delivery attempt with a 10-second timeout. Failures are logged in webhook_logs but not retried in v1.
  • No event filtering — every supported event fires to the configured URL.
  • Discord webhook URLs (matching discord.com/api/webhooks/) are auto-formatted as embed messages and not signed — Discord rejects custom headers on its webhook endpoint.

Errors

All errors follow a consistent format:

{
  "error": {
    "code": "error_code",
    "message": "Human-readable description"
  }
}
CodeStatusDescription
unauthorized401Invalid, missing, expired, or revoked API key
forbidden403Key doesn't have the required scope
not_found404Agent, call, or resource not found
invalid_request400Missing required fields or invalid JSON
validation_error400Field validation failed (e.g. invalid email)
email_exists409Email already registered (signup)
number_on_dnc400Recipient is on the Do-Not-Call list (SMS)
number_opted_out400Recipient has not opted in (SMS)
sms_send_failed500Twilio failed to deliver the SMS
vapi_error4xx/5xxVapi API call failed (create/update agent)
call_failed500Vapi failed to initiate the call
payment_required402x402 payment required (signup, when enabled)
internal_error500Unexpected server error
rate_limited429Too many requests — slow down

Rate Limits

EndpointLimit
POST /v1/calls60 requests/minute
POST /v1/agents30 requests/minute
PATCH /v1/agents60 requests/minute
POST /v1/signup5 requests/hour per IP
POST /v1/sms/send60 requests/minute
GET /v1/analytics/*120 requests/minute
GET /v1/transcripts120 requests/minute
All other GET endpoints300 requests/minute

SDKs

Python

pip install kaicalls
from kaicalls import KaiCalls

kai = KaiCalls(api_key="kc_live_xxx")

# Simple call — agent auto-enriches with CRM data
call = kai.calls.create(
    agent_id="uuid-abc123",
    to="+15125551234",
    name="John Smith"
)

# Full call with lead linking and custom opening
call = kai.calls.create(
    agent_id="uuid-abc123",
    to="+15125551234",
    name="John Smith",
    lead_id="uuid-lead123",
    context="Following up on car accident case",
    first_message="Hey John, this is Kai — do you have a minute?"
)

result = kai.calls.wait(call.id)
print(result.summary)

# Create an agent
agent = kai.agents.create(
    business_id="uuid-biz456",
    name="Kai Receptionist",
    system_prompt="You are a friendly receptionist for Smith Law.",
    first_message="Hello! How can I help you today?"
)

# Update per-channel prompts
kai.agents.update(
    id=agent.id,
    inbound_prompt="Updated inbound instructions.",
    outbound_prompt="Updated outbound instructions.",
    sms_prompt="Updated SMS instructions."
)

# Update Vapi config directly (interruption, end-call, transcriber, etc.)
kai.agents.update(
    id=agent.id,
    vapi_config={
        "stopSpeakingPlan": {
            "numWords": 2,
            "interruptionPhrases": ["stop", "wait", "hold on", "no"],
            "acknowledgementPhrases": ["okay", "yeah", "right"]
        },
        "endCallPhrases": ["goodbye", "have a nice day"]
    }
)

# Send an SMS
kai.sms.send(
    to="+15125551234",
    from_agent_id="uuid-abc123",
    message="Hi John, following up on our call!",
    lead_id="uuid-lead123"
)

JavaScript / TypeScript

npm install kaicalls
import { KaiCalls } from 'kaicalls';

const kai = new KaiCalls({ apiKey: 'kc_live_xxx' });

// Simple call
const call = await kai.calls.create({
  agentId: 'uuid-abc123',
  to: '+15125551234',
  name: 'John Smith',
});

// Full call with lead linking and custom opening
const call = await kai.calls.create({
  agentId: 'uuid-abc123',
  to: '+15125551234',
  name: 'John Smith',
  leadId: 'uuid-lead123',
  context: 'Following up on car accident case',
  firstMessage: 'Hey John, this is Kai — do you have a minute?',
});

const result = await kai.calls.get(call.id);
console.log(result.summary);

// Create an agent
const agent = await kai.agents.create({
  businessId: 'uuid-biz456',
  name: 'Kai Receptionist',
  systemPrompt: 'You are a friendly receptionist for Smith Law.',
  firstMessage: 'Hello! How can I help you today?',
});

// Update per-channel prompts
await kai.agents.update({
  id: agent.id,
  inboundPrompt: 'Updated inbound instructions.',
  outboundPrompt: 'Updated outbound instructions.',
  smsPrompt: 'Updated SMS instructions.',
});

// Send an SMS
await kai.sms.send({
  to: '+15125551234',
  fromAgentId: 'uuid-abc123',
  message: 'Hi John, following up on our call!',
  leadId: 'uuid-lead123',
});
    API Reference | KaiCalls Developer Docs