---
title: "Create Or Update Test Suite"
method: PUT
path: "/voice/testing/{agentId}/suite"
tags: ["Voice Agent Testing"]
---

# Create Or Update Test Suite

`PUT /voice/testing/{agentId}/suite`

## Request

- Base URL: `https://api.speakai.co/v1`
- URL: `https://api.speakai.co/v1/voice/testing/{agentId}/suite`
- Auth: API key in header `x-speakai-key` and API key in header `x-access-token`

### Update Test Suite

Requires the OWNER or ADMIN role. Upserts — creates the suite on first call, updates it on later calls. Send the full `scenarios` array you want to keep; it replaces the stored array rather than merging.

## Path parameters

- `agentId` string, required

## Request body

- object
  - `scenarios` object[]
    - `scenarioId` string
    - `name` string
    - `description` string
    - `userMessages` string[]: Required, at least one message. The scripted turns sent to the agent.
    - `criteria` object[]: Defaults to an empty array.
      - `criterionId` string
      - `name` string
      - `evaluationPrompt` string: What the LLM judge is asked to evaluate.
      - `weight` number
      - `isCritical` boolean
      - `type` 'llm_judged' | 'response_length' | 'regex_match' | 'tool_called': Defaults to llm_judged when omitted. The other three route through a deterministic code check before the LLM judge runs.
      - `maxWords` integer: For type=response_length: fails if any agent response exceeds this word count.
      - `regexPattern` string: For type=regex_match: JS regex source, no slashes.
      - `mustMatch` boolean: For type=regex_match: when true (default), at least one response must match; when false, none may.
      - `expectedToolName` string: For type=tool_called: the tool name to look for in the transcript's tool calls.
    - `category` 'greeting' | 'kb_retrieval' | 'off_topic' | 'edge_case' | 'custom'
    - `isEnabled` boolean
  - `maxCostPerRun` number
  - `autoRunOnKbUpdate` boolean
  - `autoRunOnInstructionSave` boolean
  - `scheduledCron` string, nullable

## Response `200`

OK

- object
  - `status` 'success'
  - `data` object
    - `suite` object
      - `_id` string: MongoDB document id, included because every document has one; use suiteId to identify it.
      - `suiteId` string
      - `agentId` string
      - `companyId` string
      - `scenarios` object[]
        - `scenarioId` string
        - `name` string
        - `description` string
        - `userMessages` string[]: Required, at least one message. The scripted turns sent to the agent.
        - `criteria` object[]: Defaults to an empty array.
          - `criterionId` string
          - `name` string
          - `evaluationPrompt` string: What the LLM judge is asked to evaluate.
          - `weight` number
          - `isCritical` boolean
          - `type` 'llm_judged' | 'response_length' | 'regex_match' | 'tool_called': Defaults to llm_judged when omitted. The other three route through a deterministic code check before the LLM judge runs.
          - `maxWords` integer: For type=response_length: fails if any agent response exceeds this word count.
          - `regexPattern` string: For type=regex_match: JS regex source, no slashes.
          - `mustMatch` boolean: For type=regex_match: when true (default), at least one response must match; when false, none may.
          - `expectedToolName` string: For type=tool_called: the tool name to look for in the transcript's tool calls.
        - `category` 'greeting' | 'kb_retrieval' | 'off_topic' | 'edge_case' | 'custom'
        - `isEnabled` boolean
      - `maxCostPerRun` number
      - `autoRunOnKbUpdate` boolean
      - `autoRunOnInstructionSave` boolean
      - `scheduledCron` string, nullable
      - `createdAt` string, date-time
      - `updatedAt` string, date-time

## Other responses

- `403`: Caller's role is not OWNER or ADMIN
- `404`: Agent not found

---

[API](https://skmtc.dev/speakai/apis/speak-ai-api.md) · [All operations](https://skmtc.dev/speakai/apis/speak-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/speakai/apis/speak-ai-api/revisions/52d777e6d46e?raw)
