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

# activate_list

> Send a saved List to its linked Luma event. The only write tool on this surface — action=confirm sends real invites.

<Note>
  This page documents the **Python MCP surface** (`mcp.heybtw.com`). It reflects `mcp-server/tools/activate_list.py` as of this page's publication — see [Schema stability](/docs/integrations/mcp-tools#schema-stability).
</Note>

<Warning>
  **This tool writes. `action="confirm"` sends real invites to real people.**

  It is the only non-read-only tool on this surface. It is registered with
  `readOnlyHint=false` and `idempotentHint=false`, and those annotations are
  accurate — do not treat this tool like the read tools, and do not retry a
  `confirm` freely. The one safe retry is narrowly defined below under
  [When a retry is safe](#when-a-retry-is-safe).
</Warning>

## Purpose

`activate_list` sends a saved heyBTW [List](/docs/lists/overview) to the Luma event it is linked to, behind a mandatory human confirmation round trip. It wraps the same server-side send service as the Activate panel in the product, so eligibility rules, Luma dedupe, quota warnings, and the resulting funnel all agree with what the UI would do.

This is the one action path for sending a List to Luma. For discovering who to invite in the first place, use [`get_invite_recommendations`](/docs/mcp-tools/get-invite-recommendations).

## The confirmation protocol

This sequence is not a convention an agent may optimise away. Every invariant in it is re-enforced server-side, and a `confirm` that skips a step is refused.

1. **Call `action="preview"`** with the `list_id`. Nothing is sent. The response carries the eligible count, the excluded counts and their reasons, the named target event with its dates, and the workspace's trailing-7-day invite usage.
2. **Present that payload to the human** and obtain their explicit go-ahead. The preview response carries a `coverage_note` saying exactly this. Do not summarise the preview into a yes/no question that hides the target event or the count.
3. **Call `action="confirm"`** with the same `list_id` and a **fresh UUID** `client_request_id`.

Two additional gates can apply, both surfaced on the preview:

* **Sends over 2,500** require `typed_count` — the eligible count typed back by the human. `preview.requires_typed_count` tells you when this applies.
* **A preview that exceeds the workspace's weekly reference** requires `weekly_warning_acknowledged=true`. `preview.exceeds_weekly_reference` tells you when this applies.

<Warning>
  Never call `action="confirm"` as the first call in a session, and never call
  it to "check whether it would work." There is no dry-run mode on `confirm`;
  `preview` is the dry run.
</Warning>

## Parameters

| Name                          | Type                                                   | Required                                      | Default     | Description                                                                                                  | Example                    |
| ----------------------------- | ------------------------------------------------------ | --------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------ | -------------------------- |
| `action`                      | `"preview"` \| `"confirm"` \| `"status"` \| `"funnel"` | No                                            | `"preview"` | Which operation to run. An unrecognised value is rejected as a structured `invalid_parameter` error.         | `"preview"`                |
| `list_id`                     | string                                                 | For `preview`, `confirm`, `funnel`            | `""`        | The saved List to act on.                                                                                    | `"lst_4c19a"`              |
| `activation_id`               | string                                                 | For `status`                                  | `""`        | The activation to report on, as returned by a `confirm`.                                                     | `"act_7d21b"`              |
| `message`                     | string                                                 | No                                            | `""`        | Optional message body accompanying the invite.                                                               | `"Hope to see you there."` |
| `client_request_id`           | string                                                 | For `confirm`                                 | `""`        | A fresh UUID per intended send. The server's dedupe key — see [When a retry is safe](#when-a-retry-is-safe). | `"3f2c…"`                  |
| `typed_count`                 | integer \| null                                        | For sends over 2,500                          | `null`      | The eligible count, typed back by the human.                                                                 | `2700`                     |
| `weekly_warning_acknowledged` | boolean                                                | When the preview exceeds the weekly reference | `false`     | Human acknowledgement of the weekly-volume warning.                                                          | `true`                     |

Omitting a required argument for the chosen `action` is rejected as a structured `invalid_parameter` error **before** any network call, so a malformed `confirm` cannot reach the send service.

## Access requirements

* **A workspace-scoped credential.** Admin and global keys are refused, on every action rather than only on `confirm` — a preview that a later confirm would refuse is a confirmation payload that can only mislead the human reading it.
* **A plan including the `agent-connections-outbound` capability.** Without it the tool raises a structured `entitlement_required` error.

## Sample request

```json theme={null}
{
  "tool": "activate_list",
  "arguments": { "action": "preview", "list_id": "lst_4c19a" }
}
```

## Sample response (`action="preview"`)

```json theme={null}
{
  "tool": "activate_list",
  "query_time_ms": 132.5,
  "result_count": 1,
  "data": [
    {
      "action": "preview",
      "result": {
        "status": "ok",
        "event_name": "Robotics Summit SF 2026",
        "event_starts_at": "2026-06-18T17:00:00+00:00",
        "event_ends_at": "2026-06-18T21:00:00+00:00",
        "eligible_count": 184,
        "excluded_count": 26,
        "total_count": 210,
        "excluded_by_reason": { "already_invited": 19, "no_consent": 5, "invalid_email": 2 },
        "previously_sent_count": 19,
        "week_sent_count": 340,
        "weekly_reference": 500,
        "exceeds_weekly_reference": false,
        "requires_typed_count": false,
        "consent_filtered": true
      }
    }
  ],
  "metadata": {
    "data_sources_queried": ["List", "ListActivation", "ListActivationRecipient"],
    "filters_applied": { "action": "preview", "list_id": "lst_4c19a", "activation_id": "" },
    "coverage_notes": [
      "Nothing was sent. Present this preview to the user and obtain their explicit confirmation before calling action=confirm."
    ]
  }
}
```

## Response field reference

### Envelope

| Field                     | Type             | Description                                                                                     |
| ------------------------- | ---------------- | ----------------------------------------------------------------------------------------------- |
| `tool`                    | string           | Always `"activate_list"`.                                                                       |
| `result_count`            | integer          | Always `1` — one action in, one result out.                                                     |
| `data`                    | array (length 1) | `{ action, result }`. `action` echoes what you asked for; `result` is shaped per action, below. |
| `metadata.coverage_notes` | array of strings | On `preview`, always carries the "nothing was sent" instruction. Empty on the other actions.    |

A `result` whose `status` is not `"ok"` (`invalid`, `unavailable`, `not_found`, `forbidden`) passes through as the service returned it, rather than being reworded here.

### `result` — `action="preview"`

| Field                                                               | Type                      | Description                                                                                                                              |
| ------------------------------------------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `event_name` / `event_starts_at` / `event_ends_at`                  | string                    | The target event, named and dated. Present it verbatim: this is what makes the human's confirmation informed.                            |
| `eligible_count`                                                    | integer                   | How many recipients would be sent to.                                                                                                    |
| `excluded_count` / `total_count`                                    | integer                   | Excluded and total list membership.                                                                                                      |
| `excluded_by_reason`                                                | object                    | Counts keyed by exclusion reason.                                                                                                        |
| `previously_sent_count`                                             | integer                   | How many were already sent to. **A count, never a roster** — recipient email addresses are deliberately not forwarded to the agent wire. |
| `week_sent_count` / `weekly_reference` / `exceeds_weekly_reference` | integer, integer, boolean | The workspace's trailing-7-day usage against its reference volume.                                                                       |
| `requires_typed_count`                                              | boolean                   | `true` when the confirm will require `typed_count`.                                                                                      |
| `consent_filtered`                                                  | boolean                   | Whether consent filtering was applied.                                                                                                   |

### `result` — `action="confirm"`

| Field           | Type    | Description                                                                                         |
| --------------- | ------- | --------------------------------------------------------------------------------------------------- |
| `activation_id` | string  | Pass to `action="status"` to follow progress.                                                       |
| `state`         | string  | The activation's state.                                                                             |
| `counts`        | object  | Send counts.                                                                                        |
| `idempotent`    | boolean | `true` when this call matched an existing `client_request_id` and therefore did **not** send again. |

### `result` — `action="status"`

| Field                          | Type   | Description                                                            |
| ------------------------------ | ------ | ---------------------------------------------------------------------- |
| `activation_id` / `event_name` | string | Which activation, against which event.                                 |
| `state`                        | string | Send progress.                                                         |
| `counts`                       | object | Per-recipient outcome counts (pending / submitted / failed / skipped). |

### `result` — `action="funnel"`

| Field                                              | Type    | Description                                                                               |
| -------------------------------------------------- | ------- | ----------------------------------------------------------------------------------------- |
| `membership_mode`                                  | string  | How the List resolves membership.                                                         |
| `members` / `invited` / `approved` / `no_response` | integer | The invited-to-approved funnel, from the same canonical resolver the Lists surface reads. |
| `engaged_non_registrants`                          | integer | Engaged contacts who have not registered.                                                 |
| `event_name`                                       | string  | The target event.                                                                         |

## When a retry is safe

<Warning>
  **`confirm` is not idempotent in general.** The tool is registered
  `idempotentHint=false` on purpose: a dynamic List that has grown since the
  last activation re-sends the delta on a second confirm. Re-issuing a confirm
  with a **new** `client_request_id` is a new send, not a retry.
</Warning>

There is exactly one safe retry, and it is bounded:

* **A `confirm` that fails with `send_outcome_unknown`.** The transport failed and the send's true state is unknown. Retry with the **same** `client_request_id`. The server dedupes on that key, so the retry cannot double-send, and the response's `idempotent` field tells you whether the original had in fact landed. This is the only outcome the tool refuses to guess about, which is why it has a dedicated error rather than a generic one.

Everything else — a fresh `client_request_id`, a re-run "just to be sure", a retry after a timeout you did not confirm was `send_outcome_unknown` — risks sending again.

`destructiveHint` is `false` because an invite is additive and Luma dedupes repeats server-side. That is a statement about damage, not about repetition being free: a duplicate confirm still consumes the workspace's invite volume and can still put a second heyBTW-originated invite in front of a recipient.

## Errors

* **`invalid_parameter`.** An unrecognised `action`, a missing per-action required argument, or a global/admin credential. Raised before any network call.
* **`entitlement_required`.** The workspace's plan does not include `agent-connections-outbound`.
* **`send_outcome_unknown`.** `confirm` only. See [When a retry is safe](#when-a-retry-is-safe).
* **Generic sanitized error.** Every other failure, as on the read tools — see [`get_workspace_overview` — Errors](/docs/mcp-tools/get-workspace-overview#errors).

## Pagination and limits

One call per invocation. No `limit` parameter, no cursor pagination. Rate limited per workspace at 30 requests/5s, 60/minute, and 1,000/day; counters are held per server instance rather than shared, so the effective ceiling scales with instance count. A 429 carries a `Retry-After` header naming the window you tripped — honor it rather than guessing a backoff.

## Related tools

* [`get_invite_recommendations`](/docs/mcp-tools/get-invite-recommendations) — find candidates before building the List you activate here.
* [`get_event_details`](/docs/mcp-tools/get-event-details) — check who is already registered for the target event.
* [Lists](/docs/lists/overview) — how static and dynamic Lists resolve membership, which is what determines the delta a second confirm would send.
