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

# Q API Reference

> The agentlink/* operations — inquire, cards, confirm, check-status, cancel, and expire — with request and response shapes.

# Q API Reference

The Queue API is a set of operations under the **`agentlink/`** prefix. Base URL:

```
https://api.botshield.ai/operations
```

There are two audiences:

* **Trusted Agent** operations — `inquire`, `check-status`, `cancel` — authenticate with an agent key (`Authorization: Bearer bs_agent_<name>__<secret>`).
* **User-app** operations — `cards`, `confirm` — authenticate with the user's BotShield session (Clerk JWT) and are called by the BotShield app, not by you. They are documented here for completeness.
* **`expire`** is an internal cron sweep with no public route.

| Operation                                 | Caller        | Auth      | Type     |
| ----------------------------------------- | ------------- | --------- | -------- |
| [`agentlink/inquire`](#inquire)           | Trusted Agent | Agent key | mutation |
| [`agentlink/check-status`](#check-status) | Trusted Agent | Agent key | query    |
| [`agentlink/cancel`](#cancel)             | Trusted Agent | Agent key | mutation |
| [`agentlink/cards`](#cards)               | BotShield app | Clerk JWT | query    |
| [`agentlink/confirm`](#confirm)           | BotShield app | Clerk JWT | mutation |
| [`agentlink/expire`](#expire)             | Internal cron | —         | mutation |

***

## inquire

`POST /agentlink/inquire` — Propose an action for the user to confirm. Queues a Q card and notifies the user.

**Auth:** agent key. **Idempotency:** `(agent_id, request_id)`.

### Request

```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                       | Type                             | Required | Notes                                               |
| --------------------------- | -------------------------------- | -------- | --------------------------------------------------- |
| `request_id`                | UUID v4                          | Yes      | You generate it. Idempotency + join key.            |
| `action.summary_title`      | string (1–120)                   | Yes      | Card headline.                                      |
| `action.summary_detail`     | `{ label (1–40), value (1–80) }` | No       | One detail row.                                     |
| `action.category`           | string (1–80)                    | Yes      | Must be in the agent's `allowed_action_categories`. |
| `action.trusted_account_id` | UUID                             | No       | Linked account the action routes through.           |
| `user_email`                | email                            | One of   | Exactly one of `user_email` / `botshield_user_id`.  |
| `botshield_user_id`         | UUID                             | One of   | —                                                   |
| `adaptive_card_payload`     | object                           | No       | Validated by the TRUST Layer.                       |
| `ttl_seconds`               | int                              | No       | Default `600`; bounded `[60, 86400]`.               |

### Response — `200`

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

On an idempotent replay the response is the existing card plus `"idempotent_replay": true`.

### Errors

| Status | Code                   | Cause                                                       |
| ------ | ---------------------- | ----------------------------------------------------------- |
| `401`  | —                      | Bad/missing agent key.                                      |
| `403`  | `category_not_allowed` | `category` not in the agent's allow-list.                   |
| `400`  | —                      | Zero or both of `user_email` / `botshield_user_id`.         |
| `404`  | —                      | User not found / not registered with BotShield.             |
| `422`  | `trust_layer_rejected` | Adaptive Card failed the TRUST Layer (`violations` echoed). |
| `400`  | `ttl_below_floor`      | `ttl_seconds` \< 60.                                        |
| `400`  | `ttl_above_ceiling`    | `ttl_seconds` > 86400.                                      |

***

## check-status

`GET /agentlink/check-status` — Poll for the user's decision on a previously proposed action.

**Auth:** agent key.

### Request

```json theme={null}
{ "request_id": "3f1b2c4d-5e6a-4b7c-8d9e-0f1a2b3c4d5e" }
```

### Response — `200`

```json theme={null}
{
  "status": "approved",
  "ttl_at": "2026-06-16T12:10:00Z",
  "resolved_at": "2026-06-16T12:03:11Z",
  "ceremony_id": "e4d3c2b1-…",
  "resolution_jwt": "eyJhbGciOiJFUzI1Ni␣…",
  "verdict": "approve",
  "delivered": true,
  "callback_attempts": 1
}
```

| Field               | Notes                                                                                                        |
| ------------------- | ------------------------------------------------------------------------------------------------------------ |
| `status`            | `queued` \| `approved` \| `denied` \| `cancelled` \| `expired`.                                              |
| `resolution_jwt`    | The [Proof of Resolution](/queue/proof-of-resolution) — present only for `approved` / `denied`, else `null`. |
| `verdict`           | `approve` \| `denied` \| `null`.                                                                             |
| `delivered`         | Whether the callback was delivered to the agent.                                                             |
| `callback_attempts` | Delivery attempt count.                                                                                      |

`404` if the `request_id` is not found for this agent.

***

## cancel

`POST /agentlink/cancel` — Withdraw a still-`queued` action.

**Auth:** agent key. **Idempotent:** a no-op if the card already resolved.

### Request

```json theme={null}
{
  "request_id": "3f1b2c4d-5e6a-4b7c-8d9e-0f1a2b3c4d5e",
  "reason": "user abandoned the chat"
}
```

`reason` (≤ 200 chars) is optional and surfaced in the audit log.

### Response — `200`

```json theme={null}
{ "status": "cancelled", "card_id": "c1d2e3f4-…", "cancelled_at": "2026-06-16T12:04:00Z" }
```

If the card had already resolved, the response echoes its current status with `"already_resolved": true` and makes no change.

***

## cards

`GET /agentlink/cards` — Returns the authenticated user's currently-`queued` Q cards. **Called by the BotShield app**, not by agents.

**Auth:** Clerk JWT. Scoped to the authenticated user — never returns another user's cards.

### Response — `200`

```json theme={null}
{
  "cards": [
    {
      "id": "c1d2e3f4-…",
      "request_id": "3f1b2c4d-…",
      "agent": { "id": "a9f2…", "name": "ticketz-agent", "display_name": "Ticketz", "avatar_asset": null },
      "summary_title": "Book Uber to SFO",
      "summary_detail": { "label": "TOTAL", "value": "$48.00" },
      "category": "travel.book",
      "adaptive_card_payload": { "type": "AdaptiveCard", "body": [] },
      "status": "queued",
      "ttl_at": "2026-06-16T12:10:00Z",
      "created_at": "2026-06-16T12:00:00Z"
    }
  ]
}
```

The app polls this to refresh the surface while foregrounded; the [BotShield Q push](/queue/resolution-flow#the-user-journey) is the actual "wake the user" signal.

***

## confirm

`POST /agentlink/confirm` — Commit the user's staged decisions after a biometric ceremony, signing one [Proof of Resolution](/queue/proof-of-resolution) per card. **Called by the BotShield app** after the Face ID / Touch ID check passes.

**Auth:** Clerk JWT.

### Request

```json theme={null}
{
  "decisions": [
    { "card_id": "c1d2e3f4-…", "verdict": "approve" },
    { "card_id": "b2c3d4e5-…", "verdict": "denied" }
  ],
  "ceremony_id": "e4d3c2b1-…"
}
```

| Field                 | Notes                                                                                            |
| --------------------- | ------------------------------------------------------------------------------------------------ |
| `decisions`           | 1–20 items. Each `card_id` must belong to the user and be `queued`.                              |
| `decisions[].verdict` | `approve` \| `denied`.                                                                           |
| `ceremony_id`         | Optional UUID v4. Server generates one if omitted. Shared across all decisions in this ceremony. |

### Response — `200`

```json theme={null}
{
  "ceremony_id": "e4d3c2b1-…",
  "signed_at": "2026-06-16T12:03:11Z",
  "resolutions": [
    { "request_id": "3f1b2c4d-…", "card_id": "c1d2e3f4-…", "token": "eyJhbGciOiJFUzI1Ni␣…" }
  ]
}
```

All resolutions in one call share an identical `iat` and `ceremony_id` — the single-act, N-proof guarantee. Each card flips to `approved` / `denied`, and BotShield fires the matching `q.resolution.confirmed` / `q.resolution.denied` webhook per card.

### Errors

| Status | Cause                                                           |
| ------ | --------------------------------------------------------------- |
| `404`  | User not registered, or a `card_id` doesn't belong to the user. |
| `409`  | A referenced card is no longer `queued`.                        |
| `410`  | A referenced card expired before confirmation.                  |

***

## expire

`POST /agentlink/expire` — Internal cron sweep. Marks every `queued` card past its `ttl_at` as `expired`, logs a `card.expired` audit event, and fires `q.resolution.expired`. **No public route** — runs on a periodic (≈60s) schedule. Documented for completeness.

### Request

```json theme={null}
{ "limit": 200 }
```

`limit` (1–1000, default 200) caps cards processed per sweep.

### Response — `200`

```json theme={null}
{ "expired_count": 3 }
```

Idempotent: only acts on still-`queued`, past-TTL cards. Expiry is terminal and produces no Proof of Resolution.

***

## Related

<CardGroup cols={2}>
  <Card title="Agent Integration" icon="plug" href="/queue/agent-integration">
    Prerequisites, idempotency, and the TRUST Layer in context.
  </Card>

  <Card title="Proof of Resolution" icon="badge-check" href="/queue/proof-of-resolution">
    Verify the token returned by these operations.
  </Card>
</CardGroup>
