Generate a simulation suite with the API
The Simulations onboarding API builds a simulation suite for an assistant from its prompt, tools, and handoffs. It runs the same flow as Composer. Call these three endpoints from a script, a CI job, or a coding agent:
You then run the suite with the existing POST /eval/simulation/run endpoint.
Beta. During the beta, only organizations with beta access can call these endpoints with a private API key. Other organizations get a 404. Contact support to ask about access. Composer and the Dashboard don’t need this access.
Prerequisites
- An assistant to test. Copy its ID from its page in the Dashboard.
- A private API key in
VAPI_API_KEY. The key needs permission to create and update simulation suites. - An HTTP client with a long timeout. Suite generation can take up to 3 minutes, so set a client timeout of at least 200 seconds.
Generated simulations mock every tool in the assistant’s tool list that would reach an external system. The response lists them in mockedTools. The following can still reach live systems:
- Tools that only an assistant hook calls.
- Webhooks you set explicitly on a scenario.
- Requests to your server from tools or destinations that aren’t mocked.
- Tools you add to a simulation later, or mocks you turn off.
Simulation lifecycle server messages, including the end-of-call report, are suppressed. Suppressing them doesn’t stop the requests above. See Mock tool responses.
Generate and run a suite
Draft requirements
Send the assistant ID. The endpoint reads the assistant’s prompt, tools, and handoffs, and drafts the behaviors the assistant must get right as numbered requirements, plus a few open questions.
The response is a draft:
Review the requirements and answer the open questions. A requirement tagged assumption is based on a guess. A requirement tagged not-testable describes behavior generated simulations can’t verify, such as latency. A requirement tagged voice-only needs a voice run with a call recording. Its checks are skipped in chat runs.
Revise the draft (optional)
To change the draft, call the same endpoint again. Send the draft exactly as you received it in previousDraft, and what you want changed in feedback. The two fields must be sent together.
The new draft has a higher version and a changeNote describing what changed. Repeat until you agree with the requirements. Each revision counts toward the daily limit.
Generate the suite
Send the agreed requirements and an idempotencyKey. Use a new key for each agreed draft. When you retry that draft, send the same key again. A retry with the same key never generates a second suite. See Idempotency and retries.
The endpoint plans scenarios that cover the requirements, writes each scenario and its success criteria, mocks the tools, then saves the suite and assigns it to the assistant. The response describes the saved suite:
Check coverage.gaps for requirements no simulation covers, and toolGaps for tool paths the simulations don’t exercise. See Coverage gaps and Tool gaps.
Edit the suite (optional)
Edit the suite with a new patchKey for each edit. In one request, you can:
- Remove simulations with
removeSimulationIds. They are unlinked from the suite, not deleted. - Rewrite up to 10 simulations with
edits, each an instruction in your own words. Put every change for a simulation in one instruction. - Set
retryFailed: trueto regenerate scenarios that failed or didn’t fit. Thecoverage.retryablefield is the number of scenarios a retry would regenerate.
The response is the updated suite. If an edit could not be applied, for example because the rewrite did not generate in time, its simulation ID is listed in failedEditSimulationIds. Retry those with a new patchKey.
Run the suite
Run the suite against the assistant with POST /eval/simulation/run. Send an Idempotency-Key header so a retried request returns the run the first request created instead of starting another.
Poll GET /eval/simulation/run/<run-id> until status is ended, as in the Simulations quickstart. Run five or more iterations before you trust a pass rate, and read the failed transcripts. See Test outcomes with Simulations.
Request fields
Draft requirements
Generate a suite
Edit a suite
The suiteId in the path must be the ID of a suite generated by POST /eval/simulation/onboarding/suite (or by Composer) in your organization.
Response fields
Requirements draft
Suite
Generating and editing a suite both return this object.
Coverage gaps
A requirement with no simulation covering it appears in coverage.gaps with one of these reasons:
Tool gaps
The toolGaps array lists tool paths the simulations don’t exercise as configured. Most are mocked handoffs or transfers. Running them for real could reach your server, a phone number, or a destination that couldn’t be checked safely. A mocked handoff or transfer answers as if it succeeded, so the conversation continues, but the destination isn’t tested. New reasons may be added.
To test a mocked path, run a separate simulation against the destination assistant, or replace the mock with a sandbox integration. See Mock tool responses.
Idempotency and retries
Suite generation. Send one idempotencyKey per agreed draft, and retry with the same key:
- On a timeout or a
502,504, or524, retry with the same key. You get the saved suite, a409while it is still generating, or a fresh generation if the first one never saved. - On a
409withSIMULATION_ONBOARDING_GENERATION_IN_PROGRESS, the first request is still generating. WaitRetry-Afterseconds (15) and retry with the same key until you get the suite. - On a
409withSIMULATION_ONBOARDING_IDEMPOTENCY_KEY_REUSED, the key was already used for a different assistant or different requirements. Don’t retry with it; send a new key.
A retry that returns a saved suite sets reused to true and doesn’t count toward your limits. To generate a new suite from a revised draft, use a new key.
Edits. Send a new patchKey for each edit. To retry an edit after a timeout or a 5xx, send the same key again. If the suite already applied that key, nothing changes. You get the current suite, with failedEditSimulationIds listing what that edit couldn’t rewrite.
A suite remembers only the last 50 patch keys it applied. If you reuse one of those keys for a different edit, the new edit is ignored and you get the current suite. Once a key drops out of the last 50, a request with it can apply again. Never reuse a key for a different edit.
Drafts. Drafting has no key, so each retry requests a new draft. A request counts toward your daily limit once generation starts, even if it later fails. Requests refused before generation starts, such as a 404 or a 429, don’t count.
Limits
Each organization has a free daily allowance, shared by the API, the Dashboard, and Composer. Limits reset at 00:00 UTC.
An organization can also have at most 2 suite generations running at once.
These don’t count toward the daily limits:
- A retry that returns a saved suite, or a replayed
patchKey. - An edit that only removes simulations, or
retryFailedwhen nothing is left to retry. - A request refused before generation starts, such as a
404or a422.
A request counts once generation starts, even if it later fails with a 502 or times out.
The same limits apply to every plan, including Enterprise. No request field raises them.
When you reach a limit, the API returns 429:
Errors
The 409, 429, and quota-unavailable 503 responses carry a JSON body and a Retry-After header when waiting helps: