# Test an agent

> Run the agent's prompt, model and knowledge base against a message and return the reply instead of delivering it. Nothing is delivered and no execution is recorded.

`POST https://api.zavu.dev/v2/agents/{agentId}/test`

Run the agent's prompt, model and knowledge base against a message and return the reply instead of delivering it. Nothing is delivered and no execution is recorded.

**Billed like a live reply.** On an agent with the `zavu` provider the run is charged to your balance at the same price per token, and refused with `402 insufficient_balance` at a balance of zero; on a bring-your-own-key agent it costs your provider. `cost` on the response is what it charged. Runs are rate limited per project (`429`).

**Tools.** By default the model is offered the agent's enabled tools but none is run: if it picks one, the reply stops there and `plannedToolCalls` says which, with what arguments. Set `executeTools` to run them. That reaches your real handlers, with whatever they cost and change; read `executedToolCalls`.

## Parameters

- `agentId` (path, string, required): Agent ID.

## Body

- `message` (string, required): What to say to the agent.
- `history` (object[]): Prior turns, oldest first, to exercise multi-turn behaviour without persisting a thread. Trimmed to the agent's context window.
  - `role` (string, required) One of: `user`, `assistant`.
  - `content` (string, required)
- `useKnowledgeBase` (boolean): Set false to skip retrieval and isolate prompt behaviour from the knowledge base.
- `executeTools` (boolean): Run the tools the agent calls instead of reporting the choice and stopping. Off by default because a tool handler talks to the outside world: a rehearsal that charges a card is not a rehearsal. Turn it on to exercise the loop that actually matters — the model picks a tool, the handler answers, the model replies with the result — without sending a message to anyone. What ran comes back in `executedToolCalls`.

## Responses

### 200

The agent's reply. Nothing was delivered.

- `success` (boolean, required)
- `text` (string, required, nullable): What the agent would reply.
- `error` (string, required, nullable)
- `inputTokens` (integer, required)
- `outputTokens` (integer, required)
- `latencyMs` (integer, required)
- `knowledgeChunksUsed` (integer, required): Knowledge-base chunks retrieved for this message. Zero means the answer was not grounded in your documents.
- `executedToolCalls` (object[]): Tools that actually ran, in order, when the request set `executeTools`. Empty on a normal dry run, where nothing is executed. An entry with `ok: false` means the agent saw an error and answered around it, which is what a customer would have received.
  - `name` (string, required)
  - `ok` (boolean, required)
  - `error` (string, nullable)
- `warnings` (string[], required): Things that are true of this agent but that a dry run cannot prove. Surfaced so a passing dry run is never mistaken for proof that the agent works live. - The agent being disabled. - Enabled tools that were offered to the model but not run: if the agent picks one, the reply stops at that choice (see `plannedToolCalls`). Pass `executeTools` to run them. - Tools that ran for real, when `executeTools` was set, and any that failed. - An agent whose sender has none of the channels it triggers on, which answers every dry run and no real message. - Contact metadata that exists on a real conversation but not here, and memory tools (`remember`, `recall`, `save_record`), which have no contact to write about on a test run.
- `cost` (number, required): USD charged to your balance for this run, rounded to 6 decimals. 0 on a bring-your-own-key agent. When the balance covers only part of a run, that part is charged and the next run is refused with `402`.
- `plannedToolCalls` (object[], required): Tools the model chose to call, with the arguments it chose, on a run without `executeTools`. Nothing was executed: the reply stops at the choice. Empty when it answered without reaching for a tool, and on a run that executed tools (see `executedToolCalls`).
  - `name` (string, required)
  - `arguments` (string, required): The arguments as a JSON string.

### 400

`message` is required, or the message and history exceed 64 KB.

- `code` (string, required)
- `message` (string, required)
- `details` (object): Machine-readable context. Keys that mean the same thing everywhere: - `errors`: present on every `400 invalid_request` for an invalid body or query parameter, `[{path, message, ...}]`, `path` naming the field (`["to"]`, `["contacts", 2, "recipient"]`, `["limit"]`). - `recommendation`: one sentence saying what to do next. - `dashboardUrl`: the dashboard screen that fixes it. - `connect`: on `409 no_channel`, the channels this project can connect to reach the recipient, each `{type, request, note, available, dashboardUrl?}`; `request` is the exact call (`POST /v2/channels` with its body, or `POST /v2/numbers`), or null for a channel connected from the dashboard (WhatsApp, Instagram, Messenger). `available: false` says why in `note` (one-way SMS closed until business verification is approved). - `address` / `from`: the address the error is about, under the name the request used for it.
  - `errors` (object[])
    - `path` (object[], required)
    - `message` (string, required)
    - `code` (string)

### 401

Unauthorized.

- `code` (string, required)
- `message` (string, required)
- `details` (object): Machine-readable context. Keys that mean the same thing everywhere: - `errors`: present on every `400 invalid_request` for an invalid body or query parameter, `[{path, message, ...}]`, `path` naming the field (`["to"]`, `["contacts", 2, "recipient"]`, `["limit"]`). - `recommendation`: one sentence saying what to do next. - `dashboardUrl`: the dashboard screen that fixes it. - `connect`: on `409 no_channel`, the channels this project can connect to reach the recipient, each `{type, request, note, available, dashboardUrl?}`; `request` is the exact call (`POST /v2/channels` with its body, or `POST /v2/numbers`), or null for a channel connected from the dashboard (WhatsApp, Instagram, Messenger). `available: false` says why in `note` (one-way SMS closed until business verification is approved). - `address` / `from`: the address the error is about, under the name the request used for it.
  - `errors` (object[])
    - `path` (object[], required)
    - `message` (string, required)
    - `code` (string)

### 402

The agent runs on a Zavu model and the balance is at or below zero.

- `code` (string, required)
- `message` (string, required)
- `details` (object): Machine-readable context. Keys that mean the same thing everywhere: - `errors`: present on every `400 invalid_request` for an invalid body or query parameter, `[{path, message, ...}]`, `path` naming the field (`["to"]`, `["contacts", 2, "recipient"]`, `["limit"]`). - `recommendation`: one sentence saying what to do next. - `dashboardUrl`: the dashboard screen that fixes it. - `connect`: on `409 no_channel`, the channels this project can connect to reach the recipient, each `{type, request, note, available, dashboardUrl?}`; `request` is the exact call (`POST /v2/channels` with its body, or `POST /v2/numbers`), or null for a channel connected from the dashboard (WhatsApp, Instagram, Messenger). `available: false` says why in `note` (one-way SMS closed until business verification is approved). - `address` / `from`: the address the error is about, under the name the request used for it.
  - `errors` (object[])
    - `path` (object[], required)
    - `message` (string, required)
    - `code` (string)

### 404

Agent not found, or it belongs to another project.

- `code` (string, required)
- `message` (string, required)
- `details` (object): Machine-readable context. Keys that mean the same thing everywhere: - `errors`: present on every `400 invalid_request` for an invalid body or query parameter, `[{path, message, ...}]`, `path` naming the field (`["to"]`, `["contacts", 2, "recipient"]`, `["limit"]`). - `recommendation`: one sentence saying what to do next. - `dashboardUrl`: the dashboard screen that fixes it. - `connect`: on `409 no_channel`, the channels this project can connect to reach the recipient, each `{type, request, note, available, dashboardUrl?}`; `request` is the exact call (`POST /v2/channels` with its body, or `POST /v2/numbers`), or null for a channel connected from the dashboard (WhatsApp, Instagram, Messenger). `available: false` says why in `note` (one-way SMS closed until business verification is approved). - `address` / `from`: the address the error is about, under the name the request used for it.
  - `errors` (object[])
    - `path` (object[], required)
    - `message` (string, required)
    - `code` (string)

### 429

Too many test runs for this project. Retry after the number of seconds in the message.

- `code` (string, required)
- `message` (string, required)
- `details` (object): Machine-readable context. Keys that mean the same thing everywhere: - `errors`: present on every `400 invalid_request` for an invalid body or query parameter, `[{path, message, ...}]`, `path` naming the field (`["to"]`, `["contacts", 2, "recipient"]`, `["limit"]`). - `recommendation`: one sentence saying what to do next. - `dashboardUrl`: the dashboard screen that fixes it. - `connect`: on `409 no_channel`, the channels this project can connect to reach the recipient, each `{type, request, note, available, dashboardUrl?}`; `request` is the exact call (`POST /v2/channels` with its body, or `POST /v2/numbers`), or null for a channel connected from the dashboard (WhatsApp, Instagram, Messenger). `available: false` says why in `note` (one-way SMS closed until business verification is approved). - `address` / `from`: the address the error is about, under the name the request used for it.
  - `errors` (object[])
    - `path` (object[], required)
    - `message` (string, required)
    - `code` (string)

## Example

```bash
curl -X POST "https://api.zavu.dev/v2/agents/agent_abc123/test" \
  -H "Authorization: Bearer $ZAVUDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "message": "Where is order ORD-12345?"
}'
```

```typescript
import Zavudev from "@zavudev/sdk";

const zavu = new Zavudev({ apiKey: process.env.ZAVUDEV_API_KEY });

const result = await zavu.v2.agents.test("agent_abc123", {
  message: "Where is order ORD-12345?",
});

console.log(result);
```
