Python
pip install sai-sdk. Give Sai tasks from Python.
View as MarkdownThe 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 before1.0.
Install
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
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 taskmachinecan 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:
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()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:
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
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
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 viewErrors
Every non-2xx response raises a SaiError subclass carrying status, code, message and 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.