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

# AI widgets (headless protocol)

> Build signed partner modules that render Cards in Airlead AI Work and the Command Deck.

# AI widgets (headless protocol)

Partners can ship **modules** that return presentation **Cards** for Airlead AI surfaces. Airlead calls your HTTPS endpoint with a signed JSON request; you respond with Widget Protocol **v1** payloads.

This is not the same as lead ingest or Connect delivery. Modules enrich the UI; they do not mutate workspace data.

## Concepts

| Term        | Meaning                                                              |
| ----------- | -------------------------------------------------------------------- |
| **Module**  | Catalog entry (`WidgetApp`) with a `dataEndpoint` and signing secret |
| **Card**    | One renderable payload (`trendMetric`, `insight`, …)                 |
| **Surface** | Where the module may appear                                          |
| **Scope**   | Which record fields Airlead includes in the request                  |

## Surfaces

| Surface                        | Typical use                   | Partner kinds allowed                                          |
| ------------------------------ | ----------------------------- | -------------------------------------------------------------- |
| `record.person.command-deck`   | Lead sidebar Command Deck     | `trendMetric`, `insight`, `entityProfile`, `actionRow`         |
| `record.interest.command-deck` | Interest sidebar Command Deck | `trendMetric`, `insight`, `entityProfile`, `actionRow`         |
| `ai.chat.empty`                | Work / empty-home dashboard   | `trendMetric`, `insight` (plus `clarify` for first-party only) |

`ai.chat.inline` is reserved for Airlead AI tool results (drafts, delivery repair, …). Partners must **not** return workflow cards such as `draft`, `deliveryRepair`, or `confirmDraft` actions.

## Registration

Airlead registers your module server-side (slug, publisher, HTTPS endpoint, surfaces, scopes). After registration, a workspace admin enables it under **Settings → Apps & Module → Partner-Widgets**.

Local / staging helper (Airlead operators):

```bash theme={null}
node --require ./scripts/stub-server-only.cjs --import tsx scripts/register-partner-widget.ts \
  --slug acme.enrichment \
  --name "Acme Enrichment" \
  --publisher "Acme" \
  --endpoint https://widgets.acme.example/v1/airlead \
  --surfaces record.person.command-deck,record.interest.command-deck,ai.chat.empty \
  --scopes record.id,record.type,record.displayName,record.city,record.postalCode
```

The script prints a `signingSecret`. Store it with the partner; it is never exposed to browsers.

Endpoint rules:

* Public `https://` URL, or
* Same-origin mock only: `/api/widgets/partner-mock` (development)

## Request (Airlead → partner)

`POST` your `dataEndpoint` with:

| Header                       | Value                  |
| ---------------------------- | ---------------------- |
| `content-type`               | `application/json`     |
| `accept`                     | `application/json`     |
| `x-airlead-widget-protocol`  | `1`                    |
| `x-airlead-widget-timestamp` | Unix seconds           |
| `x-airlead-widget-signature` | `v1=<hex>` HMAC-SHA256 |

### Signature

Canonical string:

```text theme={null}
${unixSeconds}.${rawBody}
```

HMAC-SHA256 with the shared secret, hex-encoded, prefixed with `v1=`.

Reject requests when:

* Signature missing or mismatch
* Timestamp skew greater than **5 minutes**

### Body

```json theme={null}
{
  "version": "1",
  "slug": "acme.enrichment",
  "surface": "record.person.command-deck",
  "workspace": { "id": "ws_…", "slug": "acme" },
  "locale": "de",
  "scopes": ["record.id", "record.type", "record.displayName", "record.city"],
  "record": {
    "type": "person",
    "id": "lead_…",
    "displayName": "Max Mustermann",
    "city": "Berlin",
    "postalCode": "10115"
  }
}
```

`record` only contains fields granted by `scopes`. Default partner scopes exclude email and phone unless explicitly granted at registration.

For `ai.chat.empty`, Airlead may send a workspace-level placeholder record (`id` like `workspace:…`). Prefer workspace-level metrics in that case; do not assume a real lead id.

## Response (partner → Airlead)

```json theme={null}
{
  "version": "1",
  "slug": "acme.enrichment",
  "widgets": [
    {
      "kind": "trendMetric",
      "title": "Partner Score",
      "value": 82,
      "valueFormat": "number",
      "unitLabel": "pts",
      "appSlug": "acme",
      "actions": [
        {
          "id": "ask-score",
          "type": "followUp",
          "label": "Details",
          "prompt": "Wie setzt sich der Partner Score zusammen?"
        }
      ]
    }
  ]
}
```

Rules:

* `version` must be `"1"`
* `slug` must match the request
* `widgets` is an array of Card payloads
* Airlead filters kinds to the partner allowlist ∩ surface matrix
* Actions may only be `link` or `followUp` (`confirmDraft` is stripped)

Respond quickly. Airlead aborts partner fetches around **800 ms**; timeouts soft-fail and hide the module for that paint.

## Allowed Card kinds (partner)

### `trendMetric`

Large number with optional delta, period, footnote, and actions.

### `insight`

Headline + body + optional evidence prompts (good for alerts).

### `entityProfile`

Compact entity summary (Command Deck).

### `actionRow`

Action buttons only — `link` / `followUp`.

## Scopes

| Scope                      | Field                       |
| -------------------------- | --------------------------- |
| `record.id`                | Always included             |
| `record.type`              | `person` \| `interest`      |
| `record.displayName`       | Display name                |
| `record.email`             | Email (opt-in)              |
| `record.phone`             | Phone (opt-in)              |
| `record.city`              | City                        |
| `record.postalCode`        | Postal code                 |
| `record.country`           | Country                     |
| `record.companyHint`       | Company hint                |
| `record.productName`       | Product                     |
| `record.sourceName`        | Source label                |
| `record.interestRequestId` | Interest id when applicable |

## Errors

Return HTTP 2xx only with a valid Protocol v1 body. Non-2xx responses are treated as partner errors. Airlead maps failures to soft UI errors (`timeout`, `unauthorized`, `invalid_response`, `partner_error`, `unavailable`) and does not block the rest of the deck.

## Enablement

1. Module registered and `published`
2. Workspace enables it under **Apps & Module**
3. Surfaces include the target surface
4. Endpoint reachable from Airlead servers with a valid signature

Core Airlead modules default to enabled; partner modules default to **disabled** until toggled on.

## Security checklist

* Use TLS and rotate the signing secret if leaked
* Verify signature + timestamp on every request
* Do not log full record payloads with PII
* Never return Airlead workflow cards or draft confirmations
* Keep responses under the timeout budget

## Related

* [Developers introduction](/developers/introduction)
* [Connect integration](/developers/connect-integration) (workspace-to-workspace delivery — separate from widgets)
* Product: Settings → **Apps & Module**
