Deployments
How your Bash is set up, in versions: apply a whole vestra.yaml, promote, roll back, and the interview that drafts the file.
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: []).
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.
Request19 fields
Path
deployment_idstringrequiredThe deployment's id (dep_...). The /v1/deployments routes also take its name.
- Type
string
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.
- Type
"after_tests" | "now" | "no"
Body
$schemastringFor editors reading JSON; ignored.
- Limits
- At most 500 characters.
namestringrequiredThe deployment's name, as in PUT /v1/deployments/{name}: lowercase letters, digits and dashes.
- Example
trip-rescue
instructionsstringWhat 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.
languagestringThe 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
modelstringdefault, 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"]
toolsobject[]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"]}]
skillsstring | (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"}]
knowledgestring | (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
computerobjectBash's computer, ready before the first task: a setup script, repositories, environment variables and files.
- Example
{"setup":"./setup.sh","env":{"REGION":"ap-south-1"}}
setupstringA 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"]
envmap of stringEnvironment variables. A credential is ${NAME}, never a raw value.
- Example
{"REGION":"ap-south-1","BOOKING_TOKEN":"${BOOKING_TOKEN}"}
filesobject[]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_idstringrequiredThe file's file_ id.
- Type
string
pathstringrequiredWhere it goes on the computer.
- Limits
- 1 to 200 characters.
browserobjectThe 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_domainsstring[]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_pathsstring[]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.
routinesobject[]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"}]
namestringIts name, which finds the routine again on the next apply. Absent: the start of ask.
- Limits
- 1 to 200 characters.
askstringrequiredWhat Bash does on each run, in plain words.
- Limits
- 1 to 8000 characters.
everystringWhen 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.
scheduleobjectWhen it runs, as cron.
cronstringrequiredA cron expression: 0 7 * * * runs at 7:00 every day.
- Limits
- 1 to 120 characters.
timezonestringThe 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.
- Type
"call"
filtersmap of anyWith on: call, what a call's payload must match for the routine to run.
- Type
map of any
timezonestringThe 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"requiredus: your company's own Bash. each customer: a copy for every user, made the first time they talk to Bash here.
- Type
"us" | "each customer"
enabledbooleanfalse keeps the routine but stops its runs. A routine for each user is on while it is in the file.
- Type
boolean
limitsobjectWhat one task may spend.
- Default
{"per_task_usd":2}- Example
{"per_task_usd":5}
per_task_usdnumberThe most one task may spend, in dollars: above 0, at most 100. Absent: 2.
- Default
2- Limits
- Above 0, at most 100.
testsobject[]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}}}]
namestringIts name, which finds the test again on the next apply. Absent: the start of ask.
- Limits
- 1 to 120 characters.
askstringrequiredWhat the test user wants, in their words.
- Limits
- 1 to 2000 characters.
expectstring | string[]requiredWhat a pass looks like: one line, or up to 12.
- Type
string | string[]
neverstring | string[]What must never happen: one line, or up to 12.
- Type
string | string[]
personastringWho the test user is. Absent: a person who uses the company's app.
- Limits
- 1 to 2000 characters.
tool_resultsmap of anyWhat 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.
- Type
map of any
approvesbooleantrue: 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.
- Type
boolean
webhooksmap 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 2018 fields
object"deployment_apply"Always deployment_apply.
- Type
"deployment_apply"
deploymentobjectThe deployment, after the apply.
idstringThe deployment's id.
- Example
dep_8f2k
object"deployment"Always deployment.
- Type
"deployment"
namestringIts name, as people read it.
- Type
string
slugstringIts name in URLs and refs: send it where a route takes {deployment_id}.
- Example
trip-rescue
descriptionstringWhat it is for; empty when none was given.
- Type
string
base"bash"What it is built on: always bash.
- Type
"bash"
status"active" | "archived"active, or archived (put away: no new conversation or version reaches it).
- Type
"active" | "archived"
draft_versionintegerThe 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.
- Type
integer
live_versioninteger | nullThe version live keys run; null until one is promoted.
- Type
integer | null
created_atstringWhen it was made, as an ISO 8601 date and time in UTC.
- Type
string
updated_atstringWhen it last changed, as an ISO 8601 date and time in UTC.
- Type
string
versionintegerThe version this file is: new, or the latest when nothing changed.
- Type
integer
changedstring[]The file's fields that differ from the version before, in the file's order; empty when nothing changed.
- Example
["instructions","tools"]
testsobject | nullThe test run this apply started; null when it started none.
idstringThe test run's id.
- Example
trun_8f2k
object"test_run"Always test_run.
- Type
"test_run"
deployment_idstringThe deployment it tests.
- Type
string
versionintegerThe version it tests.
- Type
integer
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.
- Type
"queued" | "running" | "passed" | "failed" | "error"
error_codestring | nullWhy 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.
- Type
string | null
scenario_countintegerHow many scenarios it plays.
- Type
integer
passed_countintegerHow many have passed so far.
- Type
integer
resumesintegerHow many times a restart interrupted it and it played on from where it was, at most 2.
- Type
integer
promotedobject | nullWhat 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.
- Type
"live" | "test"
versionintegerThe version that went live.
- Type
integer
became_defaultbooleanTrue when that promotion made the deployment the company's default: an ask with no deployment now reaches it.
- Type
boolean
cost_usdnumberWhat the run cost, in billed US dollars.
- Type
number
created_atstringWhen it was asked for, as an ISO 8601 date and time in UTC.
- Type
string
started_atstring | nullWhen it began to play; null while queued.
- Type
string | null
finished_atstring | nullWhen it ended; null while it plays.
- Type
string | null
resultsobject[]may be absentEach scenario's result, on GET /v1/test-runs/{test_run_id}; absent in lists.
object"test_result"Always test_result.
- Type
"test_result"
scenario_idstring | nullThe scenario it played; null when the scenario was deleted since.
- Type
string | null
scenarioobjectThe scenario as it was played.
- Type
object
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.
- Type
"passed" | "failed" | "error"
reasonstringWhy, in a sentence or two.
- Type
string
criteriaobject[]Each line of success, graded.
- Type
object[]
must_neverobject[]Each thing it must never do, graded.
- Type
object[]
approvals_askedobject[]What Bash asked the test user's yes for, and what it said.
- Type
object[]
transcriptobject[]The conversation, line by line.
- Type
object[]
conversation_idstring | nullThe test conversation; null when it never started.
- Type
string | null
cost_usdnumberWhat playing it cost, in billed US dollars.
- Type
number
promoteobjectWhether, and when, the version goes live.
when"after_tests" | "now" | "no"When it was asked to go live: after_tests, now or no.
- Type
"after_tests" | "now" | "no"
mode"live" | "test"The key's mode: live moves live_version, test moves draft_version.
- Type
"live" | "test"
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.
- Type
"promoted" | "already" | "waiting" | "not_promoted"
codestring | nullWhy 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.
- Type
string | null
messagestring | nullThe same, in a sentence a person can read; null with no code.
- Type
string | null
new_webhooksobject[]The webhook endpoints this call made, each with its signing secret.
idstringThe webhook endpoint's id.
- Type
string
urlstringWhere its events go.
- Type
string
secretstringIts signing secret, shown here once: store it to check each delivery.
- Type
string
became_defaultbooleanTrue 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.
- Type
boolean
const applied = await vestra.deploy('vestra.yaml');applied = vestra.deploy("vestra.yaml")vestra deploy# 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"No sample in your language for this route, so here it is over HTTP.
# 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"