Approvals
Two classes of approval, and how to answer each.
View as MarkdownSai 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.
{
"type": "data-approval-request",
"data": {
"approvalId": "ap_7Qw",
"title": "Command Approval Required",
"description": "Sai wants to run a command.",
"approvalType": "exec",
"isLinkOnly": false,
"approvalUrl": "https://sai.simular.ai/approval/3fA9kQ2xYz/ap_7Qw?from=api",
"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,
"approvalUrl": "https://sai.simular.ai/approval/3fA9kQ2xYz/ap_9Lm?from=api",
"questions": [
{
"message": "Which folder should I save the report to?",
"options": [
{ "value": "desktop", "label": "Desktop" },
{ "value": "documents", "label": "Documents" }
],
"multiple": false,
"allowOther": false
}
]
}
}{ "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.