> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zavu.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Build a working WhatsApp agent in 10 minutes.

## Quickstart

We'll build a restaurant booking agent that lives on a WhatsApp sender. By the
end, customers can text the number and the agent will show the menu, check
availability, and confirm reservations.

<Info>
  You'll need an active **WhatsApp sender** in your Zavu project. If you don't
  have one yet, [connect WhatsApp](/guides/whatsapp/connect-whatsapp) first.
</Info>

<Tip>
  **Skip writing boilerplate**: install [Zavu's Coding Agent Skills](/tools/coding-agent-skills)
  in Claude Code, Cursor, Copilot, or any of 40+ supported AI coding agents.
  Your agent will then know `defineAgent`, `defineTool`, `zavu deploy`, and
  everything in this guide — just describe what you want and it generates
  the code for you.

  ```sh theme={null}
  npx skills add zavudev/zavu-skills
  ```
</Tip>

## 1. Install the CLI

<CodeGroup>
  ```sh Homebrew (macOS / Linux) theme={null}
  brew install zavudev/tools/zavu
  ```

  ```sh Standalone binary theme={null}
  # Download the binary for your platform from
  # https://github.com/zavudev/zavu-cli/releases
  # Then:
  chmod +x ./zavu-macos-arm64
  sudo mv ./zavu-macos-arm64 /usr/local/bin/zavu
  ```
</CodeGroup>

Already installed? Upgrade with `brew upgrade zavu`.

Verify:

```sh theme={null}
zavu --version
```

## 2. Log in

```sh theme={null}
zavu login
```

This opens your browser, you sign in, pick the project this agent will live in,
and click **Authorize**. The CLI saves the API key to
`~/.zavu/credentials.json` (chmod 0600).

Confirm you're on the right project:

```sh theme={null}
zavu whoami
```

```
Project:  Acme Restaurants
Project ID: jh72w2dnzytttrxjqjtaq267nn7wcw6y
Team:     Acme
Mode:     live
API URL:  https://api.zavu.dev
Key ending: …1bmmn
```

## 3. Find your sender

```sh theme={null}
zavu senders list
```

Copy the ID of the WhatsApp sender you'll attach the agent to:

```
id                                name           phone           whatsapp
jn76vnxet8g5nq661by3v06y1581bmmn  Pizzeria Main  +15076323077    yes
```

```sh theme={null}
export SENDER_ID="jn76vnxet8g5nq661by3v06y1581bmmn"
```

## 4. Scaffold the function

```sh theme={null}
zavu fn init --template restaurant-booking -y
cd reservations
```

You'll get an `index.ts` like this (truncated):

```ts theme={null}
import { defineAgent, defineTool } from "@zavu/functions"

defineAgent({
  senderId: process.env.SENDER_ID!,
  name: "Bella",
  provider: "zavu",
  model: "openai/gpt-4o-mini",
  channels: ["whatsapp"],
  prompt: `Eres Bella, anfitriona de Bella Pizzeria…`,
})

defineTool({
  name: "view_menu",
  description: "Get the restaurant menu.",
  parameters: { type: "object", properties: { filter: { type: "string" } } },
  handler: async (args) => ({ menu: [/* … */] }),
})
// + check_availability, create_reservation, view_reservation
```

<Tip>
  The template uses `provider: "zavu"` — our managed AI gateway. No BYOK required;
  LLM costs are billed from your Zavu balance.
</Tip>

## 5. Set the sender ID as a secret

```sh theme={null}
zavu fn secrets set SENDER_ID "$SENDER_ID"
```

Output:

```
✓ Created SENDER_ID (…1bmmn)
  Environment updates on the next `zavu deploy`. Run it now to apply.
```

## 6. Deploy

```sh theme={null}
zavu deploy
```

Watch the output:

```
› Deploying Restaurant reservations agent (reservations)…
  deployment id: nx7ztwe6cwg1dfp14bk6v7nb2s86hpn6
› status: bundling
› status: uploading
› status: publishing
› status: active
✓ Deployed in 14s
  Agents synced:
    + Bella
  Tools synced:
    + view_menu
    + check_availability
    + create_reservation
    + view_reservation
```

Your agent is live. The WhatsApp sender will now hand off every inbound to it.

### How the sender → function link works

You didn't run `zavu fn triggers add` anywhere — and yet, the sender knows to
forward every inbound to your function. Here's the wiring:

* `defineAgent({ senderId, ... })` registers your agent **on that sender**.
  When `zavu deploy` syncs the manifest, Zavu writes a row that says
  *"sender `$SENDER_ID` has an active agent backed by function
  `reservations`."*
* Every inbound message to that sender automatically hands off to the agent,
  which runs your tools (`view_menu`, `check_availability`, etc.) inside the
  function.
* You can verify the link any time:
  ```sh theme={null}
  zavu agents get --sender "$SENDER_ID"
  # → enabled: true, managedByFunctionId: fn_…, model: openai/gpt-4o-mini
  ```

<Note>
  **No `defineAgent`? You need triggers.** If you want a function to react to
  sender events *without* an LLM agent — say, a webhook that logs every
  delivery, or a custom non-LLM responder — declare it with `defineFunction`
  and bind it explicitly:

  ```sh theme={null}
  zavu fn triggers add --events message.inbound --senders "$SENDER_ID"
  ```

  See the [Triggers guide](/guides/functions/triggers) for the full event
  list and cartesian-binding patterns (multiple events × multiple senders).
</Note>

## 7. Try it on WhatsApp

From your phone (not the sender's number), send the sender:

> hola, qué tienen vegano?

Expected flow:

```
👤 hola, qué tienen vegano?
🤖 [calls view_menu(filter="vegan")]
🤖 Tenemos Vegan Buddha Bowl ($11). ¿Te lo reservo?

👤 sí, mañana viernes a las 9 para 2
🤖 [calls check_availability(date="friday", partySize=2)]
🤖 Tengo 19:30 y 21:00. ¿Cuál preferís?

👤 21
🤖 [calls create_reservation(date="friday", time="21:00", partySize=2, customerName="…")]
🤖 ¡Listo! Reserva RES-LXXY confirmada para 2 personas el viernes a las 21:00.
```

## 8. Watch it run

In three terminals:

```sh theme={null}
# Live tool calls (your function's console.log + handler activity)
zavu fn logs --tail
```

```sh theme={null}
# Agent executions: which tools, tokens used, cost, latency
zavu agents executions --sender "$SENDER_ID"
```

```sh theme={null}
# Every message in & out
zavu messages list --limit 10
```

## 9. Iterate

Edit `index.ts` — say, add a `cancel_reservation` tool — and redeploy:

```sh theme={null}
zavu deploy
```

The summary shows what changed:

```
✓ Deployed in 11s
  Tools synced:
    + cancel_reservation
```

The new tool is immediately available to the agent on the next user message.
You don't need to update prompts — the LLM reads the tool's `description` and
`parameters` schema directly.

## Common pitfalls

<AccordionGroup>
  <Accordion title="The agent doesn't respond on WhatsApp">
    Run `zavu agents get --sender "$SENDER_ID"` and confirm `enabled: true`.
    If false, check that `defineAgent` is being called (deploy must show
    `Agents synced: + Bella`).
  </Accordion>

  <Accordion title="Tools never get called">
    Check that the tool `description` is specific enough. The LLM uses the
    description to decide when to call the tool, so vague descriptions
    (`"do stuff"`) don't trigger. Rewrite each `description` as the answer to
    *"when should the model call this?"*.

    Also confirm `zavu agents tools list --sender "$SENDER_ID"` shows the 4
    tools with `enabled: true`.
  </Accordion>

  <Accordion title="A tool errors out">
    Watch `zavu fn logs --tail` while you trigger the tool. The error stack
    appears live. Common causes: missing env var (run
    `zavu fn secrets list` to confirm what's set), JSON parse errors on
    response, unhandled async exceptions.
  </Accordion>

  <Accordion title="My slug is too long error">
    Function names cap at 64 chars internally, and we prefix yours with
    `zavu-fn-<projectId>-` (41 chars used). Slugs over 23 chars get rejected
    server-side with a clear message. Pick something short — `bella`, not
    `restaurant-reservations-agent-v2`.
  </Accordion>

  <Accordion title="I'm using defineFunction (no agent) and nothing fires">
    Pure `defineFunction` handlers don't get traffic automatically — they
    need an explicit trigger. Run:

    ```sh theme={null}
    zavu fn triggers list                                  # see what's bound
    zavu fn triggers add --events message.inbound \
      --senders "$SENDER_ID"                               # bind to a sender
    ```

    `defineAgent` is the only declarative shortcut that auto-binds — every
    other event flow goes through triggers. See [Triggers](/guides/functions/triggers).
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Define agents in depth" icon="brain" href="/guides/functions/defining-agents">
    Providers, models, prompts, triggers.
  </Card>

  <Card title="Define tools in depth" icon="wrench" href="/guides/functions/defining-tools">
    Schemas, handlers, returning structured data.
  </Card>

  <Card title="Customer support example" icon="headset" href="/guides/functions/examples/customer-support">
    Knowledge base lookup + ticket creation.
  </Card>

  <Card title="Ecommerce example" icon="bag-shopping" href="/guides/functions/examples/ecommerce">
    Order status + smart recommendations.
  </Card>
</CardGroup>
