Docs/Quickstart
Get started

Quickstart

From sign-in to a finished task in one sitting.

Sai works on a real computer. Choose one of two before you start:

A cloud computer from SimularYour own computer
What it isA Windows or Linux computer Simular hosts for youYour own Mac or Windows PC, the one at your desk
SetupNothing to installInstall the Sai Agent on it, once
Good forScripts, CI, coding agents, work that should not touch your desktopYour apps, your files, your signed-in browser
kind in /machinescloudown

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

  1. On that computer, sign in at sai.simular.ai with the same Google account you will use on this portal.
  2. Open Add computer and choose Your Device (or go to Settings → Computers → Connect your device).
  3. 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.

Set your keyShell
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

List your computers
curl -s "$SAI_API_URL/v1/agents/machines" \
  -H "Authorization: Bearer $SAI_API_KEY"
200 OK · ComputersJSON
{
  "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:

Your own computer in /machinesJSON
{ "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.

Check your account
curl -s "$SAI_API_URL/v1/agents/account" \
  -H "Authorization: Bearer $SAI_API_KEY"
200 OK · AccountJSON
{
  "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

Give Sai a task
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
  }'
202 Accepted · Message sentJSON
{ "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

Poll for events
curl -s "$SAI_API_URL/v1/agents/events?sessionId=cs_01HZY&wait=30" \
  -H "Authorization: Bearer $SAI_API_KEY"
200 OK · EventsJSON
{
  "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.

data-approval-request eventJSON
{
  "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:

Answer an approval
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.

200 OK · Events, task finishedJSON
{
  "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):

Prompt
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 code for a 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.js

The 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):

The learned codeJavaScript
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.

Run the learned code
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

InstallShell
pip install sai-sdk               # Python
npm install @simular-ai/sai-sdk   # TypeScript
Run a task
from 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

Add to Claude CodeShell
claude mcp add sai -e SAI_API_KEY=sapi_... -- npx -y @simular-ai/sai-mcp

Then ask Claude Code to use Sai to open Notepad and save hello.txt on the desktop. The same server works in Codex and Cursor.

© 2026 Simular Inc. All rights reserved.