Machine API Reference
One
Machineclass, two backends. This is the reference for theMachineclass in thesmolmachinesSDK (Node) /smolmodule (Python). The same API runs against the embedded in-process engine (default — no server) or the smolfleet cloud, selected viaConnectOptions({ target: 'cloud', apiKey }or theSMOL_CLOUD_TOKENenv var). TypeScript methods are async; the PythonMachineAPI is synchronous, with an awaitableAsyncMachine(same methods, non-blocking) for driving many machines from one event loop — see AsyncMachine. For the standalone hosted REST API atapi.smolmachines.com, see the Cloud API reference.
Complete API reference for the Machine class.
Static Methods
create()
Create and start a machine in one call.
TypeScriptstatic async create(
config?: MachineConfig,
conn?: ConnectOptions,
): Promise<Machine>
// Usage
const machine = await Machine.create({ name: 'my-machine' }); Python @classmethod
def create(
cls,
config: Optional[MachineConfig] = None,
conn: Optional[ConnectOptions] = None,
) -> Machine
# Usage
machine = Machine.create(MachineConfig(name="my-machine")) connect()
Attach to an existing machine (created elsewhere) without creating a new one. Locally, re-opens a persisted machine by name; on cloud, looks up the machine by id.
TypeScriptstatic async connect(
id: string,
conn?: ConnectOptions,
): Promise<Machine> Python @classmethod
def connect(
cls,
machine_id: str,
conn: Optional[ConnectOptions] = None,
) -> Machine Properties
name
The machine’s name / identifier (a getter/property, not a method).
TypeScriptget name(): string Python @property
def name(self) -> str Instance Methods
state()
Get the current lifecycle state as a string ("created" | "running" | "stopped").
async state(): Promise<string> Python def state(self) -> str url()
Public ingress URL for the machine’s first published port (cloud). null/None on local, before a host port is allocated, or with no published port.
async url(): Promise<string | null> Python def url(self) -> Optional[str] ready() / readyAt()
Whether the machine is ready to do work, and when it became so (cloud). state() becoming "started" means only that the VM process launched — the guest is still booting and is not usable yet. ready becomes true once the in-VM agent is reachable and any published port accepts connections. Gate on this, not on state. (On the local target a machine reports ready once running.)
async ready(): Promise<boolean>
async readyAt(): Promise<string | null> // RFC3339, or null if not yet ready Python def ready(self) -> bool
def ready_at(self) -> Optional[str] # RFC3339, or None if not yet ready waitUntilReady() / wait_until_ready()
Block until the machine is ready (or raise on a failed/stopped state or timeout). create() already waits for readiness, so this is for machines attached via connect(), or to re-assert readiness before use. Poll ceiling and interval are configurable (defaults: 120 s / 1 s).
async waitUntilReady(opts?: {
timeoutMs?: number; // default 120000
intervalMs?: number; // default 1000
}): Promise<void> Python def wait_until_ready(self, timeout_s: float = 120.0, interval_s: float = 1.0) -> None endpoint() / fetch()
Reach a published guest port through the control plane’s authenticated connect bridge — no tunnel, no public exposure, no egress allow-list (cloud). endpoint() builds the authed URL + headers (pure, no I/O) for your own WebSocket/HTTP client; fetch() (Node) / request() (Python) is a convenience for a one-shot HTTP call. The machine must publish the port (ports at create).
endpoint(port: number, path?: string): {
httpUrl: string; // https://…/v1/machines/:id/connect/:port[/path]
wsUrl: string; // wss://… — plug into a WebSocket client with headers
headers: Record<string, string>;
}
async fetch(port: number, path?: string, init?: RequestInit): Promise<Response> Python def endpoint(self, port: int, path: Optional[str] = None) -> PortEndpoint
# PortEndpoint has .http_url, .ws_url, .headers
def request(
self, port: int, path: Optional[str] = None,
method: str = "GET", data: Optional[bytes] = None,
) -> bytes Have the in-VM worker listen on the port and connect inbound — this inverts the fragile “worker dials out to a relay” pattern into a robust “orchestrator dials in”, and doubles as a readiness probe.
exec()
Execute a command (an array of argv) directly in the microVM.
TypeScriptasync exec(
command: string[],
opts?: ExecOptions
): Promise<ExecResult> Python def exec(
self,
command: list[str],
opts: Optional[ExecOptions] = None,
) -> ExecResult run()
Pull an image (if needed) and run a command in a container of it (local).
TypeScriptasync run(
image: string,
command: string[],
opts?: ExecOptions
): Promise<ExecResult> Python def run(
self,
image: str,
command: list[str],
opts: Optional[ExecOptions] = None,
) -> ExecResult execStream() / exec_stream()
Execute a command and stream stdout/stderr/exit events as they occur (local).
TypeScriptexecStream(
command: string[],
opts?: ExecOptions
): AsyncGenerator<ExecEvent>
type ExecEvent =
| { kind: 'stdout'; data: string }
| { kind: 'stderr'; data: string }
| { kind: 'exit'; exitCode: number }
| { kind: 'error'; message: string }; Python def exec_stream(
self,
command: list[str],
opts: Optional[ExecOptions] = None,
)
# Yields dicts:
# {"kind": "stdout" | "stderr", "data": str}
# {"kind": "exit", "exit_code": int}
# {"kind": "error", "message": str} readFile() / read_file()
Read a file from the machine as raw bytes.
TypeScriptasync readFile(path: string): Promise<Buffer> Python def read_file(self, path: str) -> bytes writeFile() / write_file()
Write a file into the machine, with an optional file mode.
TypeScriptasync writeFile(
path: string,
data: string | Uint8Array,
mode?: number
): Promise<void> Python def write_file(
self,
path: str,
data: bytes | str,
mode: Optional[int] = None,
) -> None pullImage() / pull_image()
Pull an OCI image into the machine’s storage (local).
TypeScriptasync pullImage(image: string): Promise<ImageInfo> Python def pull_image(self, image: str) -> ImageInfo listImages() / list_images()
List cached OCI images (local).
TypeScriptasync listImages(): Promise<ImageInfo[]> Python def list_images(self) -> list[ImageInfo] stop()
Stop the machine (it can be restarted).
TypeScriptasync stop(): Promise<void> Python def stop(self) -> None delete()
Stop the machine and delete its storage (permanent).
TypeScriptasync delete(): Promise<void> Python def delete(self) -> None fork()
Fork this running, forkable machine into a new clone via copy-on-write live RAM + disks (cloud). Requires the golden to have been created with forkable: true.
async fork(name: string, ports?: PortSpec[]): Promise<Machine> Python def fork(self, name: str, ports: Optional[list[PortSpec]] = None) -> Machine AsyncMachine (Python)
The Python Machine is synchronous — each call blocks the calling thread. When you’re driving many machines from one event loop (a fleet of disposable workers), use AsyncMachine: the same API, but every I/O method is a coroutine that runs off the loop, so launches and calls overlap instead of serializing. create/connect/exec/wait_until_ready/request/fork/… all have awaitable counterparts, plus async with for auto-delete. endpoint(port) stays synchronous (it only builds a URL).
import asyncio
from smol import AsyncMachine, MachineConfig, ConnectOptions, PortSpec
async def main():
cfg = MachineConfig(image="alpine:3.20", ports=[PortSpec(host=8080, guest=8080)])
conn = ConnectOptions(target="cloud") # or SMOL_CLOUD_TOKEN
# Launch a fleet concurrently — none blocks the loop.
machines = await asyncio.gather(*(AsyncMachine.create(cfg, conn) for _ in range(8)))
try:
await asyncio.gather(*(m.wait_until_ready() for m in machines))
health = await machines[0].request(8080, "healthz") # authed connect bridge, no tunnel
finally:
await asyncio.gather(*(m.delete() for m in machines))
asyncio.run(main()) The Node SDK is already fully async, so there is no separate async class there — every
Machinemethod returns aPromise.
Types
MachineConfig
TypeScriptinterface MachineConfig {
name?: string;
image?: string; // required for cloud, optional for local
mounts?: MountSpec[]; // local
ports?: PortSpec[]; // local
resources?: ResourceSpec;
persistent?: boolean; // local; default false
autoStopSeconds?: number; // cloud
ttlSeconds?: number; // cloud
forkable?: boolean; // cloud; default false
} Python @dataclass
class MachineConfig:
name: Optional[str] = None
image: Optional[str] = None # required for cloud, optional for local
mounts: Optional[list[MountSpec]] = None
ports: Optional[list[PortSpec]] = None
resources: Optional[ResourceSpec] = None
persistent: bool = False # local
auto_stop_seconds: Optional[int] = None # cloud
ttl_seconds: Optional[int] = None # cloud
forkable: bool = False # cloud ConnectOptions
Selects and configures the backend. Local (embedded) is the default.
TypeScriptinterface ConnectOptions {
target?: 'local' | 'cloud'; // default 'local'
baseUrl?: string; // cloud base URL
apiKey?: string; // cloud API key, smk_…
} Python @dataclass
class ConnectOptions:
target: Optional[Literal["local", "cloud"]] = None # default local
base_url: Optional[str] = None
api_key: Optional[str] = None # cloud API key, smk_… Cloud is selected when target == 'cloud', or when an API key is present (via apiKey/api_key or the SMOL_CLOUD_TOKEN env var) and target is not 'local'. The cloud base URL defaults to https://api.smolmachines.com (override with baseUrl or SMOL_CLOUD_URL).
ExecOptions
TypeScriptinterface ExecOptions {
env?: Record<string, string>;
workdir?: string;
timeout?: number; // seconds
} Python @dataclass
class ExecOptions:
env: Optional[dict[str, str]] = None
workdir: Optional[str] = None
timeout: Optional[int] = None # seconds ExecResult
TypeScriptinterface ExecResult {
exitCode: number;
stdout: string;
stderr: string;
success: boolean; // exitCode === 0
output: string; // stdout + stderr
assertSuccess(): void; // throws ExecutionError if exitCode !== 0
} Python @dataclass
class ExecResult:
exit_code: int
stdout: str
stderr: str
@property
def success(self) -> bool: ... # exit_code == 0
@property
def output(self) -> str: ... # stdout + stderr
def assert_success(self, command="") -> "ExecResult": ...
# raises ExecutionError if exit_code != 0 MountSpec
TypeScriptinterface MountSpec {
source: string; // absolute host path
target: string; // absolute path in the machine
readonly?: boolean; // default true
} Python @dataclass
class MountSpec:
source: str # absolute host path
target: str # absolute path in the machine
readonly: bool = True PortSpec
TypeScriptinterface PortSpec {
host: number;
guest: number;
} Python @dataclass
class PortSpec:
host: int
guest: int ResourceSpec
See Resources for defaults and per-target notes.
TypeScriptinterface ResourceSpec {
cpus?: number;
memoryMb?: number;
network?: boolean; // default false
allowCidrs?: string[]; // cloud
allowHosts?: string[]; // cloud
storageGb?: number; // default 20
overlayGb?: number; // default 10
gpu?: boolean; // local; default false
gpuVramMib?: number; // local
} Python @dataclass
class ResourceSpec:
cpus: Optional[int] = None
memory_mb: Optional[int] = None
network: Optional[bool] = None # default False
allow_cidrs: Optional[list[str]] = None # cloud
allow_hosts: Optional[list[str]] = None # cloud
storage_gb: Optional[int] = None # default 20
overlay_gb: Optional[int] = None # default 10
gpu: Optional[bool] = None # local; default False
gpu_vram_mib: Optional[int] = None # local ImageInfo
TypeScriptinterface ImageInfo {
reference: string;
digest: string;
size: number; // bytes
architecture: string;
os: string;
} Python @dataclass
class ImageInfo:
reference: str
digest: str
size: int # bytes
architecture: str
os: str MachineState
TypeScripttype MachineState = 'created' | 'running' | 'stopped'; Python # MachineState is an alias for str: "created" | "running" | "stopped"
MachineState = str