---
title: Authenticating to smol cloud
description: How an agent gets an API key for smol cloud and sends it.
canonical: https://smolmachines.com/auth.md
---

# Authenticating to smol cloud

The local runtime, smolvm, needs no account and no credentials. Only the hosted
API, smol cloud, does. It authenticates every request with an API key.

- API base URL: https://api.smolmachines.com
- API schema: https://smolmachines.com/openapi.json
- No sales contact is required; keys are self-serve.

## 1. Get a key

Sign in at https://smolmachines.com/console/keys, as a person or as an agent
driving a browser, and create a key. Keys start with `smk_`. The plaintext key
is shown once, when it is created, so store it in your secret manager straight
away. An agent that already holds a key can mint narrower ones over the API
(section 3), with no browser.

Check it works before doing anything else:

```bash
curl -sS https://api.smolmachines.com/v1/machines -H "Authorization: Bearer $SMOL_CLOUD_TOKEN"
```

A `200` with `{"machines":[...]}` means the key is good. Then create, start and
run a command:

```bash
API=https://api.smolmachines.com
H="Authorization: Bearer $SMOL_CLOUD_TOKEN"
ID=$(curl -sS -X POST $API/v1/machines -H "$H" -H 'content-type: application/json' \
  -d '{"source":{"type":"smolmachine","reference":"library/alpine:latest"}}' | jq -r .id)
curl -sS -X POST $API/v1/machines/$ID/start -H "$H"
curl -sS -X POST $API/v1/machines/$ID/exec -H "$H" -H 'content-type: application/json' \
  -d '{"command":["echo","hello"]}'
```

## 2. Send it

Send the key as a bearer token on every request:

```http
GET /v1/machines HTTP/1.1
Host: api.smolmachines.com
Authorization: Bearer smk_your_key_here
```

The SDK (`npm install smolmachines` or `pip install smolmachines`) reads the key from
the `SMOL_CLOUD_TOKEN` environment variable when you do not pass one.

Browsers cannot set an `Authorization` header on a WebSocket. WebSocket routes,
such as the interactive terminal at `GET /v1/machines/{id}/exec/interactive`,
also accept the key as an `access_token` query parameter. Prefer the header
everywhere else, since query strings can end up in logs.

## 3. Mint narrower keys for your own agents

A key holding the right scopes can create other keys:

```http
POST /v1/apikeys HTTP/1.1
Host: api.smolmachines.com
Authorization: Bearer smk_your_key_here
Content-Type: application/json

{"description": "ci runner", "scopes": ["machine:create", "machine:read", "machine:exec", "machine:delete"], "expiresInDays": 1}
```

The response is `{"id": "...", "key": "smk_..."}`. A new key can only carry
scopes its creator already holds, so a key can never mint one more powerful than
itself. List keys with `GET /v1/apikeys` and revoke one with
`DELETE /v1/apikeys/{id}`.

Common scopes:

| Scope | Allows |
|---|---|
| `machine:create` | Create, start and stop machines |
| `machine:read` | List and inspect machines |
| `machine:exec` | Run commands, including the interactive terminal |
| `machine:delete` | Delete machines |
| `machine:files` | Upload and download files |
| `usage:read` | Read usage for the account |

## When a request is refused

Every error is `application/problem+json` with a stable `code` and a
`retryable` flag.

- `401`: no key, or the key is unknown, expired or revoked. Get a new key.
- `403`: the key is valid but lacks a scope. The error names the missing scope,
  for example `missing scope: machine:exec`.
- `429`, `502`, `503`, `504`: `retryable` is true. Wait the
  `Retry-After` seconds and send the request again. A `503` with code
  `machine_interrupted` means the machine stopped while a command ran: it may
  have partly run, so resend it only if running it twice is safe.

## More

- Cloud API reference: https://smolmachines.com/docs/cloud/api-reference
- Pricing for agents: https://smolmachines.com/pricing.md
- Agent guide: https://smolmachines.com/llms.txt
