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 the smolmachines SDK’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 .smolmachine registry 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

MethodPathPurpose
POST/v1/machines/:id/forkFork a running, forkable golden into a new copy-on-write clone. Returns 201.
POST/v1/machines/:id/shareMint (or rotate) an anonymous share token + URL for the machine’s public app ingress.
DELETE/v1/machines/:id/shareRevoke the share token (idempotent, 204).
GET/v1/machines/:id/logsStream the machine’s console logs. ?follow=true follows; ?tail=N caps the backlog.
POST/v1/machines/:id/snapshotCapture the machine’s overlay to object storage. Returns 201 (503 if snapshots are unavailable on the deployment).
GET/v1/machines/:id/snapshotsList captured snapshots.
GET/v1/machines/:id/imagesList OCI images cached inside the machine.
POST/v1/machines/:id/imagesPull an image into the machine ({ "image": "...", "platform": "linux/arm64" }).
GET/v1/machines/:id/usagePer-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:

StatusMeaning
400Bad request (validation failed)
401Unauthorized (missing or invalid key)
402Payment required (tenant suspended or over budget)
403Forbidden (insufficient scopes)
404Not found (or belongs to another tenant)
409Conflict (duplicate name)
422Unprocessable (quota exceeded, concurrency limit, no capacity)
429Rate limited
500Internal server error
503Service unavailable (e.g. snapshots not configured on the deployment)