2026-11-06
New:default_instructions. Ready-made behaviour rules you can switch on instead of writing
them into system_prompt.
Nothing here is required. Leave the object out, or any field in it, and no error is returned: on
PATCH the rule stays as it is, and on create it takes its default. Agents that already exist have
every rule off until you turn one on, and read back with default_instructions empty or null.
2026-10-30
send_whatsapp_template is no longer part of the agent body. Set it up in the dashboard,
under the agent’s Send WhatsApp Message tool.
Each template now also carries the WhatsApp number it sends from, so one agent can send its
brochure from Sales and its booking confirmation from a branch number - and an agent with no
WhatsApp number of its own can send at all. A number is only offerable against the WhatsApp
account that holds the template, and only while it is connected, so the choice depends on live
account state that an API body cannot carry. Sending
templates replaces the whole list, so a
request that could not see senders would silently drop them.
Everything else under builtin_tools is unchanged. A PATCH that does not mention
send_whatsapp_template is unaffected; one that does is refused with
builtin_tools.send_whatsapp_template: Extra inputs are not permitted.
This supersedes the phone_number_id row below, from 2026-10-10, which said the sending number
was settled when a number was connected to the agent. It is a per-template setting now.
2026-10-20
A template with an image, video or document header can now carry a different file on an agent’s calls, instead of always sending the one file set on the template.media.mode is static for one file every time, or dynamic for a set of named files the agent
chooses between per send - so the same template can go out twice in one call with a different file
each time.
Nothing changes for an entry you already send.
media is optional, and an entry without it
attaches the file set on the template - which is what every entry did before this.2026-10-17
Agents can now tell when an outbound call reaches voicemail. Everything new is optional, so nothing you send today changes, and an agent you do not touch stays on"none".
2026-10-15
An agent can play a soft background sound on phone calls. Two new optional fields, so nothing you send today changes. An agent you do not touch stays on"none".
Sending
null resets either field to its default. Calls use whatever the agent’s version is set
to; POST /v1/calls has no per-call override. See
Background sound for all ten ids.
2026-10-10
An agent’s WhatsApp templates can now say what each one is for and where its values come from, so the agent no longer has to invent what goes inside a template.variables maps each placeholder to a {{token}}, to fixed text, or to the agent. This agent API
configuration was removed on 2026-10-30; see WhatsApp templates for current
dashboard setup.
Both removed fields were accepted for a short window and no agent was using either, so this
changes nothing that was live. If you sent
allowed_templates, send the same names as
templates entries - with no variables, the behaviour is identical.2026-09-30
Transfer destinations can take more than one call at a time.2026-09-27
Every organization is now billed in one currency, INR or USD, chosen when it is set up. Money the API returns is in that currency, and every amount already carries its currency beside it - so nothing you send changes, but code that assumed dollars needs to read the currency.Organizations that existed before this change were moved to INR. Calls and chats from before the
move keep
currency: "USD", so a list can hold both - always read the currency on each record
rather than assuming the organization’s.2026-09-25
A field-by-field review of all 53 operations. Most of it was wording that did not match what the API does, and those corrections need nothing from you. The removals below do.Fields removed
On a built-in analysis value,
sort_order was one of three fields you could change. The
other two, description and enabled, are unaffected.POST /v1/calls takes a version number
It took the version uuid and nothing else, while every other version route takes the number —
so after publishing v4 you had to look its uuid up before you could place a call on it.
agent_version_id still works. Sending both is a 400 invalid_request: they name the same
thing, so honouring one and ignoring the other would be a guess. Omit both and the agent’s live
version answers, as before.
The widget is one resource, not a collection
An agent has exactly one widget, so the id in the path could only ever be one value — one you had to fetch before you could use any of the other routes.
The read returns the object itself rather than a one-item array, and prepares the widget if it
does not exist yet — so it never 404s.
id is still on the body, which is what you want in a log
or when telling one rotation from another.
widget_not_found is no longer among the reason codes
on this surface: with no id to get wrong, there is nothing to miss.Every growing list pages the same way
Four shapes reached you before this:?skip= on agents, ?offset= on remembered callers,
cursors on calls and chats, and nothing at all on knowledge bases, versions, deploy history and
the analysis library — which returned every row your workspace held.
They are all cursors now, and all return the same envelope:
?skip= and ?offset= are gone. Both drift: a row written while you page can appear on two
pages or on none. A cursor names the boundary instead, so new rows land on page 1 and the point
you are reading from does not move. An offset also gets slower the deeper you page; a cursor
does not.GET /v1/custom-analysis also settles its envelope key. It was {"categories": […]}, briefly
{"groups": […]}, and is now data like every other list — the path rename to
/v1/custom-analysis/groups stands.
Call provenance is two fields, not three
call_type is removed. It was direction and origin fused into one compound value —
outbound_api is direction: outbound plus origin: api — so it could not be read without
knowing how it was assembled, and it published a second vocabulary for two questions those fields
already answer.
direction now publishes inbound, outbound, or web for a call that happened in a browser.
It used to return the raw stored value, so a widget call came back as widget_talk and a dashboard
test as talk_test — internal words, and two values these docs never promised. Use origin to
tell a real widget call from your own test click.
A web call’s from_number and to_number are now null, which is what this page has always
said they were. They were returning the sentinels "widget" and "talk".
The list filter follows: ?call_type=phone|web is now ?direction=inbound|outbound|web, taking
exactly the values the field publishes. An unknown one answers 400 invalid_direction.
The same change applies to the
call object in every call.* webhook, which is the same shape.Response speed takes four values, not any number
conversation.response_eagerness accepted any number from 0 to 1 and snapped it to the nearest
of four presets, so 0.6 saved, ran as Balanced, and read back as 0.6 forever. It now accepts
only the four, and anything else is refused rather than quietly rounded:
The type has not changed - it is still a number, and an agent already set to one of these four
needs nothing.
response_speed_seconds in
GET /v1/transcribers is keyed by the preset names, and
that page gives the pairing.
response_speed_seconds is null where seconds mean nothing
GET /v1/transcribers gave every model a table of
seconds per preset. On the two models that run their own end-of-turn detection those numbers
were their provider’s figures, measured on a different model — byte-identical to the other
models on that provider, for a behaviour they do not share. Those models answer when they are
confident the caller has finished, not after a fixed wait, so no number describes them.
Those rows now return null:
conversation.response_eagerness still applies on all of
them and still takes the same four values — only the prediction is withheld where there is
nothing to predict.
If you render the seconds in a picker, check for
null first. Reading .patient off it now
throws on two of the six models — where it previously returned a number that was wrong.ElevenLabs voices now appear in GET /v1/voices
GET /v1/tts-capabilities listed elevenlabs with its languages while
GET /v1/voices returned no ElevenLabs rows at all, so the
provider looked available and there was nothing to put in voice.voice_id.
Its 19 built-in voices are now published, the same ones the dashboard offers. Nothing else
changed: no other provider gained or lost a voice, and ?provider=elevenlabs filters them like
any other.
GET /v1/tts-capabilities names each provider’s model
Each provider is now {model, languages} rather than a bare language array:
voice.model on an agent is filled in from
voice.provider, and until now no endpoint said what that value meant or what else was legal.
Numbers read back in E.164
A transfer number and an agent’snumbers.inbound_whatsapp were returned in whatever spelling they
were stored in — +91 97935 79700 from a dashboard edit, Meta’s display format for WhatsApp. Both
are normalised on read, as this page has always promised.
Renamed
The webhook payload already called the call id
id, and carried a parenthetical explaining that
it “matches execution_id from POST /v1/calls”. That note is gone — there is one name now.Added
- A call now carries
agent_version— which version answered it — anddashboard_url, a deep link you can paste into a bug report. Chats already had both. GET /v1/modelsnow carriessupports_temperature.model.temperatureis accepted on every model, but only some honour it; read this before setting one.rate_per_minis also described correctly now: it is what the model contributes to a voice minute, not the price of a call.- New:
GET /v1/whatsapp/templates(removed on 2026-10-30) — the approved names forsend_whatsapp_template.allowed_templates. A typo there used to be stored without complaint and the agent then could not send.
Corrected, with nothing to change on your side
Idempotency-Keywas published as optional. The server has always refused a request without it, so a generated SDK failed on its first call.- The knowledge-base read described
kb_type: semantic, which the API never returns. It isnon_deterministic. - Call
statuswas described as four values; there are twelve, and they are now listed. Chatstatusis three, andcompletedmeans “no longer active” — including a visitor closing the tab. memory.fields[].sort_orderaside, every enum we return is now written down: KB document status, widget status and position, factsource, memory-changesource_kind, activationaction,ended_reason, and both number statuses.- A memory subject’s
last_channelclaimedphone, WhatsApp, the widget. It isvoiceorwhatsapp; widget conversations are never recorded in memory. - A version compare reported changed platform lines under
system_messages.*, a path this API stopped accepting in the previous release, and could name internal memory settings you cannot send. Both fixed. model.temperature: 0ran as0.4.transcriber.provideris derived fromtranscriber.model, as the model and both backups already were. A pair that disagreed used to save and then fail the call.transcriber.provider,model.providerandvoice.modelnow say they are read-only, which they always were.
2026-09-23
One release, four changes to the agent body. Three remove things the API could not honour; the fourth moves each platform-spoken line next to the setting that speaks it. All four are breaking — there is no window in which both shapes work, so update before you take the release.Tools are built in the dashboard
tools and pre_call_tools are no longer part of the agent body. POST /v1/agents and
PATCH /v1/agents/{agent_id} do not accept them, and no read returns them.
Nothing changes for an agent that already has tools: they are stored, they still run, and the
dashboard still edits them. A prompt that arms a tool the agent does not have is refused with
prompt_tool_reference_invalid, so build the tool on the agent’s Tools tab first, then write
a prompt that arms it.
GET /v1/integrations/cal-com/event-types went with them — its only purpose was filling a booking
tool’s cal_com.event_type_id.
A version compare never names a tool either. A difference always
names a field you can read and write here.
whatsapp and knowledge_messages are gone
Both were settings the API published but could not properly support.
whatsapp(history window, debounce, opt-in/out keywords, media policy) is per-workspace WhatsApp setup, configured in the dashboard. The number an agent answers on,numbers.inbound_whatsapp, is unchanged.knowledge_messagesdid not match what the runtime does with it — a custom line is shortened to 120 characters — so it is not published until that is settled.
Platform lines moved next to their settings
system_messages — the flat map keyed by stage — is removed. Each line the platform speaks now
lives on the feature that decides when it is spoken:
A hold check-in now sits beside the
follow_up_seconds that decides when it is spoken, and the
silence nudge beside the seconds that trigger it.
The value is unchanged — the same
{mode, prompt, text} object, with the same rules. Only where
it sits in the body has changed, so a client that reads a body, edits one line and sends the whole
thing back needs no rewrite beyond the field name. Sending system_messages now answers 422
naming it as an unknown field.Other refusals added in the same release
Values that used to save with a200 and then do nothing are now refused, with a message naming the
fix. The full list is in each field’s reference entry; the ones most likely to affect a working
integration:
- a backup (
transcriber.fallback,model.fallback,voice.fallback) that could never be used, including one on the main engine’s own provider, which used to save and read back asnull— see Backups - a
builtin_tools.transfer_call.numbers[]entry whosetimezoneis not an IANA zone, whose hours are an empty window, or whosenumberis not E.164 builtin_tools.hold_call.follow_up_secondsat or abovemax_secondsknowledge_base_idsoranalysis_group_idsnaming something this workspace does not holdvoice.emotionoutside the published list, or on a provider that cannot speak itvariables[].system_defined/.deletableset to anything other than what the read returned- a
memory.fields[]key declared twice, which used to keep the last one silently memory.fields[].enum_valueson a field that is not astring, where the extractor ignores it
422 now uses the same envelope as every other error. See
Validation errors.
Every body refuses a field it does not know
POST/PATCH /v1/agents has always answered 422 for a field it does not declare. Knowledge
bases, custom analysis, widgets and memory accepted one and ignored it, so {"nam": "Support"}
created a knowledge base called “Untitled” and said nothing. They now answer 422 naming the field,
like the agent routes.
A widget is configured, not created or deleted
Every agent has exactly one widget, prepared the first time you read it.POST /v1/agents/{id}/widgets
could therefore only ever answer 409 Widget already exists, and DELETE did not remove anything -
the next read made a fresh widget with a new public key, silently breaking the snippet already
embedded on your site. Both are removed.
What is left is the whole lifecycle: read it,
configure it, and
rotate its key when you mean to.
invalid_request and document_invalid mean different things
invalid_request was doing two jobs: a malformed request (a ?version= we cannot parse, an
If-Match that is not a revision) and a well-formed value the agent cannot use. They need different
fixes, so they now have different codes - the second is document_invalid, which already meant
exactly that for numbers. Messages are unchanged.
Every refusal carries a reason code
detail.code used to be missing on most 404s and 409s - the reason was prose only, and three
codes this page lists (version_not_found, agent_not_published, not_public_api) never actually
appeared. Every public refusal now carries one, and the
reason codes table lists them. Messages are unchanged, so
anything reading detail as text still reads the same sentence.