> ## 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.

# 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.

**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`.



## OpenAPI

````yaml /api-reference/v2/openapi.json post /v2/agents/{agentId}/test
openapi: 3.0.3
info:
  title: Zavu API v2
  version: 2.0.0
  description: >-
    The second version of the Zavu API. It runs on the same engine as /v1: the
    same API keys, the same per-project rate limit, the same sending limits,
    prices and reviews. /v1 keeps working unchanged, and you can mix calls to
    both.


    **Addresses, not senders.** /v2 has no sender object. Where a message leaves
    from is named the way the recipient sees it: an E.164 number, an email
    address, `@botname` for Telegram, `@username` for Instagram, the Facebook
    Page id for Messenger, and the literal `sms_oneway` for one-way SMS (sent
    from shared numbers, never a number of your own). `GET /v2/channels` lists
    one entry per (address, type); a number that carries WhatsApp and SMS is two
    entries with the same address.


    **What else changes.** No `Zavu-Sender` header (`from` or `conversationId`
    instead); every send reports how it was routed; one project webhook carries
    every event; every error is `{code, message, details}` at the root.


    **Wrong method.** A path under `/v2/messages`, `/v2/conversations`,
    `/v2/contacts`, `/v2/channels`, `/v2/templates`, `/v2/campaigns`,
    `/v2/agents`, `/v2/webhook` or `/v2/email-domains` called with a method it
    does not take answers `405 method_not_allowed`, with `details.allowed` and
    an `Allow` header.


    **Money.** Every amount is USD, rounded to 6 decimals.
servers:
  - url: https://api.zavu.dev
security:
  - bearerAuth: []
tags:
  - name: Messages
    description: Send, list and read messages.
  - name: Conversations
    description: Inbox threads, one per contact across channels.
  - name: Contacts
    description: Contacts and the identifiers they are reached at.
  - name: Channels
    description: 'Where the project can send from: one entry per (address, type).'
  - name: Templates
    description: WhatsApp message templates.
  - name: Campaigns
    description: One message to many recipients.
  - name: Webhook
    description: The one project webhook for every event.
  - name: Agents
    description: AI agents and the addresses they answer on.
  - name: Numbers
    description: Search and buy phone numbers.
  - name: Email Domains
    description: Domains to send email from.
  - name: Functions
    description: Configure Zavu Functions.
paths:
  /v2/agents/{agentId}/test:
    post:
      tags:
        - Agents
      summary: Test an agent
      description: >-
        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`.
      operationId: testAgentV2
      parameters:
        - name: agentId
          in: path
          required: true
          description: Agent ID.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AgentTestRequest'
      responses:
        '200':
          description: The agent's reply. Nothing was delivered.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgentTestResponse'
        '400':
          description: '`message` is required, or the message and history exceed 64 KB.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '402':
          description: The agent runs on a Zavu model and the balance is at or below zero.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Agent not found, or it belongs to another project.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: >-
            Too many test runs for this project. Retry after the number of
            seconds in the message.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - bearerAuth: []
components:
  schemas:
    AgentTestRequest:
      type: object
      description: >-
        Run the agent and return what it would say. Nothing is delivered to
        anyone and no execution is recorded.


        A run is **billed like a live reply**: on an agent with the `zavu`
        provider it 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, not Zavu. `cost` on the
        response is what this run charged.
      required:
        - message
      properties:
        message:
          type: string
          description: What to say to the agent.
          example: Where is order ORD-12345?
        history:
          type: array
          description: >-
            Prior turns, oldest first, to exercise multi-turn behaviour without
            persisting a thread. Trimmed to the agent's context window.
          items:
            type: object
            required:
              - role
              - content
            properties:
              role:
                type: string
                enum:
                  - user
                  - assistant
              content:
                type: string
        useKnowledgeBase:
          type: boolean
          default: true
          description: >-
            Set false to skip retrieval and isolate prompt behaviour from the
            knowledge base.
        executeTools:
          type: boolean
          default: false
          description: >-
            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`.
    AgentTestResponse:
      type: object
      required:
        - cost
        - error
        - inputTokens
        - knowledgeChunksUsed
        - latencyMs
        - outputTokens
        - plannedToolCalls
        - success
        - text
        - warnings
      properties:
        success:
          type: boolean
        text:
          type: string
          nullable: true
          description: What the agent would reply.
        error:
          type: string
          nullable: true
        inputTokens:
          type: integer
        outputTokens:
          type: integer
        latencyMs:
          type: integer
        knowledgeChunksUsed:
          type: integer
          description: >-
            Knowledge-base chunks retrieved for this message. Zero means the
            answer was not grounded in your documents.
        executedToolCalls:
          type: array
          description: >-
            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.
          items:
            type: object
            required:
              - name
              - ok
            properties:
              name:
                type: string
              ok:
                type: boolean
              error:
                type: string
                nullable: true
        warnings:
          type: array
          items:
            type: string
          description: >-
            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:
          type: number
          description: >-
            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:
          type: array
          description: >-
            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`).
          items:
            type: object
            required:
              - name
              - arguments
            properties:
              name:
                type: string
              arguments:
                type: string
                description: The arguments as a JSON string.
    Error:
      type: object
      required:
        - code
        - message
      description: >-
        Every v2 error has this shape at the root. There is no `{error: {...}}`
        wrapper anywhere in v2. A missing resource is `404 not_found` with the
        message `<Resource> not found`; an address the project does not have is
        `404 not_found`, `None of this project's channels has the address
        <address>.`, with the address in `details` under the name the request
        used (`from` or `address`). A `message` is written for the caller and
        never carries a stack trace or a source path. A body that is not JSON is
        `400 invalid_json`, with `details.errors` (`path: []`, the parse error
        in `message`).
      properties:
        code:
          type: string
          example: invalid_request
        message:
          type: string
        details:
          type: object
          additionalProperties: true
          description: >-
            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.
          properties:
            errors:
              type: array
              items:
                type: object
                required:
                  - path
                  - message
                properties:
                  path:
                    type: array
                    items:
                      oneOf:
                        - type: string
                        - type: integer
                  message:
                    type: string
                  code:
                    type: string
                additionalProperties: true
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.