# Batches API

> Many users at once: one ask each, followed together.

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

## `POST /v1/batches`

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

Start a batch: a deployment, a list of items (user_id optional, message, context, metadata) and a concurrency. Each item is one conversation message with hand_off: always.

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:

- `deployment` (`string`): Which deployment answers every item: its id or name, never a pinned version. Absent: your company's default deployment. Limits: at least 1 character.
- `items` (`object[]`, required): The jobs: 1 to 5,000, each one conversation. Limits: 1 to 5000 items.
  - `user_id` (`string`): Your id for the user this item is for. Absent: your company's own Bash. Example: `traveller-42`.
  - `message` (`string`, required): What Bash is asked, in at most 32,000 characters. It goes straight to work. Limits: 1 to 32000 characters. Example: `Is your flight hit by the storm?`.
  - `context` (`map of any`): Facts for this item only, as JSON: Bash reads them as data, never as instructions.
  - `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"}`.
- `concurrency` (`integer`): How many items run at once, 1 to 100. Default: 10. Items with no user share your company's own Bash, 5 at a time at most. Default: `10`. Limits: 1 to 100.
- `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"}`.

### Answers 201

- `id` (`string`): The batch (bat_...). Example: `bat_7xk`.
- `object` (`"batch"`): Always batch.
- `status` (`"running" | "cancelled" | "completed"`): running: items are still starting or running. cancelled: you cancelled it, so no new item starts. completed: every item has finished.
- `deployment_id` (`string | null`): The deployment that answers its items (dep_...); null when none applies.
- `concurrency` (`integer`): How many items run at once.
- `item_count` (`integer`): How many items it has.
- `counts` (`object`): Its items, by status.
  - `queued` (`integer`): Items not started yet.
  - `running` (`integer`): Items running now.
  - `succeeded` (`integer`): Items that finished well.
  - `failed` (`integer`): Items that failed.
  - `cancelled` (`integer`): Items a cancel kept from starting.
- `spend_usd` (`number`): What it has cost so far, in billed US dollars: every item's conversation and all its work. Example: `0.061`.
- `metadata` (`map of string`): Your labels, as you sent them; empty when you sent none.
- `created_at` (`string`): When it was made, as an ISO 8601 time.
- `completed_at` (`string | null`): When it stopped running, completed or cancelled, as an ISO 8601 time; null while running.

### Code

**TypeScript**

```ts
const batch = vestra.ask([{ user: 'traveller-42', message: 'Is your flight hit by the storm?' }]);
await batch.created;
```

**Python**

```python
batch = vestra.ask([{"user": "traveller-42", "message": "Is your flight hit by the storm?"}])
created = batch.created
```

**CLI**

```bash
vestra ask "Is your flight hit by the storm?" --users travellers.csv
```

**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/batches/{batch_id}`

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

Progress: counts by status, spend so far.

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

Path:

- `batch_id` (`string`, required): The batch's id (`bat_...`).

### Answers 200

- `id` (`string`): The batch (bat_...). Example: `bat_7xk`.
- `object` (`"batch"`): Always batch.
- `status` (`"running" | "cancelled" | "completed"`): running: items are still starting or running. cancelled: you cancelled it, so no new item starts. completed: every item has finished.
- `deployment_id` (`string | null`): The deployment that answers its items (dep_...); null when none applies.
- `concurrency` (`integer`): How many items run at once.
- `item_count` (`integer`): How many items it has.
- `counts` (`object`): Its items, by status.
  - `queued` (`integer`): Items not started yet.
  - `running` (`integer`): Items running now.
  - `succeeded` (`integer`): Items that finished well.
  - `failed` (`integer`): Items that failed.
  - `cancelled` (`integer`): Items a cancel kept from starting.
- `spend_usd` (`number`): What it has cost so far, in billed US dollars: every item's conversation and all its work. Example: `0.061`.
- `metadata` (`map of string`): Your labels, as you sent them; empty when you sent none.
- `created_at` (`string`): When it was made, as an ISO 8601 time.
- `completed_at` (`string | null`): When it stopped running, completed or cancelled, as an ISO 8601 time; null while running.

### Code

**TypeScript**

```ts
const batch = vestra.ask([{ user: 'traveller-42', message: 'Is your flight hit by the storm?' }]);
const progress = await batch.get();
```

**Python**

```python
batch = vestra.ask([{"user": "traveller-42", "message": "Is your flight hit by the storm?"}])
progress = batch.get()
```

**CLI**

```bash
vestra events bat_7xk
```

**HTTP**

```bash
# Its status and counts, then each item's text: the work's answer.
curl "$VESTRA_BASE_URL/v1/batches/bat_7xk" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl "$VESTRA_BASE_URL/v1/batches/bat_7xk/items" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**: No sample in your language for this route, so here it is over HTTP.

## `GET /v1/batches/{batch_id}/items`

Its own page: [GET /v1/batches/{batch_id}/items](https://vestra.ai/developers/reference/api/batches/get-v1-batches-batch-id-items.md)

Each item with its task id and result.

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

Path:

- `batch_id` (`string`, required): The batch's id (`bat_...`).

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.
  - `object` (`"batch.item"`): Always batch.item.
  - `index` (`integer`): Where it was in the items you sent, from 0.
  - `user_id` (`string | null`): Your id for its user; null on your company's own Bash.
  - `status` (`"queued" | "running" | "succeeded" | "failed" | "cancelled"`): queued: not started yet. running: its conversation is working. succeeded or failed: it has finished. cancelled: the batch was cancelled before it started.
  - `conversation_id` (`string | null`): The conversation it runs in (conv_...); null until it starts.
  - `task_id` (`string | null`): The work it started (task_...); null until it starts work.
  - `result` (`object | null`): What it came to, once it succeeded; null before, and when it failed.
    - `reply` (`string | null`): What Bash said when it took the item: an item goes straight to work, so this is the one line Started work on it. The work's answer is text.
    - `text` (`string | null`): The work's answer: its final reply, the words of its last step that said anything. Null when the item did not go to work (its reply is the answer then) or the work ended with no final reply. Example: `Your flight 6E 2134 is on time.`.
    - `data` (`any | null`): Data the work returned for an output_schema; null when it returned none.
    - `outcome` (`object | null`): Whether it met its success criteria; null when there were none.
  - `error` (`object | null`): Why it failed; null unless it did.
    - `code` (`string`): A stable code to branch on.
    - `message` (`string`): What went wrong, in plain words.
  - `metadata` (`map of string`): Your labels for this item, as you sent them.
  - `created_at` (`string`): When the batch was made, as an ISO 8601 time.
  - `finished_at` (`string | null`): When it finished, as an ISO 8601 time; null until then.
- `next_cursor` (`string | null`): Send it as `cursor` for the next page; null on the last page.

### Code

**TypeScript**

```ts
const batch = vestra.ask([{ user: 'traveller-42', message: 'Is your flight hit by the storm?' }]);
const results: unknown[] = [];
for await (const item of batch.items()) results.push(item.result);
```

**Python**

```python
batch = vestra.ask([{"user": "traveller-42", "message": "Is your flight hit by the storm?"}])
results = [item.result for item in batch.items()]
```

**CLI**

```bash
vestra events bat_7xk
```

**HTTP**

```bash
# Its status and counts, then each item's text: the work's answer.
curl "$VESTRA_BASE_URL/v1/batches/bat_7xk" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl "$VESTRA_BASE_URL/v1/batches/bat_7xk/items" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**: No sample in your language for this route, so here it is over HTTP.

## `POST /v1/batches/{batch_id}/cancel`

Its own page: [POST /v1/batches/{batch_id}/cancel](https://vestra.ai/developers/reference/api/batches/post-v1-batches-batch-id-cancel.md)

Stop starting new items; running ones finish.

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 each {…} and every value.

### Request

Path:

- `batch_id` (`string`, required): The batch's id (`bat_...`).

Headers:

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

### Answers 200

- `id` (`string`): The batch (bat_...). Example: `bat_7xk`.
- `object` (`"batch"`): Always batch.
- `status` (`"running" | "cancelled" | "completed"`): running: items are still starting or running. cancelled: you cancelled it, so no new item starts. completed: every item has finished.
- `deployment_id` (`string | null`): The deployment that answers its items (dep_...); null when none applies.
- `concurrency` (`integer`): How many items run at once.
- `item_count` (`integer`): How many items it has.
- `counts` (`object`): Its items, by status.
  - `queued` (`integer`): Items not started yet.
  - `running` (`integer`): Items running now.
  - `succeeded` (`integer`): Items that finished well.
  - `failed` (`integer`): Items that failed.
  - `cancelled` (`integer`): Items a cancel kept from starting.
- `spend_usd` (`number`): What it has cost so far, in billed US dollars: every item's conversation and all its work. Example: `0.061`.
- `metadata` (`map of string`): Your labels, as you sent them; empty when you sent none.
- `created_at` (`string`): When it was made, as an ISO 8601 time.
- `completed_at` (`string | null`): When it stopped running, completed or cancelled, as an ISO 8601 time; null while running.

### Code

**TypeScript**

```ts
const batch = vestra.ask([{ user: 'traveller-42', message: 'Is your flight hit by the storm?' }]);
await batch.cancel();
```

**Python**

```python
batch = vestra.ask([{"user": "traveller-42", "message": "Is your flight hit by the storm?"}])
batch.cancel()
```

**HTTP**

```bash
curl -X POST "$VESTRA_BASE_URL/v1/batches/{batch_id}/cancel" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**CLI, MCP**: No sample in your language for this route, so here it is over HTTP.
