# API reference

> The shedix HTTP API: create, start, stop and delete machines, wait until they're ready, SSH keys, keep awake, tokens and each machine's files and ports.

The shedix page uses this API, and so can your agents and scripts. Everything is JSON over HTTPS at `https://shedix.dev/api`.
For a walk-through an agent can follow, see [the agents' guide](/docs/agents).

## Authentication

Send a token in the `Authorization` header. Make one on [your profile](/profile), or use a Copy prompt button, which
puts a new one in a ready prompt.

```bash
curl -sS https://shedix.dev/api/machines -H "Authorization: Bearer shedix_..."
```

A token can do anything you can do with your machines, except make more tokens. It stops working a year after its last
use. Every call made with it is listed on your profile, where you can revoke it.

## Machines

| Method | Path | Does |
|---|---|---|
| GET | `/api/machines` | Your machines, newest first: `{"machines": [...]}`. Add `?project=acme/web` for one project's |
| POST | `/api/machines` | Create one. Body: `size`, and optionally `stack`, `diskGb`, `sshKey`, `project`, `label`, and `image` for the `plain` stack. Add `?wait=N` to answer once it's ready, or after N seconds (up to 300) |
| GET | `/api/machines/:name` | One machine. `?wait=N` as above |
| POST | `/api/machines/:name/start` | Start a stopped machine. `?wait=N` as above |
| POST | `/api/machines/:name/stop` | Stop it. Files are kept |
| DELETE | `/api/machines/:name` | Delete it and its disk |
| PATCH | `/api/machines/:name` | `{"keepAwake": true}` keeps it from going to sleep; `false` lets it sleep again. `{"project": "acme/web"}` names what it's for, `{"label": "api box"}` gives it a name of your own; `null` clears either |
| POST | `/api/machines/:name/copy` | New machines from its whole disk as it is now (files, installed tools, and every sign-in kept on it): `{"count": 1–5, "label": "…"}`. Answers within about 30 seconds with `{"machines": [...], "flushed": true}`. One copy is a disk clone, ready in about a minute; several wait for a snapshot (`setup: "copying"`) and are ready in three to six. `flushed` is false when a running source didn't confirm it wrote its newest files to disk first |
| POST | `/api/machines/:name/ssh-keys` | Add a public key to this machine only: `{"key": "ssh-ed25519 AAAA..."}` |

Sizes: `s`, `m`, `l`, `xl`, or `custom` with `cpus` (2 to 16), `ramGb` and `diskGb`. Stacks: `web` (the default),
`python`, `systems`, and `plain`: the operating system alone, reached over SSH, Ubuntu 24.04 unless `image` names another
free Linux from Google as `project/family` (from `ubuntu-os-cloud`, `debian-cloud`, `rocky-linux-cloud`,
`almalinux-cloud`, `centos-cloud`, `opensuse-cloud`). Prices are on [the pricing page](/pricing).

### Create and wait

```bash
curl -sS -X POST "https://shedix.dev/api/machines?wait=240" \
  -H "Authorization: Bearer $SHEDIX_TOKEN" -H "Content-Type: application/json" \
  -d "{\"size\": \"m\", \"stack\": \"web\", \"project\": \"acme/web\", \"sshKey\": \"$(cat ~/.config/shedix/id_ed25519.pub)\"}"
```

### A machine

```json
{
  "name": "arnav-3f2a1b",
  "status": "running",
  "setup": "ready",
  "ready": true,
  "size": "m",
  "stack": "web",
  "cpus": 4,
  "ramGb": 8,
  "diskGb": 45,
  "ip": "34.66.118.27",
  "pricePerHour": 0.16,
  "awakeHours": 1.25,
  "costSoFar": 0.2,
  "keepAwake": false,
  "project": "acme/web",
  "label": "api and web",
  "activeAt": "2026-11-01T09:12:30.000Z",
  "sleepsAt": "2026-11-01T10:12:30.000Z",
  "access": {
    "user": "arnav",
    "host": "arnav-3f2a1b.shedix.dev",
    "ssh": "ssh arnav@arnav-3f2a1b.shedix.dev",
    "terminal": "https://….trycloudflare.com/<secret>/",
    "ide": "https://….trycloudflare.com",
    "box": "https://….trycloudflare.com/<secret>",
    "password": "<the machine's secret>",
    "sshKeys": ["ssh-ed25519 AAAA… you@laptop"]
  },
  "agents": { "claude": "ready", "codex": "off" }
}
```

| Field | Meaning |
|---|---|
| `status` | `starting`, `running`, `stopping`, `stopped` or `deleting` |
| `setup` | While it runs, `booting` then `ready`; `copying` for a copy waiting for its snapshot; otherwise null |
| `ready` | Running, with its terminal up. SSH works from here on |
| `name` | The person's name and a random end (`arnav-3f2a1b`; machines made before 2026-11-02: `shed-3f2a1b`). It never changes |
| `ip` | Public IPv4; it changes every time the machine starts |
| `access.host` | The machine's name on the internet, `<name>.shedix.dev`, once it points at this start's address; it follows the machine to every new IP (null until then) |
| `access.user`, `access.ssh` | Who to log in as (the person's name on machines made from 2026-11-02; `dev` before), and the SSH command by name |
| `activeAt`, `sleepsAt` | When it was last in use, and when it goes to sleep if it stays unused (null while kept awake) |
| `sleptAt` | When it went to sleep, while it's asleep |
| `project` | What it's for, as named on create or with PATCH, so an agent can find it again; null if unnamed. Up to 80 letters, digits, dots, dashes, underscores or slashes, stored lower-case |
| `label` | A name of your own for it, up to 60 characters; the machine's `name` never changes |
| `image` | What it runs: shedix's image, or the plain OS |
| `access.terminal`, `access.ide` | The web terminal and VS Code in the browser; both carry or need the machine's secret |
| `access.box` | The machine's files-and-ports server, below |
| `agents` | Claude Code and Codex: `off` (not signed in), `ready`, `allow` (waiting for an answer in its terminal) or `on` (reachable from the phone app) |

## Your profile

| Method | Path | Does |
|---|---|---|
| GET | `/api/me` | Who you are, with `handle`: the name your machines start with (`arnav` in `arnav-3f2a1b`) |
| PATCH | `/api/me` | `{handle}`: change it, for machines you make from now on. 2 to 16 lowercase letters or digits, starting with a letter |
| GET | `/api/history` | Every machine you've had, deleted ones too, with events, hours and estimated cost |
| GET, POST | `/api/ssh-keys` | Your public keys. Add one with `{"key": "..."}`, or import your GitHub keys with `{"github": "username"}`. New keys go on every machine you have |
| DELETE | `/api/ssh-keys/:id` | Remove a key from your profile |
| GET | `/api/tokens` | Your working tokens; `?all=1` adds revoked and expired ones |
| PATCH | `/api/tokens/:id` | Rename one: `{"name": "laptop"}` |
| GET | `/api/tokens/:id/activity` | The latest calls made with it |
| DELETE | `/api/tokens/:id` | Revoke it at once |

Tokens are made on the shedix page, not through the API.

## A machine's files and ports

Each machine runs a small server at `access.box`. The address carries the machine's secret, so it needs no other
sign-in; keep it private. It works with files under `~/work` only.

| Method | Path | Does |
|---|---|---|
| GET | `/list?path=dir` | A folder's contents |
| GET | `/file?path=file` | A file; add `&download=1` to save it |
| GET | `/archive?path=dir` | A folder as .tar.gz, without node_modules and .venv |
| PUT | `/file?path=dir/name` | Upload the request body as a file, up to 100 MB. Add `&replace=1` to overwrite |
| POST | `/folder?path=dir/name` | Make a folder |
| GET | `/ports` | What's listening, and any preview links |
| POST | `/preview?port=3000` | Make a public preview link for a port; it appears in `/ports` within seconds |
| DELETE | `/preview?port=3000` | Stop sharing it |

## Errors

Errors come back as `{"error": "..."}` with a status: 400 (a bad request), 401 (no token, or a revoked or expired
one), 403 (not allowed), 404 (no such machine), 409 (the wrong state, like starting a running machine) or 502 (the
cloud failed; try again).
