Examples
cURL
The whole loop from a shell, with jq.
Set the key and host once:
Set your keyShell
export SAI_API_KEY="sapi_..."
export SAI_API_URL="https://api.simular.ai"Start a task
Start a taskShell
SESSION=$(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}' \
| jq -r .sessionId)
echo "$SESSION"Poll until done
Poll until doneShell
CURSOR=""
while :; do
RESP=$(curl -s "$SAI_API_URL/v1/agents/events?sessionId=$SESSION&since=$CURSOR&wait=30" \
-H "Authorization: Bearer $SAI_API_KEY")
CURSOR=$(echo "$RESP" | jq -r .cursor)
STATUS=$(echo "$RESP" | jq -r .status)
echo "$RESP" | jq -c '.events[] | select(.type=="data-progress") | .data.text'
case "$STATUS" in
running) ;;
needs_approval)
echo "$RESP" | jq '.events[] | select(.type=="data-approval-request") | .data'
break ;;
idle)
echo "$RESP" | jq -r .text
break ;;
error)
echo "$RESP" | jq '.events[] | select(.type=="error")'
break ;;
esac
doneAn empty since on the first call is the same as omitting it.
Answer an approval
When the loop prints an approval with "isLinkOnly": false:
Answer an approvalcURL
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" }'When it prints "isLinkOnly": true, open the approvalUrl in a browser. Then run the poll loop again with the same SESSION and CURSOR.
The poll response also carries the blocking approval as a top-level approval field, so jq .approval works when the event has already scrolled past.
Attach a file
Upload and attach a fileShell
ATTACHMENT=$(curl -s "$SAI_API_URL/v1/agents/upload" \
-H "Authorization: Bearer $SAI_API_KEY" \
-H "x-filename: report.pdf" \
--data-binary @./report.pdf)
curl -s "$SAI_API_URL/v1/agents/message" \
-H "Authorization: Bearer $SAI_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n --argjson a "$ATTACHMENT" \
'{message:"Summarise the attached PDF in five bullets", wait:false, attachments:[$a]}')"Stop a task
Stop the taskcURL
curl -s "$SAI_API_URL/v1/agents/abort" \
-H "Authorization: Bearer $SAI_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"sessionId\":\"$SESSION\"}"Get typed output
Add options to ask for JSON, and keep the turnId:
Start a task with typed outputShell
TURN=$(curl -s "$SAI_API_URL/v1/agents/message" \
-H "Authorization: Bearer $SAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"message": "Open https://news.ycombinator.com and read the title of the top story.",
"options": {
"output_schema": {
"type": "object",
"properties": { "title": { "type": "string" } },
"required": ["title"]
}
},
"wait": false
}' | jq -r .turnId)
echo "$TURN"Poll the turn until it ends:
Poll the turnShell
while :; do
R=$(curl -s "$SAI_API_URL/v1/agents/turns/$TURN" -H "Authorization: Bearer $SAI_API_KEY")
case $(echo "$R" | jq -r .status) in
succeeded) echo "$R" | jq .output; break ;;
failed) echo "$R" | jq '{reason_code, raw_answer, output_errors}'; break ;;
*) sleep 3 ;;
esac
donestatus is needs_input while an approval is pending. Read it from GET /events with the turn's sessionId, and answer it as above.
Stream instead
Leave wait out and read the SSE frames as they arrive:
Stream a taskcURL
curl -sN "$SAI_API_URL/v1/agents/message" \
-H "Authorization: Bearer $SAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "message": "What is on the screen right now?" }'