deploy
Publish a new version of your agent's setup. Edit vestra.yaml, run deploy, and Vestra saves it as a new version and runs your tests. If they pass, the new version starts handling test traffic. Deploy with your live key to put it in front of real users, and roll back if something goes wrong. This setup is called a deployment.
Publish a change
Vestra saves vestra.yaml as a new version and runs its tests. When they pass, the new version handles test traffic. If the file has not changed, no new version is made.
const applied = await vestra.deploy('vestra.yaml');console.log(applied.version, applied.promote.status);// 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.if (applied.became_default) console.log('default deployment: an ask without a deployment reaches it');Go live with real users
Deploy the same file with a live key.
const live = new Vestra({ apiKey: process.env.VESTRA_LIVE_KEY });await live.deploy('vestra.yaml', { live: true });Roll back to the previous version
The version that was live before comes back at once.
await live.deploy.rollback('trip-rescue');Publish a change
Vestra saves vestra.yaml as a new version and runs its tests. When they pass, the new version handles test traffic. If the file has not changed, no new version is made.
applied = vestra.deploy("vestra.yaml")print(applied.version, applied.promote.status)# 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.if applied.became_default: print("default deployment: an ask without a deployment reaches it")Go live with real users
Deploy the same file with a live key.
live = Vestra(api_key=os.environ["VESTRA_LIVE_KEY"])live.deploy("vestra.yaml", live=True)Roll back to the previous version
The version that was live before comes back at once.
live.deploy.rollback("trip-rescue")Publish a change
Vestra saves vestra.yaml as a new version and runs its tests. When they pass, the new version handles test traffic. The first deployment you promote becomes the default once its tests pass, and every ask without --deployment goes to it.
vestra deployGo live with real users
Deploy with a live key.
VESTRA_API_KEY=$VESTRA_LIVE_KEY vestra deploy --liveRoll back to the previous version
The version that was live before comes back at once.
VESTRA_API_KEY=$VESTRA_LIVE_KEY vestra deploy --rollback trip-rescueWhile you build, vestra dev --tools ./tools.mjs publishes every save to test mode, and your tools run on this computer. It runs until you press Ctrl-C.
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"/v1/mcp never deploys: publishing a version stays with you. In your editor, vestra mcp offers a deploy tool that runs on your own computer, with the key vestra login saved.
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"Good to know
Your first deploy
A deployment holds your agent's instructions, the apps and tools it may use, the actions that need the user's approval, and its limits. deploy always sends your whole file. The first time, it creates the deployment. A file with no changes makes no new version. A deployment created with POST /v1/deployments starts with its draft as version 1, so its first saved version is 2.
How deploy waits for your tests
vestra deploy and vestra deploy --live wait for the new version's tests, then promote it. Promoting a version makes it the one your agent runs in that mode, test or live. The command prints each result as it arrives (failed: <name>: <reason>), and then promoted in test mode, version 5.
The exit code tells CI and coding agents what happened. 0 means the version now runs in that mode, or there was nothing to wait for (an unchanged file, or --no-promote). 1 means a test failed, the run ended in an error or was interrupted, or the promotion was refused or another promotion got there first. 2 means the command was written wrong.
Ctrl-C stops the waiting (exit 130), but not the test run. If the run passes, it still promotes the version. --no-wait returns at once with the run's id, which you can follow with vestra events <trun_id>. If you retry a deploy whose version is already being tested, it follows that run. A version that vestra test already passed is promoted without testing it again. The deploy tool in vestra mcp only promotes in test mode, and follows the tests for up to 45 seconds. If the run is still going, it returns the run's id, which you can read with its test tool.
Your default deployment
The first deployment you promote becomes your company's default, and this happens only once. If the promotion waits for tests, the deployment becomes the default when they pass. If you deploy the file vestra init saved without changing it, it becomes the default at once. From then on, every ask that names no deployment goes to the default. If you set or clear the default yourself, Vestra leaves it as you set it.
If an ask goes to the default before it has a version in your key's mode, plain Bash answers it, without your setup. If you name a deployment that has no such version, the ask fails with deployment_not_live. Archiving the default deployment clears the default when it has no live version. When it has one, archiving it is refused with deployment_is_default.
Connecting MCP servers
Your file registers each MCP server by its URL, and one URL is one server. Its headers are shared by every deployment and mode that uses it. So if another deployment runs the server and your file would give it different headers, the deploy fails with 409 mcp_server_shared, naming those deployments.
The tool policy is not shared. The deny, require_approval and run_without_asking lists in your file belong to the version the file makes, stored as manifest.mcp_tool_policy. They take effect when that version is promoted. The policy set on the server itself (PATCH /v1/mcp-servers/{id}) is a minimum that every version runs under, and a version's own policy can only add to it.
A tool that the server does not mark read-only (readOnlyHint) needs the person's approval in every version, unless that version's run_without_asking lists it. Bash trusts your server's read-only mark, so mark only tools that change nothing. If your file's run_without_asking names a tool your server does not list, the deploy fails with 400 tool_not_on_server. A test key cannot change or delete a server that a live version uses. That fails with 409 mcp_server_live, so use a live key, or make the change as an owner or admin. Every version reaches every company server as it is, so while a deployment runs a version live, a test key (or a developer in the console) cannot add, change or delete any server. A file that would register a new one fails with 409 shared_change before anything is written.
Skills and knowledge
Skills and knowledge are not versioned yet: every version, the live one included, reads them as they are now. So while a deployment runs a version live, a test key cannot change them. A test key's write to a skill or a knowledge notebook, including the uploads vestra deploy makes, fails with 409 shared_change before anything is written, and names the live deployments. vestra deploy then exits with code 2 and names what your file changes.
Apply those changes with a live key (vestra deploy --live), which changes them for your users at once, or as an owner or admin. Until a version is live, a test key can change them freely.
Functions that need approval
Before Bash calls a function your code runs, it asks the person for approval. Two marks in your file skip that: read_only: true for a function that changes nothing, such as a lookup, and run_without_asking: true for one that changes something but needs no approval. Either mark lets the function run at once, and you can use both together. Bash trusts the marks in your file, so mark only functions that do what the mark says.
The approval shows every input of the call. So a function that charges, books or changes something should take each value the person agrees to as an input. For example, extend_rental takes rental_id, charge_usd and new_due_back, not rental_id alone. Your code then checks those values against its own before it acts, and throws if one is different. The throw becomes the call's error, and nothing is charged.
In a conversation, Bash starts a separate task to call a function that needs approval, and that task asks first. A version saved before 3 October 2026 has no marks, so every one of its functions needs approval: mark your lookups read_only: true. vestra init marks lookups for you. A routine, a batch, or an ask with no person in your app has nobody to approve, so a function it calls waits until the approval expires. Mark a function that may run there run_without_asking: true.
Going live and rolling back
A version goes live only after its tests pass. Rolling back needs a live key, and puts the version that was live before back in service at once, without running tests. Your webhooks receive deployment.version_created, deployment.promoted and deployment.rolled_back.
The key you need
Deploying and the other setup commands need a key with the setup scope, or all.
The routes behind deploy32 routes, each with every field in the API reference
setup (or all)setup (or all)setup (or all)setup (or all)setup (or all)setup (or all)setup (or all)setup (or all)setup (or all)setup (or all)setup (or all)setup (or all)setup (or all)setup (or all)setup (or all)