# Keys and scopes

> What a key may do: the cloud scopes, keys limited to some projects, end times and rates, revoking, and why a key never makes a stronger one.

A key says who is calling and what it may do. Give every program and every agent a key of its own, holding only what it needs.

wm:

```sh
$ wm keys create agent --scope machines:read,machines:write,machines:exec
id:        key_8c41d07e2b9a6f13
name:      agent
scopes:    cloud:machines:read, cloud:machines:write, cloud:machines:exec
projects:  every project
expires:   never
secret:    ak_live_...
wm: store the secret now: it is shown only this once, and cannot be shown again.
```

Python:

```python
key = client.keys.create(
    name="agent",
    scopes=["cloud:machines:read", "cloud:machines:write", "cloud:machines:exec"],
    expires_at="2026-12-31T23:59:59Z",
)
# key.secret is in this answer only: store it now
```

TypeScript:

```ts
const key = await client.keys.create({
  name: "agent",
  scopes: ["cloud:machines:read", "cloud:machines:write", "cloud:machines:exec"],
  expires_at: "2026-12-31T23:59:59Z",
});
// key.secret is in this answer only: store it now
```

curl:

```sh
curl -X POST https://api.wheelmachine.ai/v1/keys \
  -H "Authorization: Bearer $WHEELMACHINE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "agent", "scopes": ["cloud:machines:read", "cloud:machines:write", "cloud:machines:exec"]}'
```

## A key

A key is `ak_live_` and 40 letters and digits, sent with every call as `Authorization: Bearer ak_live_...`. Its secret is shown once, in the answer that makes it; after that the API shows only its first and last characters (`shown`), to know it by. It also has an id (`key_` and 16 letters and digits) and a name, for people. `wm me` (`GET /v1/me`) shows the key in use: its scopes, its projects, when it ends, and whether the account can spend.

## Scopes

| Scope | In `wm` | What the key may do |
|---|---|---|
| `cloud:machines:read` | `machines:read` | See machines, wait for them, and see what they cost (`usage`). |
| `cloud:machines:write` | `machines:write` | Make, rename, label, start, pause, stop, archive, copy, snapshot and delete machines, and see them. |
| `cloud:machines:exec` | `machines:exec` | Run commands in machines and read and write their files, and see them. A command can read its environment, so such a key can read the secrets a machine was given. |
| `cloud:gpus` | `gpus` | Make machines with a GPU, once they are open to the account. |
| `cloud:projects:write` | `projects:write` | Make, rename and delete projects. |
| `cloud:keys:write` | `keys:write` | List, make and revoke keys. |
| `cloud:secrets:write` | `secrets:write` | Set and delete the account's secrets, and give them to machines (a key of the whole account). |
| `cloud:*` | `all` | All of these. |

Any key with one of these scopes can see who it is (`wm me`), its projects, the regions and the GPUs. A call the key's scopes do not allow answers [`403 forbidden`](https://docs.wheelmachine.ai/errors/forbidden.html), naming the scope it needs. A key that holds none of these scopes is refused on every route here.

## A key never makes a stronger one

A new key is made by a key that holds `cloud:keys:write`, and it can hold no more than that key:

- only scopes the making key holds (`cloud:*` only from a key that holds `cloud:*`);
- only projects the making key reaches;
- an end no later than the making key's own;
- no more calls a minute than the making key may make.

Asking for more answers [`403 forbidden`](https://docs.wheelmachine.ai/errors/forbidden.html). So a key handed to a program, or to an agent, cannot be used to make a key that does more than it.

## Keys for some projects

Terminal:

```sh
$ wm keys create ci --scope machines:write,machines:exec --project research
```

A key limited to some projects works inside them alone:

- it sees and makes machines only in its projects; to it, a machine anywhere else does not exist ([`404 not_found`](https://docs.wheelmachine.ai/errors/not_found.html));
- when it reaches more than one project, it names the project of each machine it makes;
- it cannot make projects;
- it sees, makes and revokes only keys limited to its projects.

A key that names no projects reaches every project of the account.

## End time and rate

Terminal:

```sh
$ wm keys create nightly --scope machines:write --expires 30d --rate 120
```

`--expires` is a while (`30d`, `12h`) or a date (`2026-12-31`, or a time such as `2026-12-31T18:00:00Z`); in the API, `expires_at` is a time, at least a minute ahead. A key past its end answers [`401 key_expired`](https://docs.wheelmachine.ai/errors/key_expired.html), and its end cannot be moved: make a new key.

`--rate` (`rate_per_minute`) is the calls a minute the key may make, from 1 to 6000. Left out, the new key gets the making key's own rate, which is 600 for a key made without one. See [Calls a minute](https://docs.wheelmachine.ai/limits.html#calls-a-minute).

## Revoke

Terminal:

```sh
$ wm keys revoke agent
Revoke key_8c41d07e2b9a6f13? Whatever uses it stops working. [y/N] y
key_8c41d07e2b9a6f13
```

A revoked key stops working within seconds, everywhere, and cannot be brought back; calls with it answer [`401 key_revoked`](https://docs.wheelmachine.ai/errors/key_revoked.html). A key can revoke itself. `wm logout` is not the same: it only forgets the key on this computer, and the key still works until it is revoked.

`wm keys ls` lists the account's machine keys, with their scopes, projects, state and when each was last used; never their secrets.

## Where keys come from

For now, an account's first key is made in Wheelmachine's console by the account's owner. After that, a key that holds `cloud:keys:write` makes the others, with `wm keys create`, the libraries or `POST /v1/keys`.

## Keep keys safe

- `wm login` keeps a key in `~/.config/wheelmachine/config.json` (under `$XDG_CONFIG_HOME` when that is set), in a file only you can read. `WHEELMACHINE_API_KEY`, when set, is used before it.
- Never put a key in a web page: anyone who opens the page can read it. The TypeScript library refuses to run in a browser for this reason. Call the API from your server.
- Give each program and each agent a key of its own, with only the scopes it needs and an end time, and revoke it when the work is done.
- `wm`, `wm-mcp` and the libraries never log a key and keep it out of their errors. The one secret `wm` prints is a new key's, once, when it is made.
