Docs
Panes runs Windows machines for AI agents. Each starts in a third of a second from a template held in memory, and your code drives it with commands, files, screenshots, mouse and keyboard.
On this page
Quickstart
- Sign in. New organizations get $5 of credit, about 50 machine-hours.
- Create an API key on the API Keys page and set it as
PANES_API_KEY. - Install an SDK and start a machine:
import { Panes } from "panes-sdk";
const panes = new Panes();
const machine = await panes.machines.create({ template: "windows" });
const result = await machine.exec("(Get-CimInstance Win32_OperatingSystem).Caption");
console.log(result.stdout);
await machine.delete();from panes import Panes
panes = Panes()
machine = panes.machines.create("windows")
result = machine.exec("(Get-CimInstance Win32_OperatingSystem).Caption")
print(result.stdout)
machine.delete()curl https://api.usepanes.com/v1/machines \
-H "Authorization: Bearer $PANES_API_KEY" \
-H "Content-Type: application/json" \
-d '{"template": "windows"}'SDKs
TypeScript (Node 18 or later) and Python (3.9 or later). Both read PANES_API_KEY from the environment, and PANES_BASE_URL if you set it (otherwise https://api.usepanes.com). Errors throw PanesError with the HTTP status.
import { Panes } from "panes-sdk";
const panes = new Panes({ apiKey: process.env.PANES_API_KEY });Machines
A machine is a Windows desktop, 2 vCPU with the memory its template has. It starts running, signed in, from a template or a snapshot. The windows template is Windows Server 2025 with the desktop experience, Microsoft Edge and PowerShell, 4 GB.
const machine = await panes.machines.create({
template: "windows",
name: "invoice-run", // optional
autoStopMinutes: 30, // optional; 0 keeps it running
});
console.log(machine.id, machine.state, machine.timingsMs);
const all = await panes.machines.list();
await machine.stop(); // saves memory and disk; billing stops
await machine.start(); // every window where it was
await machine.delete(); // erased, with its key (snapshots and forks keep their own)Commands
exec runs a PowerShell script, or a program with its arguments, and returns its exit code and output. Commands run as SYSTEM by default. With desktop: true they run in the signed-in session, so their windows show on the screen.
const r = await machine.exec("Get-Process | Sort CPU -Desc | Select -First 5");
console.log(r.exitCode, r.stdout, r.stderr, r.ms);
await machine.exec(["C:/tools/report.exe", "--out", "C:/work/out.csv"], { timeoutS: 600 });
await machine.exec("Start-Process msedge https://example.com", { desktop: true });
// Pass values as PowerShell parameters, never quoted into the script
await machine.exec("param($Name) Set-Content C:/work/name.txt $Name", { params: { Name: userInput } });A command that runs past its timeout is stopped and returns with timedOut set.
Files
Read and write any absolute path. A write lands whole or not at all, and creates the folders it needs.
await machine.files.write("C:/work/data.csv", "a,b\n1,2\n");
const bytes = await machine.files.read("C:/work/out.xlsx");
const text = await machine.files.readText("C:/work/log.txt");Screen and input
Screenshots are PNGs at the desktop's real size, and input coordinates are pixels on that image, so a model can work from what it sees.
const png = await machine.screenshot();
await machine.click(640, 360);
await machine.click(640, 360, { button: "right" });
await machine.click(200, 120, { double: true });
await machine.move(800, 500);
await machine.scroll(3); // notches; positive scrolls down
await machine.type("Quarterly report");
await machine.key("ctrl", "s"); // a chordViewer links
viewer() returns a link that opens the desktop in any browser for an hour, to watch an agent work or take over. Anyone with the link can use the machine, so share it only with people you trust. An open viewer keeps the machine from stopping when idle.
const url = await machine.viewer();Snapshots and forks
A snapshot saves a running machine's memory and disk; the machine pauses for about 3 seconds and keeps running. Start new machines from it later. A fork snapshots a machine and starts copies at once, open windows and all.
const snap = await machine.snapshot();
const again = await panes.snapshots.start(snap.id);
const [a, b, c] = await machine.fork(3); // 1 to 32 copies
await panes.snapshots.delete(snap.id);Templates
Set up a machine the way you want it (apps installed, signed in), then save it as your template. The machine becomes the template, and every machine you start from it starts that way, in a third of a second. Your templates are encrypted and only your organization can use them.
const base = await panes.machines.create({ template: "windows" });
await base.files.write("C:/setup/python.exe", installer);
await base.exec("Start-Process C:/setup/python.exe '/quiet InstallAllUsers=1' -Wait");
const mine = await base.saveTemplate("python"); // base is gone after this
const worker = await panes.machines.create({ template: mine.id });
const templates = await panes.templates.list();Auto-stop
A machine with no commands, file transfers, screenshots, input or open viewer for 15 minutes stops, keeping its memory and disk, and billing stops with it. start() resumes it as it was. Set autoStopMinutes when you create a machine, change it with setAutoStop(minutes), or use 0 to keep it running.
Usage and credit
Running machines cost $0.025 per vCPU-hour and $0.0125 per GB-hour of memory, billed by the second: $0.10 an hour for 2 vCPU and 4 GB. Stopped machines are free. When credit runs out, running machines stop and keep their state, and new ones are refused until you add credit.
const account = await panes.account(); // balance_usd, running, limits, prices
const usage = await panes.usage({ from: "2026-11-01T00:00:00Z" });
for (const m of usage.machines) console.log(m.machine, m.seconds, m.usd);Errors
Errors are JSON, {"error": "…"}, with these statuses:
| Status | Meaning |
|---|---|
400 | The request is malformed. The message says which field. |
401 | The API key is missing or wrong. |
402 | Out of credit. Running machines have stopped and kept their state. |
404 | No such machine, snapshot or template in your organization. |
409 | The machine is in the wrong state for this, for example stopped, or busy with a snapshot. |
429 | A limit: 5 running machines, or 20 machines, 20 snapshots or 2 templates kept. |
503 | No server has room right now. Try again in a minute. |
HTTP API
Every call takes Authorization: Bearer pk_… at https://api.usepanes.com. Bodies are JSON, except file uploads.
| Call | What it does |
|---|---|
POST /v1/machines | {"template", "name", "auto_stop_minutes"}: start a machine |
GET /v1/machines | List your machines |
GET /v1/machines/{id} | One machine |
PATCH /v1/machines/{id} | {"auto_stop_minutes"}: 0 (never) to 10080 |
DELETE /v1/machines/{id} | Erase the machine and its key |
POST /v1/machines/{id}/exec | {"script"} (PowerShell) or {"argv"}, with desktop, timeout_s, stdin, params |
GET /v1/machines/{id}/files?path= | Download a file |
PUT /v1/machines/{id}/files?path= | Upload a file; the body is its bytes |
GET /v1/machines/{id}/screenshot | The screen as a PNG |
POST /v1/machines/{id}/input | {"actions": [...]}: clicks, moves, scrolling, typing and keys |
POST /v1/machines/{id}/viewer | A one-hour link to watch and use the desktop |
POST /v1/machines/{id}/stop | Save memory and disk, then stop |
POST /v1/machines/{id}/start | Resume where it stopped |
POST /v1/machines/{id}/fork | {"count"}: 1 to 32 running copies |
POST /v1/machines/{id}/snapshot | Save the machine's state, keep it running |
POST /v1/machines/{id}/template | {"name"}: turn the machine into your template |
GET /v1/snapshots | Your snapshots |
POST /v1/snapshots/{id}/machines | {"name", "auto_stop_minutes"}: start a machine from a snapshot |
DELETE /v1/snapshots/{id} | Erase a snapshot |
GET /v1/templates | Templates you can start from |
DELETE /v1/templates/{id} | Erase one of your templates |
GET /v1/account | Credit, limits and prices |
GET /v1/usage?from=&to= | Spending per machine; RFC 3339 times, this month by default |