Approvals
Two classes of approval, and how to answer each.
Sai stops and asks before some actions: running a command, choosing between options, connecting a Google account, typing a site password, verifying a phone. Each ask is an approval request. Your loop must answer it or the task waits.
An approval arrives as an event of type data-approval-request, and GET /events reports status: "needs_approval" while it is pending.
The one field that matters
Look at isLinkOnly first. It splits approvals into two classes.
isLinkOnly: false: answer over the API
Command approvals (approvalType: "exec"), action approvals and choice questions (approvalType: "choice"). Answer with POST /approve. These events carry no approvalUrl: the approval page completes only link-only approvals.
{
"type": "data-approval-request",
"data": {
"approvalId": "ap_7Qw",
"title": "Command Approval Required",
"description": "Sai wants to run a command.",
"approvalType": "exec",
"isLinkOnly": false,
"command": "powershell -c Get-ChildItem C:\\Users\\sai\\Desktop",
"cwd": "C:\\Users\\sai"
}
}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 is one of:
A choice question carries questions. Answer it with selections, one inner array per question:
{
"type": "data-approval-request",
"data": {
"approvalId": "ap_9Lm",
"title": "Which folder?",
"description": "",
"approvalType": "choice",
"isLinkOnly": false,
"questions": [
{
"message": "Which folder should I save the report to?",
"options": [
{ "value": "desktop", "label": "Desktop" },
{ "value": "documents", "label": "Documents" }
],
"multiple": false,
"allowOther": false
}
]
}
}curl -s "$SAI_API_URL/v1/agents/approve" \
-H "Authorization: Bearer $SAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "approvalId": "ap_9Lm", "response": "yes", "selections": [["desktop"]] }'A value that was not offered is rejected with 422, unless the question has allowOther: true or no options, in which case free text is accepted.
isLinkOnly: true: open approvalUrl in a browser
Google connect (service_connect), a site password or other typed input (user_input) and phone verification (phone_verification). These need a human in a browser. The API cannot answer them.
{
"type": "data-approval-request",
"data": {
"approvalId": "ap_2Vd",
"title": "Connect your Google account",
"description": "Sai needs access to Gmail to read your inbox.",
"approvalType": "service_connect",
"isLinkOnly": true,
"approvalUrl": "https://sai.simular.ai/approval/3fA9kQ2xYz/ap_2Vd?from=api"
}
}Hand approvalUrl to the person who owns the account. Keep polling GET /events. When they finish, status returns to running.
Calling POST /approve on a link-only approval returns 409:
{
"error": "link_only",
"message": "This approval must be completed in a browser. Open approvalUrl.",
"approvalUrl": "https://sai.simular.ai/approval/3fA9kQ2xYz/ap_2Vd?from=api"
}Finding the pending approval
GET /events repeats the blocking approval in a top-level approval field whenever status is needs_approval, with the same shape as the event's data. Read that instead of scanning events; the event itself is delivered once, on the poll where it first appeared.
Expiry
Some approvals carry expiresAt, a Unix time in milliseconds. After it passes, POST /approve returns 409 because the request is no longer pending.
Timing
A free account's first request of the day can take about two minutes while its computer starts. Nothing is wrong. Keep polling with wait=30 and act on the first approval when it arrives.
Note
approvalUrl,expiresAtand the409 link_onlyanswer are available from October 1. The URL ishttps://sai.simular.ai/approval/{uid}/{approvalId}?from=api, whereuidis theuserIdfromGET /auth; on staging the host is the staging web app.
When Sai needs a person
Some steps need a person at the screen rather than an answer: a sign-in Sai has no password for, a captcha, a code sent to someone's phone. When Sai's turn ends on one of those, hand the computer's screen to that person with a live link:
- Call
POST /v1/agents/machines/{machineId}/liveand takeliveUrlfrom the response. - Send
liveUrlto the person. They open it in a browser; no Simular account or sign-in is needed. The link works for 30 minutes. - They do the step with the mouse and keyboard. While a turn runs, the page pauses its own input and shows Sai is working. The pause is only on the page, not enforced by the server.
- When they are done, send a message to the same session, such as "I've signed in, carry on." Sai picks up from the screen as the person left it.
- Revoke the link with
DELETE /v1/agents/machines/{machineId}/live, or let it expire.
curl -s -X POST "$SAI_API_URL/v1/agents/machines/$MACHINE_ID/live" \
-H "Authorization: Bearer $SAI_API_KEY"curl -s "$SAI_API_URL/v1/agents/message" \
-H "Authorization: Bearer $SAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "message": "I have signed in. Carry on with the task.", "machineId": "m_7f2a", "sessionId": "cs_01HZY", "wait": false }'There is no separate call to take over the computer or hand it back. Sai's turn ending is what frees the screen, and your next message is what gives it back to Sai. Ask the person to finish before you send it: once a new turn starts, the page pauses their input.
Anyone with liveUrl can use the computer, even while Sai is working, so send it only to the person who should act, and revoke it when they are done. Link-only approvals are different: they come with their own approvalUrl (above), and the person completes them there, not on the screen.
With an Organisation, your backend can do the same with a Tenant token and give the link to your customer, the Tenant's own user, so they sign in to their own accounts on their machine without a Simular account.