Cloud API Reference
Surface: hosted cloud API. This documents the hosted smolmachines cloud REST API at
api.smolmachines.com. To drive machines (local or cloud) from code with thesmolmachinesSDK’s in-process engine — no server — see the Machine API reference and the guides.
Base URL: https://api.smolmachines.com
All endpoints require Authorization: Bearer <api_key> header.
Machines
Create a machine
POST /v1/machines {
"name": "my-machine",
"source": { "type": "image", "reference": "alpine" },
"resources": { "cpus": 1, "memoryMb": 256, "diskGb": 20 },
"network": { "mode": "open" },
"env": { "APP_ENV": "production" },
"workdir": "/workspace",
"autoStopSeconds": 300,
"ttlSeconds": 3600,
"ephemeral": false,
"mounts": [{ "volume": "data-vol", "mountPath": "/data" }],
"ports": [{ "port": 8080 }],
"public": false
} All fields except source are optional (name included — omit it for an unnamed machine). Returns 201 Created with the machine object in state: "stopped".
Source. Two source shapes are accepted:
{ "type": "image", "reference": "alpine" }— an OCI image reference.{ "type": "smolmachine", "reference": "myapp:v1", "arch": "arm64" }— a packed.smolmachineregistry artifact.arch(arm64/amd64) is optional; when omitted the control plane resolves it from the artifact manifest and pins placement to a matching node.
Resources. resources accepts only cpus, memoryMb, and diskGb. Defaults are 1 vCPU / 256 MB when omitted; diskGb defaults to the image’s own size and may not exceed 16 TiB (16384).
Network. network.mode defaults to blocked — omitting network means the machine has no outbound egress. Modes:
{ "mode": "blocked" }— no egress (default).{ "mode": "open" }— unrestricted egress.{ "mode": "allowCidrs", "cidrs": ["10.0.0.0/8"], "hosts": ["api.example.com"] }— allow only the listed CIDRs/hosts.
Ports & public ingress. Supply ports: [{ "port": 8080 }] with guest ports only (max 4); the control plane allocates a hostPort at first start. Set public: true to expose a published port on the unauthenticated public ingress; the default (false) keeps it reachable only via the authenticated path.
Response (abbreviated):
{
"id": "mach-abc123",
"name": "my-machine",
"source": { "type": "image", "reference": "alpine" },
"arch": "arm64",
"state": "stopped",
"resources": { "cpus": 1, "memoryMb": 256, "diskGb": 20 },
"network": { "mode": "open" },
"env": { "APP_ENV": "production" },
"workdir": "/workspace",
"autoStopSeconds": 300,
"ttlSeconds": 3600,
"ephemeral": false,
"ports": [{ "port": 8080, "hostPort": 34071 }],
"url": null,
"createdAt": "2026-07-01T12:00:00Z",
"updatedAt": "2026-07-01T12:00:00Z",
"lastActivityAt": null
} arch is the machine’s actual CPU arch. url is the public ingress URL for the first published port — null when the machine is stopped, has no published port, or the deployment exposes no public base URL. hostPort is populated once the machine is placed.
List machines
GET /v1/machines Returns an array of all your machines.
Get a machine
GET /v1/machines/:id Start a machine
POST /v1/machines/:id/start Stop a machine
POST /v1/machines/:id/stop Compute billing stops immediately. Storage persists.
Delete a machine
DELETE /v1/machines/:id Other machine endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /v1/machines/:id/fork | Fork a running, forkable golden into a new copy-on-write clone. Returns 201. |
POST | /v1/machines/:id/share | Mint (or rotate) an anonymous share token + URL for the machine’s public app ingress. |
DELETE | /v1/machines/:id/share | Revoke the share token (idempotent, 204). |
GET | /v1/machines/:id/logs | Stream the machine’s console logs. ?follow=true follows; ?tail=N caps the backlog. |
POST | /v1/machines/:id/snapshot | Capture the machine’s overlay to object storage. Returns 201 (503 if snapshots are unavailable on the deployment). |
GET | /v1/machines/:id/snapshots | List captured snapshots. |
GET | /v1/machines/:id/images | List OCI images cached inside the machine. |
POST | /v1/machines/:id/images | Pull an image into the machine ({ "image": "...", "platform": "linux/arm64" }). |
GET | /v1/machines/:id/usage | Per-machine usage + cost for the current billing period. |
Exec
Execute a command
POST /v1/machines/:id/exec {
"command": "echo hello",
"env": { "MY_VAR": "value" },
"cwd": "/workspace",
"timeoutSeconds": 30,
"stdin": "input data\n",
"stream": false,
"background": false
} command can be a string (runs via sh -lc) or an array (argv form: ["python3", "app.py"]).
Set background: true to spawn the command detached and return immediately with its PID — use it for long-lived daemons (a dev server, an agent runner).
If the machine is stopped, it auto-starts before executing.
TIP
`exec` calls are never charged — they are metered for the event timeline but priced at $0. They are still subject to the standard per-tenant rate limit, so a burst can return `429`.Response:
{
"stdout": "hello\n",
"stderr": "",
"exitCode": 0,
"durationMs": 14,
"machineId": "mach-abc123",
"stdoutTruncated": false,
"stderrTruncated": false
} stdoutTruncated/stderrTruncated are true when the captured stream exceeded the server’s per-call size cap.
Streaming exec (SSE)
Set "stream": true to receive Server-Sent Events as the command runs.
Execute code
POST /v1/machines/:id/code {
"language": "python",
"code": "print(sum(range(100)))"
} Supported languages: python (aliases python3), javascript (aliases js, node).
Files
Upload a file
PUT /v1/machines/:id/files/{path} Body: raw file bytes.
Download a file
GET /v1/machines/:id/files/{path} Sessions
Sessions maintain environment variables and working directory across multiple exec calls.
Create a session
POST /v1/machines/:id/sessions {
"cwd": "/workspace",
"env": { "PROJECT": "myapp" }
} Execute in a session
POST /v1/machines/:id/sessions/:sessionId/exec Same body as regular exec. Session env and cwd are inherited (request overrides).
List sessions
GET /v1/machines/:id/sessions Delete a session
DELETE /v1/machines/:id/sessions/:sessionId Events
Get machine events
GET /v1/machines/:id/events Returns lifecycle events (start failures, rescheduling, etc.) for debugging.
Volumes
Create a volume
POST /v1/volumes {
"name": "my-data",
"sizeGb": 10
} List volumes
GET /v1/volumes Get a volume
GET /v1/volumes/:id Delete a volume
DELETE /v1/volumes/:id Mount volumes when creating a machine using the mounts field.
Usage
Query usage
GET /v1/usage?tenantId=TENANT&from=ISO_DATE&to=ISO_DATE Response:
{
"tenantId": "acme-corp",
"from": "2026-06-01T00:00:00Z",
"to": "2026-07-01T00:00:00Z",
"totalUptimeSeconds": 86400,
"cpuHours": 48.0,
"memoryGbHours": 12.0,
"diskGbHours": 480.0,
"egressGb": 3.2,
"machineCount": 5
} from/to echo the queried window. Exec is intentionally absent — it is metered but priced at $0.
API Keys
Create an API key
POST /v1/apikeys {
"description": "CI deploy key",
"scopes": ["machine:create", "machine:exec"],
"expiresInDays": 30
} List API keys
GET /v1/apikeys Revoke an API key
DELETE /v1/apikeys/:id Health
GET /health Public endpoint, no auth required.
Error responses
All errors return a plain text body with an appropriate HTTP status code:
| Status | Meaning |
|---|---|
| 400 | Bad request (validation failed) |
| 401 | Unauthorized (missing or invalid key) |
| 402 | Payment required (tenant suspended or over budget) |
| 403 | Forbidden (insufficient scopes) |
| 404 | Not found (or belongs to another tenant) |
| 409 | Conflict (duplicate name) |
| 422 | Unprocessable (quota exceeded, concurrency limit, no capacity) |
| 429 | Rate limited |
| 500 | Internal server error |
| 503 | Service unavailable (e.g. snapshots not configured on the deployment) |