# PUT /v1/deployments/{deployment_id}

> 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: []).

In [Deployments API](https://vestra.ai/developers/reference/api/deployments.md): 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.

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.
