Skip to main content
ContactsCall tagsUser status
New Contacts, call tags and user status API endpoints

New Contacts, call tags and user status API endpoints

CloudTalk now offers many new API endpoints. From your own systems you can manage contacts (create, update, bulk upsert, search and tag them), tag your calls, and read and set your agents’ status.The older contacts, contact tags, bulks and campaigns endpoints are now under Legacy in the API reference sidebar and keep working unchanged.
Webhooks
The message.delivery_updated webhook event

Know whether your messages arrived

The new message.delivery_updated event tells you when the delivery status of a message sent from your CloudTalk number changes: queued, sent, delivered, not_delivered, failed, and seen for WhatsApp read receipts.
  • Why it failed: on not_delivered and failed, error.code gives the reason to act on (for example invalid_recipient or blocked), and error.message a sentence you can show your users.
  • Pair it with message.sent: the event carries the status change only. Match it to the message on message_id, the same value as id on message.sent.
  • Which messages: messages sent from your CloudTalk numbers. Messages you receive have no delivery status.
VoiceAgent
The calendar invite takes its e-mail from a variable

Choose where a VoiceAgent’s calendar invite goes

When a VoiceAgent books on Calendly or Cal.com, the booking skill now names the variable the calendar invite takes the caller’s e-mail from, instead of the on/off “Send the calendar invite to the caller’s e-mail” setting. It starts as contact.emails, the e-mails of the contact the call is about; an agent that had the setting on keeps sending the invite there.
  • Your own variable: set the booking skill’s invite e-mail to a variable you send in call_properties.system_prompt.variables of Initiate VoiceAgent call, for example {{customer_email}} in the dashboard for a customer_email you send, and the invite goes to the first valid e-mail in it (a single address, several separated by commas, or a list). Sending the variable alone does not change the skill: it keeps the variable it is set to. Outbound calls can then book without a CloudTalk contact.
  • Your contact.* values now reach the invite too: a non-empty contact.emails you send replaces the contact’s e-mails for the invite, as it already did for what the agent is told. ct_call_context.contact in the analysis still shows the contact.
  • Inbound calls carry only contact fields, so a variable of your own is always empty there and the agent offers the booking-link SMS instead.
  • No e-mail, no booking in the call: when the variable is empty or holds no valid address, the agent does not book an event type that needs an e-mail and sends the booking link by SMS, if it has that scenario.
The VoiceAgent reference now also lists the booking_failed scenario trigger, which the API already accepted, and says that a booking skill on Cal.com or Calendly is set up in the dashboard: the API reads it but refuses to write it.
VoiceAgent
Outbound VoiceAgent calls carry the matched contact

Outbound VoiceAgent calls know the contact they call

An outbound call started with Initiate VoiceAgent call now looks up the dialed number among your CloudTalk contacts, the same way an inbound call looks up the caller. When several contacts share the number, the first match is used. When none matches, or the lookup fails, the call goes ahead without a contact, as before.
  • Contact variables: {{contact.*}} variables in the prompt, greeting, skills and scenarios, and custom tools that reference contact fields, now receive the matched contact’s values on outbound calls. Before, they were empty. A call without a contact still gets empty values, unless you send your own (below).
  • Your own values still win: a non-empty value you send for a contact.* variable in call_properties.system_prompt.variables (as a nested "contact": {"name": …} object or a flat "contact.name" key) replaces the matched contact’s value. An empty or blank value, null, or an empty array or object does not: the contact’s value is used. Your value changes what the agent is told, what it sends to custom tools and, for the variable the booking skill takes the invite e-mail from, where the calendar invite goes. The analysis’ ct_call_context.contact stays the matched contact’s.
  • Post-call analysis: the analysis of an outbound call, including the Extract Information webhook payload, now carries the matched contact in ct_call_context.contact (id, name, emails), as it already did for inbound calls.
  • Calendar invite: when the agent books on Calendly or Cal.com with the booking skill’s invite e-mail left at contact.emails, the matched contact’s first valid e-mail address receives the invite on outbound calls too.
If your outbound prompts already use {{contact.*}} variables, check that the contact’s values are what you want the agent to say.
VoiceAgent
Name the contact of an outbound VoiceAgent call

Tell a VoiceAgent call which contact it is calling

Initiate VoiceAgent call accepts an optional contact_id: the CloudTalk contact or lead being called, as a string of digits or an integer. The call then uses exactly that contact instead of the first contact matched by the dialed number, which helps when several contacts share a number.
  • What the contact is used for: the {{contact.*}} variables, the ct_call_context.contact of the post-call analysis, and the calendar invite while the booking skill takes the invite e-mail from contact.emails. A non-empty contact.* value you send in call_properties.system_prompt.variables still wins for that variable in what the agent is told, what it sends to custom tools and, for the invite variable, where the invite goes; the analysis keeps the contact named by contact_id.
  • New errors, only when you send contact_id: 422 with errors[].message contact_not_found (location body.contact_id) when the contact does not exist in your company, belongs to another company or was deleted; 503 contact_lookup_unavailable when the contact could not be looked up. In both cases no call is queued, so a 503 is safe to retry. Requests without contact_id behave as before.
The reference now also documents what the endpoint already did: a queued call answers 200 with a session_id (not 204), a refused call answers 403 (deny or feature_not_enabled), and the 400, 403, 404 and 500 errors are application/problem+json with their codes in errors[].message: too_many_properties, property_too_large, invalid_call_number, voice_agent_not_found.500 errors now carry the fixed code internal_error instead of internal error text. Stop outbound VoiceAgent calls errors are documented as the application/problem+json it returns.
MCP
One approval per MCP action

Every MCP action runs on its first call

Tools that send, dial, or change data no longer return a preview and wait for a second call with "confirm": true. Each one acts on its first call, and your AI app’s approval prompt is the only approval, so you approve an action once instead of twice. Tools that reach a person or overwrite or delete data are marked Asks every time in the tool reference: Claude and ChatGPT ask before each call by default, unless an admin or user lets the tool run without asking.Breaking for your own code: a call that still sends confirm, or expected_digest on cloudtalk_replace_campaign_associations, is refused and does nothing. Remove the argument and call the tool once.ChatGPT workspaces: ChatGPT keeps a snapshot of a custom app’s tools from when an admin approved it, so it keeps the old confirm input and descriptions until the snapshot is refreshed. A workspace admin opens Workspace settings → Apps, picks the CloudTalk app’s ⋯ menu, then Action control → Refresh, and reviews and publishes the update. If Refresh fails, remove the app and add it again.
Webhooks
The call.completed webhook event

Receive a call’s post-call data in one webhook

The new call.completed event sends a call’s post-call data as one event: the call details, the recording, the transcript reference, Conversation Intelligence results, the data an AI Voice Agent extracted, and the Dialer disposition and survey. Subscribe to it like any other event type; you don’t need to subscribe to the individual events.
  • When: once per call, usually a few minutes after it ends: as soon as every part the call can have has arrived, or 30 minutes after the call ended, whichever comes first. Parts a call can’t have, such as voice_agent on a call no AI Voice Agent handled, are marked not_applicable instead of waited for. Data that arrives later is not added, and the event is not sent again.
  • What is included: sources gives each part’s status (ready, not_applicable, timed_out or failed), and a part that is not ready is left out.
  • Size: each delivery is at most 64 KB. Anything shortened or left out to fit is listed in truncated.
  • Not in v1: a recording URL and an insights URL. Use the API by call_id.
The individual call, Conversation Intelligence, AI Voice Agent and Dialer events are unchanged.
MCP
Sign in to the MCP with CloudTalk

Connect an AI assistant by signing in with CloudTalk

The CloudTalk MCP server now accepts OAuth sign-in next to API keys. Add https://mcp.cloudtalk.io as a custom connector in Claude (Claude.ai, Claude Desktop, Claude mobile), Claude Code, ChatGPT or Gemini, sign in with a CloudTalk admin account and approve the access: no key to create or paste, and the assistant acts as the admin who signed in. ChatGPT and Gemini can connect for the first time. API keys keep working as before. See the MCP Quickstart.
  • Only users with the Admin role can sign in.
  • An API key that the server refuses now answers 403 instead of 401. A 401 means no credentials, or an expired sign-in, and carries a WWW-Authenticate challenge that points clients to CloudTalk’s sign-in.
Webhooks
Webhooks management API

Manage webhook endpoints from the API

You can now manage webhook endpoints, their signing secrets and individual deliveries at https://api.cloudtalk.io/v1/webhooks, with the same API keys as the rest of the API. Endpoints created in the API and in the Dashboard are the same endpoints. Recovering every failed delivery since a chosen time stays in the Dashboard. See Manage webhooks with the API.
  • Endpoints: list, create, get, update and delete, plus the event types you can subscribe to. A create returns the signing secret and accepts an Idempotency-Key: retrying with the same key and the same body returns the endpoint it already made instead of creating a second one.
  • Signing secrets: read and rotate. A rotation returns the new secret and previousSecretExpiresAt: for 24 hours every delivery is signed with both secrets.
  • Deliveries: send a test event, list deliveries, get one with its payload and every attempt, and retry a delivery. Payloads are kept for 30 days.
Endpoint URLs must be https:// on a public host, and a company can have up to 50 endpoints, Dashboard ones included. Test events and retries share a limit of 10 per minute per company.
MCPDialer
Campaign statistics in the MCP

Ask your assistant how a campaign is performing

The CloudTalk MCP server has a new read-only tool, cloudtalk_get_campaign_statistics. It returns a campaign’s current contact funnel, plus dialed and answered counts, answer and conversion rates, the person / voicemail / machine split, the disposition breakdown and call durations, all-time or for a date window. It serves the same figures as Get a campaign’s statistics.
  • The Analytics profile now covers campaigns. https://mcp.cloudtalk.io/analytics includes the new tool and cloudtalk_list_campaigns, so a reporting assistant can find a campaign and read its results. The Campaigns profile has the new tool too.
  • Read-only, aggregate figures. No contact, agent or call is identified, and like every tool it only reaches your own company’s campaigns.
  • Requires the new Sales Dialer add-on, like the other campaign tools.
VoiceAgentMCP
Voice agents: provider, llm and optimized settings

Choose a voice agent’s provider and model directly

This is a breaking change to Create a voice agent, Get a voice agent and Replace a voice agent.
  • resolvedProvider and resolvedLlm are removed. Read the new provider and llm attributes instead. llm is the canonical model id, such as openai - gpt-4o-mini, not a {provider, model} object.
  • providerOverride and llmOverride are deprecated and no longer returned. Writes still accept them as aliases of provider and llm; when a request sends both, the alias wins.
  • The server no longer changes an agent’s provider or model on its own. It picks them once at create. A replace that omits provider, llm or minorVersion keeps the stored value, and linking a knowledge base leaves them as they are.
New on the voice-agent attributes:
  • provider (elevenlabs or deepgram) and llm are writable and returned on every read, as null when the agent has none set. An llm the provider does not offer is rejected with 422 and the detail code LLM_OVERRIDE_NOT_AVAILABLE_FOR_PROVIDER.
  • minorVersion is writable. A create takes 0 to the latest; a replace takes the stored value or the latest.
  • optimizedSettings (read-only) lists the values CloudTalk recommends for the agent’s current setup: isAvailable, the changes as {path, from, to}, and effects. To apply them, set each path to its to value and replace the agent.
A Deepgram agent with secondary languages is now saved instead of being rejected, as long as Deepgram serves every language; optimizedSettings then proposes ElevenLabs. Any language, primary or secondary, that the agent’s provider does not serve is rejected with 422 and the detail code LANGUAGE_NOT_SUPPORTED.The MCP voice-agent tools take provider and llm, with the old names kept as deprecated aliases. cloudtalk_update_voice_agent merges the fields you pass onto the stored agent. See MCP tools.
Dialer
Archived dispositions leave campaigns and the catalogue

Archived dispositions can no longer be used on campaign buttons

This is a behaviour change. When you remove a disposition that calls already carry, CloudTalk archives it instead of deleting it. Archived dispositions are now treated like deleted ones everywhere a disposition can be picked:Past calls keep their archived disposition’s name in statistics and exports.
Dialer
Deleted dispositions leave campaign buttons

Buttons for a deleted disposition are no longer returned

This is a behaviour change to Get a campaign’s disposition buttons.
  • A button whose call tag has been deleted is left out of the response. Previously it was returned with name, color and outcome set to null. Deleting a disposition in your account settings now removes it from every campaign, as the settings page states.
  • null resolved fields now mean the call tags could not be read. While that lasts, every button is returned with name, color and outcome set to null. color is also null for a tag that has no colour.
A campaign whose buttons all referenced deleted tags returns an empty array. Add buttons again with Replace a campaign’s disposition buttons.
DocumentationWebhooks
Webhooks documentation

Get real-time events pushed to your endpoint

CloudTalk’s account-level webhooks are documented. Register an HTTPS endpoint under Account > Webhooks and CloudTalk sends it a signed JSON POST the moment something happens on your account, so you no longer have to poll for changes. Other CloudTalk features that call an endpoint you own, such as the Call Flow Designer Webhook step or an AI Voice Agent’s Send via Webhook option, use their own delivery path and are unaffected.Webhooks are available on the Essential plan and higher, and are managed in the Dashboard under Account > Webhooks.
Dialer
A bad pagination cursor now returns 400

An undecodable pagination cursor is now a 400

This is a behaviour change, shipping in the next Dialer release. It affects every cursor-paginated Dialer list: List dialer campaigns, List a campaign’s contacts, List call scripts and List surveys.Breaking changes
  • A cursor the Dialer cannot decode is rejected instead of ignored. It now answers 400 with code: "PAGINATION.INVALID_CURSOR". Until this release such a cursor was ignored and the endpoint returned 200 with the first page — the behaviour the 2026-09-02 entry below documented, and which that entry described as deliberate.
Why it changed: a restarted first page is indistinguishable from a genuine first page. A client paging until meta.nextCursor is null therefore read page one for ever when its stored cursor was truncated or mangled, and if it stopped, it recorded a partial sync as a complete one. A 400 makes that failure visible on the request that caused it.Nothing changes for a correct paging loop. Pass meta.nextCursor back exactly as you received it and you will never see this error. An absent or empty cursor is still not an error — it means “start from the first page”.If you persist cursors between runs, treat the 400 as “my stored cursor is no longer usable”: discard it and either restart the walk from a checkpoint you control, or fail the run so an operator can recover it. The Pagination guide carries the full recipe.
Dialer
Deleted campaigns hidden by default

Deleted campaigns leave the default campaign list

This is a behaviour change, shipping in Dialer release v1.0.22. Check any code that pages through List dialer campaigns before that release rolls out.Breaking changes
  • An unfiltered campaign list no longer returns deleted campaigns. Campaigns whose status is deleted are excluded when you send no status filter. Until v1.0.22 they were included, which is what the previous entry on this page documented — that was the behaviour at the time, and v1.0.22 changes it so the list matches what Delete a dialer campaign already promises: a deleted campaign drops out of the listings while its history stays available for reporting.
Two ways to see deleted campaigns:
  • include_deleted=true — a new optional boolean query parameter, default false. Set it to true and the list returns campaigns of every status, deleted included. Only the literal strings true and false are accepted; anything else is a 400.
  • status=deleted — unchanged, and still the way to list only deleted campaigns. When status is set, include_deleted has no effect.
If your integration relied on deleted campaigns appearing in an unfiltered list, add include_deleted=true — or status=deleted if you only want the deleted ones — to those requests.Nothing else about the endpoint changed in v1.0.22: cursor, limit, status and mode behaved exactly as before, as did reading a single campaign by id. (cursor has since changed — see the 2026-09-07 entry above.)
Dialer
Dialer validation tightened

Stricter validation on the Dialer, and four corrections to these pages

These changes ship in Dialer release v1.0.21. Requests the API used to accept, or used to fail on with a 500, now come back as a clear 4xx. Check your integration against the list below before that release rolls out.Breaking changes
  • A whitespace-only name is rejected. name is now trimmed before it is validated on campaign, call-script and survey creates and updates, so " " is exactly as empty as "" and answers 400 with name should not be empty in subErrors. It previously created a resource whose name rendered blank everywhere it was shown.
  • schedule_policy.start_date and start_time are format-pinned. start_date must be a real calendar date in YYYY-MM-DD, start_time a valid HH:MM or HH:MM:SS; anything else, including a date that does not exist such as 2026-02-30, is a 400 naming the field, for example schedule_policy.start_date must be a real calendar date in YYYY-MM-DD. A 31-12-2026 typo used to be stored and silently left the campaign with no scheduled start at all.
  • Export filters are validated. On Request a campaign data export, filters.contactId and filters.userId must be 1 or greater, and filters.dateFrom / filters.dateTo must be strict ISO 8601 — otherwise 400. A window whose dateTo is not after its dateFrom now answers 422 with dateTo must be after dateFrom, and a bound that clears the strict ISO 8601 check but falls outside the published RFC 3339 date-time shape (the week date 2026-W33, for one) answers 422 with dateFrom must be a parseable ISO 8601 date-time — in both cases no job is enqueued; such requests used to be accepted and complete with a header-only file that looked like “no data”. Supplying only one bound is still valid: a missing end resolves to the time the job runs, a missing start to the campaign’s creation.
  • The unmatched-route 404 body changed. A request to a path the Dialer does not route — DELETE /v1/dialer/surveys/{surveyId}, for instance — now answers the standard error envelope with the fixed message Route not found and a correlationId. It no longer echoes an internal path or your query string back to you. Every error the Dialer service itself raises carries a correlationId, this one included — errors answered before the request reaches the service, such as the auth layer’s 401, do not.
FixesCorrections to these pagesFour things these pages claimed that the API does not do:
  • List dialer campaigns returns soft-deleted campaigns. The page said they appear only when you filter with status=deleted. They do not: an unfiltered list returns every status, deleted included. Pass status if you need them left out.
  • Policy bundles are not stored verbatim. The calling_policy description said a bundle read back may carry knobs added by a newer release. Unknown keys are dropped on write, so only the documented knobs are ever stored.
  • An unknown outcome_kind is a 400, not a 422. On Replace a campaign’s disposition buttons, enum and shape violations are 400. The 422 is for a tag_id that is not a call tag on your account; a duplicated tag_id is 409.
  • Association ids are not checked for existence. Replace a campaign’s associations accepts any ref_id for an agent, target tag or queue group without verifying that it exists — a nonexistent target tag is stored and simply contributes no contacts. Validate the ids on your side.
One clarification with no behaviour change at the time: a pagination cursor the Dialer cannot decode was not an error — the list restarted from the first page with 200. This no longer holds: as of the 2026-09-07 entry above, such a cursor is a 400. The Pagination guide documents the current contract.
DocumentationMCP
MCP documentation

Connect AI assistants to CloudTalk

The CloudTalk MCP server is documented. Connect Claude, Cursor, or any MCP-compatible client to your account and work with your calls, contacts and AI Voice Agents in natural language.
  • Overview covers what MCP is, the tool profiles, and what each one is built for.
  • Quickstart gets a client connected in five minutes.
  • Tool reference lists every tool with its parameters, defaults and limits.
MCP is available on the Essential plan and higher.
Dialer
Dialer authentication corrected

The Dialer partner API uses your standard API keys

The Dialer endpoints (https://api.cloudtalk.io/v1/dialer/*) authenticate with the same HTTP Basic credentials as the rest of the API — your API Access Key ID as the username and API Access Key Secret as the password.These pages previously documented a separate per-company x-api-key header, provisioned on request. That scheme was never deployed: requests carrying an x-api-key header are rejected with 401. If you could not authenticate against the Dialer endpoints before, use your standard key pair.Rate limiting on the Dialer surface is also per company, not per API key — a dedicated 60 requests-per-minute budget, separate from the core API’s quota.Note the Dialer is a newly released product surface: depending on your account’s rollout stage, campaigns managed through this API may not yet be visible in your CloudTalk Dashboard.
Documentation
New developer documentation

A new home for the CloudTalk API docs

These docs run on a new platform. The API reference is generated from our OpenAPI specification, so what you read here is what the API serves.What you can do now:
  • Send requests from the page. Every endpoint has a playground. Fill in your own API keys and see the real response.
  • Point your AI tools at the docs. There is an MCP server at https://developers.cloudtalk.io/mcp that lets an assistant search these pages directly. Every page also has a Copy page menu that hands the content to ChatGPT, Claude, Cursor, VS Code or Perplexity.
  • Search across every page, and a dark mode that respects your system setting.
This changelog starts here. From now on, changes to the API land as entries on this page, tagged by the part of the API they affect. Subscribe at /changelog/rss.xml.