# 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`.

```json
{
  "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"
  }
}
```

```bash
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:

| Value  | Effect                                                       |
| ------ | ------------------------------------------------------------ |
| `yes`  | Allow this one request.                                      |
| `task` | Allow this and stop asking for the rest of the current task. |
| `no`   | Refuse. Sai continues without it.                            |

A choice question carries `questions`. Answer it with `selections`, one inner array per question:

```json
{
  "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
      }
    ]
  }
}
```

```json
{ "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.

```json
{
  "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`:

```json
{
  "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`, `expiresAt` and the `409 link_only` answer are available from October 1. The URL is `https://sai.simular.ai/approval/{uid}/{approvalId}?from=api`, where `uid` is the `userId` from `GET /auth`; on staging the host is the staging web app.
