API reference

Build on CallDeskTech.

Build, publish and operate voice agents. Authenticate with Authorization: Bearer cdk_live_… (create keys in Settings → API Keys). A key is pinned to one workspace; call GET /me to get its tenant id.

Quick start

Send your key as a bearer token. Ask who it belongs to first: the response includes the workspace id used in the paths below.

1. Find your workspace
curl https://calldesk.tech/api/v1/me \
  -H "Authorization: Bearer cdk_live_..."
2. Create an agent from a template
curl -X POST \
  https://calldesk.tech/api/v1/tenants/$TENANT/agents/from-template \
  -H "Authorization: Bearer cdk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"templateId": "medical-receptionist"}'

Errors return 401 for missing or bad credentials, 404 for anything outside your workspace, and 429 when rate limited.

MCP server

Connect an AI assistant to your workspace. Add the URL, sign in when your browser opens, and pick the workspace to share. No API key needed.

Claude Code
claude mcp add --transport http calldesktech https://calldesk.tech/mcp
Claude, Cursor and other clients: add a remote MCP server with this URL
https://calldesk.tech/mcp

Each connection is a workspace API key named after the app. Revoke it under Settings, API Keys. Clients that only support keys can send one as Authorization: Bearer cdk_live_....

MCP tools

Everything the MCP server exposes. Tools that cost money or delete data say so in their description, and assistants ask before calling them.

Workspace

  • whoami Show the workspace this connection is pinned to.

Agents

  • flow_authoring_guide Read first: node types, parameters, edge rules and gotchas for building a flow.
  • list_agents List agents with their latest version’s engine and voice, and the numbers routed to them.
  • create_agent Create an agent (publish a version next).
  • get_agent Get one agent.
  • rename_agent Rename an agent.
  • delete_agent Permanently delete an agent and its versions, subflows and knowledge bases.
  • list_agent_versions List an agent’s immutable versions, newest first.
  • publish_agent_version Publish a new immutable version from a conversation-flow graph. Optional tier (standard or pro) picks the models for you; optional routingMode expert_backup (with acceptExpertBackup, Lite or Standard) adds the paid expert backup extra; advanced llmModel and ttsModel override them. Optional requireBookingTools fails the publish (422) when the flow books appointments but no calendar is connected; otherwise the result carries a warnings array.
  • list_agent_templates List the built-in agent templates.
  • create_agent_from_template Create a ready-to-call agent from a template and publish its first version. The result may carry publishWarnings (e.g. no calendar connected).
  • list_agent_environments List an agent’s staging and production environments and the version each points to.
  • promote_agent_environment Promote a version into staging or production.
  • analyze_agent_copilot Analyze recent real calls for recurring problems and propose flow edits grounded in specific transcripts.

Pricing and models

  • list_pricing_tiers List the pricing tiers (Lite, Standard, Pro) with price per minute, what each includes, carrier billing and the planned add-ons (coming soon, not purchasable yet).
  • list_model_options List the language models (llmModel) and voice models (ttsModel) a version can use, with status and notes (advanced).

Subflows and knowledge

  • list_subflows List subflows (library ones plus an agent’s own).
  • create_subflow Create a reusable sub-graph to reference from a subflow_ref node.
  • update_subflow Update a subflow. Published versions keep their snapshot.
  • delete_subflow Delete a subflow.
  • list_knowledge_bases List knowledge bases.
  • create_knowledge_base Create a knowledge base.
  • add_knowledge_items Add question-and-answer items to a knowledge base.
  • delete_knowledge_base Delete a knowledge base and its items.

Sounds

  • list_sounds List the workspace’s intro jingle and sound effects.
  • create_jingle Generate the intro jingle that plays when a call connects.
  • create_sound_effect Generate a sound effect the agent can play mid-call.
  • delete_sound Delete a jingle or sound effect.

Numbers and calls

  • list_phone_numbers List phone numbers and the agent versions they route to. Numbers bought from us are a paid extra on every plan ($2.00 per month per number plus 1.5 cents per minute of inbound calls on Twilio, $1.00 plus 1 cent on Telnyx); bringing your own number is free.
  • set_number_routing Route a number’s inbound or outbound calls to a version or an environment.
  • place_call Place a real outbound call from one of your numbers (costs money).
  • list_calls List recent calls.
  • get_call Get one call: transcript, outcome, duration and transfer status.

Batch calls

  • list_batch_calls List batch calls.
  • create_batch_call Create (not start) a batch of outbound calls for a version.
  • get_batch_call Get a batch and every target with its dial status.
  • run_batch_call Start a batch: dials every number (costs money).

Webhooks and integrations

  • list_webhooks List webhooks.
  • create_webhook Register a webhook.
  • delete_webhook Delete a webhook.
  • lookup_hubspot_contact Find a caller’s HubSpot contact by phone and return fields usable as call-time variables.

Analytics

  • get_analytics Call analytics by day.
  • get_qa_overview QA scores, resolution rate and transfer metrics.

Pricing tiers

The tier is the engine: pick one when you publish a version and we choose the right voice and intelligence for it. You never need to pick a model. Pass tier to POST /agents/{agentId}/versions or the publish_agent_version tool, and read the list from GET /pricing (no sign-in needed) or list_pricing_tiers.

Lite

$0.02 / min

An efficient engine with a clear, English-only voice.

Voice
Clear and efficient
Response speed
Good
Reasoning
Good

Standard

$0.05 / min

A fast, strong engine with a natural voice.

Voice
Natural
Response speed
Fast
Reasoning
Strong

Pro

$0.09 / min

Our strongest engine with our most expressive voice.

Voice
Most expressive
Response speed
Fast
Reasoning
Strongest
  • Included on every plan: Call summary; Full transcript; Structured field extraction (name, reason for the call, and anything else you ask for); Call transfers; Keypad tones (DTMF); Knowledge base; Calendar booking; Call testing and live call monitoring; API and MCP server; 40+ languages (non-English languages need the Standard or Pro voice; Lite's voice is English only).
  • Bring your own carrier on every plan, or add phone numbers from us. Phone numbers from us, on any plan: Telnyx number $1.00 per month plus 1 cent per inbound minute; Twilio number (premium carrier: supports payments and our existing SMS setup) $2.00 per month plus 1.5 cents per inbound minute. Bring your own number or carrier: free.
  • Standard with one Telnyx number and 500 inbound minutes a month: 500 x 5c + $1.00 + 500 x 1c = $31.00.
  • Expert backup on Lite and Standard: 1.5 cents more per minute while it is on, for better accuracy on hard turns. Publish with "routingMode": "expert_backup" and "acceptExpertBackup": true (voiceEngine poc, tier lite or standard).
  • Optional add-ons are coming soon and cannot be bought yet; amounts will be announced when they launch. Planned: caller sentiment, every turn, advanced analytics, premium voice, long prompts.
  • Lite uses our efficient voice, built for fast, high-volume calls. Publishing with "tier": "lite" needs "acceptLowerQuality": true to confirm you are choosing the Lite voice for the lower price.
  • Agents published without a tier keep their current per-minute price. Choosing a tier is optional.
Publish a version on the Standard tier
curl -X POST https://calldesk.tech/api/v1/agents/$AGENT/versions \
  -H "Authorization: Bearer cdk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"flowName":"Front desk","startNodeId":"greet","nodes":[...],
       "voiceEngine":"poc","tier":"standard"}'

Booking without a calendar

An agent can only book appointments live when a calendar is connected under Integrations and globalSettings.calendarTools is not false. If you publish a flow that collects appointments without that, the version is still published and the response carries a warnings array with code booking_without_calendar: the agent takes a request and someone must confirm it. Templates installed from the API report the same warnings as publishWarnings.

If you would rather fail than ship a request-only agent, pass "requireBookingTools": true when publishing. In that case, and only that case, the publish is refused with HTTP 422 and code booking_requires_calendar (with reasons and fix) before anything is saved or billed. Flows without booking steps, or workspaces with a calendar connected, publish as normal.

Advanced: choose models yourself

You do not need this to use a pricing tier. For API and MCP users who want control, an agent version can also choose the language model that runs the conversation and the voice model that speaks it. Models you set override the tier’s choice, and the tier’s price does not change. Pass llmModel and ttsModel when you publish a version, or read the same list from GET /models or the list_model_options tool. Leave them out and the tier (or the default) chooses.

  • llmModel applies to agents on the in-house voice engine (voiceEngine "poc"). If the engine cannot use the chosen model (for example its provider key is not configured) or the provider fails before the agent speaks, the default model answers, so the call still works.
  • ttsModel must belong to the agent’s voice backend (ttsBackend). The kokoro, piper, and minimax backends have no model choice.
  • Individual flow nodes can override the model with params.model.
  • Most people never need this list: publish with a pricing tier (tier "standard" or "pro", see GET /pricing) and the right models are chosen for you. Models you set explicitly override the tier’s choice.

Language models (llmModel)

IdStatusNotes
claude-haiku-4-5-20251001defaulttestedThe default. Fastest first response of the tested models (about 0.6 s) and the most reliable at recording fields and following the flow.
claude-sonnet-4-6testedStronger reasoning for complex flows, costs more than Haiku and is slower.
gpt-6-lunatestedA much lower-cost option than Haiku. Passed our cooperative-caller benchmark 6 of 6, but responds slower (roughly 0.5 to 1.6 s) because turns that record a field need a second request. Not yet cleared on the stress tests (corrections, misheard numbers).
gemini-3.1-flash-litepreviewA low-cost, fast option from Google. Passed 36 of 40 in our text benchmark with a first response of about 0.75 s, but has not been tried on real calls yet.

Voice models (ttsModel, with ttsBackend)

IdBackendNotes
eleven_multilingual_v2defaultelevenlabsThe current default: natural, supports every language we offer.
eleven_flash_v2_5elevenlabsLower cost than Multilingual v2 and the fastest to first audio.
eleven_turbo_v2_5elevenlabsPrevious-generation low-latency model.
eleven_v4_turboelevenlabsThe most expressive low-latency model.
sonic-3.6defaultcartesiaCartesia’s default model.
sonic-2cartesiaPrevious generation.
Publish a version with a cheaper voice and language model
curl -X POST https://calldesk.tech/api/v1/agents/$AGENT/versions \
  -H "Authorization: Bearer cdk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"flowName":"Front desk","startNodeId":"greet","nodes":[...],
       "voiceEngine":"poc","ttsBackend":"elevenlabs",
       "ttsModel":"eleven_flash_v2_5","llmModel":"gpt-6-luna"}'

What you pay per minute is set by your pricing tier (or, for agents published without one, the flat price of your voice backend), not by the models you choose. See Pricing tiers above.

Official SDKs

Client libraries that wrap this API. Both are thin, typed wrappers — the routes below are the source of truth.

Python
pip install calldesktech
# or: pip install git+https://github.com/calldesktech/calldesktech-python
TypeScript / Node (source, not yet on npm — clone and build)
git clone https://github.com/calldesktech/calldesktech-node
cd calldesktech-node && npm install && npm run build
# npm install calldesktech   <- once published
Quickstart
import { CallDeskTech } from "calldesktech";

const client = new CallDeskTech({ apiKey: process.env.CALLDESK_API_KEY! });

const { agentId, versionId } = await client.agents.createFromTemplate({
  templateId: "medical-receptionist",
});

const { phoneNumbers } = await client.phoneNumbers.list();
await client.phoneNumbers.setRouting(phoneNumbers[0].id, {
  direction: "inbound",
  agentVersionId: versionId,
});

await client.phoneNumbers.call(phoneNumbers[0].id, { toNumber: "+15551234567" });

Account

get/me

Who am I?

For an API key, returns the tenant it is pinned to — use that id in the paths below.

Returns { auth, tenantId, tenantName }

Agents

get/tenants/{tenantId}/agents

List agents

Each agent includes its latest version’s engine/voice and any routed phone numbers.

Returns { agents: Agent[] }

post/tenants/{tenantId}/agents

Create an agent

name*
string
mode
'simple' | 'advanced'

Returns { agent }

get/agents/{agentId}

Get an agent

Returns { agent }

patch/agents/{agentId}

Rename an agent

name
string

Returns { agent }

delete/agents/{agentId}

Delete an agent

Also deletes its versions, subflows and knowledge bases.

Returns { success }

get/agents/{agentId}/versions

List versions

Returns { versions: AgentVersion[] }

post/agents/{agentId}/versions

Publish a new version

Versions are immutable. Set globalSettings.language (en default; es, fr, pt-BR, it, nl, hi, de, pl, id, ar) for a non-English agent: the engine switches speech recognition, the reply language and the voice, and a non-English poc agent is pinned to the ElevenLabs voice (billed at the ElevenLabs rate). nodes is the conversation-flow graph; subflow_ref nodes are embedded as snapshots at publish time. The response may include a non-blocking warnings array of { code, message }: booking_without_calendar means the flow collects appointments but the workspace has no calendar connected (or globalSettings.calendarTools is false), so the agent takes a request instead of booking. The version is still published. Set requireBookingTools: true to make that case an error instead: the request is refused with HTTP 422 and code booking_requires_calendar (plus reasons and fix) before anything is saved or billed. When the flow has no booking intent, or a calendar is connected, the flag changes nothing.

flowName*
string
startNodeId*
string
nodes*
FlowNode[]
globalSettings
object
requireBookingTools
boolean (optional, default false; true refuses the publish with 422 code booking_requires_calendar when the flow collects appointments but no calendar is connected or globalSettings.calendarTools is false; nothing is saved or billed)
voiceEngine*
'poc' | 'retell'
voiceId
string
tier
'lite' | 'standard' | 'pro' (optional; poc engine only; see GET /pricing; 'lite' also needs acceptLowerQuality: true, to confirm you are choosing its efficient, lower-cost voice; the tier picks the language model and voice for you; omit to keep the flat per-minute price of the voice backend)
routingMode
'expert_backup' (optional; Expert backup: hands the hard turns to a stronger model for better accuracy on hard turns; 1.5 cents per minute extra while on; poc engine with tier lite or standard only, not pro; needs acceptExpertBackup: true; omit for standard routing)
acceptExpertBackup
boolean (required true with routingMode expert_backup; without it you get 400 code expert_backup_acceptance_required with the terms)
ttsBackend
'kokoro' | 'elevenlabs' | 'cartesia' | 'minimax' | 'piper' (advanced)
llmModel
string (advanced, optional; poc engine only; overrides the tier; see GET /models; default claude-haiku-4-5-20251001)
ttsModel
string (advanced, optional; elevenlabs or cartesia only; overrides the tier; see GET /models)

Returns { version, flow, warnings?: { code, message }[] }. 422 { error, code: "booking_requires_calendar", reasons: string[], fix } when requireBookingTools is true and the agent cannot book

get/pricing

List pricing tiers

The pricing tiers (Lite, Standard, Pro) and planned optional add-ons (coming soon, not purchasable yet), with price per minute, the engine behind each tier (ratings for voice, response speed and reasoning), what every plan includes, and the phone number extra. Every tier is bring-your-own carrier. No authentication needed. Pass a tier id as tier when publishing a version. Lite is coming_soon and cannot be selected yet. Agents published without a tier keep the flat per-minute price of their voice backend. On every plan you can also buy a phone number from us as a paid extra (premium Twilio carrier: $2.00 per month per number plus 1.5 cents per minute of inbound calls; Telnyx carrier: $1.00 per month plus 1 cent per minute of inbound calls; outbound uses your own carrier); bringing your own number or carrier is free. The response also lists the expert backup extra (expertBackup: price, tiers, terms). Add-ons cannot be bought yet; their amounts are null until they launch.

Returns { currency, unit, tiers: { id, name, pricePerMinuteCents, pricePerMinuteDollars, tagline, whoItsFor, ratings: { voice, responseSpeed, reasoning }, carrierMode: 'byo' | 'managed', availability: 'live' | 'coming_soon' }[], addOns: { id, label, description, centsPerMinute | null, proposed, defaultOn }[], includedOnAllTiers, carrierNote, notes, phoneNumbers, expertBackup: { id, label, description, availability, tiers, centsPerMinute, publishFields, terms } }

get/models

List model options

The language models (llmModel) and voice models (ttsModel) you can choose when publishing a version, each with a tested or preview status and notes on speed and reliability. If the voice engine cannot use the chosen model, or the provider fails before the agent speaks, the default model answers, so a call never fails because of this setting. A flow node can override the language model with params.model.

Returns { defaults: { llmModel }, llmModels: { id, label, provider, default?, status, notes }[], ttsModels: { id, backend, label, default?, notes }[], notes: string[] }

get/agent-templates

List agent templates

Built-in templates (receptionist, medical receptionist, payment collection, IVR navigation and more) that can be installed as an agent.

Returns { templates: { id, label, description, category, defaultVariables: {name: value}, variables: string[] (the {{placeholders}} you can set on install) }[] }

post/tenants/{tenantId}/agents/from-template

Create an agent from a template

Creates the agent, its subflows and knowledge base, and publishes version 1. voiceEngine "poc" runs on CallDesk; "retell" also creates the equivalent Retell conversation-flow agent (some node types are approximated; see warnings). transferTo and functionUrl fill empty transfer numbers and function webhooks. variables sets the template's {{placeholders}} (see GET /agent-templates); business_name defaults to the tenant name.

templateId*
string (from GET /agent-templates)
name
string
voiceEngine
'poc' | 'retell'
transferTo
E.164 string
functionUrl
https URL
calendarTools
false to turn off live calendar lookups and bookings
language
agent language: 'en' (default), 'es', 'fr', 'pt-BR', 'it', 'nl', 'hi', 'de', 'pl', 'id' or 'ar'
variables
object, e.g. {"business_name": "Acme Dental", "agent_name": "Sam"}

Returns { agentId, versionId, versionNumber, template, voiceEngine, retellAgentId?, warnings? (Retell conversion notes, string[]), publishWarnings?: { code, message }[] (e.g. booking_without_calendar) }

get/agents/{agentId}/environments

List an agent's environments

Every agent has "staging" and "production", each pointing at the version it currently runs (null if nothing has been promoted into it yet).

Returns { environments: { id, name, version_id, updated_at }[] }

post/agents/{agentId}/environments/{name}/promote

Promote a version into staging or production

Every phone number (or batch call) routed to this environment picks up the new version immediately — no need to re-route. Rolling back is promoting an older version again.

versionId*
string

Returns { environment, resynced }

Subflows

get/tenants/{tenantId}/subflows

List subflows

?agentId Only library subflows plus this agent’s own

Returns { subflows: Subflow[] }

post/tenants/{tenantId}/subflows

Create a subflow

name*
string
scope
'agent' | 'library'
agentId
string (agent scope)
nodes
FlowNode[]
startNodeId
string

Returns { subflow }

get/tenants/{tenantId}/subflows/{subflowId}

Get a subflow

Returns { subflow }

patch/tenants/{tenantId}/subflows/{subflowId}

Update a subflow

Already-published versions keep the snapshot they embedded.

name
string
nodes
FlowNode[]
startNodeId
string
scope
string

Returns { subflow }

delete/tenants/{tenantId}/subflows/{subflowId}

Delete a subflow

Returns { ok }

Knowledge bases

get/tenants/{tenantId}/knowledge-bases

List knowledge bases

Returns { knowledgeBases }

post/tenants/{tenantId}/knowledge-bases

Create a knowledge base

Set agent_id — a knowledge_base node only sees content from a KB attached to its agent.

name*
string
source_type*
'website' | 'pdf' | 'manual'
source_url
string
agent_id
string

Returns { knowledgeBase }

patch/knowledge-bases/{knowledgeBaseId}

Rename or re-attach to an agent

name
string
agent_id
string | null

Returns { knowledgeBase }

delete/knowledge-bases/{knowledgeBaseId}

Delete a knowledge base

Returns { success }

get/knowledge-bases/{knowledgeBaseId}/items

List Q&A items

Returns { items }

post/knowledge-bases/{knowledgeBaseId}/items

Add Q&A items

items*
{ question: string, answer: string }[]

Returns { items }

Sounds

get/tenants/{tenantId}/call-audio

List jingle and sound effects

Returns the workspace's jingle and sound effects (asset_type is jingle or sound_effect); enabled: false rows are ones that were replaced.

Returns { assets }

post/tenants/{tenantId}/call-audio

Generate a jingle or sound effect

Generates the sound from a text description, then stores it. Spends generation credits. A workspace has one jingle and up to 10 sound effects; a new jingle replaces the current one, and reusing a sound effect name replaces that effect. durationSec is 1-12. A sound effect needs a description saying WHEN the agent should play it: the agent reads it. Only plays on agents running the in-house engine; Retell agents are unaffected. Can take up to a minute.

type*
'jingle' | 'sound_effect'
name*
string (letters, numbers, _ and -)
description
string (required for sound_effect)
prompt*
string (what the sound should be, max 500 chars)
durationSec*
number (1-12)

Returns { asset }

delete/tenants/{tenantId}/call-audio/{soundId}

Delete a jingle or sound effect

Callers stop hearing it immediately.

Returns { ok }

get/tenants/{tenantId}/call-audio/{soundId}/audio

Preview a sound

Returns a playable WAV (audio/wav) of exactly what callers hear.

Returns audio/wav

Phone numbers

get/tenants/{tenantId}/phone-numbers

List phone numbers

Returns { phoneNumbers }

post/tenants/{tenantId}/phone-numbers/purchase

Buy a phone number

On every plan this is a paid extra. Choose the carrier: twilio (premium) is $2.00 per month per number plus 1.5 cents per minute of inbound calls to your purchased numbers; telnyx is $1.00 per month plus 1 cent per minute of inbound calls; outbound calling uses your own carrier. Pass acceptNumberAddOn: true to accept the terms (without it you get 400 code number_addon_acceptance_required with the terms). Billing is set up before the number is bought and undone if the purchase fails. Bringing your own number is free. Requires a payment method on file.

areaCode
string (optional)
carrier
'twilio' (default) | 'telnyx'
acceptNumberAddOn
boolean

Returns { phoneNumber }

delete/phone-numbers/{phoneNumberId}

Release a phone number

Removes the number from your workspace. A number bought from us is released on its carrier first, and its monthly charge drops by one number (the charges end when you hold none). A number you registered yourself is only unregistered.

Returns { deleted: true }

post/phone-numbers/{phoneNumberId}/routing

Route a number to an agent version or environment

Pass exactly one of agentVersionId (a specific version, direct pin) or environmentId (staging/production — the number always runs whatever version that environment currently points to, so promoting later needs no further call here).

direction*
'inbound' | 'outbound'
agentVersionId
string | null
environmentId
string

Returns { phoneNumber }

Calls

post/phone-numbers/{phoneNumberId}/call

Place an outbound call

Calls toNumber from this number using its outbound agent. Rate-limited per workspace (429).

toNumber*
E.164 string

Returns { call: { sid, to } }

get/tenants/{tenantId}/calls

List calls

Each call includes analysis.issues (problems found automatically after the call; see Get a call).

?limit default 50

Returns { callLogs: CallLog[] }

get/calls/{callId}

Get a call

Includes transcript, outcome, duration, transfer status, and analysis (post-call analysis fields, null unless configured on the agent). analysis.issues lists problems found automatically after the call, each { code, severity: 'high'|'medium'|'low', message, evidence: string[], fix }; codes: claimed_booking_without_tool, placeholder_read_aloud, number_readback_mismatch, no_fields_collected, long_silence. The key is absent until the call has been checked and [] when nothing was found. Blocked and internal-test calls are never checked.

Returns { callLog }

get/calls/{callId}/recording

Stream a call recording

Returns audio

Batch calls

get/tenants/{tenantId}/batch-calls

List batch calls

Returns { batches }

post/tenants/{tenantId}/batch-calls

Create a batch

phoneNumbers accepts either a plain list (newline/comma/semicolon-separated) or a CSV with a header row — a column named phone/phone_number/to/to_number/number is the recipient, every other column becomes a per-call dynamic variable (e.g. a "first_name" column lets the agent say {{first_name}}). Omit scheduledAt to leave the batch pending for a manual/API "run" call now; set it to a future ISO timestamp to have the platform run it automatically at that time instead.

agentVersionId*
string
phoneNumbers*
string (plain list) or CSV text with a phone/phone_number/to/to_number/number column plus optional variable columns
name
string, optional label
scheduledAt
ISO datetime, optional — future time to run automatically instead of on manual/API trigger
callTimeWindow
optional { timezone: IANA string, days: number[] (0=Sun..6=Sat), start_hour: 0-23, end_hour: 1-24 } — restricts when this batch is allowed to dial

Returns { batch }

get/batch-calls/{batchId}

Get a batch and its targets

Each target includes its phone number, dial status, dynamic_variables used, and call_log_id once placed.

Returns { batchCall, targets }

post/batch-calls/{batchId}/run

Start a batch

Dials paced by the platform-wide and per-workspace rate limits. Returns 409 if the batch is scheduled for later and not yet due, or outside its callTimeWindow — retry after that time.

Returns { started, ... }

Webhooks

get/tenants/{tenantId}/webhooks

List webhooks

Returns { webhooks }

post/tenants/{tenantId}/webhooks

Register a webhook

Events: call.started (Retell-engine calls only), call.completed (includes analysis when configured), call.transferred, call.analyzed (post-call analysis results). Deliveries are signed with the returned whsec_ secret (X-CallDesk-Event header names the event).

url*
https URL
events
('call.started' | 'call.completed' | 'call.transferred' | 'call.analyzed')[]

Returns { webhook }

patch/tenants/{tenantId}/webhooks/{webhookId}

Update a webhook

url
string
events
string[]
enabled
boolean

Returns { webhook }

delete/tenants/{tenantId}/webhooks/{webhookId}

Delete a webhook

Returns { success }

post/tenants/{tenantId}/webhooks/{webhookId}/test

Send a test delivery

Returns { ok }

Contacts

get/tenants/{tenantId}/contacts

List contacts

Returns { contacts }

CRM

get/tenants/{tenantId}/crm

List CRM connections

Access/refresh tokens are never returned — write-only, like API keys.

Returns { connections: { id, provider, provider_account_id, expires_at, created_at }[] }

delete/tenants/{tenantId}/crm

Disconnect a CRM

?provider 'hubspot' | 'salesforce'

Returns { success }

get/tenants/{tenantId}/crm/hubspot/connect

Start HubSpot OAuth connect

Browser-navigation endpoint (not JSON) — 302s to HubSpot's authorize screen. Owner/admin only.

Returns 302 redirect

get/tenants/{tenantId}/crm/hubspot/lookup

Look up a caller in HubSpot (CRM→us)

On-demand lookup by phone for call-time personalization, e.g. injecting {{crm_company_name}} as a dynamic variable. No background sync.

?phone E.164 phone number

Returns { variables: { crm_contact_found, crm_first_name?, crm_last_name?, crm_company_name?, crm_email?, crm_job_title? } }

Analytics

get/tenants/{tenantId}/analytics

Call analytics by day

?days 7 | 30 | 90

Returns { series, totals }

Quality

get/tenants/{tenantId}/qa/overview

QA scores, resolution and transfer metrics

?days 7 | 30 | 90

Returns { avgScore, resolutionRate, transferSuccessRate, ... }

get/agents/{agentId}/test-cases

List simulation test cases

Returns { testCases }

post/agents/{agentId}/test-cases

Create a test case

name
string
persona
string
successCriteria
string

Returns { testCase }

post/agents/{agentId}/test-cases/{testCaseId}/run

Run a simulation

Returns { passed, transcript, reasoning }

get/agents/{agentId}/copilot/analyze

List Copilot suggestions

Returns { suggestions }

post/agents/{agentId}/copilot/analyze

Analyze recent calls for flow-edit suggestions

Looks at this agent's recent real calls (transcripts + QA critiques, whichever number is currently routed to it) for recurring problems and proposes concrete node-level prompt edits, each grounded in specific call transcripts. Requires at least 5 usable calls, otherwise returns a not_enough_history status instead of guessing. Never writes to the flow — suggestions are persisted as pending and must be accepted or dismissed via the suggestions route.

Returns { status, callCount, summary, suggestions: { id, node_id, current_text, suggested_text, rationale, supporting_call_ids, status }[] }