Quickstart
From sign-in to a finished task in one sitting.
Sai works on a real computer. Choose one of two before you start:
The API calls are the same for both. Only the machineId you send differs.
Path A: a cloud computer
Nothing to set up. A free account gets a Windows cloud computer that starts on the first task. A paid account creates one with Create a Windows computer or Create a Linux computer in the Playground. Go on to step 1.
Path B: your own Mac or Windows PC
- On that computer, sign in at sai.simular.ai with the same Google account you will use on this portal.
- Open Add computer and choose Your Device (or go to Settings → Computers → Connect your device).
- Download the Sai Agent for macOS or Windows, run it, sign in, and grant the permissions it asks for.
The computer then shows up in step 3 with "kind": "own". This works on every plan, including free. Sai works on it only while the Sai Agent is running and the computer is awake, so a laptop that is asleep takes no tasks.
1. Sign in
Open platform.simular.ai and sign in with Google.
Free accounts verify a phone number before their first key. The portal asks for it. If it was skipped, key creation returns 403 with { "error": "phone_verification_required" }.
2. Create an API key
In the portal, create a key. The key is shown once. Copy it.
export SAI_API_KEY="sapi_..."
export SAI_API_URL="https://api.simular.ai"Free accounts hold one key. Paid accounts hold up to 20. See Keys.
3. Check your computers
curl -s "$SAI_API_URL/v1/agents/machines" \
-H "Authorization: Bearer $SAI_API_KEY"{
"machines": [
{
"machineId": "free-3fA9kQ2xYz",
"name": "Sai cloud computer (free)",
"kind": "cloud",
"online": false
}
]
}If you connected your own computer in path B, it is listed too:
{ "machineId": "Kx2mP9qRtV", "name": "Jordan's MacBook", "os": "macOS", "kind": "own" }With one computer you can leave machineId out of every request that follows. With more than one, for example the free cloud computer and your own, pass machineId on each message to say which one Sai should use.
Optional: check what the account can spend.
curl -s "$SAI_API_URL/v1/agents/account" \
-H "Authorization: Bearer $SAI_API_KEY"{
"userId": "3fA9kQ2xYz",
"userState": "active_free",
"plan": { "id": null, "title": "Free", "free": true, "unlimited": false },
"credits": { "usd": 0.35 },
"authType": "apiKey"
}4. Send the first message
curl -s "$SAI_API_URL/v1/agents/message" \
-H "Authorization: Bearer $SAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "Open Notepad, type hello, save it to the desktop as hello.txt",
"wait": false
}'{ "sessionId": "cs_01HZY", "machineId": "free-3fA9kQ2xYz", "queued": false }Note
A free account's first request can take about two minutes while its cloud computer starts. Keep polling. The session is not stuck. Your own computer starts the task at once while the Sai Agent is running.
On your own Mac, ask for TextEdit or Notes instead of Notepad.
Each message continues your API conversation on that computer: Sai remembers the earlier messages, and the desktop is as the last task left it. To start fresh, call /new-session with "channel": "api" first.
5. Poll for events
curl -s "$SAI_API_URL/v1/agents/events?sessionId=cs_01HZY&wait=30" \
-H "Authorization: Bearer $SAI_API_KEY"{
"events": [
{ "type": "data-progress", "data": { "text": "Opening Notepad", "tool": "computer" } }
],
"cursor": "1790000012345:3",
"status": "running",
"text": ""
}Pass the returned cursor as since on the next call. Repeat while status is running.
6. Handle an approval
When status is needs_approval, the response carries the pending approval in a top-level approval field, and the events array holds the matching data-approval-request.
{
"type": "data-approval-request",
"data": {
"approvalId": "ap_7Qw",
"title": "Command Approval Required",
"description": "Sai wants to run a command.",
"approvalType": "exec",
"isLinkOnly": false,
"command": "notepad.exe",
"cwd": "C:\\Users\\sai"
}
}isLinkOnly is false, so answer over the API:
curl -s "$SAI_API_URL/v1/agents/approve" \
-H "Authorization: Bearer $SAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "approvalId": "ap_7Qw", "response": "yes" }'"response": "task" allows this request and similar ones for the rest of the task, so a task that inspects several files asks once. An exec approval carries no approvalUrl; it is answered only with POST /approve.
If isLinkOnly were true, you would open approvalUrl in a browser instead. See Approvals.
7. Read the result
Keep polling. When status is idle, text is Sai's answer.
{
"events": [{ "type": "finish", "finishReason": "stop" }],
"cursor": "1790000098765:12",
"status": "idle",
"text": "Done. hello.txt is on the desktop with the text \"hello\"."
}8. Make the next run faster and cheaper: turn the task into code
In steps 4 to 7, Sai asked a model what to do at every step. That is how it handles a task it has never seen, and it is why a run takes a while and costs model calls. A task you repeat doesn't need that every time. One learn call turns Sai's run into code you own: a function you can read, keep in version control and rerun on your computer with new inputs. A rerun follows the code instead of asking a model at every step, so it is faster and cheaper; a model is called only where the code reads the screen.
The code doesn't replay recorded clicks or screen positions. Each time it runs, it finds pages, windows and buttons by what they show, so a moved or resized window or a shifted layout doesn't break it. If a bigger change does, Sai can repair the code.
Try it first with Sai added to your coding agent (Claude Code, Codex or Cursor):
Use Sai to open TechCrunch, find all AI news published today, write their headlines into Notepad and save the file as techcrunch_headlines.txt, then turn what it did into code I can rerun.Or make the call yourself. Sai does the task once, writes it as Simulang code, then tests it: two runs back to back, each checked against criteria. Each run starts where the last one left off, with Notepad still open and the file already saved, just as your rerun will, so code that only works from a clean screen fails the test and gets repaired. If a run fails, Sai repairs the code and tries again. The examples use the first computer in your list. On your own computer, turn on code on it first, and on a Mac change Notepad to TextEdit in the task.
LEARN_ID=$(curl -s "$SAI_API_URL/v1/agents/learn" \
-H "Authorization: Bearer $SAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"taskTemplate": "Open {{newsSite}}, find all {{topic}} news published today, write their headlines into Notepad, one per line, and save the file as {{fileName}}",
"inputs": { "newsSite": "TechCrunch", "topic": "AI", "fileName": "techcrunch_headlines.txt" },
"criteria": "techcrunch_headlines.txt is saved and holds only the headlines of the AI articles TechCrunch published today, one per line, with no text left from earlier runs",
"maxIterations": 3,
"rollouts": 2
}' | jq -r .learnId)
# Wait for the job to end, then save the code.
until [ "$(curl -s "$SAI_API_URL/v1/agents/learn/$LEARN_ID" \
-H "Authorization: Bearer $SAI_API_KEY" | tee job.json | jq -r .status)" != running ]; do sleep 20; done
jq -r '.status, .testResult.status' job.json
jq -r .code job.json > main.js && cat main.jsThe job keeps the computer busy until it ends; how long depends on the task. Then it prints succeeded passed (both runs passed the check) and the code, which looks like this (shortened):
async function main(
params = { newsSite: 'TechCrunch', topic: 'AI', fileName: 'techcrunch_headlines.txt' },
) {
try {
console.log('Opening the ' + params.topic + ' section of ' + params.newsSite + '...')
var sectionUrl = callLLM({
prompt: `What is the full URL of the "${params.topic}" news section on "${params.newsSite}"? Reply with only the URL.`,
}).trim()
var page = await machine.browser.newtab(sectionUrl)
await page.wait({ waitTime: 3 })
if (
!(await machine.stateSatisfies({
condition: `the browser shows ${params.newsSite} listing ${params.topic} news articles`,
}))
) {
throw new Error(
`precondition_failed: the ${params.newsSite} ${params.topic} section did not load`,
)
}
var snap = await page.snapshot()
var raw = callLLM({
prompt: `List the exact headline of every ${params.topic} article published today. Return only a JSON array of strings.`,
context: { text: snap.snapshot.outline(), imageBase64: await page.screenshot() },
})
var headlines = JSON.parse(raw.match(/\[[\s\S]*\]/)[0])
console.log('Found ' + headlines.length + ' headlines published today')
await machine.copyToClipboard({ text: headlines.join('\n'), intent: 'copy the headlines' })
var notepad = await machine.launch('notepad')
// ... paste into a new tab, then Save As with params.fileName (if the dialog fails,
// write the file directly and open it in Notepad)
return { file: params.fileName, count: headlines.length, headlines: headlines }
} catch (e) {
console.log('Task failed: ' + (e && e.message ? e.message : e))
throw e
}
}Now rerun it yourself. This is the fast, cheap run: the code decides the steps, not the agent.
MACHINE_ID=$(curl -s "$SAI_API_URL/v1/agents/machines" \
-H "Authorization: Bearer $SAI_API_KEY" | jq -r '.machines[0].machineId')
curl -s -X POST "$SAI_API_URL/v1/machines/$MACHINE_ID/control" -H "Authorization: Bearer $SAI_API_KEY"
jq -n --rawfile code main.js '{ code: ($code + "\nawait main()"), timeoutMs: 300000 }' |
curl -s "$SAI_API_URL/v1/machines/$MACHINE_ID/control/exec" \
-H "Authorization: Bearer $SAI_API_KEY" -H "Content-Type: application/json" -d @- | jq .result
curl -s -X DELETE "$SAI_API_URL/v1/machines/$MACHINE_ID/control" -H "Authorization: Bearer $SAI_API_KEY"It saves today's AI headlines from TechCrunch to techcrunch_headlines.txt and returns them. Pass other inputs to run the same code on another topic, for example await main({ newsSite: "TechCrunch", topic: "Fintech", fileName: "techcrunch_fintech.txt" }).
To read the job while it runs, see each run and its judgement, or choose how many versions and runs to allow, see Learn jobs.
Or use an SDK
pip install sai-sdk # Python
npm install @simular-ai/sai-sdk # TypeScriptfrom sai import Sai
result = Sai().sessions.create().run("Open Notepad, type hello, save it to the desktop as hello.txt")
print(result.text)The SDK does the polling and maps errors to exceptions; an approval comes back on the result. See the Python SDK and the TypeScript SDK.
Or add Sai to your coding agent
claude mcp add sai -e SAI_API_KEY=sapi_... -- npx -y @simular-ai/sai-mcpThen ask Claude Code to use Sai to open Notepad and save hello.txt on the desktop. The same server works in Codex and Cursor.