# Users API

> Your users: set one up, read their spend, change their cap, export or erase them.

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

## `POST /v1/users`

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

Create one of your users, or return the existing one. Safe to repeat with the same user_id.

Your API key, scope `admin` or `all`. On the card: [`users`](https://vestra.ai/developers/users.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:

- `user_id` (`string`, required): Your own id for the user. Sending one that exists changes nothing and makes no second user. Example: `traveller-42`.
- `name` (`string`): The user's name, as Bash may call them: at most 200 characters. Limits: 1 to 200 characters. Example: `Priya Sharma`.
- `email` (`string (email)`): The user's email address. Limits: at most 320 characters. Example: `priya@example.com`.
- `locale` (`string`): The user's language and region, as a BCP 47 tag. Limits: 2 to 35 characters. Example: `en-IN`.
- `timezone` (`string`): The user's time zone, as an IANA name: their routines run in it. Limits: 1 to 64 characters. Example: `Asia/Kolkata`.
- `metadata` (`map of any`): Your own facts about the user, as JSON, given back as you sent them.

### Answers 200

- `id` (`string`): Your own id for the user. Example: `traveller-42`.
- `object` (`"user"`): Always user.
- `name` (`string | null`): The user's name; null when you gave none.
- `email` (`string | null`): The user's email address; null when you gave none.
- `locale` (`string | null`): The user's language and region (BCP 47); null when you gave none.
- `timezone` (`string | null`): The user's time zone (IANA); null when you gave none, and their routines then run in UTC.
- `metadata` (`map of any`): Your own facts about the user; empty when you gave none.
- `status` (`"active" | "disabled"`): active, or disabled: a disabled user's calls are refused (403 user_disabled) until you turn them back on.
- `spend_cap_usd` (`number | null`): The user's own cap per day, in billed US dollars, never above your company's; null: your company's cap for each user, if any.
- `monthly_cap_usd` (`number | null`): The user's own cap per calendar month (UTC), in billed US dollars; null: your company's cap for each user, if any.
- `default_deployment` (`string | null`): The deployment their conversations use when none is named (dep_...); null: your company's default.
- `memory_mode` (`"vestra" | "customer" | "none"`): Where Bash's memory of this user lives: `vestra`, `customer` (your memory endpoint), or `none`.
- `is_service_user` (`boolean`): true for a user Vestra keeps for itself, such as your company's own Bash; false for your users.
- `created_at` (`string`): When the user was made, as an ISO 8601 time.
- `spend_today_usd` (`number`): What they have spent today (UTC), in billed US dollars. Example: `1.24`.
- `spend_month_usd` (`number`): What they have spent this calendar month (UTC), in billed US dollars. Example: `18.6`.
- `caps_remaining` (`object`): What is left under every cap that applies to them: the smaller of their own and your company's.
  - `day_usd` (`number | null`): What they may still spend today, in US dollars; null when no cap applies.
  - `month_usd` (`number | null`): What they may still spend this month, in US dollars; null when no cap applies.

### Code

**TypeScript**

```ts
await vestra.users.create('traveller-42');
```

**Python**

```python
vestra.users.create("traveller-42")
```

**HTTP**

```bash
# Create a user. Do it before their first ask to set their name and time zone. It is safe to repeat with the
# same user_id.
curl "$VESTRA_BASE_URL/v1/users" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_id": "traveller-42", "name": "Priya Sharma", "timezone": "Asia/Kolkata" }'

# Read their spend. Their profile shows what they spent today and this month, against their caps.
curl "$VESTRA_BASE_URL/v1/users/traveller-42" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Set a monthly cap. This one is 20 US dollars.
curl -X PATCH "$VESTRA_BASE_URL/v1/users/traveller-42" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "monthly_cap_usd": 20 }'

# Wake their computer. Do it before they need it.
curl -X POST "$VESTRA_BASE_URL/v1/users/traveller-42/warm" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Open a page in their browser with no model, and keep the page's own data calls. The site must be in their
# deployment's browser.allowed_domains.
curl -X POST "$VESTRA_BASE_URL/v1/users/traveller-42/browser/fetch" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://www.ixigo.com/search/result/flight?from=DEL&to=GOI&date=06112026&adults=2&class=e", "capture": [{ "match": "/flights/v2/search/stream" }] }'

# Erase a user. It runs as a job: the response is 202 with deleted false and status deleting, and then
# user.deleted says it is done.
curl -X DELETE "$VESTRA_BASE_URL/v1/users/traveller-42" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**

```json
{
  "method": "tools/call",
  "params": {
    "name": "users_get",
    "arguments": { "user": "traveller-42" }
  }
}
```

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

## `GET /v1/users`

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

List your users.

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

### Answers 200

- `data` (`object[]`): The items on this page.
  - `id` (`string`): Your own id for the user. Example: `traveller-42`.
  - `object` (`"user"`): Always user.
  - `name` (`string | null`): The user's name; null when you gave none.
  - `email` (`string | null`): The user's email address; null when you gave none.
  - `locale` (`string | null`): The user's language and region (BCP 47); null when you gave none.
  - `timezone` (`string | null`): The user's time zone (IANA); null when you gave none, and their routines then run in UTC.
  - `metadata` (`map of any`): Your own facts about the user; empty when you gave none.
  - `status` (`"active" | "disabled"`): active, or disabled: a disabled user's calls are refused (403 user_disabled) until you turn them back on.
  - `spend_cap_usd` (`number | null`): The user's own cap per day, in billed US dollars, never above your company's; null: your company's cap for each user, if any.
  - `monthly_cap_usd` (`number | null`): The user's own cap per calendar month (UTC), in billed US dollars; null: your company's cap for each user, if any.
  - `default_deployment` (`string | null`): The deployment their conversations use when none is named (dep_...); null: your company's default.
  - `memory_mode` (`"vestra" | "customer" | "none"`): Where Bash's memory of this user lives: `vestra`, `customer` (your memory endpoint), or `none`.
  - `is_service_user` (`boolean`): true for a user Vestra keeps for itself, such as your company's own Bash; false for your users.
  - `created_at` (`string`): When the user was made, as an ISO 8601 time.
- `next_cursor` (`string | null`): Send it as `cursor` for the next page; null on the last page.

### Code

**TypeScript**

```ts
const users = await vestra.users.list();
```

**Python**

```python
users = vestra.users.list()
```

**CLI**

```bash
vestra users
```

**HTTP**

```bash
curl "$VESTRA_BASE_URL/v1/users" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

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

## `GET /v1/users/{user_id}`

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

One user: status, profile, memory mode, default deployment, and what they spent today and this month against their caps.

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

### Request

Path:

- `user_id` (`string`, required): Your own id for the user, as you named them.

### Answers 200

- `id` (`string`): Your own id for the user. Example: `traveller-42`.
- `object` (`"user"`): Always user.
- `name` (`string | null`): The user's name; null when you gave none.
- `email` (`string | null`): The user's email address; null when you gave none.
- `locale` (`string | null`): The user's language and region (BCP 47); null when you gave none.
- `timezone` (`string | null`): The user's time zone (IANA); null when you gave none, and their routines then run in UTC.
- `metadata` (`map of any`): Your own facts about the user; empty when you gave none.
- `status` (`"active" | "disabled"`): active, or disabled: a disabled user's calls are refused (403 user_disabled) until you turn them back on.
- `spend_cap_usd` (`number | null`): The user's own cap per day, in billed US dollars, never above your company's; null: your company's cap for each user, if any.
- `monthly_cap_usd` (`number | null`): The user's own cap per calendar month (UTC), in billed US dollars; null: your company's cap for each user, if any.
- `default_deployment` (`string | null`): The deployment their conversations use when none is named (dep_...); null: your company's default.
- `memory_mode` (`"vestra" | "customer" | "none"`): Where Bash's memory of this user lives: `vestra`, `customer` (your memory endpoint), or `none`.
- `is_service_user` (`boolean`): true for a user Vestra keeps for itself, such as your company's own Bash; false for your users.
- `created_at` (`string`): When the user was made, as an ISO 8601 time.
- `spend_today_usd` (`number`): What they have spent today (UTC), in billed US dollars. Example: `1.24`.
- `spend_month_usd` (`number`): What they have spent this calendar month (UTC), in billed US dollars. Example: `18.6`.
- `caps_remaining` (`object`): What is left under every cap that applies to them: the smaller of their own and your company's.
  - `day_usd` (`number | null`): What they may still spend today, in US dollars; null when no cap applies.
  - `month_usd` (`number | null`): What they may still spend this month, in US dollars; null when no cap applies.

### Code

**TypeScript**

```ts
const traveller = await vestra.user('traveller-42').get();
```

**Python**

```python
traveller = vestra.user("traveller-42").get()
```

**CLI**

```bash
vestra users traveller-42
```

**HTTP**

```bash
# Create a user. Do it before their first ask to set their name and time zone. It is safe to repeat with the
# same user_id.
curl "$VESTRA_BASE_URL/v1/users" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_id": "traveller-42", "name": "Priya Sharma", "timezone": "Asia/Kolkata" }'

# Read their spend. Their profile shows what they spent today and this month, against their caps.
curl "$VESTRA_BASE_URL/v1/users/traveller-42" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Set a monthly cap. This one is 20 US dollars.
curl -X PATCH "$VESTRA_BASE_URL/v1/users/traveller-42" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "monthly_cap_usd": 20 }'

# Wake their computer. Do it before they need it.
curl -X POST "$VESTRA_BASE_URL/v1/users/traveller-42/warm" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Open a page in their browser with no model, and keep the page's own data calls. The site must be in their
# deployment's browser.allowed_domains.
curl -X POST "$VESTRA_BASE_URL/v1/users/traveller-42/browser/fetch" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://www.ixigo.com/search/result/flight?from=DEL&to=GOI&date=06112026&adults=2&class=e", "capture": [{ "match": "/flights/v2/search/stream" }] }'

# Erase a user. It runs as a job: the response is 202 with deleted false and status deleting, and then
# user.deleted says it is done.
curl -X DELETE "$VESTRA_BASE_URL/v1/users/traveller-42" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**

```json
{
  "method": "tools/call",
  "params": {
    "name": "users_get",
    "arguments": { "user": "traveller-42" }
  }
}
```

## `PATCH /v1/users/{user_id}`

Its own page: [PATCH /v1/users/{user_id}](https://vestra.ai/developers/reference/api/users/patch-v1-users-user-id.md)

Change name, language, time zone, metadata, day or month spend cap, default deployment, or status (active or disabled).

Your API key, scope `admin` or `all`. On the card: [`users`](https://vestra.ai/developers/users.md). Every call here is a checked sample.

### Request

Path:

- `user_id` (`string`, required): Your own id for the user, as you named them.

Body:

- `name` (`string | null`): A new name; null clears it. Limits: 1 to 200 characters.
- `email` (`string (email) | null`): A new email address; null clears it. Limits: at most 320 characters.
- `locale` (`string | null`): A new language and region (BCP 47); null clears it. Limits: 2 to 35 characters.
- `timezone` (`string | null`): A new time zone (IANA); null clears it, and their routines then run in UTC. Limits: 1 to 64 characters.
- `metadata` (`map of any`): Your facts about the user: this takes the place of what was there.
- `spend_cap_usd` (`number | null`): Their own cap per day, in US dollars, never above your company's; null goes back to your company's cap for each user. Limits: 0 to 1000000.
- `monthly_cap_usd` (`number | null`): Their own cap per calendar month, in US dollars; null goes back to your company's cap for each user. Limits: 0 to 1000000.
- `default_deployment` (`string | null`): The deployment their conversations use when none is named: its id or name; null goes back to your company's default. Limits: at least 1 character.
- `status` (`"active" | "disabled"`): disabled refuses their calls (403 user_disabled) until you set active again.

### Answers 200

- `id` (`string`): Your own id for the user. Example: `traveller-42`.
- `object` (`"user"`): Always user.
- `name` (`string | null`): The user's name; null when you gave none.
- `email` (`string | null`): The user's email address; null when you gave none.
- `locale` (`string | null`): The user's language and region (BCP 47); null when you gave none.
- `timezone` (`string | null`): The user's time zone (IANA); null when you gave none, and their routines then run in UTC.
- `metadata` (`map of any`): Your own facts about the user; empty when you gave none.
- `status` (`"active" | "disabled"`): active, or disabled: a disabled user's calls are refused (403 user_disabled) until you turn them back on.
- `spend_cap_usd` (`number | null`): The user's own cap per day, in billed US dollars, never above your company's; null: your company's cap for each user, if any.
- `monthly_cap_usd` (`number | null`): The user's own cap per calendar month (UTC), in billed US dollars; null: your company's cap for each user, if any.
- `default_deployment` (`string | null`): The deployment their conversations use when none is named (dep_...); null: your company's default.
- `memory_mode` (`"vestra" | "customer" | "none"`): Where Bash's memory of this user lives: `vestra`, `customer` (your memory endpoint), or `none`.
- `is_service_user` (`boolean`): true for a user Vestra keeps for itself, such as your company's own Bash; false for your users.
- `created_at` (`string`): When the user was made, as an ISO 8601 time.
- `spend_today_usd` (`number`): What they have spent today (UTC), in billed US dollars. Example: `1.24`.
- `spend_month_usd` (`number`): What they have spent this calendar month (UTC), in billed US dollars. Example: `18.6`.
- `caps_remaining` (`object`): What is left under every cap that applies to them: the smaller of their own and your company's.
  - `day_usd` (`number | null`): What they may still spend today, in US dollars; null when no cap applies.
  - `month_usd` (`number | null`): What they may still spend this month, in US dollars; null when no cap applies.

### Code

**TypeScript**

```ts
await vestra.user('traveller-42').update({ monthly_cap_usd: 20 });
```

**Python**

```python
vestra.user("traveller-42").update(monthly_cap_usd=20)
```

**CLI**

```bash
vestra users traveller-42 --cap 20
```

**HTTP**

```bash
# Create a user. Do it before their first ask to set their name and time zone. It is safe to repeat with the
# same user_id.
curl "$VESTRA_BASE_URL/v1/users" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_id": "traveller-42", "name": "Priya Sharma", "timezone": "Asia/Kolkata" }'

# Read their spend. Their profile shows what they spent today and this month, against their caps.
curl "$VESTRA_BASE_URL/v1/users/traveller-42" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Set a monthly cap. This one is 20 US dollars.
curl -X PATCH "$VESTRA_BASE_URL/v1/users/traveller-42" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "monthly_cap_usd": 20 }'

# Wake their computer. Do it before they need it.
curl -X POST "$VESTRA_BASE_URL/v1/users/traveller-42/warm" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Open a page in their browser with no model, and keep the page's own data calls. The site must be in their
# deployment's browser.allowed_domains.
curl -X POST "$VESTRA_BASE_URL/v1/users/traveller-42/browser/fetch" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://www.ixigo.com/search/result/flight?from=DEL&to=GOI&date=06112026&adults=2&class=e", "capture": [{ "match": "/flights/v2/search/stream" }] }'

# Erase a user. It runs as a job: the response is 202 with deleted false and status deleting, and then
# user.deleted says it is done.
curl -X DELETE "$VESTRA_BASE_URL/v1/users/traveller-42" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**

```json
{
  "method": "tools/call",
  "params": {
    "name": "users_get",
    "arguments": { "user": "traveller-42" }
  }
}
```

## `DELETE /v1/users/{user_id}`

Its own page: [DELETE /v1/users/{user_id}](https://vestra.ai/developers/reference/api/users/delete-v1-users-user-id.md)

Erase the user and everything Bash holds for them: computer, files, memory. Runs as a job; a webhook says when it is done.

Your API key, scope `admin` or `all`. On the card: [`users`](https://vestra.ai/developers/users.md). Every call here is a checked sample.

### Request

Path:

- `user_id` (`string`, required): Your own id for the user, as you named them.

### Answers 202

- `id` (`string`): Your own id for the user.
- `object` (`"user"`): Always user.
- `deleted` (`false`): Always false here: erasing runs in the background, and user.deleted says when it is done.
- `status` (`"deleting"`): Always deleting.

### Code

**TypeScript**

```ts
await vestra.user('traveller-42').delete();
```

**Python**

```python
vestra.user("traveller-42").delete()
```

**CLI**

```bash
vestra users delete traveller-42 --yes
```

**HTTP**

```bash
# Create a user. Do it before their first ask to set their name and time zone. It is safe to repeat with the
# same user_id.
curl "$VESTRA_BASE_URL/v1/users" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_id": "traveller-42", "name": "Priya Sharma", "timezone": "Asia/Kolkata" }'

# Read their spend. Their profile shows what they spent today and this month, against their caps.
curl "$VESTRA_BASE_URL/v1/users/traveller-42" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Set a monthly cap. This one is 20 US dollars.
curl -X PATCH "$VESTRA_BASE_URL/v1/users/traveller-42" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "monthly_cap_usd": 20 }'

# Wake their computer. Do it before they need it.
curl -X POST "$VESTRA_BASE_URL/v1/users/traveller-42/warm" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

# Open a page in their browser with no model, and keep the page's own data calls. The site must be in their
# deployment's browser.allowed_domains.
curl -X POST "$VESTRA_BASE_URL/v1/users/traveller-42/browser/fetch" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://www.ixigo.com/search/result/flight?from=DEL&to=GOI&date=06112026&adults=2&class=e", "capture": [{ "match": "/flights/v2/search/stream" }] }'

# Erase a user. It runs as a job: the response is 202 with deleted false and status deleting, and then
# user.deleted says it is done.
curl -X DELETE "$VESTRA_BASE_URL/v1/users/traveller-42" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**

```json
{
  "method": "tools/call",
  "params": {
    "name": "users_get",
    "arguments": { "user": "traveller-42" }
  }
}
```

## `POST /v1/users/{user_id}/export`

Its own page: [POST /v1/users/{user_id}/export](https://vestra.ai/developers/reference/api/users/post-v1-users-user-id-export.md)

Package everything held for the user into one download, for data-rights requests.

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

### Request

Path:

- `user_id` (`string`, required): Your own id for the user, as you named them.

### Answers 200

- `object` (`"user_export"`): Always user_export.
- `url` (`string`): A link to everything Vestra holds for the user, as one JSON file: good for 15 minutes.
- `expires_at` (`string`): When the link stops working, as an ISO 8601 time.
- `bytes` (`integer`): The size of the file, in bytes.
- `truncated` (`boolean`): true when the user has more than the 200 tasks it holds: the oldest are left out.

### Code

**TypeScript**

```ts
const download = await vestra.user('traveller-42').export();
```

**Python**

```python
download = vestra.user("traveller-42").export()
```

**CLI**

```bash
vestra users export traveller-42
```

**HTTP**

```bash
curl -X POST "$VESTRA_BASE_URL/v1/users/{user_id}/export" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

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