> ## Documentation Index
> Fetch the complete documentation index at: https://docs.botshield.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Ask through an agent

> Put one BotShield Action in front of a person already linked to you, as a card in the BotShield app, and collect the signed token when they approve.

Sometimes the act happens when the person is not on your page: your agent has found the ticket, the hold or the offer, and the person has to say yes. From your server, you ask BotShield to put **one action** in front of that person as a card in their BotShield app, naming the agent that asked. The person approves with their passkey, and you collect the same token a tap on your page gives you, with your act string in `claims.act`.

This works only for a person already linked to your company: a [Secured Account](https://docs.botshield.ai/secured-accounts/overview.md). Nothing in this flow links anyone. To ask someone who is not linked yet, use the [widget or `create-verification-link`](https://docs.botshield.ai/actions/overview#server-flow) while they are with you.

## Before you start

You need three things, all in the same environment:

| You need | Where |
| - | - |
| A live [BotShield Action](https://docs.botshield.ai/actions/overview.md#place-an-action), for example act key `hold` | **Verified Accounts › BotShield Actions**, or `gates-api/actions/create` |
| A registered agent | **Trusted Agents** in the Console. See [Register an agent](https://docs.botshield.ai/agents-ask/register-an-agent.md). You name it by its **agent name**; its display name heads the card. |
| An API key with **Gates API access** | **Settings → Developer Tools → API Keys**. See [Gates API](https://docs.botshield.ai/gate/gates-api.md#authentication). |

The environment comes from the API key. A `bs_dev_` key finds Development actions and agents, and reaches people linked in Development.

## Flow

**1. Ask**

`POST /gates-api/actions/request` with your own `request_id`, a UUID you generate and store against this ask.

```bash curl theme={null}
curl -s https://api.botshield.ai/operations/gates-api/actions/request \
  -H "Authorization: Bearer $BOTSHIELD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "request_id": "3d6f1c2a-8b4e-4f7a-9c1d-2e5b7a9f0c13",
    "agent": "MeridianConcierge",
    "act": "hold:tee_summer26:9f2c1a",
    "act_label": "Hold Summer ’26 tee · $40",
    "partner_user_ref": "usr_48121",
    "requested_by": "Muse"
  }'
```

```typescript TypeScript SDK theme={null}
import { BotShield } from "botshield-sdk";

const botshield = new BotShield({ serverURL: "https://api.botshield.ai/operations" });
const auth = { gatesKeyAuth: `Bearer ${process.env.BOTSHIELD_API_KEY}` };

const ask = await botshield.gates.actions.request(auth, {
  requestId: "3d6f1c2a-8b4e-4f7a-9c1d-2e5b7a9f0c13",
  agent: "MeridianConcierge",
  act: "hold:tee_summer26:9f2c1a",
  actLabel: "Hold Summer ’26 tee · $40",
  partnerUserRef: "usr_48121",
  requestedBy: "Muse",
});
if (ask.data.error) throw new Error(ask.data.error.message);
```

| Request field | Description |
| - | - |
| `request_id` | A UUID you choose. It names this ask in every later call, and it becomes `claims.request_id` on the token. |
| `agent` | The agent name of an agent your organization registered, in the key's environment and not revoked. |
| `act` | The [act string](https://docs.botshield.ai/actions/overview.md#the-act-string). The part before the first `:` names a placed, live action. Up to 128 characters. |
| `act_label` | Optional. The card's title: what the person is confirming. Up to 90 characters. Defaults to the action's **Name**. Display only: it is not in the token. |
| `partner_user_ref` | Your user ref for the person. They must already be linked to your company. |
| `requested_by` | Optional. Who asked, for display on the card, for example the person's own assistant. Up to 40 characters. |
| `ttl_seconds` | Optional. How long the card waits for an answer: `60`–`86400`. Default `600` (10 minutes). |

```json theme={null}
{
  "data": {
    "data": {
      "request_id": "3d6f1c2a-8b4e-4f7a-9c1d-2e5b7a9f0c13",
      "status": "queued",
      "expires_at": "2026-11-10T18:52:00.000Z"
    }
  }
}
```

The person gets a notification, and the card waits in the BotShield app until `expires_at`. The call is safe to repeat: the same `agent` and `request_id` answer with the card already queued and its current `status`. Reusing a `request_id` for a different act is refused with `request_id_reused`.

**2. The person approves**

The card shows your agent's display name, `act_label` and `requested_by`. Approving an action card always takes the person's passkey: the passkey signs the act itself. If you turn the action off before they approve, the approval is refused and nothing is confirmed.

**3. Collect the token**

`GET /gates-api/actions/request-status` with the `request_id`. Send `wait_seconds` (up to `25`) to hold the call open until the status changes, instead of polling in a tight loop.

```bash curl theme={null}
curl -s "https://api.botshield.ai/operations/gates-api/actions/request-status?request_id=3d6f1c2a-8b4e-4f7a-9c1d-2e5b7a9f0c13&wait_seconds=25" \
  -H "Authorization: Bearer $BOTSHIELD_API_KEY"
```

```json theme={null}
{
  "data": {
    "data": {
      "request_id": "3d6f1c2a-8b4e-4f7a-9c1d-2e5b7a9f0c13",
      "status": "approved",
      "act": "hold:tee_summer26:9f2c1a",
      "expires_at": "2026-11-10T18:52:00.000Z",
      "resolved_at": "2026-11-10T18:44:31.000Z",
      "token": "eyJhbGciOiJFUzI1NiIsImtpZCI6…",
      "token_expires_in": 583
    }
  }
}
```

| `status` | Meaning |
| - | - |
| `queued` | Waiting for the person. |
| `approved` | The person approved with their passkey. `token` is present while it can be collected. |
| `denied` | The person declined. No token. |
| `expired` | Nobody answered by `expires_at`. No token. |
| `cancelled` | The card was withdrawn. No token. |

You can collect the token for 10 minutes after `resolved_at`. `token_expires_in` says how many seconds are left. After that the status stays `approved`, with `token: null` and `token_expires_in: 0`.

**4. Verify the token once**

Check the token with `POST /sdk/verify-token`, exactly as for a tap on your page. See [Verify the token once](https://docs.botshield.ai/actions/overview.md#server-flow). Accept the act only when:

1. `valid` is `true`.
2. `claims.request_id` is the `request_id` you sent.
3. `claims.act` is exactly the act string you expect, rebuilt from your own records.

The token carries `request_id`, `verified`, `organization_id`, `audience`, `timestamp`, `nonce`, `issued_at`, `expires_at` and `act`. It carries `trusted: false`: it is a confirmation of one act, not an account check. It lives until the collection window closes, not 120 seconds.

Every copy of the token you collect for one card shares one `nonce`, so `verify-token` answers `valid: true` once per card, however many times you call `request-status`. Check it in one place and keep the first answer.

## Webhooks

When the person approves or declines the card, BotShield sends `agents_ask.resolution.confirmed` or `agents_ask.resolution.denied`, with your `request_id`. See [Webhook events](https://docs.botshield.ai/webhooks/events.md#agents-ask-events). The webhook tells you it happened. The action token comes only from `request-status`; the webhook's `proof_token` is the Agents Ask resolution, not the action token.

## Error codes

Errors arrive as `data.error` with HTTP `200`. A body that fails input validation, for example a `request_id` that is not a UUID or a `ttl_seconds` out of range, is rejected with HTTP `400` before the operation runs.

| `code` | `statusCode` | Operation | Cause |
| - | - | - | - |
| `invalid_key`, `gates_scope_required`, `org_not_found` | 401, 403, 404 | Both | See [Gates API](https://docs.botshield.ai/gate/gates-api.md#error-codes). |
| `action_invalid` | 400 | `request` | `act` does not start with an action key. |
| `action_not_found` | 404 | `request` | No action with this act key is placed in the key's environment. |
| `action_off` | 404 | `request` | The action is turned off. |
| `agent_not_found` | 404 | `request` | No live agent with this name is registered for your organization in this environment. |
| `not_linked` | 409 | `request` | This user ref is not linked to your company. Ask the person to link first. |
| `request_id_reused` | 409 | `request` | This `request_id` was already used for a different ask. |
| `request_not_found` | 404 | `request-status` | No action request with this `request_id` for your organization. |
| `internal` | 500 | `request` | BotShield could not send the action. |

## Next steps

> [card] [BotShield Actions](https://docs.botshield.ai/actions/overview.md)
>
> Place an action, the act string and binding the terms.

> [card] [Secured Accounts](https://docs.botshield.ai/secured-accounts/overview.md)
>
> How a person links to your company.

> [card] [Register an agent](https://docs.botshield.ai/agents-ask/register-an-agent.md)
>
> The agent name and the display name on the card.

> [card] [Gates API](https://docs.botshield.ai/gate/gates-api.md)
>
> Authentication and the shared error codes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.