Control
/v1/machines/{machineId}/control — open, run code on, interrupt and close direct control of a machine.
View as MarkdownThe endpoints behind direct control (research preview). Control is a lease on one machine, so it lives under the machine:
https://api.simular.ai/v1/machines/{machineId}/controlmachineId is one of your cloud computers from GET /v1/agents/machines. You hold at most one lease per machine, so there is no separate id: the machine is the handle.
Every call carries Authorization: Bearer sapi_..., as everywhere else. See Authentication.
Open
POST /v1/machines/{machineId}/control
No body.
{ "machineId": "m_7f2a", "expiresAfterIdleSec": 600 }Answers 201 once the lease holds the computer. If a Sai task holds it, or the computer has to start, that can take up to three minutes. If you already hold an open lease on the machine, it answers 200 at once with the same body.
A lease whose computer stopped answering (for example, the service behind it was restarted abruptly) still looks open to this call. The first exec that gets no answer closes it with 410 control_closed and reason unresponsive, and the next open starts a fresh lease.
Exec
POST /v1/machines/{machineId}/control/exec
{
"code": "var r = await machine.exec({ command: 'hostname' }); r.stdout.trim()",
"timeoutMs": 60000
}Any other field, or a value out of range, answers 400 invalid_body.
{
"result": "WIN-CLOUD-7F2A",
"logs": [],
"observations": {},
"variables": [
{ "name": "r", "type": "object", "preview": "{ stdout: 'WIN-CLOUD-7F2A\\r\\n', … }" }
],
"durationMs": 412
}A block that throws is still a 200. Read error.kind:
A stopped block normally keeps the REPL's variables. A block that does not stop within a few seconds of a timeout or interrupt is ended by restarting the REPL, and the earlier variables are gone.
Blocks run one at a time, in the order they arrive. A second exec sent while one is running waits for it, and that wait counts toward its own timeoutMs.
If the lease has ended, exec answers 410 control_closed with a reason:
{
"error": "control_closed",
"message": "The computer stopped answering, so this control lease was closed. Open a new one.",
"reason": "unresponsive"
}Open control again for a fresh lease. With no lease on the machine at all, exec answers 404 control_not_open.
Interrupt
POST /v1/machines/{machineId}/control/interrupt
Stops the running block at its next step or loop iteration. Its exec answers with error.kind: "interrupted".
{ "interrupted": true }false means nothing was running. Answers 202.
Get
GET /v1/machines/{machineId}/control
Your latest lease on the machine, open or closed.
{
"machineId": "m_7f2a",
"status": "open",
"createdAt": 1791273600000,
"lastExecAt": 1791273712000,
"expiresAfterIdleSec": 600
}A closed lease has status: "closed" and a closedReason, one of the exec reason values above. createdAt and lastExecAt are milliseconds since the epoch; lastExecAt is null before the first block. A lease that failed to open never replaces the one shown here. 404 control_not_open if you never opened control on the machine.
Close
DELETE /v1/machines/{machineId}/control
Clears the REPL and releases the computer. Answers 204, also when no lease is open. Always close when you are done: until then, Sai's tasks on that computer wait.
Errors
Error bodies are { "error": "<code>", "message": "..." }.
Gotchas
var, notletorconst, for anything you want in the next block.- Screenshots are base64 in the JSON. Decode before saving; do not log them.
- The free plan's computer is deleted when the lease closes: copy out anything you need first.
- Every exec counts as activity. A lease you stop using releases the computer after 10 minutes, or 2 when a Sai task is waiting for it.