---
title: Machines
description: Create, get and list machines; drive their lifecycle; wait for a desktop; read the event log.
---

## Create

<CodeGroup>

```ts TypeScript
const machine = await client.machines.create({ name: "research", size: "small" });
```

```python Python
machine = client.machines.create(name="research", size="small")
```

</CodeGroup>

| Prop | Type | Default | Description |
| - | - | - | - |
| `name?` | `string` | - | Lowercase, digits and dashes. Generated when omitted. |
| `size?` | `"small" \| "standard" \| "large"` | `"small"` | A named size rather than free-form CPU and memory. The numbers behind each name are server-owned so they can change without a client release. |
| `template?` | `string` | - | A template id to start from. The machine boots that template's captured disk. The template pins the image; the size is still this call's to choose. |
| `imageId?` | `string` | - | TypeScript `imageId` / Python `image_id`. |
| `region?` | `string` | - | A placement hint. |
| `metadata?` | `Record<string, string>` | - | Caller-defined pairs, echoed back unchanged. |
| `start?` | `boolean` | `true` | Boot immediately. When false the machine is born `stopped`. |
| `idempotencyKey?` | `string` | - | TypeScript `idempotencyKey` / Python `idempotency_key`. Makes the call safe to retry. |

## Running is not ready

`create` returns once the machine is **running**. A machine has a working shell before it has a
working desktop, so `running` says nothing about whether the screen routes will answer.

<CodeGroup>

```ts TypeScript
const display = await machine.waitForDesktop();
display.width;
display.height;
```

```python Python
display = machine.wait_for_desktop()
display["width"], display["height"]
```

</CodeGroup>

`waitForDesktop` polls the display until it reports `up`. Until then that route answers
`compute_unavailable`, which is a normal boot state to wait through rather than a failure — see
[errors](/docs/sdk/errors#compute-unavailable-is-usually-not-a-failure).

To wait on a state instead:

<CodeGroup>

```ts TypeScript
await machine.waitForState("stopped", { timeoutMs: 60_000 });
await machine.waitForState(["running", "error"]);
```

```python Python
machine.wait_for_state("stopped", timeout=60)
machine.wait_for_state(["running", "error"])
```

</CodeGroup>

`error` is a rest state and is **returned rather than raised** when you ask for it — which states
are acceptable is your decision, not the method's. Ask for it and you get it; leave it out and it
throws.

Both take a timeout, a poll interval, and a cancellation handle (`signal` in TypeScript, `cancel`
in Python).

## Get and list

<CodeGroup>

```ts TypeScript
const machine = await client.machines.get("mch_...");

for await (const m of client.machines.list({ state: "running" })) {
  console.log(m.id, m.name);
}
```

```python Python
machine = client.machines.get("mch_...")

for m in client.machines.list(state="running"):
    print(m.id, m.name)
```

</CodeGroup>

`list` filters on `state` and `name`, and walks every page for you. See
[pagination](/docs/sdk/errors#pagination).

## Lifecycle

<CodeGroup>

```ts TypeScript
await machine.stop(); // keeps the disk
await machine.start();
await machine.restart();
await machine.refresh(); // replace the local snapshot with what the api reports
await machine.update({ name: "renamed", metadata: { owner: "ana" } });
await machine.delete(); // destroys the disk
```

```python Python
machine.stop()             # keeps the disk
machine.start()
machine.restart()
machine.refresh()          # replace the local snapshot with what the api reports
machine.update(name="renamed", metadata={"owner": "ana"})
machine.delete()           # destroys the disk
```

</CodeGroup>

A handle carries the last representation it saw. `machine.id`, `machine.state` and `machine.name`
read off it, and `machine.data` is the whole record. **Nothing polls in the background**, so a
state you read is a state the API actually reported — call `refresh()` when you want a newer one.

Starting is a normal boot, not a resume: nothing that was running survives a stop. See
[snapshots](/docs/sdk/snapshots#what-a-capture-holds).

## Events

<CodeGroup>

```ts TypeScript
for await (const event of machine.events()) {
  console.log(event.type, event.created_at);
}
```

```python Python
for event in machine.events():
    print(event["type"], event["created_at"])
```

</CodeGroup>

Newest first, cursor-paginated. The type list is a closed set — see
[machines](/docs/reference/machines#the-event-log) for all nineteen. The same events stream live
over [the gateway's events channel](/docs/reference/realtime).

## Cleaning up

<CodeGroup>

```ts TypeScript
await machine.close();
```

```python Python
with machine:
    ...
# or: machine.close()
```

</CodeGroup>

`close` releases the [input lease](/docs/sdk/input#the-input-lease) and closes the session. Call
it. Without it the machine holds a lease until it expires and nobody else can type.

`close` does not stop or delete the machine — it stays running and keeps its disk.
