A computer is one HTTP call away. boringd boots a Firecracker
microVM — jailed, resource-capped, network-isolated — and hands you a serial console, a VNC
display, or an AI that drives it. Snapshot-restore means a shell is ready in ~3 ms. Machines self-destruct when their TTL expires — or
pass "persistent": true to keep one running until you delete it.
Run your own boringd (see the repo) and point everything at your deployment. It listens on this by default:
http://localhost:8080Boot a machine and read it back:
# boot a shell (python3 + node), 60s TTL
curl -s -X POST http://localhost:8080/v1/machines \
-H 'content-type: application/json' \
-d '{"template":"python","ttl_seconds":60}'
# → {"id":"m-1a2b3c4d","mode":"snapshot","boot_ms":3,
# "template":"python","expires_at":"..."}| POST | /v1/machines | Boot a machine. Body: {template, ttl_seconds, net} |
| GET | /v1/machines | List running machines |
| GET | /v1/machines/{id} | Fetch one machine |
| DELETE | /v1/machines/{id} | Destroy a machine now |
| POST | /v1/machines/{id}/branch | Fork a running machine into a live clone |
| GET | /v1/machines/{id}/screenshot | PNG screenshot of a desktop |
| POST | /v1/machines/{id}/upload | Upload a file to /root (X-Filename header) |
| GET | /v1/machines/{id}/download?path=… | Download a file from the machine |
| GET | /healthz | Liveness + running count |
Templates: python (headless shell, snapshot, ~3 ms) and desktop (GUI over VNC). ttl_seconds is clamped to 15–900. Pass "net": true to give the machine
internet (cold-boots instead of snapshot; pip/npm/apk install work). Guests are NAT'd and egress-firewalled. File
transfer and previews (below) need a connected machine — a desktop, or a shell with net. Pass "persistent": true for a
machine with no TTL (runs until you delete it) — honored only when the server sets BORING_ALLOW_PERSISTENT=1, else it falls back to the TTL.
Interactive channels upgrade to WebSocket (binary frames):
| /v1/machines/{id}/tty | Serial console — bytes ⇄ the guest /dev/ttyS0 |
| /v1/machines/{id}/vnc | RFB/VNC framebuffer for desktop machines |
| /v1/machines/{id}/agent?goal=… | Computer-use agent — drives the screen; streams narration JSON |
| /v1/machines/{id}/shell-agent?goal=… | Terminal agent — writes + runs code; streams narration JSON |
Run a server inside a connected machine and open its port through boringd — works locally (over a tunnel) and on public deployments, no wildcard DNS:
http://localhost:8080/v1/machines/<machine-id>/web/<port>/An OpenAI-compatible gateway — Claude runs on Anthropic, everything else routes through
OpenRouter (set your own BORING_OPENROUTER_KEY).
| POST | /v1/chat/completions | Chat completions (streaming + non-streaming) |
| GET | /v1/models | List available models |
curl -s http://localhost:8080/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model":"claude-sonnet-4-6","messages":[{"role":"user","content":"hi"}]}'Persistent volumes (S3-backed) that outlive a machine. Create one, save a machine's /root into it, then restore it into a fresh machine by passing its id as volume on launch. Volumes are addressed by an unguessable id and garbage-collected
on a TTL.
| POST | /v1/volumes | Create a volume. Body: {ttl_seconds} |
| GET | /v1/volumes/{id} | Metadata + usage |
| DELETE | /v1/volumes/{id} | Delete a volume |
| GET | /v1/volumes/{id}/files | List files |
| PUT | /v1/volumes/{id}/file?path=… | Upload a file |
| GET | /v1/volumes/{id}/file?path=… | Download a file |
| POST | /v1/machines/{id}/save?volume=… | Save a machine's /root into a volume |
Attach on launch: POST /v1/machines with {"volume":"vol-…"} restores the volume into /root first.
An Effect-native client lives in the repo at packages/sdk (not on npm) — Schema-validated
responses, typed errors, a streaming serial console, retries built in:
import { Effect, Stream } from 'effect';
import { make } from '@boring/sdk';
const boring = make({ baseUrl: 'http://localhost:8080' });
Effect.runPromise(
Effect.gen(function* () {
const vm = yield* boring.createMachine({ template: 'python', ttlSeconds: 60 });
console.log(vm.id, vm.mode, `${vm.boot_ms}ms`);
// the serial console is a Stream; the socket closes with the Scope
yield* Effect.scoped(
Effect.gen(function* () {
const tty = yield* boring.connectTty(vm.id);
yield* tty.send('print("hello from a microVM")\n');
yield* tty.output.pipe(Stream.runForEach((b) => Effect.sync(() => process.stdout.write(b))));
})
);
yield* boring.destroyMachine(vm.id);
})
);Also: listMachines, getMachine(id), branchMachine(id). Errors are tagged (RequestError, ResponseError).
Let any AI — Claude Desktop, Cursor, … — spin up and drive a computer over the Model Context Protocol. It lives in the repo at packages/mcp (not on npm yet); run it from
source and point your MCP client at it:
{
"mcpServers": {
"boring-computers": { "command": "node", "args": ["/path/to/packages/mcp/index.mjs"] }
}
}Tools: launch_computer, run_task (plain-English task → an agent writes + runs the code,
returns a live URL), screenshot, preview_url, fork_computer, stop_computer.