---
title: Sandboxes quickstart
description: List the shapes, create a lease, run a command, write and read a file, and release the machine.
type: tutorial
product: sandboxes
summary: From an empty environment to a command running in a lease, in five calls
updated: 2026-09-28
order: 1
---

This takes an environment to a machine running your command, and then gives the
machine back. Every step exists in the console too, but the calls are the
clearest statement of what is happening.

<Prerequisites
	items={[
		'An account, and a project with an environment — see the platform start page',
		'A key with the sandboxes:write and sandboxes:exec scopes, or the console',
		'A shape from the catalog that offers the kind of machine you want',
	]}
/>

## 1. Find a shape

The shape catalog is read-only, and it is the list of machines you may ask for:

```bash
sylphx sandboxes sandbox-shapes list
```

```bash
curl "https://api.sylphx.com/v1/sandbox_shapes" \
  -H "Authorization: Bearer $SYLPHX_API_KEY"
```

```ts
const response = await sylphx.sandboxes.sandboxShapes.list({})
```

A shape carries the operating system, the vCPUs, the memory, the disk, the
Cell capability Placement matches, the `kinds` it can serve, and
`min_lease_duration` and `max_lease_duration` — the shortest and longest lease
of it. `offered` says whether the shape is sold now, and a lease naming one
that is not is refused `SHAPE_NOT_OFFERED`. [The collection
page](/docs/api/sandbox_shapes) lists every field.

## 2. Create a lease

A lease is created under an environment, names a shape, and is granted now or
refused. This one asks for `sandbox_shapes/linux-xs` — one vCPU, 1024 MiB of
memory and a 5 GiB disk on the `kata` capability — and runs it for ten
minutes:

```bash
sylphx sandboxes leases create \
  --parent orgs/acme/projects/shop/envs/production \
  --spec.shape sandbox_shapes/linux-xs \
  --spec.ttl 10m
```

```bash
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/leases" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"spec":{"shape":"sandbox_shapes/linux-xs","ttl":"10m"}}'
```

```ts
const response = await sylphx.sandboxes.leases.create({
  lease: { spec: { shape: 'sandbox_shapes/linux-xs', ttl: '10m' } },
  parent: 'orgs/acme/projects/shop/envs/production',
})
```

`ttl` is required: it is the longest the lease may live, counted from the
grant. A microVM takes a `ttl` from one minute to 24 hours.

`create` waits for `READY` and gives up on the wait after 60 seconds; pass
`skip_wait_ready` to be answered at `GRANTED` instead and poll the lease
yourself. A machine that cannot be granted this second is refused, not
queued — the answer carries `status.refusal`, one of `no_capacity`,
`no_matching_cell`, `shape_not_offered`, `shape_not_entitled`,
`image_not_found` or `abuse_hold`.

## 3. Run a command

`exec` runs one command in a ready lease and answers when it finishes, with
`exit_code`, `stdout` and `stderr`:

```bash
sylphx sandboxes leases exec \
  orgs/acme/projects/shop/envs/production/leases/lease \
  --command echo --command hello
```

```bash
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/leases/lease:exec" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command":["bash","-lc","echo hello"]}'
```

```ts
const response = await sylphx.sandboxes.leases.exec({
  command: ['bash', '-lc', 'echo hello'],
  name: 'orgs/acme/projects/shop/envs/production/leases/lease',
})
```

The command does not go through a shell unless you ask for one: the request
takes an argument list, and `["bash", "-lc", "…"]` is the shape to use when
you want shell syntax. The default timeout is 60 seconds and the longest is
10 minutes; a command that outlives both belongs behind `guest_uri`, which
serves the guest's own port, not in `exec`.

<Callout tone="note" title="Decode the streams before you print them">
`stdout` and `stderr` are bytes on the wire, so they arrive base64-encoded,
and the generated TypeScript type declares them as plain strings. Decode
before printing, or a byte count will look right and the text will not.
</Callout>

## 4. Write and read a file

Files go in with `write_file` and come back with `read_file`, at most 16 MiB
at a time. Missing parent directories are created for you.

```bash
sylphx sandboxes leases write-file \
  orgs/acme/projects/shop/envs/production/leases/lease \
  --path /home/user/hello.txt \
  --content hello
```

```bash
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/leases/lease:writeFile" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"path":"/home/user/hello.txt","content":"…"}'
```

```ts
const response = await sylphx.sandboxes.leases.writeFile({
  name: 'orgs/acme/projects/shop/envs/production/leases/lease',
  path: '/home/user/hello.txt',
  content: 'hello',
})
```

Reading it back is the same call the other way:

```bash
sylphx sandboxes leases read-file \
  orgs/acme/projects/shop/envs/production/leases/lease \
  --path /home/user/hello.txt
```

```bash
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/leases/lease:readFile" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"path":"/home/user/hello.txt"}'
```

```ts
const response = await sylphx.sandboxes.leases.readFile({
  name: 'orgs/acme/projects/shop/envs/production/leases/lease',
  path: '/home/user/hello.txt',
})
```

A file written this way lives on the lease's own disk. When the lease ends the
disk goes with it — a [volume](/docs/sandboxes/volumes-and-snapshots) is what
makes work outlive a lease.

## 5. Release the lease

```bash
sylphx sandboxes leases release \
  orgs/acme/projects/shop/envs/production/leases/lease \
  --yes
```

```bash
curl -X POST "https://api.sylphx.com/v1/orgs/acme/projects/shop/envs/production/leases/lease:release" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

```ts
const response = await sylphx.sandboxes.leases.release({
  name: 'orgs/acme/projects/shop/envs/production/leases/lease',
})
```

`release` ends the lease now and answers with the ended lease, whose
`end_reason` is `END_REASON_RELEASED`. The machine is destroyed and never
leased again. Leaving the lease alone instead ends it at its `ttl`, and the
`end_reason` says which one happened.

The lease's own history outlives it, so the last call of the run is the one
that says what happened to the machine:

```bash
sylphx sandboxes lease-events list \
  orgs/acme/projects/shop/envs/production/leases/lease
```

## Next

<RelatedDocs
	links={[
		{
			href: '/docs/sandboxes/leases',
			label: 'Lease lifecycle',
			description: 'The states, the two clocks, the budgets and the event stream.',
		},
		{
			href: '/docs/sandboxes/computer-use',
			label: 'Computer use',
			description: 'Screens, streams, control, screenshots and Android apps.',
		},
		{
			href: '/docs/api/leases',
			label: 'The leases collection',
			description: 'Every method, its scope and its examples.',
		},
		{
			href: '/docs/cli/leases',
			label: 'The CLI reference',
			description: 'Every verb and flag of sylphx sandboxes.',
		},
	]}
/>
