# Screens

> What a screen is, its two kinds, templates, snapshots, its states, and how power, delete and export work.

A **screen** is a Linux desktop (Debian 13 in Ubuntu's look, with Chromium, Firefox and the usual tools) in its own virtual machine. It keeps running until you stop it, and its disk is yours: files, browser logins and settings stay between sessions. You have root inside it.

## Two kinds

Every screen is the same machine. What differs is how you pay for it and how long it stays.

| | `persistent` (the default) | `disposable` |
|---|---|---|
| Paid | Monthly, one price per screen, running or stopped | From usage credit, for the time it runs |
| Stays | Until you delete it | Until its end time, or 7 days after it stops |
| Delete | Undo within 7 days | Final, gone at once |
| Good for | An agent that lives on its screen | Runs, benchmarks, short jobs |

Pick the kind when you create the screen:

```bash
curl -X POST https://api.trykubi.com/v1/screens \
  -H "Authorization: Bearer $KUBI_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"kind": "disposable", "ends_in_s": 3600}'
```

- **End time:** `ends_in_s` (5 minutes to 30 days) deletes a disposable screen when the time is up. Move or clear it later with `PATCH /screens/{id}` and `ends_at`. The screen's `ends_at` shows it; `null` means no end time.
- **Stopped:** a stopped disposable screen pays a small fee for its disk, and `delete_after` says when it goes unless you start it again or make it persistent.
- **Switch kinds** with `POST /screens/{id}/convert`, either way, with no restart. To `persistent`, it joins the monthly bill and its end time goes; to `disposable`, it leaves the bill and draws on credit from now.
- **List one kind** with `GET /screens?kind=disposable`. Each screen's `created_by` says which person, key or app made it.
- What each kind costs and what happens when credit runs out: [Billing and history](/concepts/billing-and-history).

## Templates

Save any screen as a **template**, then start new screens of either kind from it, already set up the way you left it:

```bash
curl -X POST https://api.trykubi.com/v1/templates \
  -H "Authorization: Bearer $KUBI_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"screen_id": "scr_7f2kq9w3x8m4b6tzp1ac", "name": "osworld-base"}'
```

Then pass its id as `template_id` when you create a screen.

- Saving returns an operation; the template shows as `saving` until it is `ready`. A running screen pauses for a few seconds while it is saved.
- A template holds the screen's disk. `with_memory` says whether screens started from it also come back with the windows that were open.
- It keeps whatever was installed on the screen, browser logins done by hand included, so treat it as you would the screen. It never holds a secret: your organization's secrets are put into a screen at every start and are never written to its disk.
- Your organization has a template cap; over it, saving gets `402 QUOTA_EXCEEDED`. Deleting a template leaves the screens started from it running.

## Snapshots and reset

Save a screen's disk as a **snapshot** before risky work, then **reset** to it if something breaks:

```bash
curl -X POST https://api.trykubi.com/v1/screens/scr_7f2kq9w3x8m4b6tzp1ac/snapshots \
  -H "Authorization: Bearer $KUBI_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name": "before-upgrade"}'
```

- A running screen keeps running. The snapshot holds the disk as a sudden power cut would leave it, never memory or a secret.
- Up to 3 per screen, holding at most 50 GB of changed data, included in the price. Over that, `402 QUOTA_EXCEEDED`: delete one first.
- `POST /screens/{id}/reset` with a `snapshot_id` puts the disk back to it and starts the screen; with `null`, back to how the screen started. Work since then is lost, and so are the snapshots taken after that point. The id, name, kind, bill, proxy and secrets stay.
- Snapshots live with the screen and go when it is deleted. They are not in backups or exports, so keep your own copies of anything important.
- API keys need `power`; agents signed in through MCP can't reset.

## States

| State | Meaning |
|---|---|
| `provisioning` | Being created: a blank screen is ready in seconds, one from a template boots its disk |
| `running` | Ready for screenshots, actions and commands |
| `stopping`, `stopped` | Shut down; the disk is kept |
| `starting`, `restarting` | Booting again |
| `deleting`, `deleted` | Going away; a persistent screen sits in the bin for 7 days first |
| `failed` | Something broke; `state_reason` says what |

A stopped screen's `state_reason` says who stopped it: `stopped_by_user`, `stopped_by_billing`, `stopped_by_credit` or `stopped_by_admin`. New states and reasons may appear later, so treat an unknown state like `failed` and show an unknown reason as words.

## Slow work returns an operation

Create, start, stop, restart, delete, switching kind, saving a template, taking a snapshot and reset answer at once with `202` and an **operation**. Poll it until it is done:

```bash
curl "https://api.trykubi.com/v1/operations/op_01jb7q0x9v2k8m4n6p3r?wait_s=25" \
  -H "Authorization: Bearer $KUBI_API_KEY"
```

With `wait_s`, the call holds for up to 25 seconds and returns as soon as something changes, so you never busy-loop. The SDK does it for you with `client.operations.wait(op.id)`.

## Deleting

Deleting a persistent screen is soft: it goes to the bin for 7 days and you can restore it from the dashboard. Deleting a disposable screen is final. API keys need the `power` permission to delete; agents signed in through MCP can never delete.

## Taking your data with you

A person can export a screen's disk from the dashboard. The download link shows in the dashboard and arrives by email, and works for 7 days. Your account data downloads as JSON, and invoices come from Stripe.
