# GET /v1/test-runs/{test_run_id}

> Pass or fail per scenario, the conversation and the judge's reason.

In [Tests API](https://vestra.ai/developers/reference/api/tests.md): The scenarios a version must pass, drafted or written, and the runs that check it before your users see it.

Send `Authorization: Bearer vk_test_...` on every call.

Pass or fail per scenario, the conversation and the judge's reason.

Your API key, scope `setup` or `all`. On the card: [`test`](https://vestra.ai/developers/test.md). Every call here is a checked sample.

## Request

Path:

- `test_run_id` (`string`, required): The test run's id (`trun_...`).

## Answers 200

- `id` (`string`): The test run's id. Example: `trun_8f2k`.
- `object` (`"test_run"`): Always `test_run`.
- `deployment_id` (`string`): The deployment it tests.
- `version` (`integer`): The version it tests.
- `status` (`"queued" | "running" | "passed" | "failed" | "error"`): `queued` or `running` while it plays. `passed`: every scenario passed. `failed`: one or more did not. `error`: it could not finish, as `error_code` says.
- `error_code` (`string | null`): Why it ended in `error`: `interrupted` (a restart stopped it too often), `test_wallet_empty`, `judge_unreadable` (the grader's answer could not be read) or `internal`. Null otherwise.
- `scenario_count` (`integer`): How many scenarios it plays.
- `passed_count` (`integer`): How many have passed so far.
- `resumes` (`integer`): How many times a restart interrupted it and it played on from where it was, at most 2.
- `promoted` (`object | null`): What its pass promoted, for an apply that promotes after its tests; null when it promoted nothing (none asked, refused, or it did not pass).
  - `mode` (`"live" | "test"`): The mode the version went live in.
  - `version` (`integer`): The version that went live.
- `became_default` (`boolean`): True when that promotion made the deployment the company's default: an ask with no deployment now reaches it.
- `cost_usd` (`number`): What the run cost, in billed US dollars.
- `created_at` (`string`): When it was asked for, as an ISO 8601 date and time in UTC.
- `started_at` (`string | null`): When it began to play; null while queued.
- `finished_at` (`string | null`): When it ended; null while it plays.
- `results` (`object[]`, may be absent): Each scenario's result, on GET /v1/test-runs/{test_run_id}; absent in lists.
  - `object` (`"test_result"`): Always `test_result`.
  - `scenario_id` (`string | null`): The scenario it played; null when the scenario was deleted since.
  - `scenario` (`object`): The scenario as it was played.
    - `name` (`string`): Its name, 1 to 120 characters. Limits: 1 to 120 characters. Example: `Bag allowance`.
    - `persona` (`string`): Who the test user is, in a few sentences, up to 2,000 characters. Limits: 1 to 2000 characters. Example: `A first-time flyer, anxious, on a phone.`.
    - `goal` (`string`): What the test user wants, in their words, up to 2,000 characters. Limits: 1 to 2000 characters. Example: `How many kilos can I take on booking TN-48213?`.
    - `success_criteria` (`string[]`): What a pass looks like: 1 to 12 lines, each up to 500 characters. Every one must be met. Limits: 1 to 12 items. Example: `["Says 7 kg of cabin baggage."]`.
    - `must_never` (`string[]`): What must never happen: up to 12 lines, each up to 500 characters. Any one fails the test. Default: `[]`. Limits: at most 12 items. Example: `["Promises a refund."]`.
    - `tool_results` (`map of any`, may be absent): What each tool answers in this test, as any JSON, at most 20: a declared tool by its name, and a tool of an MCP server as <server>.<tool> (the server's slug), since a test never runs one for real unless the server marks it read-only. A tool with none answers an error. Example: `{"get_booking":{"cabin_kg":7}}`.
    - `approves` (`boolean`, may be absent): true: the test user says yes to each approval for a tool of one of the deployment's own MCP servers, which in a test plays its tool_results entry or does not run. Absent or false: it says no to every approval. Anything else that waits for a yes is always declined. Example: `true`.
  - `verdict` (`"passed" | "failed" | "error"`): `passed`: every line of success met and nothing it must never do. `failed`: not so. `error`: it could not be played to the end.
  - `reason` (`string`): Why, in a sentence or two.
  - `criteria` (`object[]`): Each line of success, graded.
    - `criterion` (`string`): One line of what a pass looks like.
    - `met` (`boolean`): Whether the conversation met it.
    - `evidence` (`string`): Why the grader says so, from the conversation.
  - `must_never` (`object[]`): Each thing it must never do, graded.
    - `rule` (`string`): One line of what must never happen.
    - `violated` (`boolean`): Whether it happened.
    - `evidence` (`string`): Why the grader says so, from the conversation.
  - `approvals_asked` (`object[]`): What Bash asked the test user's yes for, and what it said.
    - `kind` (`string`): The kind of ask Bash raised, such as `hands_approval`, `credit_approval` or `tool_permission`.
    - `category` (`"payment" | "account_action" | "connect_app" | "action" | "custom"`): What it would have done: a `payment`, an `account_action`, `connect_app`, another `action`, or `custom`.
    - `summary` (`string`): What it asked to do, in its words.
    - `decision` (`"denied" | "approved"`): What the test user said. It denies anything that would act for it, so a test never moves money or changes an account; a test with approves: true approves the tools of the deployment's own MCP servers, which a test plays from its tool_results or does not run.
  - `transcript` (`object[]`): The conversation, line by line.
    - `role` (`"customer" | "bash" | "note"`): Who said it: `customer` (the test user), `bash`, or a `note` on what happened (work started, a tool called).
    - `text` (`string`): What was said, or what happened.
  - `conversation_id` (`string | null`): The test conversation; null when it never started.
  - `cost_usd` (`number`): What playing it cost, in billed US dollars.

## Code

**TypeScript**

```ts
const run = await vestra.test.wait('trun_5kd2');
```

**Python**

```python
run = vestra.test.wait("trun_5kd2")
```

**CLI**

```bash
vestra events trun_5kd2
```

**HTTP**

```bash
# Draft more tests. The tests in your file are the deployment's scenarios, and Bash can draft more from your
# instructions.
curl "$VESTRA_BASE_URL/v1/deployments/dep_4kq/scenarios/generate" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "count": 3 }'

# Run the tests. This plays every scenario against the latest version, by the deployment's name. Then read
# each verdict. If a restart cuts a run off, it resumes on its own: resumes says how often, and error_code says
# why a run ended in error.
curl -X POST "$VESTRA_BASE_URL/v1/deployments/trip-rescue/test-runs" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl "$VESTRA_BASE_URL/v1/test-runs/trun_2k1" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**

```json
{
  "method": "tools/call",
  "params": {
    "name": "test",
    "arguments": { "deployment": "trip-rescue" }
  }
}
```
