Let the avatar do things in your app — book a demo, look up an order — with every change read back and confirmed by the user first.
An action is something your app can do that the avatar may offer during a call. You declare it with a name, a description and the details it needs; the avatar collects those details in conversation, reads them back, and your handler runs only once the user says yes.
Handlers never leave the device. Only name, description and params are
sent to the server. The server decides whether an action runs; your handler
decides how.
Actions need two things, and without either one nothing reaches the avatar:
| Requirement | Where it is set |
|---|---|
| Call mode | mode: "call" in your config. Only a call has a turn to confirm in, so registering in any other mode reports a non-fatal INVALID_MODE error. |
| The Site actions grant | Site actions in the avatar's Tool calling panel on the Platform. Off by default. While it is off the SDK declares nothing and logs one warning per session. |
Use actions when the avatar should change something or fetch something your app
owns. To tell the avatar what the user is doing, use
notify() or addLiveContext() instead.
triggerAction and availableActions on useAvatar() are rig gestures
(a wave, a nod), not these actions. See
Gestures.
Every action of the default kind goes through the same steps, enforced by the server:
| Stage | What happens |
|---|---|
collecting | The avatar asks for each detail it still needs, by voice. The user can also type a value into your UI. |
review | The avatar reads back the exact details it will use and waits for a yes. |
running | The user agreed — by voice on a later turn, or by tapping Confirm in your UI. Your handler runs with those exact details. |
done | The handler returned. The avatar tells the user how it went, in the words your handler gave it. |
cancelled | The user backed out before the run. |
expired | No yes arrived within 3 user turns or 2 minutes, or a different action started and replaced this one. |
Because the user has already agreed to the exact details when the handler runs, your handler never needs to ask "are you sure?" There is no option to skip the read-back: confirmation is server policy, not a flag in the SDK.
Registers on mount and unregisters on unmount, so an action exists only while
the component that can perform it is on screen. Call it inside any component
under <AvatarProvider>.
A re-render with a new handler closure is picked up without re-declaring it.
Only a change to the schema — name, description, params, kind, ui, or
whether there is a check — sends a new declaration.
The imperative form, from useAvatar(). It returns the unregister function.
INVALID_INPUT error naming the problem, and gets an unregister that does
nothing.| Prop | Type | Default | Description |
|---|---|---|---|
nameRequired | string | No default | Matches ^[a-z][a-z0-9_]{0,63}$ — lowercase, digits and underscores. It becomes a tool name for the model, so it cannot contain dots (unlike an app-event slug). confirm_action and cancel_action are reserved. |
descriptionRequired | string | No default | What it does, written for the model. Max 300 characters. State the order the avatar should ask for details in, if it matters. |
paramsOptional | Record<string, ActionParam> | No default | The details the action needs. At most 8. Param names match ^[a-zA-Z][a-zA-Z0-9_]{0,39}$. |
kindOptional | "action" | "lookup" | "action" | "action" is read back and confirmed before it runs. "lookup" only finds something out and runs at once. See Lookups below. |
uiOptional | ActionUi | No default | What your own UI shows for this action, so the avatar can mention it. See Your own UI below. |
handlerRequired | (args, context) => ActionResult | Promise<ActionResult> | No default | Runs only after the user confirmed these exact args (a lookup: at once). A throw is reported as a failure. |
checkOptional | (param, value, args) => ActionCheckResult | Promise<ActionCheckResult> | No default | Vets the value of any param marked check: true before the read-back. Required if any param sets check: true. |
| Prop | Type | Default | Description |
|---|---|---|---|
typeRequired | ActionParamType | No default | The value's shape. See the table below. |
descriptionOptional | string | No default | Written for the model. Max 120 characters. |
requiredOptional | boolean | true | Set false for an optional detail. The handler receives only the params that have a value. |
valuesOptional | string[] | No default | enum only, and required for it: 1–20 non-empty strings. |
checkOptional | boolean | No default | Vet each value with the action's check function before it is read back. Not allowed on a lookup. |
| Type | Your handler receives |
|---|---|
string | A string. |
email | A string. The server normalizes a spoken address before it reaches you. |
phone | A string. |
date | A string, YYYY-MM-DD. |
datetime | A string, YYYY-MM-DDTHH:mm:ss. |
number | A finite number. |
boolean | true or false. |
enum | One of the strings in values. |
date and datetime are the user's local time, with no zone. The SDK sends
the device's time zone when the call starts, so "Thursday at 2" means the
user's Thursday. new Date(args.slot) already reads the value as local time.
result and error are what the avatar is told happened, so write them as plain
words — the avatar turns them into its reply. Both are capped at 1000 characters.
Before your handler runs, the SDK re-checks the args against the current declaration. Every run ends in exactly one result sent back to the server, even when something goes wrong:
| What happened | What the avatar is told |
|---|---|
| The handler threw, or returned something without ok | the action failed |
| The handler took longer than 12 seconds | it took too long — and context.signal aborts |
| The action was unregistered before the run | that action is not available right now |
| The args no longer match the declaration | the details did not match what that action needs |
| ok: false with an empty error | the action failed |
A callId runs at most once. A replayed run would be a second booking, so
the SDK ignores a duplicate.
Pass context.signal to your fetch calls. If the run times out, the request
is cancelled instead of completing after the avatar has already said it failed.
kind: "lookup" is for finding something out: open slots, an order's status, a
price. It runs at once, with no read-back and no yes.
A lookup must change nothing. The server cannot tell what your handler does,
so it trusts the kind you declare. Anything that books, buys, sends or saves
is an "action".
A lookup cannot use check or ui.review.
Mark a param check: true and give the action a check function to vet each
value as the user gives it — a slot that is still free, an email domain you
accept. A failed check drops the value, and the avatar offers your suggestions
instead.
suggestions are alternatives in the param's own type — for a datetime,
YYYY-MM-DDTHH:mm:ss strings. The SDK forwards the first 5 and the first 200
characters of reason.
A check gets 2.5 seconds. A throw or a timeout lets the value through unverified, so a broken validator can never block a booking. Keep the same check inside your handler — it is the one that counts.
Voice alone completes every action. To also show the user what is happening —
an input box for the detail the avatar is asking for, a confirm card at the
read-back, the outcome — use useAvatarActionFlow.
Pass a name to see only that action's flow, or no name to see any action's.
| Prop | Type | Default | Description |
|---|---|---|---|
flowOptional | ActionFlow | null | No default | The current flow as the server last reported it, or null. It stays at its last stage until another action starts or the session ends. |
inputOptional | (args: Record<string, unknown>) => boolean | No default | Send what the user typed or picked. Only declared params of the declared type are accepted. Starts the action if none is in progress. |
confirmOptional | () => boolean | No default | The user tapped Confirm on the details shown. Only valid during review. |
cancelOptional | () => boolean | No default | The user backed out, at any stage before the run. |
Each method returns false when nothing was sent — no live session, no action
in progress, or a value that does not match its param — and reports a non-fatal
error saying why.
| Prop | Type | Default | Description |
|---|---|---|---|
actionIdOptional | string | No default | Identifies this run of the action. |
revisionOptional | number | No default | Rises on every report. confirm() sends it back, so a card showing old details cannot agree to new ones. |
nameOptional | string | No default | The action's name. |
stageOptional | ActionFlowStage | No default | collecting, review, running, done, cancelled or expired. Ignore a stage you do not recognise — a newer server may add one. |
argsOptional | Record<string, unknown> | No default | The exact values so far, in the shapes your handler receives. |
missingOptional | string[] | No default | Params still to be collected. |
awaitingOptional | ActionAwaiting | null | No default | What the avatar is waiting for right now — your cue for which card to show. |
outcomeOptional | { ok: boolean; message: string } | No default | On done: how the handler's run went, in its own words. |
| kind | Fields | Show |
|---|---|---|
input | param, type, description?, values? | An input for that param. values lists the choices for an enum. |
check | param | A pending state: your check function is vetting the value. |
confirm | — | The details in flow.args with Confirm and Cancel. |
A newer server may send a kind not listed here. Render nothing for it.
ui tells the avatar what your screen offers, so it can say "you can also type
it". It is a hint only: input() is accepted for any declared param either way.
| Prop | Type | Default | Description |
|---|---|---|---|
inputsOptional | string[] | "*" | No default | The params your UI shows an input for, or "*" for all of them. Every name must be a declared param. |
reviewOptional | boolean | No default | Your UI shows the details with Confirm and Cancel during the read-back. Not allowed on a lookup. |
No method runs a handler directly — that would skip the user's yes. To start
an action from your own UI, send its first details with input() from a flow
hook named for that action. The avatar takes it from there: it asks for anything
missing, reads the details back, and waits for a yes.
Typed values are taken as exact. A change made during review still needs a
fresh yes.
expired.With the page grants on (see page awareness), the avatar can also press buttons and fill fields on the page it sits on. Those controls are deliberately narrower than an action:
Anything with consequences beyond the page — a booking, a purchase, an email — belongs in an action, where the details are read back first.
Actions add no events to AvatarEventMap. Problems surface through the usual
error channel, and none of them ends the
session.
| Code | When |
|---|---|
INVALID_MODE | registerAction() outside call mode. |
INVALID_INPUT | A refused definition (bad name, too many actions or params, oversized description, a bad enum, check: true with no check function), or input() / confirm() / cancel() with a value or a stage that does not fit. |
NOT_CONNECTED | input(), confirm() or cancel() with no live session. |
| Limit | Value |
|---|---|
| Actions registered at once | 8 |
| Params per action | 8 |
| Action name | ^[a-z][a-z0-9_]{0,63}$ |
| Param name | ^[a-zA-Z][a-zA-Z0-9_]{0,39}$ |
| description | 300 characters |
| Param description | 120 characters |
| enum values | 1–20 |
| result / error | 1000 characters |
| Handler time | 12 s, then the signal aborts |
| check time | 2.5 s, then the value passes unverified |
| Waiting for a yes | 3 user turns or 2 minutes |
A booking action with a slot check, and a card that follows the avatar through the flow. The card is optional — remove it and the same booking works by voice.
Turn on Site actions for this avatar in the Platform before you test. With
it off, the code above runs without errors and the avatar simply cannot book —
check the console for actions not granted for this avatar.