answer
Reply when Bash needs something from a person or from your code. Each request is an interaction: an approval, a question, a request to connect an app, a hand-off (a step only a person can do), or a call to one of your tools. One route answers every kind. You send the body that fits its type.
Answer during a run
When Bash needs something, the run sends an interaction.created event.
const run = vestra.user('traveller-42').ask('Rebook my cancelled flight.'); for await (const event of run) { if (event.type !== 'interaction.created') continue; const asked = event.interaction; // what Bash waits on, read for you (null for a call to your own tool) if (asked?.type === 'approval') { // A payment goes through only when the person who saw the approval says yes. await event.answer({ approve: await app.confirm(event.user, asked.summary), present_user: true }); } if (asked?.type === 'question' && asked.questions) { const answers: string[] = []; for (const q of asked.questions) answers.push(await app.choose(event.user, q.question, q.options.map((o) => o.label))); await event.answer({ answers }); }}Answer later, by id
List everything Bash is waiting on, from anywhere, and answer each one by its id.
const waiting = await vestra.answer.list({ user: 'traveller-42' });for (const item of waiting.data) { if (item.type === 'handoff') await vestra.answer(item.id, { done: true });}Answer during a run
When Bash needs something, the run sends an interaction.created event.
run = vestra.user("traveller-42").ask("Rebook my cancelled flight.") for event in run: if event.type != "interaction.created": continue asked = event.interaction # what Bash waits on, read for you (None for a call to your own tool) if asked and asked.type == "approval": # A payment goes through only when the person who saw the approval says yes. event.answer(approve=app.confirm(event.user, asked.summary), present_user=True) if asked and asked.type == "question": event.answer(answers=[app.choose(event.user, q.question, [o.label for o in q.options]) for q in asked.questions])Answer later, by id
List everything Bash is waiting on, from anywhere, and answer each one by its id.
for item in vestra.answer.list(user="traveller-42").data: if item.type == "handoff": vestra.answer(item.id, done=True)List what Bash is waiting on
Leave out --user to see what it waits on for every user.
vestra answer --user traveller-42Approve or deny
--present-user says the person who saw the approval is the one saying yes. To say no, give a reason.
vestra answer int_3j9 --approve --present-uservestra answer int_7kq --deny --message "Not that hotel, it is too far from the airport."Answer a question
vestra answer int_2mx --answer "Window"Finish a hand-off
A hand-off is a step only a person can do.
vestra answer int_9hd --doneReturn your tool's result
vestra answer call_4mz --output '{ "status": "on time", "gate": "B4" }'List what Bash is waiting on
For one user, this lists approvals, questions, hand-offs, apps to connect and calls to your tools.
curl "$VESTRA_BASE_URL/v1/interactions?user_id=traveller-42" \ -H "Authorization: Bearer $VESTRA_API_KEY"Approve or deny
A payment or hands-on approval needs present_user: true, and a hands-on approval also needs its nonce. To say no, send approve: false.
curl "$VESTRA_BASE_URL/v1/interactions/int_3j9/answer" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "approve": true, "present_user": true }'Answer a question
Send one answer per question.
curl "$VESTRA_BASE_URL/v1/interactions/int_7qp/answer" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "answers": ["The 09:15 flight"] }'Return your tool's result
Answer a call to one of your tools with its output, or an error.
curl "$VESTRA_BASE_URL/v1/interactions/call_4mz/answer" \ -H "Authorization: Bearer $VESTRA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "output": { "status": "confirmed", "pnr": "100042" } }'{ "method": "tools/call", "params": { "name": "answer", "arguments": { "interaction": "int_3j9", "approve": true, "present_user": true } }}Good to know
What to send for each type
An approval takes { approve, present_user?, message?, nonce? }. A question takes { answers }, with one answer per question, or { approve: false } to skip it. A connect request takes { approve: false } to decline it. A hand-off takes { done }, { could_not }, { not_now } or { answers }. A call to your tool takes { output } or { error }. Any other body fails with 400 answer_mismatch, and the error names the shapes that fit.
Approvals that need the person present
A payment approval, or a hands-on approval (Bash operating its computer for the user), is accepted only with present_user: true, from the person who saw it. The same goes for approving a company action: a tool on one of your MCP servers, or one of your functions that is not marked read-only or run without asking.
A company action's summary says what will happen, taken from the tool's description, then names the tool and each argument. For example: Extends a rental by one day. extend_rental: rental_id R-1042, charge_usd 15, new_due_back 2026-10-05 18:00. The summary is at most 120 characters and never cuts an argument in half. When space runs out, it drops the description first, then each argument that does not fit, and ends with and N more. details.rows holds the call's first 8 arguments, each value up to 200 characters. Its moves_money is null, because only your code knows what your tools cost. A tool that takes its price as an input shows the price in the rows. On a hands-on approval, the SDK sends the interaction's nonce for you.
Let the SDK run your tools
Pass new Vestra({ tools }) one function per tool, and the SDK answers each call with what the function returns. If the function throws, that becomes the call's error. This works both on a run and in the webhook handler.
Running several processes
Each process runs your function once per call. If you run several processes, answer your tools through one path only: either your runs or a single webhook endpoint. Otherwise two processes that receive the same call at the same moment can both run it.
When a request expires
A call to your tool that nobody answers expires after its timeout_s, and sends interaction.expired. For now, only tool calls send expiry events. Other interactions end with interaction.resolved.
The routes behind answer4 routes, each with every field in the API reference
runtime (or all)runtime (or all)runtime (or all)runtime (or all)