<!-- AvatarFactory Docs · https://avatarfactory.in/docs/sdk/react-native/actions · Full map: https://avatarfactory.in/llms.txt -->

# Avatar actions

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. Only the name, description and params leave the
device — the handler stays in your app.

The API, the rules and the limits are the same as on the web. This page covers
the React Native specifics; the [React actions page](/docs/sdk/react/actions)
is the full reference.

## Before you start

| Requirement | Where it is set |
| --- | --- |
| Call mode | mode: \"call\" in your config. 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. |

> `triggerAction` and `availableActions` on `useAvatar()` are **rig gestures**,
>   not these actions.

## Registering an action

`useAvatarAction` registers on mount and unregisters on unmount, so an action
exists only while the screen that can perform it is mounted.

```tsx
import { useAvatarAction } from "@avatarfactory/react-native";

function DemoBookingAction() {
  useAvatarAction({
    name: "book_demo",
    description: "Book a 30-minute demo call. Ask for the time first, then the email.",
    params: {
      slot: { type: "datetime", description: "Demo start time" },
      email: { type: "email", description: "Where to send the invite" },
    },
    handler: async ({ slot, email }, { signal }) => {
      const response = await fetch("https://example.com/api/demo", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ slot, email }),
        signal,
      });
      return response.ok
        ? { ok: true, result: `Booked. The invite is on its way to ${email}.` }
        : { ok: false, error: "that slot was just taken" };
    },
  });
  return null;
}
```

`useAvatarAction` takes the full definition, including `kind`, `ui` and `check`. To
register outside a component's lifetime, use `registerAction` from `useAvatar()`; it
returns the function that unregisters it.

```tsx
const { registerAction } = useAvatar();

useEffect(() => {
  if (!registerAction) return;
  return registerAction({
    name: "track_order",
    description: "Look up where an order is.",
    kind: "lookup",
    params: { orderNumber: { type: "string" } },
    handler: async ({ orderNumber }) => {
      const status = await fetchOrderStatus(String(orderNumber));
      return { ok: true, result: `Order ${orderNumber} is ${status}.` };
    },
  });
}, [registerAction]);
```

The definition fields, param types and handler results are documented in full
under [ActionDefinition](/docs/sdk/react/actions#actiondefinition).

## How an action runs

| Stage | What happens |
| --- | --- |
| collecting | The avatar asks for each detail it still needs. The user can also type a value into your UI. |
| review | The avatar reads back the exact details and waits for a yes. |
| running | The user agreed — by voice on a later turn, or by tapping Confirm. Your handler runs with those exact details. |
| done | The handler returned. The avatar tells the user how it went. |
| cancelled | The user backed out before the run. |
| expired | No yes within 3 user turns or 2 minutes, or a different action replaced this one. |

There is no option to skip the read-back. A `kind: "lookup"` runs at once
without one, so a lookup must change nothing.

## Your own UI

`useAvatarActionFlow` reports each stage so you can show an input, a confirm
card, or the outcome. Voice alone completes every action, so this is optional.

```tsx
import { View, Text, Pressable } from "react-native";
import { useAvatarActionFlow } from "@avatarfactory/react-native";

function DemoBookingCard() {
  const { flow, confirm, cancel } = useAvatarActionFlow("book_demo");
  if (!flow) return null;

  if (flow.stage === "review") {
    return (
      <View>
        <Text>{new Date(String(flow.args.slot)).toLocaleString()}</Text>
        <Text>{String(flow.args.email)}</Text>
        <Pressable onPress={confirm}><Text>Confirm</Text></Pressable>
        <Pressable onPress={cancel}><Text>Cancel</Text></Pressable>
      </View>
    );
  }

  if (flow.stage === "done" && flow.outcome) {
    return <Text>{flow.outcome.message}</Text>;
  }

  return null;
}
```

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `flow` | `ActionFlow \| null` | — | The current flow as the server last reported it, or null. Read flow.stage and flow.awaiting to pick a card. |
| `input` | `(args: Record<string, unknown>) => boolean` | — | Send what the user typed or picked. Starts the action if none is in progress. |
| `confirm` | `() => boolean` | — | The user tapped Confirm during the read-back. |
| `cancel` | `() => boolean` | — | The user backed out, at any stage before the run. |

See [Your own UI](/docs/sdk/react/actions#your-own-ui) for every `ActionFlow`
field and `awaiting` kind.

## Limits

| Limit | Value |
| --- | --- |
| Actions registered at once | 8 |
| Params per action | 8 |
| Action name | ^[a-z][a-z0-9_]{0,63}$ |
| description | 300 characters |
| result / error | 1000 characters |
| Handler time | 12 s, then the signal aborts |
| check time | 2.5 s, then the value passes unverified |

Action names cannot contain dots. Problems are reported through the `error`
event as non-fatal `INVALID_MODE`, `INVALID_INPUT` or `NOT_CONNECTED` errors;
none of them ends the session.
