# Deployments API

> How your Bash is set up, in versions: apply a whole vestra.yaml, promote, roll back, and the interview that drafts the file.

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

## `GET /v1/deployments`

Its own page: [GET /v1/deployments](https://vestra.ai/developers/reference/api/deployments/get-v1-deployments.md)

Your deployments.

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in every value.

### Request

Query:

- `limit` (`integer`): How many items per page, 1 to 100. Default 20.
- `cursor` (`string`): Where to go on from: the `next_cursor` of the page before, unchanged. Absent: the first page.

### Answers 200

- `data` (`object[]`): The items on this page.
  - `id` (`string`): The deployment's id. Example: `dep_8f2k`.
  - `object` (`"deployment"`): Always `deployment`.
  - `name` (`string`): Its name, as people read it.
  - `slug` (`string`): Its name in URLs and refs: send it where a route takes `{deployment_id}`. Example: `trip-rescue`.
  - `description` (`string`): What it is for; empty when none was given.
  - `base` (`"bash"`): What it is built on: always `bash`.
  - `status` (`"active" | "archived"`): `active`, or `archived` (put away: no new conversation or version reaches it).
  - `draft_version` (`integer`): The version test keys run. A new save moves it there; a promote with a test key moves it to the version named, older ones included.
  - `live_version` (`integer | null`): The version live keys run; null until one is promoted.
  - `created_at` (`string`): When it was made, as an ISO 8601 date and time in UTC.
  - `updated_at` (`string`): When it last changed, as an ISO 8601 date and time in UTC.
- `next_cursor` (`string | null`): Send it as `cursor` for the next page; null on the last page.

### Code

**HTTP**

```bash
curl "$VESTRA_BASE_URL/v1/deployments" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `POST /v1/deployments`

Its own page: [POST /v1/deployments](https://vestra.ai/developers/reference/api/deployments/post-v1-deployments.md)

Create one on base 'bash'. It starts with a draft version.

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in every value.

### Request

Headers:

- `Idempotency-Key` (`string`): Send the same key to retry this call safely for 24 hours.

Body:

- `name` (`string`, required): Its name, as people read it, 1 to 120 characters. Limits: 1 to 120 characters. Example: `Trip rescue`.
- `slug` (`string`): Its name in URLs and refs: lowercase letters, digits and dashes, up to 63. Absent: made from `name`. Example: `trip-rescue`.
- `description` (`string`): What it is for, up to 2,000 characters. Limits: at most 2000 characters.
- `manifest` (`map of any`): The version's settings as a manifest: `instructions`, `language`, `model`, `mcp_servers`, `mcp_tool_policy`, `apps`, `approvals`, `memory`, `notebooks`, `skills`, `tools`, `environment`, `browser` and `limits`. A field left out takes its default.
- `note` (`string`): A note on this version, up to 500 characters, such as what changed. Limits: at most 500 characters. Example: `Adds the refund tool.`.

### Answers 201

- `id` (`string`): The deployment's id. Example: `dep_8f2k`.
- `object` (`"deployment"`): Always `deployment`.
- `name` (`string`): Its name, as people read it.
- `slug` (`string`): Its name in URLs and refs: send it where a route takes `{deployment_id}`. Example: `trip-rescue`.
- `description` (`string`): What it is for; empty when none was given.
- `base` (`"bash"`): What it is built on: always `bash`.
- `status` (`"active" | "archived"`): `active`, or `archived` (put away: no new conversation or version reaches it).
- `draft_version` (`integer`): The version test keys run. A new save moves it there; a promote with a test key moves it to the version named, older ones included.
- `live_version` (`integer | null`): The version live keys run; null until one is promoted.
- `created_at` (`string`): When it was made, as an ISO 8601 date and time in UTC.
- `updated_at` (`string`): When it last changed, as an ISO 8601 date and time in UTC.

### Code

**HTTP**

```bash
curl -X POST "$VESTRA_BASE_URL/v1/deployments" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Trip rescue"
  }'
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `GET /v1/deployments/{deployment_id}`

Its own page: [GET /v1/deployments/{deployment_id}](https://vestra.ai/developers/reference/api/deployments/get-v1-deployments-deployment-id.md)

One deployment, by its id or its name: the live version and the draft. With format=yaml, its latest version as vestra.yaml.

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

### Request

Path:

- `deployment_id` (`string`, required): The deployment's id (`dep_...`). The /v1/deployments routes also take its name.

Query:

- `format` (`"json" | "yaml"`): `json` (the default): the deployment. `yaml`: its newest version, as vestra.yaml.

### Answers 200

- `id` (`string`): The deployment's id. Example: `dep_8f2k`.
- `object` (`"deployment"`): Always `deployment`.
- `name` (`string`): Its name, as people read it.
- `slug` (`string`): Its name in URLs and refs: send it where a route takes `{deployment_id}`. Example: `trip-rescue`.
- `description` (`string`): What it is for; empty when none was given.
- `base` (`"bash"`): What it is built on: always `bash`.
- `status` (`"active" | "archived"`): `active`, or `archived` (put away: no new conversation or version reaches it).
- `draft_version` (`integer`): The version test keys run. A new save moves it there; a promote with a test key moves it to the version named, older ones included.
- `live_version` (`integer | null`): The version live keys run; null until one is promoted.
- `created_at` (`string`): When it was made, as an ISO 8601 date and time in UTC.
- `updated_at` (`string`): When it last changed, as an ISO 8601 date and time in UTC.

### Code

**HTTP**

```bash
# Publish a change. Send the whole file as JSON, by its name. The first call creates the deployment, and a
# file that changes nothing makes no new version. Its tests run, and when they pass the version goes live in
# your key's mode: test traffic with a test key, real users with a live key. A test plays your own tools with
# tool_results. The first deployment you promote becomes the default, and every ask that names no deployment
# goes to it. became_default is true when this call made it the default. If the call waits for tests, that
# happens when they pass.
curl -X PUT "$VESTRA_BASE_URL/v1/deployments/trip-rescue?promote=after_tests" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "trip-rescue",
    "instructions": "You help Tripnest travellers fix broken trips. Always offer two options.",
    "apps": ["gmail", "google-calendar"],
    "tools": [{ "function": "get_booking", "description": "A booking by its PNR", "input": { "pnr": "string" }, "read_only": true }],
    "approvals": ["payment", "booking", "cancellation"],
    "tests": [{
      "name": "Bag allowance",
      "ask": "How many kilos can I take on booking TN-48213?",
      "expect": "Says 7 kg of cabin baggage.",
      "tool_results": { "get_booking": { "cabin_kg": 7 } }
    }]
  }'

# Read the file and its versions. The file comes back as YAML.
curl "$VESTRA_BASE_URL/v1/deployments/trip-rescue?format=yaml" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl "$VESTRA_BASE_URL/v1/deployments/dep_4kq/versions" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Roll back to the previous version. The version that was live before comes back at once. This needs a live key.
curl -X POST "$VESTRA_BASE_URL/v1/deployments/dep_4kq/rollback" \
  -H "Authorization: Bearer $VESTRA_LIVE_KEY"
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `PUT /v1/deployments/{deployment_id}`

Its own page: [PUT /v1/deployments/{deployment_id}](https://vestra.ai/developers/reference/api/deployments/put-v1-deployments-deployment-id.md)

Apply a whole vestra.yaml, sent as JSON, by the deployment's name: the first call makes it, and a file that changes nothing makes no version (changed: []). MCP servers are registered by URL, and the tool policy the file gives each one is this version's own, on top of the server's floor, from the moment the version runs. Tests become scenarios, and the routines and webhooks the file declares are its own. ?promote=after_tests (the default), now or no promotes in the key's mode. 400 secret_missing names the secrets to store first; 409 mcp_server_shared names the other deployments that run an MCP server whose headers the file would change.

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

### Request

Path:

- `deployment_id` (`string`, required): The deployment's id (`dep_...`). The /v1/deployments routes also take its name.

Query:

- `promote` (`"after_tests" | "now" | "no"`): When the new version goes live in the key's mode: `after_tests` (the default) once the file's tests pass, `now`, or `no`.

Body:

- `$schema` (`string`): For editors reading JSON; ignored. Limits: at most 500 characters.
- `name` (`string`, required): The deployment's name, as in `PUT /v1/deployments/{name}`: lowercase letters, digits and dashes. Example: `trip-rescue`.
- `instructions` (`string`): What Bash does here, and how, in plain words: up to 8,000 characters. Default: empty. Limits: at most 8000 characters. Example: `You help Tripnest travellers fix broken trips. Always offer two options.`.
- `language` (`string`): The language Bash speaks: a tag such as `en`, `hi` or `pt-BR`, or `auto` to follow each user. Default: `auto`. Limits: 1 to 35 characters. Example: `hi`.
- `model` (`string`): `default`, or a model id from `GET /v1/models`. Default: `default`. Limits: 1 to 100 characters. Example: `default`.
- `apps` (`"all" | string[]`): The apps its users may connect: `all`, or a list. Default: `all`. Example: `["gmail","google-calendar"]`.
- `tools` (`object[]`): Tools Bash may call: MCP servers by their URL, and functions your own code answers. At most 50. Default: `[]`. Limits: at most 50 items. Example: `[{"function":"get_booking","description":"A booking by its PNR","input":{"pnr":"string"},"read_only":true},{"mcp":"https://mcp.example.com/travel","require_approval":["book_flight"]}]`.
- `skills` (`string | (string | object)[]`): Skills Bash may use: a local folder of skills, or a list of folders, company skills by id and skills Bash has by name. At most 100. Default: `[]`. Example: `["./skills/refunds",{"name":"pdf"}]`.
- `knowledge` (`string | (string | object)[]`): What Bash reads before it answers: a local folder or file, or a list of them and notebooks by id. At most 50. Default: `[]`. Example: `./policies`.
- `approvals` (`("payment" | "booking" | "cancellation" | "send_email" | "publish" | "delete")[]`): What Bash asks the person before doing. A system approval enforces what `approvals` lists for your own functions and your MCP servers' tools that wait for a yes; in a browser or a connected app, Bash is instructed to ask. Default: `["payment","booking","cancellation"]`. Limits: at most 6 items. Example: `["payment","booking","cancellation","send_email"]`.
- `memory` (`"vestra" | "customer" | "none"`): What Bash remembers about each user: `vestra` (Bash keeps it), `customer` (your side keeps it) or `none`. Absent: the company's default. Example: `vestra`.
- `computer` (`object`): Bash's computer, ready before the first task: a setup script, repositories, environment variables and files. Example: `{"setup":"./setup.sh","env":{"REGION":"ap-south-1"}}`.
  - `setup` (`string`): A setup script, run once on each computer. Name a local file: the CLI and the SDKs read it and send its text. Limits: at most 20000 characters. Example: `./setup.sh`.
  - `repos` (`(string | object)[]`): Repositories cloned onto the computer, at most 10. Limits: at most 10 items. Example: `["github.com/acme/booking-tools"]`.
  - `env` (`map of string`): Environment variables. A credential is `${NAME}`, never a raw value. Example: `{"REGION":"ap-south-1","BOOKING_TOKEN":"${BOOKING_TOKEN}"}`.
  - `files` (`object[]`): Files uploaded with `POST /v1/files`, placed on the computer. Limits: at most 50 items. Example: `[{"file_id":"file_8f2k","path":"data/fares.csv"}]`.
    - `file_id` (`string`, required): The file's `file_` id.
    - `path` (`string`, required): Where it goes on the computer. Limits: 1 to 200 characters.
- `browser` (`object`): The sites a browser fetch may open, and the pages the browser never opens or acts on. Absent: a fetch opens nothing, and no page is closed. Example: `{"allowed_domains":["ixigo.com"],"blocked_paths":["ixigo.com/payments/"]}`.
  - `allowed_domains` (`string[]`): The sites it may open, as bare host names: `ixigo.com` also allows `www.ixigo.com`. Up to 20. Limits: 1 to 20 items.
  - `blocked_paths` (`string[]`): Pages the browser never opens or acts on, as a site and the start of a path, such as `ixigo.com/payments/`. Up to 20. Limits: 1 to 20 items.
- `routines` (`object[]`): Jobs Bash runs on a schedule or on a call, for your company or for each user. At most 50. Default: `[]`. Limits: at most 50 items. Example: `[{"ask":"Check today's trips for delays","every":"weekday at 08:00","for":"each customer"}]`.
  - `name` (`string`): Its name, which finds the routine again on the next apply. Absent: the start of `ask`. Limits: 1 to 200 characters.
  - `ask` (`string`, required): What Bash does on each run, in plain words. Limits: 1 to 8000 characters.
  - `every` (`string`): When it runs, in plain words: `weekday at 08:00`, `mon, wed, fri at 18:30`, `30 minutes`, `month on the 1st at 09:00`. Give one of `every`, `schedule` or `on`. Limits: 1 to 100 characters.
  - `schedule` (`object`): When it runs, as cron.
    - `cron` (`string`, required): A cron expression: `0 7 * * *` runs at 7:00 every day. Limits: 1 to 120 characters.
    - `timezone` (`string`): The zone the cron is read in, an IANA name such as `Asia/Kolkata`. Limits: 1 to 64 characters.
  - `on` (`"call"`): `call`: it runs whenever your system posts to its `trigger_url`.
  - `filters` (`map of any`): With `on: call`, what a call's payload must match for the routine to run.
  - `timezone` (`string`): The zone `every` is read in, an IANA name. Absent: each user's own (or your company's own Bash's), then UTC. Limits: 1 to 64 characters.
  - `for` (`"us" | "each customer"`, required): `us`: your company's own Bash. `each customer`: a copy for every user, made the first time they talk to Bash here.
  - `enabled` (`boolean`): false keeps the routine but stops its runs. A routine for each user is on while it is in the file.
- `limits` (`object`): What one task may spend. Default: `{"per_task_usd":2}`. Example: `{"per_task_usd":5}`.
  - `per_task_usd` (`number`): The most one task may spend, in dollars: above 0, at most 100. Absent: 2. Default: `2`. Limits: above 0, at most 100.
- `tests` (`object[]`): Tests each new version runs before it is promoted. At most 20. Default: `[]`. Limits: at most 20 items. Example: `[{"name":"Bag allowance","ask":"How many kilos can I take on booking TN-48213?","expect":"Says 7 kg of cabin baggage.","tool_results":{"get_booking":{"cabin_kg":7}}}]`.
  - `name` (`string`): Its name, which finds the test again on the next apply. Absent: the start of `ask`. Limits: 1 to 120 characters.
  - `ask` (`string`, required): What the test user wants, in their words. Limits: 1 to 2000 characters.
  - `expect` (`string | string[]`, required): What a pass looks like: one line, or up to 12.
  - `never` (`string | string[]`): What must never happen: one line, or up to 12.
  - `persona` (`string`): Who the test user is. Absent: a person who uses the company's app. Limits: 1 to 2000 characters.
  - `tool_results` (`map of any`): What each tool answers in this test: a function tool by its name, and a tool of an MCP server as <server>.<tool> (the server's slug, from its URL's host), since a test never runs one for real unless the server marks it read-only. A tool with none answers an error.
  - `approves` (`boolean`): true: the test user says yes to each approval for a tool of your MCP server, which plays its `tool_results` entry or does not run, so the test can check what Bash does after a yes. Absent or false: it says no to every approval. Anything else that waits for a yes is always declined.
- `webhooks` (`map of (string | "*")[]`): Where events go: each https URL, with the events it gets, or `*` for every one. Default: `{}`. Example: `{"https://example.com/vestra":["task.completed","interaction.created"]}`.

### Answers 200 or 201

- `object` (`"deployment_apply"`): Always `deployment_apply`.
- `deployment` (`object`): The deployment, after the apply.
  - `id` (`string`): The deployment's id. Example: `dep_8f2k`.
  - `object` (`"deployment"`): Always `deployment`.
  - `name` (`string`): Its name, as people read it.
  - `slug` (`string`): Its name in URLs and refs: send it where a route takes `{deployment_id}`. Example: `trip-rescue`.
  - `description` (`string`): What it is for; empty when none was given.
  - `base` (`"bash"`): What it is built on: always `bash`.
  - `status` (`"active" | "archived"`): `active`, or `archived` (put away: no new conversation or version reaches it).
  - `draft_version` (`integer`): The version test keys run. A new save moves it there; a promote with a test key moves it to the version named, older ones included.
  - `live_version` (`integer | null`): The version live keys run; null until one is promoted.
  - `created_at` (`string`): When it was made, as an ISO 8601 date and time in UTC.
  - `updated_at` (`string`): When it last changed, as an ISO 8601 date and time in UTC.
- `version` (`integer`): The version this file is: new, or the latest when nothing changed.
- `changed` (`string[]`): The file's fields that differ from the version before, in the file's order; empty when nothing changed. Example: `["instructions","tools"]`.
- `tests` (`object | null`): The test run this apply started; null when it started none.
  - `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.
    - `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.
    - `must_never` (`object[]`): Each thing it must never do, graded.
    - `approvals_asked` (`object[]`): What Bash asked the test user's yes for, and what it said.
    - `transcript` (`object[]`): The conversation, line by line.
    - `conversation_id` (`string | null`): The test conversation; null when it never started.
    - `cost_usd` (`number`): What playing it cost, in billed US dollars.
- `promote` (`object`): Whether, and when, the version goes live.
  - `when` (`"after_tests" | "now" | "no"`): When it was asked to go live: `after_tests`, `now` or `no`.
  - `mode` (`"live" | "test"`): The key's mode: `live` moves `live_version`, `test` moves `draft_version`.
  - `status` (`"promoted" | "already" | "waiting" | "not_promoted"`): `promoted`: this call moved it. `already`: that mode already runs this version. `waiting`: its tests run first. `not_promoted`: see `code`.
  - `code` (`string | null`): Why it was not promoted, as an error code; null when you sent `promote=no`. After `promoted` or `already`: the first thing in the file that could not be made in this mode, such as a missing secret. Fix it and apply the file again.
  - `message` (`string | null`): The same, in a sentence a person can read; null with no `code`.
- `new_webhooks` (`object[]`): The webhook endpoints this call made, each with its signing secret.
  - `id` (`string`): The webhook endpoint's id.
  - `url` (`string`): Where its events go.
  - `secret` (`string`): Its signing secret, shown here once: store it to check each delivery.
- `became_default` (`boolean`): True when this made the deployment the company's default, which it had none of: an ask that names no deployment now reaches it. A promotion that waits for its tests sets it when they pass.

### Code

**TypeScript**

```ts
const applied = await vestra.deploy('vestra.yaml');
```

**Python**

```python
applied = vestra.deploy("vestra.yaml")
```

**CLI**

```bash
vestra deploy
```

**HTTP**

```bash
# Publish a change. Send the whole file as JSON, by its name. The first call creates the deployment, and a
# file that changes nothing makes no new version. Its tests run, and when they pass the version goes live in
# your key's mode: test traffic with a test key, real users with a live key. A test plays your own tools with
# tool_results. The first deployment you promote becomes the default, and every ask that names no deployment
# goes to it. became_default is true when this call made it the default. If the call waits for tests, that
# happens when they pass.
curl -X PUT "$VESTRA_BASE_URL/v1/deployments/trip-rescue?promote=after_tests" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "trip-rescue",
    "instructions": "You help Tripnest travellers fix broken trips. Always offer two options.",
    "apps": ["gmail", "google-calendar"],
    "tools": [{ "function": "get_booking", "description": "A booking by its PNR", "input": { "pnr": "string" }, "read_only": true }],
    "approvals": ["payment", "booking", "cancellation"],
    "tests": [{
      "name": "Bag allowance",
      "ask": "How many kilos can I take on booking TN-48213?",
      "expect": "Says 7 kg of cabin baggage.",
      "tool_results": { "get_booking": { "cabin_kg": 7 } }
    }]
  }'

# Read the file and its versions. The file comes back as YAML.
curl "$VESTRA_BASE_URL/v1/deployments/trip-rescue?format=yaml" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl "$VESTRA_BASE_URL/v1/deployments/dep_4kq/versions" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Roll back to the previous version. The version that was live before comes back at once. This needs a live key.
curl -X POST "$VESTRA_BASE_URL/v1/deployments/dep_4kq/rollback" \
  -H "Authorization: Bearer $VESTRA_LIVE_KEY"
```

**MCP**: No sample in your language for this route, so here it is over HTTP.

## `PATCH /v1/deployments/{deployment_id}`

Its own page: [PATCH /v1/deployments/{deployment_id}](https://vestra.ai/developers/reference/api/deployments/patch-v1-deployments-deployment-id.md)

Rename, describe or archive it.

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in each {…} and every value.

### Request

Path:

- `deployment_id` (`string`, required): The deployment's id (`dep_...`). The /v1/deployments routes also take its name.

Body:

- `name` (`string`): A new name, 1 to 120 characters. Limits: 1 to 120 characters.
- `description` (`string`): A new description, up to 2,000 characters. Limits: at most 2000 characters.
- `status` (`"active" | "archived"`): `archived` puts it away: no new conversation or version reaches it. `active` brings it back.

### Answers 200

- `id` (`string`): The deployment's id. Example: `dep_8f2k`.
- `object` (`"deployment"`): Always `deployment`.
- `name` (`string`): Its name, as people read it.
- `slug` (`string`): Its name in URLs and refs: send it where a route takes `{deployment_id}`. Example: `trip-rescue`.
- `description` (`string`): What it is for; empty when none was given.
- `base` (`"bash"`): What it is built on: always `bash`.
- `status` (`"active" | "archived"`): `active`, or `archived` (put away: no new conversation or version reaches it).
- `draft_version` (`integer`): The version test keys run. A new save moves it there; a promote with a test key moves it to the version named, older ones included.
- `live_version` (`integer | null`): The version live keys run; null until one is promoted.
- `created_at` (`string`): When it was made, as an ISO 8601 date and time in UTC.
- `updated_at` (`string`): When it last changed, as an ISO 8601 date and time in UTC.

### Code

**HTTP**

```bash
curl -X PATCH "$VESTRA_BASE_URL/v1/deployments/{deployment_id}" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `POST /v1/deployments/{deployment_id}/versions`

Its own page: [POST /v1/deployments/{deployment_id}/versions](https://vestra.ai/developers/reference/api/deployments/post-v1-deployments-deployment-id-versions.md)

Save a new version: instructions, language, MCP servers and this version's own tool policy for them (mcp_tool_policy, on top of each server's floor), allowed apps, approval rules, model, memory mode, notebooks, limits per task.

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in each {…} and every value.

### Request

Path:

- `deployment_id` (`string`, required): The deployment's id (`dep_...`). The /v1/deployments routes also take its name.

Headers:

- `Idempotency-Key` (`string`): Send the same key to retry this call safely for 24 hours.

Body:

- `manifest` (`map of any`, required): The version's settings as a manifest: `instructions`, `language`, `model`, `mcp_servers`, `mcp_tool_policy`, `apps`, `approvals`, `memory`, `notebooks`, `skills`, `tools`, `environment`, `browser` and `limits`. A field left out takes its default.
- `note` (`string`): A note on this version, up to 500 characters, such as what changed. Limits: at most 500 characters. Example: `Adds the refund tool.`.

### Answers 201

- `object` (`"deployment_version"`): Always `deployment_version`.
- `deployment_id` (`string`): The deployment's id. Example: `dep_8f2k`.
- `version` (`integer`): A version number: 1 is the first, and each save adds one.
- `manifest` (`object`): The version's settings, every default filled in.
  - `base` (`"bash"`): What the deployment is built on: always `bash`. Default: `bash`.
  - `instructions` (`string`): What Bash does here, and how, in plain words, up to 8,000 characters. Empty by default. Default: empty. Limits: at most 8000 characters.
  - `language` (`string`): The language Bash speaks: a tag such as `en`, `hi` or `pt-BR`, or `auto` (the default) to follow each user. Default: `auto`.
  - `mcp_servers` (`string[]`): The MCP servers Bash may call, by their `mcp_` ids (POST /v1/mcp-servers). Default: `[]`.
  - `mcp_tool_policy` (`object[]`, may be absent): This version's own tool policy for its MCP servers, on top of each server's floor. Absent (every version saved before it): each server's own policy alone. Limits: at most 50 items.
    - `server` (`string`): The MCP server, by its `mcp_` id, one `mcp_servers` lists.
    - `deny` (`string[]`): Tools of the server this version never calls, on top of the server's own floor. Default: `[]`. Limits: at most 200 items.
    - `require_approval` (`string[]`): Tools of the server that wait for the person's yes in this version, on top of the server's own floor. Default: `[]`. Limits: at most 200 items.
    - `run_without_asking` (`string[]`, may be absent): Tools of the server this version runs without waiting for the person's yes. Every other tool of the server waits for it, unless the server marks the tool read-only (readOnlyHint). Limits: at most 200 items.
  - `apps` (`object`): Which apps its users may connect. Default: `{"allow":"all"}`.
    - `allow` (`"all" | string[]`): The apps its users may connect: `all` (the default), or a list such as `["gmail"]`. Default: `all`.
  - `approvals` (`object`): What Bash asks the person before doing. Default: `{"always":["payment","booking","cancellation"]}`.
    - `always` (`("payment" | "booking" | "cancellation" | "send_email" | "publish" | "delete")[]`): What Bash asks the person before doing: any of `payment`, `booking`, `cancellation`, `send_email`, `publish` and `delete`. Default the first three. A system approval enforces what `approvals` lists for your own functions and your MCP servers' tools that wait for a yes; in a browser or a connected app, Bash is instructed to ask. Default: `["payment","booking","cancellation"]`.
  - `model` (`string`): `default` (the default), or a model id from GET /v1/models. Default: `default`.
  - `memory` (`object`): What Bash remembers about each user here. Default: `{}`.
    - `mode` (`"vestra" | "customer" | "none"`, may be absent): `vestra`, `customer` or `none`. Absent: the company's `memory_mode`.
  - `notebooks` (`string[]`): What Bash reads before it answers: notebooks, by their `nb_` ids. Default: `[]`.
  - `skills` (`string[]`): Skills Bash may use: company skills by their `skl_` ids, or skills Bash already has, by name. Default: `[]`.
  - `tools` (`object[]`): Tools your own code answers: Bash's call reaches you as an interaction of type `tool_call`, after the person's yes unless the tool is marked `read_only` or `run_without_asking`. Default: `[]`.
    - `name` (`string`): The tool's name: lowercase letters, digits and underscores, starting with a letter, up to 64. Example: `get_booking`.
    - `description` (`string`): What the tool does, so Bash knows when to call it, up to 1024 characters. Limits: 1 to 1024 characters. Example: `A booking by its PNR`.
    - `input_schema` (`map of any`): Its arguments, as JSON Schema with `type: object`. Bash's call fills them in. Example: `{"type":"object","properties":{"pnr":{"type":"string"}},"required":["pnr"]}`.
    - `timeout_s` (`integer`): Seconds Bash waits for your answer, 5 to 600. Default 120; after it, the call expires. Default: `120`. Limits: 5 to 600.
    - `read_only` (`boolean`, may be absent): true: the tool changes nothing (a lookup), so Bash calls it without waiting for the person's yes. Absent: false.
    - `run_without_asking` (`boolean`, may be absent): true: the tool changes something that needs no yes, so Bash calls it without waiting for the person's yes. Absent: false.
  - `environment` (`object`, may be absent): Bash's computer, ready before the first task: a setup script, environment variables, repositories and files. Absent: a plain computer.
    - `setup_script` (`string`, may be absent): A shell script run once on each computer before its first task, and again when the environment changes, up to 20,000 characters. Absent: none. Limits: at most 20000 characters.
    - `env` (`map of string`): Environment variables on the computer, by name. A credential is `${secret:NAME}`, filled in from your secrets, never a raw value. Default: `{}`. Example: `{"REGION":"ap-south-1"}`.
    - `repos` (`object[]`): Repositories cloned onto the computer. Default: `[]`.
    - `files` (`object[]`): Files placed on the computer. Default: `[]`.
  - `browser` (`object`, may be absent): The sites a browser fetch may open, and the pages the browser never opens or acts on. Absent: a fetch opens nothing, and no page is closed.
    - `allowed_domains` (`string[]`, may be absent): The sites a browser fetch may open, as bare host names such as `ixigo.com`, which also allows `www.ixigo.com`. Up to 20. Absent: a browser fetch opens nothing. Limits: 1 to 20 items. Example: `["ixigo.com"]`.
    - `blocked_paths` (`string[]`, may be absent): Pages the browser never opens or acts on, as a site and the start of a path, such as `ixigo.com/payments/`. Enforced in code for a browser fetch (403 page_blocked) and Bash's own browser tools. Up to 20. Limits: 1 to 20 items. Example: `["ixigo.com/payments/"]`.
  - `routines` (`object[]`): Not used: a version holds no routines. Write them in vestra.yaml, or make them with POST /v1/routines. Default: `[]`.
    - `name` (`string`): The routine's name.
    - `schedule` (`string`): When it runs.
    - `message` (`string`): What it asks.
  - `limits` (`object`): What one piece of work may spend. There is no time limit. Default: `{"spend_per_task_usd":2}`.
    - `spend_per_task_usd` (`number`): The most one piece of work may spend, in US dollars, above 0 and at most 100. Default 2. Default: `2`. Limits: above 0, at most 100.
- `manifest_sha256` (`string`): A hash of the settings: two versions with the same hash run the same.
- `skill_pins` (`object[]`): The company skills this version runs, each at the version of the skill current when it was saved.
  - `skill` (`string`): The company skill, by its `skl_` id.
  - `slug` (`string`): The skill's name.
  - `pressing_sha256` (`string | null`): The content hash of the skill's version when this version was saved: the one it runs. Null when none was recorded.
- `note` (`string | null`): The note it was saved with; null when none.
- `created_by` (`string`): Who saved it: a key's id (`key_...`), `console` (a person in the console), `client` (a session token from your app) or `interview` (the draft the interview wrote).
- `created_at` (`string`): When it was saved, as an ISO 8601 date and time in UTC.
- `is_draft` (`boolean`): True when test keys run this version.
- `is_live` (`boolean`): True when live keys run it.
- `test_verdict` (`"passed" | "failed" | "none"`, may be absent): Its latest test run's verdict: `passed`, `failed`, or `none` (never tested). Only when you read one version (`GET .../versions/{version}`); absent in the list.
- `interview` (`object`, may be absent): The interview's closing words, on the version the interview saved; absent on every other version.
  - `summary` (`string`): What the draft sets Bash up to do, in the interview's words.
  - `tools` (`object[]`): The tools the file declares that your own code answers.
    - `name` (`string`): The tool's name.
    - `description` (`string`): What it does.
  - `per_task_usd` (`number`): The spending cap per piece of work, in US dollars.
  - `per_task_usd_is_default` (`boolean`): True when that cap is the default, 2, because the file left it.
  - `tests` (`integer`): How many starter tests the file holds.
  - `next_steps` (`string[]`): What to do next, one plain line each, in order.

### Code

**HTTP**

```bash
curl -X POST "$VESTRA_BASE_URL/v1/deployments/{deployment_id}/versions" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "manifest": {}
  }'
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `GET /v1/deployments/{deployment_id}/versions`

Its own page: [GET /v1/deployments/{deployment_id}/versions](https://vestra.ai/developers/reference/api/deployments/get-v1-deployments-deployment-id-versions.md)

Every version.

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

### Request

Path:

- `deployment_id` (`string`, required): The deployment's id (`dep_...`). The /v1/deployments routes also take its name.

Query:

- `limit` (`integer`): How many items per page, 1 to 100. Default 20.
- `cursor` (`string`): Where to go on from: the `next_cursor` of the page before, unchanged. Absent: the first page.

### Answers 200

- `data` (`object[]`): The items on this page.
  - `object` (`"deployment_version"`): Always `deployment_version`.
  - `deployment_id` (`string`): The deployment's id. Example: `dep_8f2k`.
  - `version` (`integer`): A version number: 1 is the first, and each save adds one.
  - `manifest` (`object`): The version's settings, every default filled in.
    - `base` (`"bash"`): What the deployment is built on: always `bash`. Default: `bash`.
    - `instructions` (`string`): What Bash does here, and how, in plain words, up to 8,000 characters. Empty by default. Default: empty. Limits: at most 8000 characters.
    - `language` (`string`): The language Bash speaks: a tag such as `en`, `hi` or `pt-BR`, or `auto` (the default) to follow each user. Default: `auto`.
    - `mcp_servers` (`string[]`): The MCP servers Bash may call, by their `mcp_` ids (POST /v1/mcp-servers). Default: `[]`.
    - `mcp_tool_policy` (`object[]`, may be absent): This version's own tool policy for its MCP servers, on top of each server's floor. Absent (every version saved before it): each server's own policy alone. Limits: at most 50 items.
    - `apps` (`object`): Which apps its users may connect. Default: `{"allow":"all"}`.
    - `approvals` (`object`): What Bash asks the person before doing. Default: `{"always":["payment","booking","cancellation"]}`.
    - `model` (`string`): `default` (the default), or a model id from GET /v1/models. Default: `default`.
    - `memory` (`object`): What Bash remembers about each user here. Default: `{}`.
    - `notebooks` (`string[]`): What Bash reads before it answers: notebooks, by their `nb_` ids. Default: `[]`.
    - `skills` (`string[]`): Skills Bash may use: company skills by their `skl_` ids, or skills Bash already has, by name. Default: `[]`.
    - `tools` (`object[]`): Tools your own code answers: Bash's call reaches you as an interaction of type `tool_call`, after the person's yes unless the tool is marked `read_only` or `run_without_asking`. Default: `[]`.
    - `environment` (`object`, may be absent): Bash's computer, ready before the first task: a setup script, environment variables, repositories and files. Absent: a plain computer.
    - `browser` (`object`, may be absent): The sites a browser fetch may open, and the pages the browser never opens or acts on. Absent: a fetch opens nothing, and no page is closed.
    - `routines` (`object[]`): Not used: a version holds no routines. Write them in vestra.yaml, or make them with POST /v1/routines. Default: `[]`.
    - `limits` (`object`): What one piece of work may spend. There is no time limit. Default: `{"spend_per_task_usd":2}`.
  - `manifest_sha256` (`string`): A hash of the settings: two versions with the same hash run the same.
  - `skill_pins` (`object[]`): The company skills this version runs, each at the version of the skill current when it was saved.
    - `skill` (`string`): The company skill, by its `skl_` id.
    - `slug` (`string`): The skill's name.
    - `pressing_sha256` (`string | null`): The content hash of the skill's version when this version was saved: the one it runs. Null when none was recorded.
  - `note` (`string | null`): The note it was saved with; null when none.
  - `created_by` (`string`): Who saved it: a key's id (`key_...`), `console` (a person in the console), `client` (a session token from your app) or `interview` (the draft the interview wrote).
  - `created_at` (`string`): When it was saved, as an ISO 8601 date and time in UTC.
  - `is_draft` (`boolean`): True when test keys run this version.
  - `is_live` (`boolean`): True when live keys run it.
  - `test_verdict` (`"passed" | "failed" | "none"`, may be absent): Its latest test run's verdict: `passed`, `failed`, or `none` (never tested). Only when you read one version (`GET .../versions/{version}`); absent in the list.
  - `interview` (`object`, may be absent): The interview's closing words, on the version the interview saved; absent on every other version.
    - `summary` (`string`): What the draft sets Bash up to do, in the interview's words.
    - `tools` (`object[]`): The tools the file declares that your own code answers.
    - `per_task_usd` (`number`): The spending cap per piece of work, in US dollars.
    - `per_task_usd_is_default` (`boolean`): True when that cap is the default, 2, because the file left it.
    - `tests` (`integer`): How many starter tests the file holds.
    - `next_steps` (`string[]`): What to do next, one plain line each, in order.
- `next_cursor` (`string | null`): Send it as `cursor` for the next page; null on the last page.

### Code

**HTTP**

```bash
# Publish a change. Send the whole file as JSON, by its name. The first call creates the deployment, and a
# file that changes nothing makes no new version. Its tests run, and when they pass the version goes live in
# your key's mode: test traffic with a test key, real users with a live key. A test plays your own tools with
# tool_results. The first deployment you promote becomes the default, and every ask that names no deployment
# goes to it. became_default is true when this call made it the default. If the call waits for tests, that
# happens when they pass.
curl -X PUT "$VESTRA_BASE_URL/v1/deployments/trip-rescue?promote=after_tests" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "trip-rescue",
    "instructions": "You help Tripnest travellers fix broken trips. Always offer two options.",
    "apps": ["gmail", "google-calendar"],
    "tools": [{ "function": "get_booking", "description": "A booking by its PNR", "input": { "pnr": "string" }, "read_only": true }],
    "approvals": ["payment", "booking", "cancellation"],
    "tests": [{
      "name": "Bag allowance",
      "ask": "How many kilos can I take on booking TN-48213?",
      "expect": "Says 7 kg of cabin baggage.",
      "tool_results": { "get_booking": { "cabin_kg": 7 } }
    }]
  }'

# Read the file and its versions. The file comes back as YAML.
curl "$VESTRA_BASE_URL/v1/deployments/trip-rescue?format=yaml" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl "$VESTRA_BASE_URL/v1/deployments/dep_4kq/versions" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Roll back to the previous version. The version that was live before comes back at once. This needs a live key.
curl -X POST "$VESTRA_BASE_URL/v1/deployments/dep_4kq/rollback" \
  -H "Authorization: Bearer $VESTRA_LIVE_KEY"
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `GET /v1/deployments/{deployment_id}/versions/{version}`

Its own page: [GET /v1/deployments/{deployment_id}/versions/{version}](https://vestra.ai/developers/reference/api/deployments/get-v1-deployments-deployment-id-versions-version.md)

One version, in full.

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

### Request

Path:

- `deployment_id` (`string`, required): The deployment's id (`dep_...`). The /v1/deployments routes also take its name.
- `version` (`string`, required): The version number: 1 is the first, and each save adds one.

### Answers 200

- `object` (`"deployment_version"`): Always `deployment_version`.
- `deployment_id` (`string`): The deployment's id. Example: `dep_8f2k`.
- `version` (`integer`): A version number: 1 is the first, and each save adds one.
- `manifest` (`object`): The version's settings, every default filled in.
  - `base` (`"bash"`): What the deployment is built on: always `bash`. Default: `bash`.
  - `instructions` (`string`): What Bash does here, and how, in plain words, up to 8,000 characters. Empty by default. Default: empty. Limits: at most 8000 characters.
  - `language` (`string`): The language Bash speaks: a tag such as `en`, `hi` or `pt-BR`, or `auto` (the default) to follow each user. Default: `auto`.
  - `mcp_servers` (`string[]`): The MCP servers Bash may call, by their `mcp_` ids (POST /v1/mcp-servers). Default: `[]`.
  - `mcp_tool_policy` (`object[]`, may be absent): This version's own tool policy for its MCP servers, on top of each server's floor. Absent (every version saved before it): each server's own policy alone. Limits: at most 50 items.
    - `server` (`string`): The MCP server, by its `mcp_` id, one `mcp_servers` lists.
    - `deny` (`string[]`): Tools of the server this version never calls, on top of the server's own floor. Default: `[]`. Limits: at most 200 items.
    - `require_approval` (`string[]`): Tools of the server that wait for the person's yes in this version, on top of the server's own floor. Default: `[]`. Limits: at most 200 items.
    - `run_without_asking` (`string[]`, may be absent): Tools of the server this version runs without waiting for the person's yes. Every other tool of the server waits for it, unless the server marks the tool read-only (readOnlyHint). Limits: at most 200 items.
  - `apps` (`object`): Which apps its users may connect. Default: `{"allow":"all"}`.
    - `allow` (`"all" | string[]`): The apps its users may connect: `all` (the default), or a list such as `["gmail"]`. Default: `all`.
  - `approvals` (`object`): What Bash asks the person before doing. Default: `{"always":["payment","booking","cancellation"]}`.
    - `always` (`("payment" | "booking" | "cancellation" | "send_email" | "publish" | "delete")[]`): What Bash asks the person before doing: any of `payment`, `booking`, `cancellation`, `send_email`, `publish` and `delete`. Default the first three. A system approval enforces what `approvals` lists for your own functions and your MCP servers' tools that wait for a yes; in a browser or a connected app, Bash is instructed to ask. Default: `["payment","booking","cancellation"]`.
  - `model` (`string`): `default` (the default), or a model id from GET /v1/models. Default: `default`.
  - `memory` (`object`): What Bash remembers about each user here. Default: `{}`.
    - `mode` (`"vestra" | "customer" | "none"`, may be absent): `vestra`, `customer` or `none`. Absent: the company's `memory_mode`.
  - `notebooks` (`string[]`): What Bash reads before it answers: notebooks, by their `nb_` ids. Default: `[]`.
  - `skills` (`string[]`): Skills Bash may use: company skills by their `skl_` ids, or skills Bash already has, by name. Default: `[]`.
  - `tools` (`object[]`): Tools your own code answers: Bash's call reaches you as an interaction of type `tool_call`, after the person's yes unless the tool is marked `read_only` or `run_without_asking`. Default: `[]`.
    - `name` (`string`): The tool's name: lowercase letters, digits and underscores, starting with a letter, up to 64. Example: `get_booking`.
    - `description` (`string`): What the tool does, so Bash knows when to call it, up to 1024 characters. Limits: 1 to 1024 characters. Example: `A booking by its PNR`.
    - `input_schema` (`map of any`): Its arguments, as JSON Schema with `type: object`. Bash's call fills them in. Example: `{"type":"object","properties":{"pnr":{"type":"string"}},"required":["pnr"]}`.
    - `timeout_s` (`integer`): Seconds Bash waits for your answer, 5 to 600. Default 120; after it, the call expires. Default: `120`. Limits: 5 to 600.
    - `read_only` (`boolean`, may be absent): true: the tool changes nothing (a lookup), so Bash calls it without waiting for the person's yes. Absent: false.
    - `run_without_asking` (`boolean`, may be absent): true: the tool changes something that needs no yes, so Bash calls it without waiting for the person's yes. Absent: false.
  - `environment` (`object`, may be absent): Bash's computer, ready before the first task: a setup script, environment variables, repositories and files. Absent: a plain computer.
    - `setup_script` (`string`, may be absent): A shell script run once on each computer before its first task, and again when the environment changes, up to 20,000 characters. Absent: none. Limits: at most 20000 characters.
    - `env` (`map of string`): Environment variables on the computer, by name. A credential is `${secret:NAME}`, filled in from your secrets, never a raw value. Default: `{}`. Example: `{"REGION":"ap-south-1"}`.
    - `repos` (`object[]`): Repositories cloned onto the computer. Default: `[]`.
    - `files` (`object[]`): Files placed on the computer. Default: `[]`.
  - `browser` (`object`, may be absent): The sites a browser fetch may open, and the pages the browser never opens or acts on. Absent: a fetch opens nothing, and no page is closed.
    - `allowed_domains` (`string[]`, may be absent): The sites a browser fetch may open, as bare host names such as `ixigo.com`, which also allows `www.ixigo.com`. Up to 20. Absent: a browser fetch opens nothing. Limits: 1 to 20 items. Example: `["ixigo.com"]`.
    - `blocked_paths` (`string[]`, may be absent): Pages the browser never opens or acts on, as a site and the start of a path, such as `ixigo.com/payments/`. Enforced in code for a browser fetch (403 page_blocked) and Bash's own browser tools. Up to 20. Limits: 1 to 20 items. Example: `["ixigo.com/payments/"]`.
  - `routines` (`object[]`): Not used: a version holds no routines. Write them in vestra.yaml, or make them with POST /v1/routines. Default: `[]`.
    - `name` (`string`): The routine's name.
    - `schedule` (`string`): When it runs.
    - `message` (`string`): What it asks.
  - `limits` (`object`): What one piece of work may spend. There is no time limit. Default: `{"spend_per_task_usd":2}`.
    - `spend_per_task_usd` (`number`): The most one piece of work may spend, in US dollars, above 0 and at most 100. Default 2. Default: `2`. Limits: above 0, at most 100.
- `manifest_sha256` (`string`): A hash of the settings: two versions with the same hash run the same.
- `skill_pins` (`object[]`): The company skills this version runs, each at the version of the skill current when it was saved.
  - `skill` (`string`): The company skill, by its `skl_` id.
  - `slug` (`string`): The skill's name.
  - `pressing_sha256` (`string | null`): The content hash of the skill's version when this version was saved: the one it runs. Null when none was recorded.
- `note` (`string | null`): The note it was saved with; null when none.
- `created_by` (`string`): Who saved it: a key's id (`key_...`), `console` (a person in the console), `client` (a session token from your app) or `interview` (the draft the interview wrote).
- `created_at` (`string`): When it was saved, as an ISO 8601 date and time in UTC.
- `is_draft` (`boolean`): True when test keys run this version.
- `is_live` (`boolean`): True when live keys run it.
- `test_verdict` (`"passed" | "failed" | "none"`, may be absent): Its latest test run's verdict: `passed`, `failed`, or `none` (never tested). Only when you read one version (`GET .../versions/{version}`); absent in the list.
- `interview` (`object`, may be absent): The interview's closing words, on the version the interview saved; absent on every other version.
  - `summary` (`string`): What the draft sets Bash up to do, in the interview's words.
  - `tools` (`object[]`): The tools the file declares that your own code answers.
    - `name` (`string`): The tool's name.
    - `description` (`string`): What it does.
  - `per_task_usd` (`number`): The spending cap per piece of work, in US dollars.
  - `per_task_usd_is_default` (`boolean`): True when that cap is the default, 2, because the file left it.
  - `tests` (`integer`): How many starter tests the file holds.
  - `next_steps` (`string[]`): What to do next, one plain line each, in order.

### Code

**HTTP**

```bash
# Describe the job. The interview runs as a conversation, so its questions arrive like any other reply.
curl "$VESTRA_BASE_URL/v1/deployments/interview" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Trip rescue", "slug": "trip-rescue", "job": "Help Tripnest travellers fix broken trips: cancellations, delays, missed connections. Look bookings up with get_booking." }'

# Answer its questions. Follow the conversation the response names, and reply in it.
curl -N "$VESTRA_BASE_URL/v1/conversations/conv_iv7/events" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl "$VESTRA_BASE_URL/v1/conversations/conv_iv7/messages" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Ask first before paying a fare difference." }'

# Save the draft to your repo. When the interview ends, download the draft as vestra.yaml, starter tests
# included.
curl "$VESTRA_BASE_URL/v1/deployments/trip-rescue?format=yaml" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -o vestra.yaml

# Read the closing words. The draft version (the deployment's draft_version) has them in its interview field:
# what it set up, the tools your code must answer, the spending cap per task, the tests, and the next steps.
curl "$VESTRA_BASE_URL/v1/deployments/trip-rescue/versions/2" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `GET /v1/deployments/{deployment_id}/diff`

Its own page: [GET /v1/deployments/{deployment_id}/diff](https://vestra.ai/developers/reference/api/deployments/get-v1-deployments-deployment-id-diff.md)

What changed between two versions (?from=&to=).

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in each {…} and every value.

### Request

Path:

- `deployment_id` (`string`, required): The deployment's id (`dep_...`). The /v1/deployments routes also take its name.

Query:

- `from` (`integer`, required): The version to compare from.
- `to` (`integer`, required): The version to compare to.

### Answers 200

- `object` (`"deployment_diff"`): Always `deployment_diff`.
- `deployment_id` (`string`): The deployment's id. Example: `dep_8f2k`.
- `from` (`integer`): The version compared from.
- `to` (`integer`): The version compared to.
- `changes` (`object[]`): Each setting that differs; empty when the two run the same.
  - `path` (`string`): The setting that changed, as a path such as `limits.spend_per_task_usd`.
  - `from` (`any`): Its value in the version compared from; absent when it was not set.
  - `to` (`any`): Its value in the version compared to; absent when it is not set.

### Code

**HTTP**

```bash
curl "$VESTRA_BASE_URL/v1/deployments/{deployment_id}/diff?from=...&to=..." \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `POST /v1/deployments/{deployment_id}/versions/{version}/promote`

Its own page: [POST /v1/deployments/{deployment_id}/versions/{version}/promote](https://vestra.ai/developers/reference/api/deployments/post-v1-deployments-deployment-id-versions-version-promote.md)

Make it live. Refused unless its latest test run passed.

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in each {…} and every value.

### Request

Path:

- `deployment_id` (`string`, required): The deployment's id (`dep_...`). The /v1/deployments routes also take its name.
- `version` (`string`, required): The version number: 1 is the first, and each save adds one.

Headers:

- `Idempotency-Key` (`string`): Send the same key to retry this call safely for 24 hours.

### Answers 200

- `deployment` (`object`): The deployment, after the promotion.
  - `id` (`string`): The deployment's id. Example: `dep_8f2k`.
  - `object` (`"deployment"`): Always `deployment`.
  - `name` (`string`): Its name, as people read it.
  - `slug` (`string`): Its name in URLs and refs: send it where a route takes `{deployment_id}`. Example: `trip-rescue`.
  - `description` (`string`): What it is for; empty when none was given.
  - `base` (`"bash"`): What it is built on: always `bash`.
  - `status` (`"active" | "archived"`): `active`, or `archived` (put away: no new conversation or version reaches it).
  - `draft_version` (`integer`): The version test keys run. A new save moves it there; a promote with a test key moves it to the version named, older ones included.
  - `live_version` (`integer | null`): The version live keys run; null until one is promoted.
  - `created_at` (`string`): When it was made, as an ISO 8601 date and time in UTC.
  - `updated_at` (`string`): When it last changed, as an ISO 8601 date and time in UTC.
- `promotion` (`object | null`): What moved; null when the version was already live, so nothing did.
  - `object` (`"deployment_promotion"`): Always `deployment_promotion`.
  - `deployment_id` (`string`): The deployment's id. Example: `dep_8f2k`.
  - `action` (`"promote" | "rollback"`): `promote` (a version went live) or `rollback` (an earlier one went back live).
  - `from_version` (`integer | null`): The version live before; null when none was.
  - `to_version` (`integer`): The version live now.
  - `verdict` (`"passed" | "failed" | "none" | "rollback"`): The version's tests when it went live: `passed`, `none` (there were none to run), or `rollback`.
  - `created_at` (`string`): When it went live, as an ISO 8601 date and time in UTC.
- `became_default` (`boolean`): True when this made the deployment the company's default: an ask that names no deployment now reaches it.

### Code

**HTTP**

```bash
curl -X POST "$VESTRA_BASE_URL/v1/deployments/{deployment_id}/versions/{version}/promote" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `POST /v1/deployments/{deployment_id}/rollback`

Its own page: [POST /v1/deployments/{deployment_id}/rollback](https://vestra.ai/developers/reference/api/deployments/post-v1-deployments-deployment-id-rollback.md)

Put an earlier version back live, instantly.

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

### Request

Path:

- `deployment_id` (`string`, required): The deployment's id (`dep_...`). The /v1/deployments routes also take its name.

Headers:

- `Idempotency-Key` (`string`): Send the same key to retry this call safely for 24 hours.

Body, if you send one:

- `version` (`integer`): The earlier version to put back live. Absent: the one live before this. Limits: at least 1.

### Answers 200

- `deployment` (`object`): The deployment, after the promotion.
  - `id` (`string`): The deployment's id. Example: `dep_8f2k`.
  - `object` (`"deployment"`): Always `deployment`.
  - `name` (`string`): Its name, as people read it.
  - `slug` (`string`): Its name in URLs and refs: send it where a route takes `{deployment_id}`. Example: `trip-rescue`.
  - `description` (`string`): What it is for; empty when none was given.
  - `base` (`"bash"`): What it is built on: always `bash`.
  - `status` (`"active" | "archived"`): `active`, or `archived` (put away: no new conversation or version reaches it).
  - `draft_version` (`integer`): The version test keys run. A new save moves it there; a promote with a test key moves it to the version named, older ones included.
  - `live_version` (`integer | null`): The version live keys run; null until one is promoted.
  - `created_at` (`string`): When it was made, as an ISO 8601 date and time in UTC.
  - `updated_at` (`string`): When it last changed, as an ISO 8601 date and time in UTC.
- `promotion` (`object | null`): What moved; null when the version was already live, so nothing did.
  - `object` (`"deployment_promotion"`): Always `deployment_promotion`.
  - `deployment_id` (`string`): The deployment's id. Example: `dep_8f2k`.
  - `action` (`"promote" | "rollback"`): `promote` (a version went live) or `rollback` (an earlier one went back live).
  - `from_version` (`integer | null`): The version live before; null when none was.
  - `to_version` (`integer`): The version live now.
  - `verdict` (`"passed" | "failed" | "none" | "rollback"`): The version's tests when it went live: `passed`, `none` (there were none to run), or `rollback`.
  - `created_at` (`string`): When it went live, as an ISO 8601 date and time in UTC.
- `became_default` (`boolean`): True when this made the deployment the company's default: an ask that names no deployment now reaches it.

### Code

**TypeScript**

```ts
const live = new Vestra({ apiKey: process.env.VESTRA_LIVE_KEY });
await live.deploy.rollback('travel-help');
```

**Python**

```python
live = Vestra(api_key=os.environ["VESTRA_LIVE_KEY"])
live.deploy.rollback("travel-help")
```

**CLI**

```bash
VESTRA_API_KEY=$VESTRA_LIVE_KEY vestra deploy --rollback travel-help
```

**HTTP**

```bash
# Publish a change. Send the whole file as JSON, by its name. The first call creates the deployment, and a
# file that changes nothing makes no new version. Its tests run, and when they pass the version goes live in
# your key's mode: test traffic with a test key, real users with a live key. A test plays your own tools with
# tool_results. The first deployment you promote becomes the default, and every ask that names no deployment
# goes to it. became_default is true when this call made it the default. If the call waits for tests, that
# happens when they pass.
curl -X PUT "$VESTRA_BASE_URL/v1/deployments/trip-rescue?promote=after_tests" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "trip-rescue",
    "instructions": "You help Tripnest travellers fix broken trips. Always offer two options.",
    "apps": ["gmail", "google-calendar"],
    "tools": [{ "function": "get_booking", "description": "A booking by its PNR", "input": { "pnr": "string" }, "read_only": true }],
    "approvals": ["payment", "booking", "cancellation"],
    "tests": [{
      "name": "Bag allowance",
      "ask": "How many kilos can I take on booking TN-48213?",
      "expect": "Says 7 kg of cabin baggage.",
      "tool_results": { "get_booking": { "cabin_kg": 7 } }
    }]
  }'

# Read the file and its versions. The file comes back as YAML.
curl "$VESTRA_BASE_URL/v1/deployments/trip-rescue?format=yaml" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl "$VESTRA_BASE_URL/v1/deployments/dep_4kq/versions" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Roll back to the previous version. The version that was live before comes back at once. This needs a live key.
curl -X POST "$VESTRA_BASE_URL/v1/deployments/dep_4kq/rollback" \
  -H "Authorization: Bearer $VESTRA_LIVE_KEY"
```

**MCP**: No sample in your language for this route, so here it is over HTTP.

## `POST /v1/deployments/interview`

Its own page: [POST /v1/deployments/interview](https://vestra.ai/developers/reference/api/deployments/post-v1-deployments-interview.md)

Build a deployment by talking to Bash: describe the job, answer its questions, receive a draft version.

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

### Request

Headers:

- `Idempotency-Key` (`string`): Send the same key to retry this call safely for 24 hours.

Body:

- `name` (`string`, required): The name of the new deployment the interview makes, 1 to 120 characters. It is always new: a taken name gets a numbered slug, and a taken `slug` is refused with 409 `slug_taken`. Limits: 1 to 120 characters. Example: `Trip rescue`.
- `slug` (`string`): Its name in URLs and refs. Absent: made from `name`. Example: `trip-rescue`.
- `job` (`string`, required): What Bash should do for your users, in your own words, 20 to 4,000 characters. Bash asks what it needs, then drafts the file. Limits: 20 to 4000 characters. Example: `Help Tripnest travellers fix broken trips: rebook, refund, find a hotel.`.

### Answers 202

- `object` (`"deployment_interview"`): Always `deployment_interview`.
- `deployment` (`object`): The deployment the interview drafts.
  - `id` (`string`): The deployment's id. Example: `dep_8f2k`.
  - `object` (`"deployment"`): Always `deployment`.
  - `name` (`string`): Its name, as people read it.
  - `slug` (`string`): Its name in URLs and refs: send it where a route takes `{deployment_id}`. Example: `trip-rescue`.
  - `description` (`string`): What it is for; empty when none was given.
  - `base` (`"bash"`): What it is built on: always `bash`.
  - `status` (`"active" | "archived"`): `active`, or `archived` (put away: no new conversation or version reaches it).
  - `draft_version` (`integer`): The version test keys run. A new save moves it there; a promote with a test key moves it to the version named, older ones included.
  - `live_version` (`integer | null`): The version live keys run; null until one is promoted.
  - `created_at` (`string`): When it was made, as an ISO 8601 date and time in UTC.
  - `updated_at` (`string`): When it last changed, as an ISO 8601 date and time in UTC.
- `conversation_id` (`string`): The interview's conversation: follow its events for Bash's questions, and answer with POST /v1/conversations/{conversation_id}/messages.

### Code

**TypeScript**

```ts
const draft = vestra.init({ describe: 'Help Tripnest travellers fix broken trips.' });
await draft.started;
```

**Python**

```python
draft = vestra.init("Help Tripnest travellers fix broken trips.")
started = draft.started
```

**CLI**

```bash
vestra init "Help Tripnest travellers fix broken trips." --answers answers.txt
```

**HTTP**

```bash
# Describe the job. The interview runs as a conversation, so its questions arrive like any other reply.
curl "$VESTRA_BASE_URL/v1/deployments/interview" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Trip rescue", "slug": "trip-rescue", "job": "Help Tripnest travellers fix broken trips: cancellations, delays, missed connections. Look bookings up with get_booking." }'

# Answer its questions. Follow the conversation the response names, and reply in it.
curl -N "$VESTRA_BASE_URL/v1/conversations/conv_iv7/events" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl "$VESTRA_BASE_URL/v1/conversations/conv_iv7/messages" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Ask first before paying a fare difference." }'

# Save the draft to your repo. When the interview ends, download the draft as vestra.yaml, starter tests
# included.
curl "$VESTRA_BASE_URL/v1/deployments/trip-rescue?format=yaml" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -o vestra.yaml

# Read the closing words. The draft version (the deployment's draft_version) has them in its interview field:
# what it set up, the tools your code must answer, the spending cap per task, the tests, and the next steps.
curl "$VESTRA_BASE_URL/v1/deployments/trip-rescue/versions/2" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**

```json
{
  "method": "tools/call",
  "params": {
    "name": "init",
    "arguments": {
      "describe": "Help Tripnest travellers fix broken trips: cancellations, delays, missed connections. Look bookings up with get_booking.",
      "for": "customers"
    }
  }
}
```
