# Ask a named person to approve

Have an AI agent ask one named person to approve an action on their own device, and receive an access token that names both of them.

Sometimes an AI agent needs the approval of one particular person: the customer whose money it is moving, or the engineer whose change it is merging. The agent asks, the person answers on their own device, and the agent receives an access token that names that person.

This is OpenID Connect Client-Initiated Backchannel Authentication ([CIBA](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html)), in poll mode. _Backchannel_ means the person is asked outside the agent's own connection. _Poll mode_ means the agent asks the token endpoint for the result until there is one.

To ask the agent's owner or approval group instead of a named person, see [Ask before a sensitive action](/agents/approvals).

## Before you begin

You need:

- A registered agent and its private key. See [Give an AI agent an identity](/quickstart/agent).
- The name the person signs in with, for example `ada@acme.com`.

The agent authenticates here exactly as it does to sign in: with a _client assertion_, a short statement signed with the agent's own key. `$ASSERTION` in the examples is a client assertion built as in [Give an AI agent an identity](/quickstart/agent). An assertion is accepted once, so build a new one for every request.

<Steps>

### Send the request to the person

<Endpoint method="POST" path="/apis/regional/v1/ai/auth/backchannel-authorize" />

The body is form-encoded.

```bash
curl https://acme.authfi.io/apis/regional/v1/ai/auth/backchannel-authorize \
  -d client_id=agt_7f3a9c21 \
  -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
  -d client_assertion="$ASSERTION" \
  -d login_hint=ada@acme.com \
  -d scope="transfer:accounts" \
  --data-urlencode "binding_message=Send 1,200 USD to Northwind Ltd" \
  -d requested_expiry=300
```

| Parameter          | Meaning                                                         |
| ------------------ | --------------------------------------------------------------- |
| `login_hint`       | Required. The person to ask, by the name they sign in with.     |
| `scope`            | What the agent wants to do with this person's authority         |
| `binding_message`  | One short sentence the person reads before deciding             |
| `requested_expiry` | Seconds the person has to answer. 120 if left out, 600 at most. |

`client_id` is the agent ID, `agt_7f3a9c21` in this example. The console shows it as "Client id" on the agent's **Connect** tab, under **Identity > AI Agents**.

```json
{ "auth_req_id": "bXl0aWNrZXQtNDgyMQ...", "expires_in": 300, "interval": 5 }
```

The person is notified. They see the agent's name and your binding message.

The person answers on the **Approvals** page of the console, under "Asking to act as you". They choose **Approve** to approve, or **Refuse** to deny. Only the named person can answer the request.

```bash
export AUTH_REQ_ID=<the auth_req_id from the response>
```

### Poll for the answer

Ask the token endpoint with the `auth_req_id`, no more often than every `interval` seconds. Each poll carries a fresh client assertion.

<Endpoint method="POST" path="/apis/regional/v1/ai/auth/token" />

```bash
curl https://acme.authfi.io/apis/regional/v1/ai/auth/token \
  -d grant_type=urn:openid:params:grant-type:ciba \
  -d auth_req_id="$AUTH_REQ_ID" \
  -d client_id=agt_7f3a9c21 \
  -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
  -d client_assertion="$ASSERTION"
```

Until the person answers, the response is a `400` with an OAuth error:

```json
{ "error": "authorization_pending" }
```

| Response                      | Meaning                     | What the agent does                   |
| ----------------------------- | --------------------------- | ------------------------------------- |
| `400` `authorization_pending` | The person has not answered | Waits `interval` seconds, polls again |
| `400` `slow_down`             | The agent polls too fast    | Waits longer between polls            |
| `400` `access_denied`         | The person denied           | Stops                                 |
| `400` `expired_token`         | Nobody answered in time     | Stops, or asks again                  |
| `200` with an access token    | The person approved         | Goes ahead                            |

### Use the access token

The access token says who approved and who is acting.

| Claim       | Value                       |
| ----------- | --------------------------- |
| `sub`       | The person who approved     |
| `act.sub`   | The agent, as `agent:agt_…` |
| `client_id` | The agent                   |
| `scope`     | What the person approved    |
| `exp`       | 15 minutes after issue      |

Send it as a Bearer credential. A service that receives it sees both identities: this is the agent, acting for that person. To check these claims in your own service, see [Accept agents at your own API](/agents/verify).

</Steps>

## Check that it works

Send the request with your own sign-in name as `login_hint`.

1. Poll once. The response is `400` with `authorization_pending`.
2. In the console, open **Approvals**. Under "Asking to act as you", choose **Approve** on the request. The console shows "Approved".
3. Poll again. The response is `200` with an access token.
4. Decode the access token. `sub` is you, and `act.sub` is the agent.

## How the approval is protected

- **The agent cannot approve its own request.** The person decides on a channel that belongs to them. The agent holds only the `auth_req_id`, which approves nothing.
- **The scope is the one the person saw.** The access token carries what was approved, not what the agent sends when it collects the token.
- **An approval is used once.** The first successful poll spends it. A second poll with the same `auth_req_id` is rejected.
- **No answer is a denial.** An unanswered request expires and can never be approved afterwards.
- **A stolen `auth_req_id` is useless alone.** Every poll must be signed with the agent's key.

## Write a good binding message

The binding message is the only thing the person reads before deciding, usually on a phone. Say exactly what will happen.

| Instead of            | Write                              |
| --------------------- | ---------------------------------- |
| `transfer:accounts`   | Send 1,200 USD to Northwind Ltd    |
| Approve agent request | Merge pull request 482 into `main` |
| Access requested      | Read the Q3 payroll export         |

Name the amount, the destination and the thing. If the person cannot tell what will happen, they cannot approve it.

## Troubleshooting

| What you see      | Cause                                                            | Fix                                                                      |
| ----------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `invalid_request` | The body could not be read, or a field is missing                | Send the body form-encoded, with `login_hint`                            |
| `invalid_client`  | The agent is unknown or revoked, or its assertion did not verify | Check the key ID, the audience and the clock. Use a new `jti` each time. |
| `slow_down`       | The agent polls more often than `interval`                       | Wait longer between polls                                                |
| `expired_token`   | The request expired before anyone answered                       | Send a new request. Raise `requested_expiry`, up to 600.                 |

Every error of the token endpoint is listed in the [errors reference](/reference/errors).

## Next steps

<Cards>
  <Card href="/agents/verify" title="Accept agents at your own API">
    Read from the access token who approved and who is acting.
  </Card>
  <Card href="/agents/approvals" title="Ask before a sensitive action">
    Get a decision for one action, with approval by the agent's owner or approval group.
  </Card>
  <Card href="/agents/how-it-works" title="How agent sign-in works">
    Where CIBA fits among the standards an agent uses.
  </Card>
</Cards>
