Skip to content

Assistants

Create an assistant

The full assistant lifecycle: create, configure voice and behavior, attach knowledge and tasks, wire up webhooks, and enable SMS.

An assistant is a configured AI voice agent that answers calls (and optionally texts) on behalf of your business. This guide walks the whole lifecycle: pick the building blocks, create the assistant, tune its behavior, attach knowledge and task flows, point webhooks at your server, and toggle the SMS channel.

For endpoint-by-endpoint request/response schemas, see the Assistants API reference.

Pick a voice, model, and transcriber#

Every assistant references three lookup resources by UUID:

ResourceEndpointWhat it is
VoiceGET /voicesThe voice the assistant speaks with
ModelGET /modelsThe LLM that powers reasoning and responses
TranscriberGET /transcribersThe speech-to-text engine

All three lists support limit and offset query params, and each has a GET /{resource}/{id} detail endpoint.

bash
curl "https://core-api.heysadie.ai/voices?limit=50" \
  -H "Authorization: ApiKey YOUR_API_KEY"

curl "https://core-api.heysadie.ai/models?limit=50" \
  -H "Authorization: ApiKey YOUR_API_KEY"

curl "https://core-api.heysadie.ai/transcribers?limit=50" \
  -H "Authorization: ApiKey YOUR_API_KEY"

Keep the id of the voice, model, and transcriber you want. You'll pass them as voiceId, modelId, and transcriberId when creating the assistant.

To help you choose, each voice row carries optional language (the language codes that voice can speak with its underlying TTS model) and tags (guidance badges such as gender, accent, and personality). See Voices, Models & Transcribers. Voice display names are not unique (the same voice can appear once per underlying model), so always reference voices by id.

Create the assistant#

POST /assistants

bash
curl -X POST https://core-api.heysadie.ai/assistants \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Front Desk",
    "voiceId": "0195f1e2-...",
    "modelId": "0195f1e2-...",
    "transcriberId": "0195f1e2-...",
    "developerPrompt": "We are open {{opening_hours}}. Always ask for a callback number before ending the call.",
    "parameters": { "opening_hours": "10am to 10pm" },
    "firstMessage": "Hi! You have reached Joe'\''s Pizza. How can I help?",
    "endOfCallMessage": "Thanks for calling, and have a great day!",
    "recordingEnabled": true,
    "failoverNumber": "+15551234567",
    "failoverMode": false,
    "toolIds": [],
    "timezone": "Australia/Sydney",
    "address": "1 Example Street, Sydney",
    "phoneNumberPassThrough": true
  }'

Placeholders must match

Make sure your parameters keys match the {{placeholders}} used in developerPrompt. If the prompt says {{opening_hours}}, then parameters must include "opening_hours".

Core field guide#

FieldTypeDescription
namestringDisplay name of the assistant (e.g. "OnCore", "Alex").
voiceId / modelId / transcriberIdUUIDThe voice, LLM, and speech-to-text engine (see lookups above).
developerPromptstringYour own section injected into the default system prompt. Example: "Always ask for membership ID before confirming a reservation."
parametersobjectDynamic values like {{opening_hours}} or {{branch_name}}, inserted into the prompt.
firstMessagestringWhat the assistant says at the start of the call.
endOfCallMessagestringThe last thing the assistant says before hanging up. On updates you can clear it (empty string or null) for "no scripted goodbye".
recordingEnabledbooleanWhether calls are recorded. Defaults to true; omit it and the assistant records. Set false to keep transcripts and summaries without storing audio.
failoverNumberstringE.164 number the call is forwarded to if the assistant fails or needs to escalate. On updates, an extension may follow a comma (e.g. +15551234567,101).
failoverModebooleanIf true, calls are forwarded to the failover number.
phoneNumberIdUUIDOptional: links the assistant to a phone number you own.
toolIdsUUID[]Tools the assistant may use during a call.
timezonestringIANA timezone of the business (e.g. "Australia/Sydney").
addressstringThe business address.
phoneNumberPassThroughbooleanWhether the assistant may reuse the caller's number ("Would you like to use the number ending in 595?") rather than always asking. Defaults to true.
summaryPromptstringGuides how the assistant summarizes the call at the end.
structuredDataPromptstringWorks with categories to classify calls.
categoriesstring[]Call classification labels, e.g. ["booking", "edit", "cancel"].
promptIdUUIDThe prompt the assistant uses; omit for the default.
chatPromptIdUUID | nullOptional chat/messaging prompt (nullable so it can be unset).
whisperSummaryPromptIdUUID | nullOptional whisper-summary prompt (nullable so it can be unset).
backgroundSoundbooleanWhether the assistant plays ambient background audio on the call. Defaults to true.
serverUrlURL | nullWebhook URL override for this assistant (see below). On updates, null (or an empty string) clears it.
notificationsURL[]Up to 10 notification webhook URLs. New URLs are appended to existing ones, not replaced.
csatFeedbackbooleanEnables CSAT (customer satisfaction) feedback. Defaults to false.
transferModestringTransfer mode for escalations. One of cold-transfer, smart-summary, warm-return; anything else is a 400. Defaults to cold-transfer. See Transfers.
warmWhisperDelaySecondsintegerWarm-transfer whisper delay, 0 to 30 seconds. Defaults to 0.
transferDialTimeoutintegerTransfer dial timeout, 1 to 60 seconds.
transferSettingsobject | nullTransfer knobs: { amd_detection?: boolean, use_assistant_number?: boolean }. Its own field, not part of settings, but it merges the same way. amd_detection defaults to on. use_assistant_number defaults to off; on, the person answering a transfer sees your assistant's number instead of the customer's. See Transfers and Caller ID on a transfer. Unknown keys are rejected. null clears it.
warmReturnMessagestringWhat the assistant says when a warm-return transfer goes unanswered and the caller comes back (max 2000 characters). Stored under settings.warmReturnMessage; send "" to restore the built-in wording. Pairs with supervisor_answered: false on the end-of-call report.
callTimeLimitSecondsintegerMaximum call length, 10 to 43200 seconds.
idleMessages / idleTimeoutSeconds / idleMessageMaxSpokenCountn/aIdle-caller behavior: messages spoken after idleTimeoutSeconds (max 60) of silence, at most idleMessageMaxSpokenCount (max 10) times.
settings.conversationConfig.idleNoMessagebooleanChanges what a silence does. When true, an idle timeout re-prompts the assistant to speak for itself instead of playing one of idleMessages, so it responds in context rather than repeating a canned line.
taskDrivenModebooleanEnables task-group-driven conversation flow.
ivrSettingsobject | nullPuts a short router in front of this assistant, which transfers the caller or hands the call over mid-conversation. Its own field, not part of settings, but it merges the same way. null clears it. See IVR router mode.
settings.conversationConfigobjectConversation timing and speech delivery tuning; see below.

settings merges; it does not replace

A PATCH that sends part of settings keeps everything you did not send, at any depth. Sending settings.conversationConfig.idleNoMessage on its own leaves vadPlan, endpointingPlan and turnDetectionPlan exactly as they were. You do not need to read the object, modify it, and write the whole thing back.

Two consequences worth knowing. Nothing is removed by omission. To remove a stored value, send null for it. expressiveOptions: null drops stored delivery overrides, and null on a timing plan or on one of its fields puts it back on its default. And because merging is per-key, two people patching different parts of settings will not overwrite each other.

Three settings default to on

recordingEnabled, backgroundSound and phoneNumberPassThrough are each true when you omit them. Creating an assistant with a minimal body therefore produces one that records every call, plays ambient audio, and may read a caller's number back to them.

Recording in particular is a decision with legal weight in many jurisdictions, and it is not one the API makes conservatively on your behalf. Set recordingEnabled: false explicitly if you do not want audio stored. Transcripts, summaries and analysis are produced either way, so turning recording off does not cost you the call record. See Recording & transcripts.

Conversation timing (settings.conversationConfig)#

Each time the caller pauses, the assistant judges whether they have finished. If it thinks they have, it waits minDelay seconds of silence and then replies. If it thinks they are still mid-sentence, it waits the longer maxDelay instead. A pause that sounds finished, such as the gap between digits of a phone number, gets the short wait. That is when an assistant cuts in, and raising minDelay is the fix. vadPlan tunes voice activity detection, the step that decides when sound counts as speech.

Every plan, and every field inside one, is optional. A field you leave out uses the default below. Times are in seconds.

FieldDefaultWhat it controls
endpointingPlan.mode"dynamic"Whether the minDelay wait adapts during a call. "dynamic" lengthens it for a caller who takes longer pauses, never past maxDelay, and never shortens it below minDelay. "fixed" always uses minDelay as set. Both modes use maxDelay when the caller sounds mid-sentence.
endpointingPlan.minDelay0.5The wait after a caller who sounds finished. Raising it delays every reply by roughly the amount it exceeds vadPlan.minSilenceDuration. A few transcribers set their own default instead, so set minDelay yourself if you need to know the exact value.
endpointingPlan.maxDelay2.5The wait after a caller who sounds mid-sentence.
turnDetectionPlan.unlikelyThresholdNot setThe assistant's confidence, 0 to 1, that the caller has finished, below which it waits maxDelay. Unset, each language uses its own tuned threshold. A value here replaces them for every language.
turnDetectionPlan.timeoutn/aNo longer used. Still accepted, so existing configurations validate.
vadPlan.minSpeechDuration0.04Sound shorter than this does not count as speech.
vadPlan.minSilenceDuration0.55Silence before the caller's speech counts as ended. Values below 0.25 are raised to 0.25. It overlaps with minDelay rather than adding to it. Both count from the moment the caller stops, so a reply waits for the longer of the two.
vadPlan.prefixPaddingDuration0.35Audio kept from just before speech starts, so the first syllable is not clipped.
interruptionPlan.minDuration0.5Sustained caller speech, 0 to 5, before the caller talking over the assistant stops it.
interruptionPlan.minWords0Transcribed words, 0 to 10, required before the caller talking over the assistant counts as an interruption.

To go back to a default, send null for the whole plan or for one field. The API deletes the stored value, and the next call uses the default. conversationConfig itself does not accept null. This body resets mode, minDelay and maxDelay together:

PATCH /assistants/{id}
{
  "settings": {
    "conversationConfig": {
      "endpointingPlan": null
    }
  }
}

To reset one field and keep the others, send null for that field only, as in "endpointingPlan": { "minDelay": null }.

Speech delivery (settings.conversationConfig.deliveryMode)#

How expressive the assistant's voice sounds. One mode per assistant; the mode is read at call start, so a change applies from the next call.

ValueBehavior
"none"Plain delivery. The default. On eleven_v3 you can still write audio tags into firstMessage or the prompt yourself.
"emotion_tagging"A classifier picks at most one fitting emotion per response and injects the matching emotion tag into the speech. Supported by Cartesia Sonic 3 / 3.5 / 3.6 and ElevenLabs v3 voices; English calls only. Other voices keep their normal delivery. Walkthrough: Emotion tagging.
"expressive"The model itself steers delivery: pacing, natural disfluencies, and nonverbal sounds such as laughter. Supported by Cartesia, Fish Audio, xAI, and Inworld voices. Other voices keep their normal delivery. Walkthrough: Expressive mode.

With deliveryMode: "expressive", the optional expressiveOptions object (also under settings.conversationConfig) tunes the built-in defaults. Only the keys you set override a default; send expressiveOptions: null to clear all stored overrides.

FieldTypeDescription
disfluenciesbooleanNatural fillers ("um", "uh"). On by default; set false to opt out.
nonverbalSoundsboolean | objecttrue / false switches all nonverbal sounds at once, or pass an object of per-category booleans: laughing, breathing, sighing, crying, vocalizing, mouthSounds, reflexSounds.
pacestring"slow", "normal", or "fast". Omit to let the model pace itself.
instructionsAppendstringExtra delivery guidance appended to the built-in instructions (max 2000 characters).

Legacy flag

settings.conversationConfig.emotionTaggingEnabled (boolean) is the older opt-in for emotion tagging. It is superseded by deliveryMode but still honored when deliveryMode is absent.

Update the assistant#

PATCH /assistants/{id} applies a partial update; send only the fields you want to change. The body accepts the same fields as create.

Terminal
curl -X PATCH https://core-api.heysadie.ai/assistants/ASSISTANT_ID \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "firstMessage": "Thanks for calling Joe'\''s! What can I do for you?" }'

Attach knowledge and task flows#

Once the assistant exists, wire in its supporting resources:

  • Documents: attach knowledge-base documents with POST /assistants/{id}/documents. See Knowledge base. To narrow when lookup runs, see Control when the assistant looks up.
  • Task groups: link structured task flows with POST /assistants/{id}/taskgroups and set per-assistant task overrides. See Task groups.
  • Tools: set toolIds on the assistant (create or PATCH). See Tools.
  • Custom instructions: add up to six short behavior tweaks. See Custom instructions.
  • Capture forms: collect structured caller details. See Capture forms.

Point webhooks at your server (serverUrl)#

serverUrl is the per-assistant webhook URL override: set it (on create or PATCH) to receive call results on your own endpoint, e.g. the end-of-call report. notifications holds up to 10 additional webhook URLs. Remember that PATCHing notifications appends new URLs rather than replacing the list.

Terminal
curl -X PATCH https://core-api.heysadie.ai/assistants/ASSISTANT_ID \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "serverUrl": "https://api.example.com/sadie/webhooks" }'

URLs are validated for safety at write time: unresolvable or disallowed hosts are rejected with a validation error.

To remove the override, PATCH serverUrl to null (an empty string works too); deliveries then fall back to your tenant-level webhook URL.

Enable or disable SMS#

PATCH /assistants/{id}/sms-enabled

Terminal
curl -X PATCH https://core-api.heysadie.ai/assistants/ASSISTANT_ID/sms-enabled \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "smsEnabled": true }'
  • smsEnabled: true turns the SMS channel on; false turns it off.
  • Enabling requires a linked Twilio phone number. The number is attached to the SMS sender pool automatically (idempotent). If no Twilio number is linked, the request fails with 400 (NO_PHONE_NUMBER_LINKED / NON_TWILIO_PROVIDER) and the flag is left unchanged.
  • Disabling only clears the flag: the number intentionally stays registered for SMS so re-enabling doesn't require re-registration.
  • The response returns the assistant id and its featureFlags map; other channel flags are preserved.

SMS traffic then shows up as conversations.

Enable or disable WhatsApp#

In the dashboard: finish Sign up to WhatsApp on the assistant, then turn WhatsApp Enabled on under Messaging and save.

PATCH /assistants/{id}/whatsapp-enabled

Terminal
curl -X PATCH https://core-api.heysadie.ai/assistants/ASSISTANT_ID/whatsapp-enabled \
  -H "Authorization: ApiKey YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "whatsappEnabled": true }'
  • whatsappEnabled: true turns the WhatsApp text channel on; false turns it off.
  • Enabling requires a linked phone number that has been onboarded to WhatsApp. If no number is linked, or the number has no Meta phone-number ID, the request fails with 400 (NO_PHONE_NUMBER_LINKED / NUMBER_NOT_WHATSAPP_ONBOARDED) and the flag is left unchanged.
  • Disabling only clears the flag. WABA registration is not undone.
  • The response returns the assistant id and its featureFlags map; other channel flags are preserved.
  • OnCore answers customer-initiated text only. It does not send templates or start a conversation.

WhatsApp threads then show up as conversations.

Delete the assistant#

DELETE /assistants/{id} deletes the assistant. Attached sub-resources (documents, task groups, overrides) are managed through their own endpoints.

Terminal
curl -X DELETE https://core-api.heysadie.ai/assistants/ASSISTANT_ID \
  -H "Authorization: ApiKey YOUR_API_KEY"

Next steps#

  • Expressive mode: pace, fillers, and nonverbal sounds on Cartesia, Fish Audio, xAI, and Inworld.
  • Emotion tagging: one classifier-picked emotion per English reply on Sonic 3 / 3.5 / 3.6 and eleven_v3.
  • Prompts: reusable system prompts and global templates.
  • Phone numbers: buy a number and link it via phoneNumberId.

New to this? Voice assistants explains what it does and when to use it, without the API detail.