{"openapi":"3.1.0","info":{"title":"CallDeskTech API","version":"1.0.0","description":"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."},"servers":[{"url":"https://0.0.0.0:3000/api/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"cdk_live_… API key"}}},"paths":{"/me":{"get":{"tags":["Account"],"summary":"Who am I?","description":"For an API key, returns the tenant it is pinned to — use that id in the paths below.","responses":{"200":{"description":"Returns { auth, tenantId, tenantName }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/agents":{"get":{"tags":["Agents"],"summary":"List agents","description":"Each agent includes its latest version’s engine/voice and any routed phone numbers.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"responses":{"200":{"description":"Returns { agents: Agent[] }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"post":{"tags":["Agents"],"summary":"Create an agent","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"string"},"mode":{"description":"'simple' | 'advanced'"}},"required":["name"]}}}},"responses":{"200":{"description":"Returns { agent }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/agents/{agentId}":{"get":{"tags":["Agents"],"summary":"Get an agent","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { agent }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"patch":{"tags":["Agents"],"summary":"Rename an agent","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"string"}}}}}},"responses":{"200":{"description":"Returns { agent }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"delete":{"tags":["Agents"],"summary":"Delete an agent","description":"Also deletes its versions, subflows and knowledge bases.","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { success }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/agents/{agentId}/versions":{"get":{"tags":["Agents"],"summary":"List versions","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { versions: AgentVersion[] }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"post":{"tags":["Agents"],"summary":"Publish a new version","description":"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.","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"flowName":{"description":"string"},"startNodeId":{"description":"string"},"nodes":{"description":"FlowNode[]"},"globalSettings":{"description":"object"},"requireBookingTools":{"description":"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":{"description":"'poc' | 'retell'"},"voiceId":{"description":"string"},"tier":{"description":"'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":{"description":"'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":{"description":"boolean (required true with routingMode expert_backup; without it you get 400 code expert_backup_acceptance_required with the terms)"},"ttsBackend":{"description":"'kokoro' | 'elevenlabs' | 'cartesia' | 'minimax' | 'piper' (advanced)"},"llmModel":{"description":"string (advanced, optional; poc engine only; overrides the tier; see GET /models; default claude-haiku-4-5-20251001)"},"ttsModel":{"description":"string (advanced, optional; elevenlabs or cartesia only; overrides the tier; see GET /models)"}},"required":["flowName","startNodeId","nodes","voiceEngine"]}}}},"responses":{"200":{"description":"Returns { version, flow, warnings?: { code, message }[] }. 422 { error, code: \"booking_requires_calendar\", reasons: string[], fix } when requireBookingTools is true and the agent cannot book"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/pricing":{"get":{"tags":["Agents"],"summary":"List pricing tiers","description":"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.","security":[],"responses":{"200":{"description":"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 } }"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/models":{"get":{"tags":["Agents"],"summary":"List model options","description":"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`.","responses":{"200":{"description":"Returns { defaults: { llmModel }, llmModels: { id, label, provider, default?, status, notes }[], ttsModels: { id, backend, label, default?, notes }[], notes: string[] }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/agent-templates":{"get":{"tags":["Agents"],"summary":"List agent templates","description":"Built-in templates (receptionist, medical receptionist, payment collection, IVR navigation and more) that can be installed as an agent.","responses":{"200":{"description":"Returns { templates: { id, label, description, category, defaultVariables: {name: value}, variables: string[] (the {{placeholders}} you can set on install) }[] }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/agents/from-template":{"post":{"tags":["Agents"],"summary":"Create an agent from a template","description":"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.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"templateId":{"description":"string (from GET /agent-templates)"},"name":{"description":"string"},"voiceEngine":{"description":"'poc' | 'retell'"},"transferTo":{"description":"E.164 string"},"functionUrl":{"description":"https URL"},"calendarTools":{"description":"false to turn off live calendar lookups and bookings"},"language":{"description":"agent language: 'en' (default), 'es', 'fr', 'pt-BR', 'it', 'nl', 'hi', 'de', 'pl', 'id' or 'ar'"},"variables":{"description":"object, e.g. {\"business_name\": \"Acme Dental\", \"agent_name\": \"Sam\"}"}},"required":["templateId"]}}}},"responses":{"200":{"description":"Returns { agentId, versionId, versionNumber, template, voiceEngine, retellAgentId?, warnings? (Retell conversion notes, string[]), publishWarnings?: { code, message }[] (e.g. booking_without_calendar) }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/subflows":{"get":{"tags":["Subflows"],"summary":"List subflows","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"agentId","in":"query","required":false,"description":"Only library subflows plus this agent’s own","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { subflows: Subflow[] }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"post":{"tags":["Subflows"],"summary":"Create a subflow","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"string"},"scope":{"description":"'agent' | 'library'"},"agentId":{"description":"string (agent scope)"},"nodes":{"description":"FlowNode[]"},"startNodeId":{"description":"string"}},"required":["name"]}}}},"responses":{"200":{"description":"Returns { subflow }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/subflows/{subflowId}":{"get":{"tags":["Subflows"],"summary":"Get a subflow","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"subflowId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { subflow }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"patch":{"tags":["Subflows"],"summary":"Update a subflow","description":"Already-published versions keep the snapshot they embedded.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"subflowId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"string"},"nodes":{"description":"FlowNode[]"},"startNodeId":{"description":"string"},"scope":{"description":"string"}}}}}},"responses":{"200":{"description":"Returns { subflow }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"delete":{"tags":["Subflows"],"summary":"Delete a subflow","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"subflowId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { ok }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/knowledge-bases":{"get":{"tags":["Knowledge bases"],"summary":"List knowledge bases","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"responses":{"200":{"description":"Returns { knowledgeBases }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"post":{"tags":["Knowledge bases"],"summary":"Create a knowledge base","description":"Set `agent_id` — a knowledge_base node only sees content from a KB attached to its agent.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"string"},"source_type":{"description":"'website' | 'pdf' | 'manual'"},"source_url":{"description":"string"},"agent_id":{"description":"string"}},"required":["name","source_type"]}}}},"responses":{"200":{"description":"Returns { knowledgeBase }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/knowledge-bases/{knowledgeBaseId}":{"patch":{"tags":["Knowledge bases"],"summary":"Rename or re-attach to an agent","parameters":[{"name":"knowledgeBaseId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"string"},"agent_id":{"description":"string | null"}}}}}},"responses":{"200":{"description":"Returns { knowledgeBase }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"delete":{"tags":["Knowledge bases"],"summary":"Delete a knowledge base","parameters":[{"name":"knowledgeBaseId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { success }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/knowledge-bases/{knowledgeBaseId}/items":{"get":{"tags":["Knowledge bases"],"summary":"List Q&A items","parameters":[{"name":"knowledgeBaseId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { items }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"post":{"tags":["Knowledge bases"],"summary":"Add Q&A items","parameters":[{"name":"knowledgeBaseId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"description":"{ question: string, answer: string }[]"}},"required":["items"]}}}},"responses":{"200":{"description":"Returns { items }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/call-audio":{"get":{"tags":["Sounds"],"summary":"List jingle and sound effects","description":"Returns the workspace's jingle and sound effects (`asset_type` is `jingle` or `sound_effect`); `enabled: false` rows are ones that were replaced.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"responses":{"200":{"description":"Returns { assets }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"post":{"tags":["Sounds"],"summary":"Generate a jingle or sound effect","description":"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.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"description":"'jingle' | 'sound_effect'"},"name":{"description":"string (letters, numbers, _ and -)"},"description":{"description":"string (required for sound_effect)"},"prompt":{"description":"string (what the sound should be, max 500 chars)"},"durationSec":{"description":"number (1-12)"}},"required":["type","name","prompt","durationSec"]}}}},"responses":{"200":{"description":"Returns { asset }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/call-audio/{soundId}":{"delete":{"tags":["Sounds"],"summary":"Delete a jingle or sound effect","description":"Callers stop hearing it immediately.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"soundId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { ok }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/call-audio/{soundId}/audio":{"get":{"tags":["Sounds"],"summary":"Preview a sound","description":"Returns a playable WAV (`audio/wav`) of exactly what callers hear.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"soundId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns audio/wav"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/agents/{agentId}/environments":{"get":{"tags":["Agents"],"summary":"List an agent's environments","description":"Every agent has \"staging\" and \"production\", each pointing at the version it currently runs (null if nothing has been promoted into it yet).","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { environments: { id, name, version_id, updated_at }[] }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/agents/{agentId}/environments/{name}/promote":{"post":{"tags":["Agents"],"summary":"Promote a version into staging or production","description":"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.","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}},{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"versionId":{"description":"string"}},"required":["versionId"]}}}},"responses":{"200":{"description":"Returns { environment, resynced }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/phone-numbers":{"get":{"tags":["Phone numbers"],"summary":"List phone numbers","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"responses":{"200":{"description":"Returns { phoneNumbers }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/phone-numbers/purchase":{"post":{"tags":["Phone numbers"],"summary":"Buy a phone number","description":"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.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"areaCode":{"description":"string (optional)"},"carrier":{"description":"'twilio' (default) | 'telnyx'"},"acceptNumberAddOn":{"description":"boolean"}}}}}},"responses":{"200":{"description":"Returns { phoneNumber }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/phone-numbers/{phoneNumberId}":{"delete":{"tags":["Phone numbers"],"summary":"Release a phone number","description":"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.","parameters":[{"name":"phoneNumberId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { deleted: true }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/phone-numbers/{phoneNumberId}/routing":{"post":{"tags":["Phone numbers"],"summary":"Route a number to an agent version or environment","description":"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).","parameters":[{"name":"phoneNumberId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"direction":{"description":"'inbound' | 'outbound'"},"agentVersionId":{"description":"string | null"},"environmentId":{"description":"string"}},"required":["direction"]}}}},"responses":{"200":{"description":"Returns { phoneNumber }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/phone-numbers/{phoneNumberId}/call":{"post":{"tags":["Calls"],"summary":"Place an outbound call","description":"Calls `toNumber` from this number using its outbound agent. Rate-limited per workspace (429).","parameters":[{"name":"phoneNumberId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"toNumber":{"description":"E.164 string"}},"required":["toNumber"]}}}},"responses":{"200":{"description":"Returns { call: { sid, to } }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/calls":{"get":{"tags":["Calls"],"summary":"List calls","description":"Each call includes `analysis.issues` (problems found automatically after the call; see Get a call).","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"limit","in":"query","required":false,"description":"default 50","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { callLogs: CallLog[] }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/calls/{callId}":{"get":{"tags":["Calls"],"summary":"Get a call","description":"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.","parameters":[{"name":"callId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { callLog }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/calls/{callId}/recording":{"get":{"tags":["Calls"],"summary":"Stream a call recording","parameters":[{"name":"callId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns audio"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/batch-calls":{"get":{"tags":["Batch calls"],"summary":"List batch calls","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"responses":{"200":{"description":"Returns { batches }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"post":{"tags":["Batch calls"],"summary":"Create a batch","description":"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.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"agentVersionId":{"description":"string"},"phoneNumbers":{"description":"string (plain list) or CSV text with a phone/phone_number/to/to_number/number column plus optional variable columns"},"name":{"description":"string, optional label"},"scheduledAt":{"description":"ISO datetime, optional — future time to run automatically instead of on manual/API trigger"},"callTimeWindow":{"description":"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"}},"required":["agentVersionId","phoneNumbers"]}}}},"responses":{"200":{"description":"Returns { batch }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/batch-calls/{batchId}":{"get":{"tags":["Batch calls"],"summary":"Get a batch and its targets","description":"Each target includes its phone number, dial status, dynamic_variables used, and call_log_id once placed.","parameters":[{"name":"batchId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { batchCall, targets }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/batch-calls/{batchId}/run":{"post":{"tags":["Batch calls"],"summary":"Start a batch","description":"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.","parameters":[{"name":"batchId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { started, ... }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhooks","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"responses":{"200":{"description":"Returns { webhooks }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"post":{"tags":["Webhooks"],"summary":"Register a webhook","description":"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).","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"description":"https URL"},"events":{"description":"('call.started' | 'call.completed' | 'call.transferred' | 'call.analyzed')[]"}},"required":["url"]}}}},"responses":{"200":{"description":"Returns { webhook }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/webhooks/{webhookId}":{"patch":{"tags":["Webhooks"],"summary":"Update a webhook","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"webhookId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"description":"string"},"events":{"description":"string[]"},"enabled":{"description":"boolean"}}}}}},"responses":{"200":{"description":"Returns { webhook }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"webhookId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { success }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/webhooks/{webhookId}/test":{"post":{"tags":["Webhooks"],"summary":"Send a test delivery","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"webhookId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { ok }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/contacts":{"get":{"tags":["Contacts"],"summary":"List contacts","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"responses":{"200":{"description":"Returns { contacts }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/crm":{"get":{"tags":["CRM"],"summary":"List CRM connections","description":"Access/refresh tokens are never returned — write-only, like API keys.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"responses":{"200":{"description":"Returns { connections: { id, provider, provider_account_id, expires_at, created_at }[] }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"delete":{"tags":["CRM"],"summary":"Disconnect a CRM","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"provider","in":"query","required":false,"description":"'hubspot' | 'salesforce'","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { success }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/crm/hubspot/connect":{"get":{"tags":["CRM"],"summary":"Start HubSpot OAuth connect","description":"Browser-navigation endpoint (not JSON) — 302s to HubSpot's authorize screen. Owner/admin only.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"}],"responses":{"200":{"description":"Returns 302 redirect"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/crm/hubspot/lookup":{"get":{"tags":["CRM"],"summary":"Look up a caller in HubSpot (CRM→us)","description":"On-demand lookup by phone for call-time personalization, e.g. injecting {{crm_company_name}} as a dynamic variable. No background sync.","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"phone","in":"query","required":false,"description":"E.164 phone number","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { variables: { crm_contact_found, crm_first_name?, crm_last_name?, crm_company_name?, crm_email?, crm_job_title? } }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/analytics":{"get":{"tags":["Analytics"],"summary":"Call analytics by day","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"days","in":"query","required":false,"description":"7 | 30 | 90","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { series, totals }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/tenants/{tenantId}/qa/overview":{"get":{"tags":["Quality"],"summary":"QA scores, resolution and transfer metrics","parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Workspace id — from GET /me"},{"name":"days","in":"query","required":false,"description":"7 | 30 | 90","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { avgScore, resolutionRate, transferSuccessRate, ... }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/agents/{agentId}/test-cases":{"get":{"tags":["Quality"],"summary":"List simulation test cases","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { testCases }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"post":{"tags":["Quality"],"summary":"Create a test case","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"description":"string"},"persona":{"description":"string"},"successCriteria":{"description":"string"}}}}}},"responses":{"200":{"description":"Returns { testCase }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/agents/{agentId}/test-cases/{testCaseId}/run":{"post":{"tags":["Quality"],"summary":"Run a simulation","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}},{"name":"testCaseId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { passed, transcript, reasoning }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}},"/agents/{agentId}/copilot/analyze":{"get":{"tags":["Quality"],"summary":"List Copilot suggestions","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { suggestions }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}},"post":{"tags":["Quality"],"summary":"Analyze recent calls for flow-edit suggestions","description":"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.","parameters":[{"name":"agentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Returns { status, callCount, summary, suggestions: { id, node_id, current_text, suggested_text, rationale, supporting_call_ids, status }[] }"},"401":{"description":"Missing or invalid credentials"},"404":{"description":"Not found, or not in your workspace"},"429":{"description":"Rate limited"}}}}}}