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

# Campaign Tools

> Reference for the campaigns:draft tools — read, preview audience reach, and prepare non-sending campaign drafts a human sends from the dashboard.

The campaign tools let an agent **read** your campaigns, **preview** how many contacts an audience would reach, and **prepare** a campaign — but never fire the blast.

All five tools require the **`campaigns:draft`** scope.

<Warning>
  **Draft-only, by construction.** Every campaign an agent creates or edits is forced server-side into a non-sending `draft` state (`enabled: false`). The agent prepares the blast and reports the reach; a **human clicks Send** in the dashboard. This is an enforced boundary, not a failure — see [Safety & Boundaries](/ai-agents/safety-boundaries).
</Warning>

## Tools

| Tool                        | Purpose                                           |
| --------------------------- | ------------------------------------------------- |
| `list_campaigns`            | List all campaigns (drafts, scheduled, completed) |
| `get_campaign`              | Fetch one campaign by display id                  |
| `preview_campaign_audience` | Count the contacts a label audience would reach   |
| `draft_campaign`            | Create a non-sending draft                        |
| `update_campaign_draft`     | Edit one of your own drafts                       |

***

### list\_campaigns

List the account's campaigns.

Takes no arguments. Returns a list of campaigns, each with `id` (display id), `title`, `message`, `inbox`, `campaign_type`, `campaign_status` (`scheduled` / `sending` / `completed` / `draft`), `enabled`, `audience`, `audience_count`, `sent_count`, `reply_count`, and `scheduled_at`.

### get\_campaign

Fetch one campaign by its display id.

| Argument      | Type    | Required | Description                                                                |
| ------------- | ------- | -------- | -------------------------------------------------------------------------- |
| `campaign_id` | integer | yes      | The campaign's display id (the per-account number, from `list_campaigns`). |

Returns the full campaign object.

### preview\_campaign\_audience

Preview how many contacts a label-based audience would reach — call this **before** drafting so you can report the real number.

| Argument    | Type             | Required | Description                                                                                                          |
| ----------- | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `label_ids` | list of integers | yes      | Numeric contact-label ids. A contact matches if it has **any** of them — exactly how the send resolves the audience. |

Returns `{count: N}`.

<Note>
  Audience is **numeric label ids**, not titles. Resolve ids first with `list_labels` (scope `contacts:manage`). Passing a title string yields a **zero-reach** audience.
</Note>

### draft\_campaign

Create a campaign in a non-sending `draft` state.

| Argument             | Type             | Required | Description                                                                                                                 |
| -------------------- | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
| `title`              | string           | yes      | Internal campaign name.                                                                                                     |
| `message`            | string           | yes      | The message body sent to each contact.                                                                                      |
| `inbox_id`           | integer          | yes      | The inbox to send from — an SMS/WhatsApp/API inbox for a one-off blast, or a Website inbox for an ongoing on-site campaign. |
| `audience_label_ids` | list of integers | no       | Contact-label ids defining who receives it (any-match). Omit for Website campaigns.                                         |
| `description`        | string           | no       | Optional internal description.                                                                                              |
| `sender_id`          | integer          | no       | Optional agent id to send as.                                                                                               |

Returns the created draft campaign (`campaign_status` will be `"draft"`).

<Tabs>
  <Tab title="One-off blast (SMS / WhatsApp / API)">
    A one-off blast goes to a **label audience**. Preview the reach first, then draft with the label ids.

    ```
    preview_campaign_audience(label_ids=[7, 12])   → {count: 340}
    draft_campaign(
      title="July promo",
      message="20% off this weekend — reply STOP to opt out.",
      inbox_id=3,
      audience_label_ids=[7, 12]
    )
    ```
  </Tab>

  <Tab title="Ongoing (Website)">
    An ongoing on-site campaign shows to visitors of a Website inbox — there is no label audience, so **omit** `audience_label_ids`.

    ```
    draft_campaign(
      title="Chat nudge",
      message="Questions about pricing? Ask us here.",
      inbox_id=5
    )
    ```
  </Tab>
</Tabs>

### update\_campaign\_draft

Edit a draft. Only the fields you pass are changed, and the campaign stays a draft.

| Argument             | Type             | Required | Description                                      |
| -------------------- | ---------------- | -------- | ------------------------------------------------ |
| `campaign_id`        | integer          | yes      | The campaign's display id.                       |
| `title`              | string           | no       | New internal name.                               |
| `message`            | string           | no       | New message body.                                |
| `audience_label_ids` | list of integers | no       | **Replaces** the entire audience (not additive). |
| `description`        | string           | no       | New internal description.                        |

Returns the updated draft campaign.

<Warning>
  An agent can edit **only its own drafts**. Editing a non-draft campaign (scheduled, sending, or completed) returns **403** — surfaced verbatim, e.g. `can only edit draft campaigns`. This is what prevents an agent from pulling a live campaign back out of the queue or forcing it to send.
</Warning>

## The send loop

<Steps>
  <Step title="Resolve the audience">
    `list_labels` → take the numeric `id`s of the audience labels (contacts are tagged by these).
  </Step>

  <Step title="Report the reach">
    `preview_campaign_audience(label_ids=[…])` → show the human the real contact count before anything is drafted.
  </Step>

  <Step title="Draft it">
    `draft_campaign(title, message, inbox_id, audience_label_ids=[…])` → the campaign is saved as a non-sending draft.
  </Step>

  <Step title="Hand off to a human">
    Tell the user: "Draft ready — open Campaigns and click **Send** to blast to N contacts." The agent stops here; only a human's dashboard action promotes the draft to a live send.
  </Step>
</Steps>

<Note>
  Never imply the blast was sent. A drafted campaign never auto-sends, no matter how it is scheduled.
</Note>
