Docs/Python
SDKs

Python

pip install sai-sdk. Give Sai tasks from Python.

View as Markdown

The Python SDK wraps the API on these pages:

  • Sessions: give Sai a task on a computer, and get the answer back. Sai decides every click.

Note

Alpha (0.1). The surface may still change before 1.0.

Install

bash
pip install sai-sdk
export SAI_API_KEY="sapi_..."

The import name is sai. Python 3.10 or later; one dependency, httpx. Create the key in the portal (see Quickstart).

Give Sai a task

python
from sai import Sai

sai = Sai()
pc = sai.machines.list()[0]

session = sai.sessions.create(machine=pc)
result = session.run("Open Notepad, type hello, save it to the desktop as hello.txt")

print(result.status)  # idle, needs_approval or error
print(result.text)    # Sai's answer
print(result.usage)   # tokens and cost of this task
  • machine can be left out when the account has one computer.
  • More session.run(...) calls continue the same conversation. sai.sessions.create() starts a new one with no memory of the old. See Sessions.
  • run() sends the task and waits. On a free account the first task of the day waits about two minutes while the computer starts.

Watch it work

send() returns at once with a turn. Iterate its events, then read the result:

python
turn = session.send("Find last month's invoices in Downloads and total them")
for ev in turn.events():
    if ev.kind == "step":
        print("  ", ev.text)
    elif ev.kind == "approval":
        turn.respond(ev.approval, "yes")
result = turn.wait()
ev.kindWhat it is
stepA progress line: what Sai is doing
messageSai's text
approvalSai is waiting for permission. ev.approval holds it
tool_errorA step failed. Sai usually recovers
errorThe turn failed. ev.code is set when the account ran out (below)
finishedThe turn is complete

turn.events(raw=True) also yields reasoning, tool-call and framing frames as other, with the wire event in ev.raw. See Events.

Approvals

Sai asks before risky actions (see Approvals). Without a handler, run() and wait() return with status == "needs_approval" and the request in result.approval. To answer as it comes, pass on_approval:

python
def decide(approval):
    if approval.is_link_only:
        print("Approve in a browser:", approval.url)
        return None  # a person answers it; keep waiting
    if approval.type == "choice":
        return ("yes", [[q["options"][0]["value"]] for q in approval.questions])
    return "yes"

result = session.run("Clean up the temp folder", on_approval=decide)

The handler returns "yes", "no", "task" (allow the rest of this task), a (response, selections) pair for a choice, or None to leave the approval to someone else. A link-only approval cannot be answered over the API: respond() raises LinkOnlyApprovalError with approval_url.

Files, models, stopping

python
report = sai.files.upload("report.pdf")
session.run("Summarise the attached report", attachments=[report])

allowed = [m.id for m in sai.models.list() if m.allowed]
session.run("...", model="anthropic/claude-opus-5-5")  # sticks to the session

turn = session.send("...")
turn.abort()

A message sent while a turn is running steers that turn; turn.queued is then True.

Other calls

python
sai.account.get()      # plan and spendable credit
sai.models.list()      # models, with `allowed` for this plan
sai.machines.live(pc)  # a view-only stream of the screen; see Live view

Errors

Every non-2xx response raises a SaiError subclass carrying status, code, message and retry_after:

ExceptionWhen
AuthenticationError401: the key is missing, unknown or revoked
BadRequestError400: for example several computers and no machine=
PermissionDeniedError403: plan or ownership, such as model_not_allowed_for_plan
ConflictError409: for example machine_busy, or an approval already answered
LinkOnlyApprovalErrorThe approval must be answered in a browser: e.approval_url
RateLimitError429: a rate limit, or the free plan's daily computer time
ServiceUnavailableError503: the computer did not come online. Honor retry_after

A task that ran out of credit is not an exception. It ends with result.status == "error" and result.error_code set to insufficient_credits or free_computer_time_exhausted. Stop retrying for today. See Billing and limits.

Configuration

ArgumentEnvironmentDefault
api_keySAI_API_KEYrequired
base_urlSAI_API_URLhttps://api.simular.ai
timeout60 seconds per request