# Computer setup API

> Wake a user's computer before they need it.

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

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

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

Wake the user's computer now (for example when they open your app), so their next task starts without waiting. Answers 200 when it is already up and 202 while it wakes. At most 10 computers wake at once per company (429 warm_limit), and a second call for one user within a minute rides the first. keep_ms keeps it awake that long once it is up, up to two minutes; calling again extends it.

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.

Body, if you send one:

- `keep_ms` (`integer`): Keep the computer awake this long once it is up, in ms: 1000 to 120000. Absent: it sleeps again soon after if nothing uses it. Calling warm again extends it. Limits: 1000 to 120000.

### Answers 200 or 202

- `user_id` (`string`): Your id for the user whose computer it is.
- `status` (`"ready" | "waking"`): `ready`: the computer is up. `waking`: a wake started now; their next task starts without waiting once it lands.

### Code

**TypeScript**

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

**Python**

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

**CLI**

```bash
vestra users warm 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" }
  }
}
```

## `POST /v1/users/{user_id}/browser/fetch`

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

Open one page in a background tab of the user's own browser, with no model, and get back the bodies of the page's own data calls that match capture. Only on sites the user's deployment lists in browser.allowed_domains (403 domain_not_allowed), and never on a page in its browser.blocked_paths, such as a payment page (403 page_blocked). Wakes the computer when it sleeps. Answers as soon as every required capture has its responses, with timings; 409 browser_in_use while a person drives the browser, 409 browser_busy past four at once; 504 timeout carries what came back in partial. frames returns small pictures of the tab at each milestone. A page_fetch step calls one of the deployment's sites from inside the page, as the page's own script would; keep_open_ms keeps the tab for the next fetch, which sends no url (409 no_open_tab when none is kept).

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.

Body:

- `url` (`string (URL)`): The page to open, http or https, on a site the user's deployment allows (browser.allowed_domains). Absent: the steps run on the tab an earlier fetch kept open (keep_open_ms), as it is; 409 no_open_tab when none is kept. Limits: at most 4000 characters. Example: `https://www.ixigo.com/search/result/flight?from=DEL&to=GOI&date=06112026&adults=4&class=e`.
- `capture` (`object[]`): The responses to keep, up to 10. The fetch answers as soon as every step is done and every required capture has its responses. Absent: none, for a fetch made of page_fetch steps. Limits: at most 10 items.
  - `match` (`string`, required): Part of the address of a response to keep, such as `/api/v3/search`: a finished response whose address contains it is kept. Ending in `$`, it must end the path instead (the query aside), so `/v3/services$` keeps `/v3/services` and not `/v3/services/meta/105`. Limits: 1 to 500 characters. Example: `/flights/v2/search/stream`.
  - `required` (`boolean`): Whether the fetch waits for it. Default true: the fetch answers as soon as every required capture has min_count responses. false: kept if it comes before then, never waited for.
  - `min_count` (`integer`): How many responses a required capture waits for, 1 to 10. Default 1. A page that loads its second page by itself (hotels) needs 2. Limits: 1 to 10.
  - `max_count` (`integer`): The most responses it keeps, 1 to 10. Default min_count, or 1. Limits: 1 to 10.
  - `max_bytes` (`integer`): The most bytes of each body to keep, up to 1048576, or up to 4194304 with sse "last". Default 524288; a longer body comes back cut, with truncated true. Limits: 1 to 4194304.
  - `sse` (`"last"`): For an event stream (text/event-stream) whose every event is the whole answer so far: "last" keeps only its last complete event, as a stream of that one event, and max_bytes is that event's limit. When the last event is past max_bytes, or the stream ends partway through one, the newest complete event that fits comes back instead, with truncated true. A body that is not an event stream is kept as it is. Absent: the body as it came.
- `steps` (`object[]`): What to do on the page after it opens, in order, up to 20. Absent: nothing; the page is only opened. Limits: at most 20 items.
- `frames` (`object`): Small pictures of the tab as it works: when the page first shows, when each required capture arrives, after each step, every 0.7 s, and at the end. At most 6 and about 400 KB in all. Absent: none.
  - `width` (`integer`): How wide each frame is, in pixels: 160 to 1280. Default 640. Limits: 160 to 1280.
  - `settle_ms` (`integer`): Once every required capture is in, keep taking frames, labelled `results`, about every 170 ms for this long while the page draws what came, then answer: 0 to 700 ms. A page can blank for a moment as it draws; a run of frames catches the drawn one. Default 0: answer at once, and the last frame may still show the page loading. Limits: 0 to 700.
- `timeout_ms` (`integer`): How long the page may take, from when it starts opening, in ms: 1000 to 20000. Default 8000. Waking the computer comes on top. Limits: 1000 to 20000.
- `grace_ms` (`integer`): Once every required capture has its min_count, wait up to this long for every capture to fill to its max_count, optional ones too, then answer with what came: 0 to 3000 ms. Default 0: answer at once. Limits: 0 to 3000.
- `keep_open_ms` (`integer`): Keep this fetch's tab open on its page this long after it answers, with the computer awake, so the next fetch with no url runs there at once: 0 to 600000 ms. Default 0. Limits: 0 to 600000.
- `tab` (`string`): Which kept tab: keep_open_ms keeps this tab under the name, and a fetch with no url takes the tab of that name. Up to 4 are kept. Default `default`. Example: `stays`.
- `tag` (`string`): Your label for this fetch; the browser watch names its tab with it.
- `front` (`boolean`): A hand-off the person finishes themselves, such as a sign-in: the tab's window opens in front of theirs, maximized, with the keyboard, and with keep_open_ms stays there for them. Default false: behind their window.
- `front_device` (`"phone" | "desktop"`): With front: `phone` (default) or `desktop`, the site's desktop page, as the other fetches see it, at a phone's shape (440 wide, as high as the window): a sign-in service's tab it opens draws into the same part of the screen, so a session's screen shows one rectangle whatever is in front.
- `front_crop` (`object`): On a hand-off (front, or a fetch with no url on its kept tab): a session's screen shows only this element of the page, such as a site's sign-in dialog at a phone's size. From a front fetch's start until the element is on the page, the screen answers `crop_pending` instead of a crop, for up to 20 seconds; past that it shows the page as before.
  - `role` (`string`): The element's accessibility role, such as `dialog`. Limits: 1 to 40 characters. Example: `dialog`.
  - `name` (`string`): Its accessible name, exactly. Limits: 1 to 200 characters.
  - `text` (`string`): Words its accessible name contains. Limits: 1 to 200 characters.
- `stage` (`boolean`): In front, in one kept tab (`tab`), shown alone in the watch.
- `private` (`boolean`): Never shown in the browser watch, and no frames taken: a person's own account page, say.

### Answers 200

- `final_url` (`string | null`): The page's address when the fetch ended, after any redirects and steps; null if it never opened.
- `elapsed_ms` (`integer`): How long the whole call took, in ms, waking included.
- `woke` (`boolean`): true when the computer was asleep and this call woke it.
- `timings` (`object`): Where the time went, each in ms after the call arrived.
  - `wake_ms` (`integer`): How long waking the computer took: 0 when it was up.
  - `nav_ms` (`integer | null`): When the page started showing (its navigation committed); null if it never did, or when the fetch ran on a kept tab with nothing to open.
  - `first_capture_ms` (`integer | null`): When the first kept response finished; null if none did.
  - `done_ms` (`integer`): When the computer finished the fetch.
- `captures` (`object[]`): Every response kept, in the order they finished.
  - `match` (`string`): The capture this response answers, or the page_fetch step's capture_as.
  - `url` (`string`): The full address of the response.
  - `status` (`integer`): Its HTTP status.
  - `content_type` (`string | null`): Its content type, such as application/json; null when it had none.
  - `body` (`string`): Its body, as text.
  - `truncated` (`boolean`): true when the body was longer than max_bytes, or the fetch had kept 8000000 bytes in all, and it was cut. With sse "last": true when the event kept is not the stream's last one (the last was too long, or the stream ended partway through it).
  - `at_ms` (`integer`): When it finished, in ms after the call arrived.
- `frames` (`object[]`, may be absent): With `frames` asked: the pictures, in the order they were taken. A frame that would have slowed the answer is left out.
  - `label` (`string`): Which moment: `navigated`, `captured <match>`, `after <step>` or `done`. Example: `navigated`.
  - `at_ms` (`integer`): When it was taken, in ms after the call arrived.
  - `page_url` (`string`): The page's address when it was taken; always on an allowed site.
  - `jpeg_base64` (`string`): The picture, a JPEG, as base64.
- `step` (`string`, may be absent): With a `timeout`: where the fetch was when the time ran out: `navigate`, a step by its number and op such as `2 scroll`, or `captures` when every step was done and it was waiting for the required captures. Example: `2 scroll`.
- `challenge` (`"turnstile"`, may be absent): `turnstile`: the page called Cloudflare's Turnstile check.

### Code

**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" }
  }
}
```

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

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

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

Which cookies of some names the user's browser holds for one site, and until when: names, sites and expiry only, never a value. Only a site on the deployment's browser.allowed_domains (403 domain_not_allowed). Opens no page and acts on nothing, so it never disturbs a person using the browser: read it every few seconds to see a person finish signing in on a page you handed them (front), as the site's session cookie appears or its expiry moves on. Wakes the computer when it sleeps.

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

### Request

Path:

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

Query:

- `domain` (`string`, required): The site, a bare host name on the deployment's browser.allowed_domains, such as `ixigo.com`. Cookies of its subdomains count too.
- `names` (`string`, required): The cookie names to look for, comma-separated, up to 10.

### Answers 200

- `cookies` (`object[]`): The cookies of those names the browser holds for the site and its subdomains. Never a value. Empty when it holds none.
  - `name` (`string`): The cookie's name, one of those asked for.
  - `domain` (`string`): The host the cookie is set for, with no leading dot.
  - `expires_at` (`string | null`): When it expires, as an ISO 8601 time; null for a cookie that lasts only while the browser runs.
- `woke` (`boolean`): true when the computer was asleep and this call woke it.

### Code

**HTTP**

```bash
curl "$VESTRA_BASE_URL/v1/users/{user_id}/browser/cookies?domain=...&names=..." \
  -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/users/{user_id}/browser/watch`

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

Watch the browser fetch's tabs live, as text/event-stream. Read only: allowed sites only, a tab on a blocked path is hidden, and it never wakes the computer.

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

Query:

- `fps` (`integer`): Pictures a second, 1 to 10.
- `width` (`integer`): Picture width, 320 to 1280.

Headers:

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

### Answers 200

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

### Code

**HTTP**

```bash
curl -N "$VESTRA_BASE_URL/v1/users/{user_id}/browser/watch" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

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