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

# Agent Integration

> How a Trusted Agent proposes an action — the inquire flow, prerequisites, idempotency, and TRUST Layer validation.

# Agent Integration

A **Trusted Agent** is an AI agent you register with BotShield to act on a user's behalf. Once registered, the agent calls a single endpoint — **`agentlink/inquire`** — to propose an action for the user to confirm. This page covers how to make that call and what to expect back.

## Prerequisites

Before an agent can propose an action, three things must be true:

<Info>
  The target only needs to be a **registered BotShield user**. A linked Trusted Account is recommended (see below) but is **not** a hard precondition — `inquire` does not reject a user who lacks one.
</Info>

<Steps>
  <Step title="The agent is registered and holds an agent key">
    Each Trusted Agent authenticates with a bearer key of the form `bs_agent_<agent_name>__<secret>`. You pass it on every Q API call:

    ```http theme={null}
    Authorization: Bearer bs_agent_<agent_name>__<secret>
    ```
  </Step>

  <Step title="The action category is allowed for the agent">
    Each agent has an `allowed_action_categories` list (e.g. `travel.book`, `commerce.purchase`). Proposing an action whose `category` is not on that list is rejected with `403 category_not_allowed`. An empty allow-list means "no restriction."
  </Step>

  <Step title="The user is registered with BotShield">
    `inquire` resolves the target to a registered BotShield user; an unknown email returns `404`. A linked **Trusted Account** is *recommended* — it is a V2.1 routing input (target it via `action.trusted_account_id`) and it strengthens MultiPass durability — but it is **optional**. A user with no linked Trusted Account is **not** rejected; the proposal is queued normally.
  </Step>
</Steps>

## The inquire flow

```mermaid theme={null}
sequenceDiagram
    participant A as Trusted Agent
    participant B as BotShield (Q API)
    participant U as User (BotShield app)

    A->>B: POST agentlink/inquire (request_id, action, user, card)
    B->>B: Authenticate agent + authorize category
    B->>B: Resolve user → BotShield user
    B->>B: TRUST Layer validates Adaptive Card
    B->>U: BotShield Q push, Q card queued
    B-->>A: { status: "queued", card_id, ttl_at }
    A->>B: POST agentlink/check-status (poll request_id)
    U->>B: Reviews card → biometric Confirm/Deny
    B-->>A: { status: "approved" | "denied", resolution_jwt }
```

The agent's job is two calls: **propose** (`inquire`), then **poll** (`check-status`) — or instead receive the result via a [webhook](/queue/proof-of-resolution#webhook-events).

## Proposing an action

`POST /agentlink/inquire`

```json theme={null}
{
  "request_id": "3f1b2c4d-5e6a-4b7c-8d9e-0f1a2b3c4d5e",
  "action": {
    "summary_title": "Book Uber to SFO",
    "summary_detail": { "label": "TOTAL", "value": "$48.00" },
    "category": "travel.book",
    "trusted_account_id": "7a8b9c0d-1e2f-4a3b-8c5d-6e7f8a9b0c1d"
  },
  "user_email": "rider@example.com",
  "adaptive_card_payload": { "type": "AdaptiveCard", "body": [] },
  "ttl_seconds": 600
}
```

| Field                                   | Required | Notes                                                                                                      |
| --------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| `request_id`                            | Yes      | A UUID v4 **you generate**. This is the idempotency key and the join key for every later call and webhook. |
| `action.summary_title`                  | Yes      | 1–120 chars. The card's headline.                                                                          |
| `action.summary_detail`                 | No       | One `{ label, value }` row (e.g. `TOTAL · $48.00`).                                                        |
| `action.category`                       | Yes      | Must be in the agent's `allowed_action_categories`.                                                        |
| `action.trusted_account_id`             | No       | The linked account the action routes through.                                                              |
| `user_email` **or** `botshield_user_id` | Yes      | Provide **exactly one**. Identifies the user to notify.                                                    |
| `adaptive_card_payload`                 | No       | The rich card body the user sees. Validated by the TRUST Layer.                                            |
| `ttl_seconds`                           | No       | Default `600`. Bounded `[60, 86400]`. Out of range → `ttl_below_floor` / `ttl_above_ceiling`.              |

### Success response

```json theme={null}
{
  "status": "queued",
  "card_id": "c1d2e3f4-…",
  "ttl_at": "2026-06-16T12:10:00Z"
}
```

The user has been notified and the card is live until `ttl_at`. Hold onto your `request_id` — you'll poll with it.

## User resolution

You identify the user by **`user_email`** or **`botshield_user_id`** (exactly one). BotShield resolves email to a registered BotShield user through Clerk. The email is used only to find the user — it is **not** stored on the card and **never** appears in the Proof of Resolution or any webhook. If the email maps to a Clerk user who has not completed BotShield registration, the call returns `404` ("not registered with BotShield").

## Idempotency

The pair `(agent_id, request_id)` is unique. If you retry `inquire` with a `request_id` you already used, BotShield returns the **existing** card unchanged — it does not queue a duplicate or re-notify the user:

```json theme={null}
{
  "status": "queued",
  "card_id": "c1d2e3f4-…",
  "idempotent_replay": true
}
```

Always generate one `request_id` per logical action and reuse it on retries.

## The TRUST Layer

If you supply an `adaptive_card_payload`, BotShield runs it through the **TRUST Layer** *before* queuing the card. This is a three-stage check — a schema allow-list, a semantic judge (Claude Haiku), and a regex backstop — that prevents a malicious or buggy agent from pushing a phishing prompt, injection payload, or off-policy content onto the user's device.

A rejected payload returns `422` with the violations echoed back so you can tune your card:

```json theme={null}
{
  "error": {
    "message": "TRUST Layer rejected the Adaptive Card payload.",
    "statusCode": 422,
    "violations": [
      { "type": "disallowed_element", "path": "body[2].actions[0]", "severity": "high" }
    ]
  }
}
```

A card that passes is hashed (`ac_hash`) and stored; the hash binds the exact content the user saw.

## Error reference

| Status | Code                   | Cause                                                            |
| ------ | ---------------------- | ---------------------------------------------------------------- |
| `401`  | —                      | Missing or invalid agent key.                                    |
| `403`  | `category_not_allowed` | `action.category` not in the agent's allow-list.                 |
| `400`  | —                      | Neither / both of `user_email` and `botshield_user_id` supplied. |
| `404`  | —                      | User not found, or not registered with BotShield.                |
| `422`  | `trust_layer_rejected` | The Adaptive Card payload failed the TRUST Layer.                |
| `400`  | `ttl_below_floor`      | `ttl_seconds` \< 60.                                             |
| `400`  | `ttl_above_ceiling`    | `ttl_seconds` > 86400.                                           |

## Next steps

<CardGroup cols={2}>
  <Card title="Resolution Flow" icon="route" href="/queue/resolution-flow">
    What the user sees, and how the card moves through its states.
  </Card>

  <Card title="Q API Reference" icon="code" href="/queue/api-reference">
    Full request/response shapes for every operation.
  </Card>
</CardGroup>
