# Conversations API

> Ask Bash in a conversation, follow its reply as it happens, and stop it.

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

## `POST /v1/conversations`

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

Ask. Start a conversation, optionally with its first message and that message's options (context, attachments, output_schema, success_criteria, tools, hand_off). user_id names whose Bash it is, created on first use; leave it out for the company's own Bash. stream: true answers the first turn's events as text/event-stream and ends when the turn settles.

Your API key, scope `runtime` or `all`. On the card: [`ask`](https://vestra.ai/developers/ask.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, if you send one:

- `user_id` (`string`): Your id for the user this conversation is for; Vestra makes the user the first time you name them. Absent: your company's own Bash. Example: `traveller-42`.
- `deployment` (`string`): Which deployment answers: its id (dep_...) or its name. Absent: the user's default deployment, then your company's. Limits: at least 1 character. Example: `travel-help`.
- `metadata` (`map of string`): Your own labels, given back as you sent them: at most 32 keys of 1 to 64 characters, each value a string of at most 512. Bash never reads them. Example: `{"order_id":"ORD-1042","channel":"app"}`.
- `message` (`string`): A first message, in at most 32,000 characters. Absent, with no attachment: the conversation starts empty. Limits: at most 32000 characters. Example: `When does my flight on TN-48213 leave?`.
- `context` (`map of any`): Facts for this message only, as JSON. Bash reads them as data, never as instructions. Example: `{"booking_reference":"TN-48213","tier":"gold"}`.
- `attachments` (`object[]`): Files Bash should read with this message: at most 10. Limits: at most 10 items.
  - `file_id` (`string`, required): A file you uploaded with POST /v1/files (file_...). Limits: at least 1 character.
- `output_schema` (`map of any`): A JSON Schema, at most 32 KB, for the data you want back: the turn's data matches it. Example: `{"type":"object","properties":{"flight":{"type":"string"}},"required":["flight"]}`.
- `success_criteria` (`string`): What a good result looks like, in your words: when the turn settles, its outcome says whether it was met, and why. Limits: 1 to 4000 characters. Example: `A new booking on a flight that leaves before noon.`.
- `tools` (`object[]`): Tools your code answers, for this message only, on top of the deployment's: at most 32. Limits: at most 32 items.
  - `name` (`string`, required): The tool's name: lowercase letters, digits and underscores, starting with a letter, up to 64. Example: `get_booking`.
  - `description` (`string`, required): 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`, required): 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`): true: the tool changes nothing (a lookup), so Bash calls it without waiting for the person's yes. Absent: false.
  - `run_without_asking` (`boolean`): true: the tool changes something that needs no yes, so Bash calls it without waiting for the person's yes. Absent: false.
- `hand_off` (`"auto" | "always"`): For the first message: auto lets Bash reply first; always sends it straight to work. Default: auto.
- `stream` (`boolean`): true answers with the turn's events as they happen (text/event-stream), ending when the turn settles. Default: one JSON answer.

### Answers 201

- `id` (`string`): The conversation (conv_...). Example: `conv_8f2k`.
- `object` (`"conversation"`): Always conversation.
- `user_id` (`string | null`): Your id for the user; null on your company's own Bash, and on an agent's conversation over A2A that names no user.
- `deployment_id` (`string | null`): The deployment that answers it (dep_...); null when it has none.
- `deployment_version` (`integer | null`): The version of the deployment it runs on: in test mode the one it started on, in live mode the one live at its last message. Null with no deployment.
- `status` (`"idle" | "replying" | "working" | "waiting"`): What the conversation is doing. idle: nothing. replying: Bash is writing its reply. working: work Bash started is running. waiting: Bash waits on an approval, a question or a call to one of your tools.
- `metadata` (`map of string`): Your labels, as you sent them; empty when you sent none.
- `created_at` (`string`): When it started, as an ISO 8601 time.
- `last_active_at` (`string`): When a message or its work last changed it, as an ISO 8601 time.
- `open_work` (`object[]`): Work Bash started that has not ended, oldest first.
  - `task_id` (`string`): The task (task_...).
  - `title` (`string | null`): What the work is, in a few words; null until Bash names it.
  - `status` (`"queued" | "working" | "waiting" | "done" | "failed"`): Where a piece of work is. queued: not started yet. working: running on the user's computer. waiting: it waits on an approval or a question. done or failed: it has ended.
- `open_interactions` (`object[]`): Approvals and questions Bash waits on.
  - `interaction_id` (`string`): The approval or question, to answer by its id (int_...).
  - `task_id` (`string`): The task that asked (task_...).
  - `kind` (`string`): What it asks for, as stored: such as tool_permission, ask_question or composio_auth.
- `pending_tool_calls` (`object[]`): Calls to your tools that wait on your answer.
  - `call_id` (`string`): The call, to answer by its id (call_...).
  - `name` (`string`): The tool Bash called.
  - `arguments` (`any`): What Bash passed, matching the input schema of the tool.
  - `task_id` (`string | null`): The task that made the call; null when Bash's reply made it.
- `last_event_seq` (`integer`): The seq of the newest event on its stream: read the conversation, then follow its events after this seq, and you miss nothing.
- `cost_usd` (`number`): What it has cost so far, in billed US dollars: Bash's replies, all its work and its voice calls. Example: `0.0412`.
- `cost_breakdown` (`object`): The same cost, by kind of spend.
  - `orchestrator` (`number`): Bash's own replies in the conversation, in billed US dollars; 0 on a piece of work.
  - `intelligence` (`number`): The thinking Bash did while it worked, in billed US dollars.
  - `tools` (`number`): The tools and apps Bash used, in billed US dollars.
  - `compute` (`number`): Time on Bash's computer, in billed US dollars.
  - `background` (`number`): Work Bash did in the background, such as remembering and tidying, in billed US dollars.
  - `voice` (`number`): Voice call time, in billed US dollars.
- `turn_id` (`string`, may be absent): The turn of the first message, when you sent one (turn_...).

### Code

**TypeScript**

```ts
const reply = await vestra.user('traveller-42').ask('My 6am flight got cancelled. Get me on the next one.').text();
```

**Python**

```python
reply = vestra.user("traveller-42").ask("My 6am flight got cancelled. Get me on the next one.").text()
```

**CLI**

```bash
vestra ask "My 6am flight got cancelled. Get me on the next one." --user traveller-42
```

**HTTP**

```bash
# Ask for one of your users. Their Bash is created the first time you name them. With stream: true, the
# response streams this turn's events as they happen, and ends when the turn is over.
curl -N "$VESTRA_BASE_URL/v1/conversations" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "traveller-42",
    "message": "My 6am flight got cancelled. What should I do?",
    "stream": true
  }'

# Ask a follow-up. It goes to the same conversation.
curl -N "$VESTRA_BASE_URL/v1/conversations/conv_8f2k/messages" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Can I get a refund for it?", "stream": true }'

# Stop a reply and its work. This stops the reply and every task the conversation started.
curl -X POST "$VESTRA_BASE_URL/v1/conversations/conv_8f2k/stop" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Ask for your company. Leave user_id out, and the job goes to your company's own Bash.
curl "$VESTRA_BASE_URL/v1/conversations" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Write a two-line notice for our travellers about storm delays at Mumbai airport today." }'

# Ask for many users at once. Each user gets their own conversation. If Bash asks one of them a question,
# that item waits for the answer.
curl "$VESTRA_BASE_URL/v1/batches" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "user_id": "traveller-42", "message": "A storm may delay flights from Mumbai today. What should I do before I leave for the airport? Two lines, in general: no need to look up my booking." },
      { "user_id": "traveller-77", "message": "A storm may delay flights from Pune today. What should I do before I leave for the airport? Two lines, in general: no need to look up my booking." }
    ]
  }'
```

**MCP**

```json
{
  "method": "tools/call",
  "params": {
    "name": "ask",
    "arguments": {
      "user": "traveller-42",
      "message": "My 6am flight got cancelled. Get me on the next one."
    }
  }
}
```

## `GET /v1/conversations`

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

Conversations, newest first: one user's with user_id, or every one of the company's without it (the company's own Bash shows user_id null).

Your API key, scope `runtime` or `all`. On the card: [`ask`](https://vestra.ai/developers/ask.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.
- `user_id` (`string`): Only this user's conversations, by your id for them. Absent: every conversation of your company.

### Answers 200

- `data` (`object[]`): The items on this page.
  - `id` (`string`): The conversation (conv_...). Example: `conv_8f2k`.
  - `object` (`"conversation"`): Always conversation.
  - `user_id` (`string | null`): Your id for the user; null on your company's own Bash, and on an agent's conversation over A2A that names no user.
  - `deployment_id` (`string | null`): The deployment that answers it (dep_...); null when it has none.
  - `deployment_version` (`integer | null`): The version of the deployment it runs on: in test mode the one it started on, in live mode the one live at its last message. Null with no deployment.
  - `status` (`"idle" | "replying" | "working" | "waiting"`): What the conversation is doing. idle: nothing. replying: Bash is writing its reply. working: work Bash started is running. waiting: Bash waits on an approval, a question or a call to one of your tools.
  - `metadata` (`map of string`): Your labels, as you sent them; empty when you sent none.
  - `created_at` (`string`): When it started, as an ISO 8601 time.
  - `last_active_at` (`string`): When a message or its work last changed it, as an ISO 8601 time.
- `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/conversations" \
  -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/conversations/{conversation_id}`

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

One conversation: status (idle, replying, working, waiting), its open work, its open approvals.

Your API key, or a session's token, scope `runtime` or `all`. On the card: [`ask`](https://vestra.ai/developers/ask.md). Every call here is a checked sample.

### Request

Path:

- `conversation_id` (`string`, required): The conversation's id (`conv_...`).

### Answers 200

- `id` (`string`): The conversation (conv_...). Example: `conv_8f2k`.
- `object` (`"conversation"`): Always conversation.
- `user_id` (`string | null`): Your id for the user; null on your company's own Bash, and on an agent's conversation over A2A that names no user.
- `deployment_id` (`string | null`): The deployment that answers it (dep_...); null when it has none.
- `deployment_version` (`integer | null`): The version of the deployment it runs on: in test mode the one it started on, in live mode the one live at its last message. Null with no deployment.
- `status` (`"idle" | "replying" | "working" | "waiting"`): What the conversation is doing. idle: nothing. replying: Bash is writing its reply. working: work Bash started is running. waiting: Bash waits on an approval, a question or a call to one of your tools.
- `metadata` (`map of string`): Your labels, as you sent them; empty when you sent none.
- `created_at` (`string`): When it started, as an ISO 8601 time.
- `last_active_at` (`string`): When a message or its work last changed it, as an ISO 8601 time.
- `open_work` (`object[]`): Work Bash started that has not ended, oldest first.
  - `task_id` (`string`): The task (task_...).
  - `title` (`string | null`): What the work is, in a few words; null until Bash names it.
  - `status` (`"queued" | "working" | "waiting" | "done" | "failed"`): Where a piece of work is. queued: not started yet. working: running on the user's computer. waiting: it waits on an approval or a question. done or failed: it has ended.
- `open_interactions` (`object[]`): Approvals and questions Bash waits on.
  - `interaction_id` (`string`): The approval or question, to answer by its id (int_...).
  - `task_id` (`string`): The task that asked (task_...).
  - `kind` (`string`): What it asks for, as stored: such as tool_permission, ask_question or composio_auth.
- `pending_tool_calls` (`object[]`): Calls to your tools that wait on your answer.
  - `call_id` (`string`): The call, to answer by its id (call_...).
  - `name` (`string`): The tool Bash called.
  - `arguments` (`any`): What Bash passed, matching the input schema of the tool.
  - `task_id` (`string | null`): The task that made the call; null when Bash's reply made it.
- `last_event_seq` (`integer`): The seq of the newest event on its stream: read the conversation, then follow its events after this seq, and you miss nothing.
- `cost_usd` (`number`): What it has cost so far, in billed US dollars: Bash's replies, all its work and its voice calls. Example: `0.0412`.
- `cost_breakdown` (`object`): The same cost, by kind of spend.
  - `orchestrator` (`number`): Bash's own replies in the conversation, in billed US dollars; 0 on a piece of work.
  - `intelligence` (`number`): The thinking Bash did while it worked, in billed US dollars.
  - `tools` (`number`): The tools and apps Bash used, in billed US dollars.
  - `compute` (`number`): Time on Bash's computer, in billed US dollars.
  - `background` (`number`): Work Bash did in the background, such as remembering and tidying, in billed US dollars.
  - `voice` (`number`): Voice call time, in billed US dollars.

### Code

**HTTP**

```bash
# The conversation answers what it has cost so far.
curl "$VESTRA_BASE_URL/v1/conversations/conv_8f2k" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# The receipt: every charge behind it, newest first.
curl "$VESTRA_BASE_URL/v1/usage/line-items?conversation_id=conv_8f2k" \
  -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/conversations/{conversation_id}/messages`

Its own page: [POST /v1/conversations/{conversation_id}/messages](https://vestra.ai/developers/reference/api/conversations/post-v1-conversations-conversation-id-messages.md)

Send a message. Options: context (data for this turn only), attachments, output_schema, success_criteria, tools, hand_off (auto or always). Answers the turn at once; Prefer: wait=N holds for the final answer; stream: true answers this turn's events as text/event-stream and ends when the turn settles.

Your API key, or a session's token, scope `runtime` or `all`. On the card: [`ask`](https://vestra.ai/developers/ask.md). Every call here is a checked sample.

### Request

Path:

- `conversation_id` (`string`, required): The conversation's id (`conv_...`).

Headers:

- `Idempotency-Key` (`string`): Send the same key to retry this call safely for 24 hours.
- `Prefer` (`string`): wait=N (N at most 120) holds the call until the turn and its work finish.

Body:

- `message` (`string`): What you or your user say to Bash, in at most 32,000 characters. Default: empty, for a send that carries only attachments. Default: empty. Limits: at most 32000 characters. Example: `My 6am flight got cancelled. Book me on the next one.`.
- `context` (`map of any`): Facts for this message only, as JSON. Bash reads them as data, never as instructions. Example: `{"booking_reference":"TN-48213","tier":"gold"}`.
- `attachments` (`object[]`): Files Bash should read with this message: at most 10. Limits: at most 10 items.
  - `file_id` (`string`, required): A file you uploaded with POST /v1/files (file_...). Limits: at least 1 character.
- `output_schema` (`map of any`): A JSON Schema, at most 32 KB, for the data you want back: the turn's data matches it. Example: `{"type":"object","properties":{"flight":{"type":"string"}},"required":["flight"]}`.
- `success_criteria` (`string`): What a good result looks like, in your words: when the turn settles, its outcome says whether it was met, and why. Limits: 1 to 4000 characters. Example: `A new booking on a flight that leaves before noon.`.
- `tools` (`object[]`): Tools your code answers, for this message only, on top of the deployment's: at most 32. Limits: at most 32 items.
  - `name` (`string`, required): The tool's name: lowercase letters, digits and underscores, starting with a letter, up to 64. Example: `get_booking`.
  - `description` (`string`, required): 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`, required): 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`): true: the tool changes nothing (a lookup), so Bash calls it without waiting for the person's yes. Absent: false.
  - `run_without_asking` (`boolean`): true: the tool changes something that needs no yes, so Bash calls it without waiting for the person's yes. Absent: false.
- `hand_off` (`"auto" | "always"`): How a message reaches Bash. auto: Bash reads it, replies, and starts work when the job needs it. always: it goes straight to work, as a task; the reply is the one line Started work on it. Default: `auto`.
- `metadata` (`map of string`): Your own labels, given back as you sent them: at most 32 keys of 1 to 64 characters, each value a string of at most 512. Bash never reads them. Example: `{"order_id":"ORD-1042","channel":"app"}`.
- `stream` (`boolean`): true answers with the turn's events as they happen (text/event-stream), ending when the turn settles. Default: one JSON answer.

### Answers 200 or 202

- `id` (`string`): The turn: one message and all it led to (turn_...).
- `object` (`"conversation.turn"`): Always conversation.turn.
- `conversation_id` (`string`): Its conversation (conv_...).
- `status` (`"running" | "working" | "requires_action" | "completed" | "failed" | "stopped"`): Where one message is. running: Bash is replying. working: the work it started is open. requires_action: it waits on a call to one of your tools. completed, failed or stopped: it has settled.
- `reply` (`string | null`): Bash's reply; null until it is written. For a message sent straight to work it is the one line Started work on it.
- `answer` (`string | null`): The reply's answer: its final words, the part after any search or lookup Bash made on the way, trimmed; null until the reply is written, or when it said nothing. A turn from before this field holds its whole reply. When Bash went to work, the work's own answer is GET /v1/tasks/{id}'s result.text.
- `data` (`any | null`): The data you asked for with output_schema, matching it; null when you asked for none.
- `outcome` (`object | null`): Your success_criteria, judged once the turn and its work settle; null when you gave none.
  - `met` (`boolean`): Whether the result meets your success_criteria.
  - `reason` (`string`): Why, in a sentence.
- `work` (`object[]`): The work this turn started.
  - `task_id` (`string`): The task (task_...).
  - `title` (`string | null`): What the work is, in a few words; null until Bash names it.
  - `status` (`"queued" | "working" | "waiting" | "done" | "failed"`): Where a piece of work is. queued: not started yet. working: running on the user's computer. waiting: it waits on an approval or a question. done or failed: it has ended.
  - `result` (`string | null`): Its answer, its final reply, cut to 280 characters; null until it has any. The whole answer is GET /v1/tasks/{id}'s result.text.
- `tool_calls` (`object[]`): While it requires action: the calls to your tools it waits on. Empty otherwise.
  - `call_id` (`string`): The call, to answer by its id (call_...).
  - `name` (`string`): The tool Bash called.
  - `arguments` (`any`): What Bash passed, matching the input schema of the tool.
  - `task_id` (`string | null`): The task that made the call; null when Bash's reply made it.
- `error` (`object | null`): Why the turn failed; null unless it did.
  - `code` (`string`): Why it failed, as a code: output_invalid (the answer did not match output_schema), work_lost (your answer never reached the work), process_lost (our side was cut off mid-reply; send it again), or Bash's own: credits_exhausted, model_timeout, tool_failed, bad_tool_args, loop_exhausted, unauthorized or internal.
  - `message` (`string`): What went wrong, in plain words.
- `created_at` (`string`): When the message was sent, as an ISO 8601 time.
- `finished_at` (`string | null`): When the turn settled, as an ISO 8601 time; null while it runs.

### Code

**TypeScript**

```ts
const reply = await vestra.user('traveller-42').ask('Make it a window seat.', { conversation: 'conv_8f2k' }).text();
```

**Python**

```python
reply = vestra.user("traveller-42").ask("Make it a window seat.", conversation="conv_8f2k").text()
```

**CLI**

```bash
vestra ask "Make it a window seat." --user traveller-42 --conversation conv_8f2k
```

**HTTP**

```bash
# Ask for one of your users. Their Bash is created the first time you name them. With stream: true, the
# response streams this turn's events as they happen, and ends when the turn is over.
curl -N "$VESTRA_BASE_URL/v1/conversations" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "traveller-42",
    "message": "My 6am flight got cancelled. What should I do?",
    "stream": true
  }'

# Ask a follow-up. It goes to the same conversation.
curl -N "$VESTRA_BASE_URL/v1/conversations/conv_8f2k/messages" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Can I get a refund for it?", "stream": true }'

# Stop a reply and its work. This stops the reply and every task the conversation started.
curl -X POST "$VESTRA_BASE_URL/v1/conversations/conv_8f2k/stop" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Ask for your company. Leave user_id out, and the job goes to your company's own Bash.
curl "$VESTRA_BASE_URL/v1/conversations" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Write a two-line notice for our travellers about storm delays at Mumbai airport today." }'

# Ask for many users at once. Each user gets their own conversation. If Bash asks one of them a question,
# that item waits for the answer.
curl "$VESTRA_BASE_URL/v1/batches" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "user_id": "traveller-42", "message": "A storm may delay flights from Mumbai today. What should I do before I leave for the airport? Two lines, in general: no need to look up my booking." },
      { "user_id": "traveller-77", "message": "A storm may delay flights from Pune today. What should I do before I leave for the airport? Two lines, in general: no need to look up my booking." }
    ]
  }'
```

**MCP**

```json
{
  "method": "tools/call",
  "params": {
    "name": "ask",
    "arguments": {
      "user": "traveller-42",
      "message": "My 6am flight got cancelled. Get me on the next one."
    }
  }
}
```

## `GET /v1/conversations/{conversation_id}/messages`

Its own page: [GET /v1/conversations/{conversation_id}/messages](https://vestra.ai/developers/reference/api/conversations/get-v1-conversations-conversation-id-messages.md)

The conversation, paged: your messages, Bash's replies, and summaries of the work it did.

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

### Request

Path:

- `conversation_id` (`string`, required): The conversation's id (`conv_...`).

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 line.
  - `object` (`"conversation.message"`): Always conversation.message.
  - `turn_id` (`string`): The turn it belongs to (turn_...).
  - `role` (`"user" | "assistant"`): user: what was sent. assistant: what Bash said back.
  - `text` (`string`): The words. A send of tool results reads "Tool results:" and the tools it answered.
  - `work` (`object[]`): On Bash's line: the work its reply started.
    - `task_id` (`string`): The task (task_...).
    - `title` (`string | null`): What the work is; null until Bash names it.
  - `created_at` (`string`): When it was said, as an ISO 8601 time.
- `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/conversations/{conversation_id}/messages" \
  -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/conversations/{conversation_id}/events`

Its own page: [GET /v1/conversations/{conversation_id}/events](https://vestra.ai/developers/reference/api/conversations/get-v1-conversations-conversation-id-events.md)

The one stream (SSE): replies as they are typed, work started and finished, progress, approvals, tool calls, files. Resume with Last-Event-ID. Filter to one task's events with task_id.

Your API key, or a session's token, scope `runtime` or `all`. On the card: [`events`](https://vestra.ai/developers/events.md). Every call here is a checked sample.

### Request

Path:

- `conversation_id` (`string`, required): The conversation's id (`conv_...`).

Query:

- `format` (`"sse" | "json"`): sse follows the events as they happen (text/event-stream); json reads one page of them. Default: sse.
- `after` (`integer`): Only events after this seq. On the stream, a Last-Event-ID header does the same. Absent: from the first kept event.
- `limit` (`integer`): With format json: at most this many events, 1 to 500. Default: 100.
- `task_id` (`string`): Only this task's events (task_...).

Headers:

- `Last-Event-ID` (`string`): Resume after this event id.

### Answers 200

A stream of events; with format=json, a page of them.

- `data` (`object[]`): The events, oldest first.
  - `seq` (`integer`): Its place on the conversation's stream, counting up: send the last one you saw to pick up after it.
  - `type` (`string`): What happened, from the one catalog of events: the events reference says what each carries and where it is delivered. Limits: one of 34 values. Example: `message.completed`.
  - `data` (`map of any`): What it says: its fields depend on its type, as the events reference lists them.
  - `created_at` (`string`): When it happened, as an ISO 8601 time.
- `next_after` (`integer`): The seq to send as after for the next page: the last one here, or the after you sent (0 without one) when the page is empty.

### Code

**TypeScript**

```ts
for await (const event of vestra.events('conv_8f2k')) {
  if (event.type === 'message.completed') break;
}
```

**Python**

```python
for event in vestra.events("conv_8f2k"):
    if event.type == "message.completed":
        break
```

**CLI**

```bash
vestra events conv_8f2k
```

**HTTP**

```bash
# Stream a conversation live. If the connection drops, reconnect with the last seq you saw.
curl -N "$VESTRA_BASE_URL/v1/conversations/conv_8f2k/events" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Last-Event-ID: 41"

# Read the same events as JSON. You get them in pages, after the seq you give.
curl "$VESTRA_BASE_URL/v1/conversations/conv_8f2k/events?format=json&after=41" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Read a task afterwards. You get every step of one task, with tokens and cost per turn.
curl "$VESTRA_BASE_URL/v1/tasks/task_91d/trace" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Send events to your server. Register a URL and the events it wants. The signing secret is shown only once.
curl "$VESTRA_BASE_URL/v1/webhooks" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://hooks.example.com/vestra", "events": ["task.completed", "interaction.created", "usage.cap_reached"] }'

# Catch up on what you missed. List the events your webhooks were sent, newest first.
curl "$VESTRA_BASE_URL/v1/events?type=task.failed" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**

```json
{
  "method": "tools/call",
  "params": {
    "name": "events",
    "arguments": { "user": "traveller-42", "conversation": "conv_8f2k", "after": 0 }
  }
}
```

## `POST /v1/conversations/{conversation_id}/stop`

Its own page: [POST /v1/conversations/{conversation_id}/stop](https://vestra.ai/developers/reference/api/conversations/post-v1-conversations-conversation-id-stop.md)

Stop the reply and every piece of work the conversation started.

Your API key, or a session's token, scope `runtime` or `all`. On the card: [`ask`](https://vestra.ai/developers/ask.md). Every call here is a checked sample.

### Request

Path:

- `conversation_id` (`string`, required): The conversation's id (`conv_...`).

Headers:

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

### Answers 200

- `id` (`string`): The conversation (conv_...).
- `object` (`"conversation.stop"`): Always conversation.stop.
- `stopped_work` (`string[]`): The tasks the stop ended (task_...).

### Code

**TypeScript**

```ts
await vestra.user('traveller-42').ask('Find me a hotel near the airport.').stop();
```

**Python**

```python
vestra.user("traveller-42").ask("Find me a hotel near the airport.").stop()
```

**CLI**

```bash
vestra ask --stop conv_8f2k
```

**HTTP**

```bash
# Ask for one of your users. Their Bash is created the first time you name them. With stream: true, the
# response streams this turn's events as they happen, and ends when the turn is over.
curl -N "$VESTRA_BASE_URL/v1/conversations" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "traveller-42",
    "message": "My 6am flight got cancelled. What should I do?",
    "stream": true
  }'

# Ask a follow-up. It goes to the same conversation.
curl -N "$VESTRA_BASE_URL/v1/conversations/conv_8f2k/messages" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Can I get a refund for it?", "stream": true }'

# Stop a reply and its work. This stops the reply and every task the conversation started.
curl -X POST "$VESTRA_BASE_URL/v1/conversations/conv_8f2k/stop" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Ask for your company. Leave user_id out, and the job goes to your company's own Bash.
curl "$VESTRA_BASE_URL/v1/conversations" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "message": "Write a two-line notice for our travellers about storm delays at Mumbai airport today." }'

# Ask for many users at once. Each user gets their own conversation. If Bash asks one of them a question,
# that item waits for the answer.
curl "$VESTRA_BASE_URL/v1/batches" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "user_id": "traveller-42", "message": "A storm may delay flights from Mumbai today. What should I do before I leave for the airport? Two lines, in general: no need to look up my booking." },
      { "user_id": "traveller-77", "message": "A storm may delay flights from Pune today. What should I do before I leave for the airport? Two lines, in general: no need to look up my booking." }
    ]
  }'
```

**MCP**

```json
{
  "method": "tools/call",
  "params": {
    "name": "ask",
    "arguments": {
      "user": "traveller-42",
      "message": "My 6am flight got cancelled. Get me on the next one."
    }
  }
}
```
