> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.vapi.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.vapi.ai/_mcp/server.

# Generate a simulation suite

POST https://api.vapi.ai/eval/simulation/onboarding/suite
Content-Type: application/json

Generates a simulation suite from requirements your user agreed to, saves it and assigns it to the assistant. Takes up to 3 minutes, so set a client timeout of at least 200 seconds.

Send one `idempotencyKey` per agreed draft. Retrying with the same key never generates a second suite:

* On a timeout or a 5xx (502, 504, 524), retry with the same key. You get the saved suite, a 409 while it is still generating, or a fresh generation if the first one never saved.
* On a 409 `SIMULATION_ONBOARDING_GENERATION_IN_PROGRESS`, the first request is still generating. Wait `Retry-After` seconds (15) and retry with the same key.
* On a 409 `SIMULATION_ONBOARDING_IDEMPOTENCY_KEY_REUSED`, the key was used for other requirements. Send a new key.

Then review the suite with your user, edit it with `PATCH /eval/simulation/onboarding/suite/{suiteId}`, and run it with `POST /eval/simulation/run`.

Each org can generate 20 suites per UTC day, shared with the dashboard, and 2 at a time. A retry that returns a saved suite doesn't count. On a 429 `SIMULATION_ONBOARDING_CONCURRENCY_LIMITED`, wait `Retry-After` seconds (30). On a 429 `SIMULATION_ONBOARDING_QUOTA_EXCEEDED`, don't retry: the limit resets at 00:00 UTC.

Reference: https://docs.vapi.ai/api-reference/simulations/simulation-onboarding-controller-suite-create

## Authentication

- `Authorization` header (bearer token, required) — Authenticate server-side requests with a private Vapi API key. Create or copy a key from the [Vapi Dashboard](https://dashboard.vapi.ai) and send it in the `Authorization` header as `Bearer <token>`. Keep private API keys out of client-side code and public repositories.

## Request

### Body (application/json)

This endpoint expects a SimulationOnboardingSuiteCreateDTO.

- `assistantId` (string, required) — Assistant the suite tests and is assigned to.
- `idempotencyKey` (string, required) — One key per agreed draft. Retrying with the same key returns the suite already saved, or a 409 while it is still generating, instead of generating another.
- `requirements` (list of SimulationOnboardingRequirementDTO, required) — The requirements your user agreed to, from the requirements draft. Each id at most once.

## Response

### 200

- `suite` (SimulationOnboardingSuiteRefDTO, required) — The suite.
- `assistantId` (string, required) — Assistant the suite is assigned to.
- `simulations` (list of SimulationOnboardingSuiteSimulationDTO, required) — The suite's simulations, in order.
- `coverage` (SimulationOnboardingSuiteCoverageDTO, required) — Coverage of the agreed requirements.
- `requirements` (list of SimulationOnboardingRequirementDTO, required) — The agreed requirements the suite was generated from, to name the gaps.
- `docs` (SimulationOnboardingSuiteDocsDTO, required) — Documentation to link when you summarize the suite.
- `reused` (boolean, required) — True when the request matched one already applied (same `idempotencyKey` or `patchKey`) and nothing changed.
- `failedEditSimulationIds` (list of string, optional) — Simulations an edit asked to rewrite that were not rewritten, for example because the rewrite did not generate in time. Retry them with a new `patchKey`.
- `toolGaps` (list of SimulationOnboardingToolGapDTO, optional) — Tool paths the suite's simulations do not exercise as configured, such as a tool mocked although it runs natively. Omitted when there are none.

## Errors

### 400 Bad Request Error

The body is invalid, or the generated suite uses something the org's data-region policy doesn't allow, so it isn't saved.

- `any`

### 403 Forbidden Error

The key can't create simulation suites.

- `any`

### 404 Not Found Error

The assistant wasn't found, or the org doesn't have API access to these endpoints yet.

- `any`

### 409 Conflict Error

`SIMULATION_ONBOARDING_GENERATION_IN_PROGRESS`: a request with this key is still generating; retry with the same key after `Retry-After`. `SIMULATION_ONBOARDING_IDEMPOTENCY_KEY_REUSED`: the key was used for other requirements; send a new key.

- `code` (enum, required) — Which refusal this is. Retry `SIMULATION_ONBOARDING_GENERATION_IN_PROGRESS` with the same key and `SIMULATION_ONBOARDING_CONCURRENCY_LIMITED` or `SIMULATION_ONBOARDING_QUOTA_UNAVAILABLE` after `retryAfterSeconds`, send a new key for `SIMULATION_ONBOARDING_IDEMPOTENCY_KEY_REUSED`, and stop for `SIMULATION_ONBOARDING_QUOTA_EXCEEDED`.
  - Allowed values: `SIMULATION_ONBOARDING_GENERATION_IN_PROGRESS`, `SIMULATION_ONBOARDING_IDEMPOTENCY_KEY_REUSED`, `SIMULATION_ONBOARDING_CONCURRENCY_LIMITED`, `SIMULATION_ONBOARDING_QUOTA_EXCEEDED`, `SIMULATION_ONBOARDING_QUOTA_UNAVAILABLE`
- `message` (string, required) — What happened, written for the user.
- `retryAfterSeconds` (double, optional) — Seconds to wait before retrying, the same value as the `Retry-After` header. For a daily limit, the seconds until 00:00 UTC.
- `limit` (double, optional) — The daily limit that was reached, on a daily-limit refusal only.

### 422 Unprocessable Entity Error

None of the requirements can be checked in a chat simulation, or an MCP server's tools could not be listed, so simulations would call them for real. Fix the requirements or the server and retry.

- `any`

### 429 Too Many Requests Error

`SIMULATION_ONBOARDING_CONCURRENCY_LIMITED`: the org already has 2 suites generating; retry after `Retry-After`. `SIMULATION_ONBOARDING_QUOTA_EXCEEDED`: the org generated 20 suites today; don't retry, the limit resets at 00:00 UTC.

- `code` (enum, required) — Which refusal this is. Retry `SIMULATION_ONBOARDING_GENERATION_IN_PROGRESS` with the same key and `SIMULATION_ONBOARDING_CONCURRENCY_LIMITED` or `SIMULATION_ONBOARDING_QUOTA_UNAVAILABLE` after `retryAfterSeconds`, send a new key for `SIMULATION_ONBOARDING_IDEMPOTENCY_KEY_REUSED`, and stop for `SIMULATION_ONBOARDING_QUOTA_EXCEEDED`.
  - Allowed values: `SIMULATION_ONBOARDING_GENERATION_IN_PROGRESS`, `SIMULATION_ONBOARDING_IDEMPOTENCY_KEY_REUSED`, `SIMULATION_ONBOARDING_CONCURRENCY_LIMITED`, `SIMULATION_ONBOARDING_QUOTA_EXCEEDED`, `SIMULATION_ONBOARDING_QUOTA_UNAVAILABLE`
- `message` (string, required) — What happened, written for the user.
- `retryAfterSeconds` (double, optional) — Seconds to wait before retrying, the same value as the `Retry-After` header. For a daily limit, the seconds until 00:00 UTC.
- `limit` (double, optional) — The daily limit that was reached, on a daily-limit refusal only.

### 502 Bad Gateway Error

The model failed to generate a usable result. Retry.

- `any`

### 503 Service Unavailable Error

Simulation generation is paused, no caller personalities are configured, or the quota store is unavailable for a private-key request. Quota outages return SIMULATION_ONBOARDING_QUOTA_UNAVAILABLE with Retry-After; retry with the same key.

- `SimulationOnboardingController_suiteCreateRequestServiceUnavailableError`
  - SimulationOnboardingErrorDTO: SimulationOnboardingErrorDTO
  - SimulationOnboardingControllerSuiteCreateRequestServiceUnavailableError1

### 504 Gateway Timeout Error

Generation took too long. Retry with the same `idempotencyKey`: it may still save.

- `any`

## Types

### SimulationOnboardingRequirementDTO

- `id` (string, required) — Stable requirement id, e.g. `R3`.
- `text` (string, required) — One observable behaviour the assistant must get right.
- `tag` (enum, optional) — Why this requirement needs the user's attention, when it does.
  - Allowed values: `assumption`, `not-testable`, `voice-only`
- `note` (string, optional) — What was assumed, why only a voice run can check it, or why it cannot be tested.

### SimulationOnboardingSuiteRefDTO

- `id` (string, required) — Suite id; pass it to edit or run the suite.
- `name` (string, required) — Suite name.

### SimulationOnboardingSuiteSimulationDTO

- `id` (string, required) — Simulation id; pass it back to edit or remove the simulation.
- `scenarioId` (string, required) — Scenario the simulation runs.
- `name` (string, required) — Scenario name.
- `successCriteria` (list of string, required) — Each success criterion, as the yes/no question it is judged by.
- `requirementIds` (list of string, required) — Requirement ids this simulation covers, e.g. `R3`.
- `mockedTools` (list of string, required) — Tools the scenario mocks.
- `personalityName` (string, optional) — Name of the simulated caller's personality.

### SimulationOnboardingSuiteCoverageDTO

- `covered` (double, required) — Requirements at least one simulation covers.
- `total` (double, required) — Requirements in the agreed draft.
- `gaps` (list of SimulationOnboardingCoverageGapDTO, required) — Requirements no simulation covers, and why.
- `retryable` (double, required) — Planned scenarios an edit with `retryFailed` would regenerate.

### SimulationOnboardingSuiteDocsDTO

- `bestPractices` (string, required) — Link to testing best practices.
- `toolMocks` (string, required) — Link explaining why tools are mocked by default.

### SimulationOnboardingToolGapDTO

- `toolName` (string, required) — Function name of the tool, e.g. `handoff_to_group_1` or `transferCall`.
- `reason` (enum, required) — Why the path is not exercised: a reason the simulations mock the tool, or why a native path is never reached. New reasons may be added.
  - Allowed values: `dynamic-handoff`, `destination-not-plain`, `destination-unresolved`, `destination-truncated`, `transfer-external`, `destination-name-unscoped`, `native-tool-shadowed`
- `assistantName` (string, optional) — Assistant the tool belongs to, when it is a handoff destination's tool. Unset for the assistant's own tools.
- `destinationNames` (list of string, optional) — Static destinations the simulations do not reach through this tool.

### SimulationOnboardingErrorDTO

- `code` (enum, required) — Which refusal this is. Retry `SIMULATION_ONBOARDING_GENERATION_IN_PROGRESS` with the same key and `SIMULATION_ONBOARDING_CONCURRENCY_LIMITED` or `SIMULATION_ONBOARDING_QUOTA_UNAVAILABLE` after `retryAfterSeconds`, send a new key for `SIMULATION_ONBOARDING_IDEMPOTENCY_KEY_REUSED`, and stop for `SIMULATION_ONBOARDING_QUOTA_EXCEEDED`.
  - Allowed values: `SIMULATION_ONBOARDING_GENERATION_IN_PROGRESS`, `SIMULATION_ONBOARDING_IDEMPOTENCY_KEY_REUSED`, `SIMULATION_ONBOARDING_CONCURRENCY_LIMITED`, `SIMULATION_ONBOARDING_QUOTA_EXCEEDED`, `SIMULATION_ONBOARDING_QUOTA_UNAVAILABLE`
- `message` (string, required) — What happened, written for the user.
- `retryAfterSeconds` (double, optional) — Seconds to wait before retrying, the same value as the `Retry-After` header. For a daily limit, the seconds until 00:00 UTC.
- `limit` (double, optional) — The daily limit that was reached, on a daily-limit refusal only.

### SimulationOnboardingControllerSuiteCreateRequestServiceUnavailableError1

- `statusCode` (double, required)
- `message` (string, required)
- `error` (string, required)

### SimulationOnboardingCoverageGapDTO

- `requirementId` (string, required) — Requirement the gap is for, e.g. `R4`.
- `reason` (enum, required) — Why no simulation covers it. `failed` and `budget` gaps are regenerated by an edit with `retryFailed`.
  - Allowed values: `not-testable`, `unmapped`, `failed`, `budget`
- `detail` (string, optional) — A short explanation for the user.

## Examples

**Request**

```json
{
  "assistantId": "string",
  "idempotencyKey": "string",
  "requirements": [
    {
      "id": "string",
      "text": "string"
    }
  ]
}
```

**Response**

```json
{
  "suite": {
    "id": "string",
    "name": "string"
  },
  "assistantId": "string",
  "simulations": [
    {
      "id": "string",
      "scenarioId": "string",
      "name": "string",
      "successCriteria": [
        "string"
      ],
      "requirementIds": [
        "string"
      ],
      "mockedTools": [
        "string"
      ],
      "personalityName": "string"
    }
  ],
  "coverage": {
    "covered": 1.1,
    "total": 1.1,
    "gaps": [
      {
        "requirementId": "string",
        "reason": "not-testable",
        "detail": "string"
      }
    ],
    "retryable": 1.1
  },
  "requirements": [
    {
      "id": "string",
      "text": "string",
      "tag": "assumption",
      "note": "string"
    }
  ],
  "docs": {
    "bestPractices": "string",
    "toolMocks": "string"
  },
  "reused": true,
  "failedEditSimulationIds": [
    "string"
  ],
  "toolGaps": [
    {
      "toolName": "string",
      "reason": "dynamic-handoff",
      "assistantName": "string",
      "destinationNames": [
        "string"
      ]
    }
  ]
}
```