# Edit a DM campaign

> Replace a draft campaign's settings: send the whole draft (read it with dm.campaign first). Steps are the first message plus up to 2 follow-ups, each with up to three A/B variants. Write the messages and any per-person first lines with your own model: import the lines as a CSV column (dm.import_csv, e.g. first_line) and use [first_line]. The [ai_line] variable makes Xholic's AI write a line per recipient at launch and spends AI credits; use it only when the user asks for Xholic to write them. Call it only when the user asked for this in the conversation; never on your own initiative.

`dm.update_campaign` · write

- **MCP tool:** `dm_update_campaign`. Catalog: reach it with `find_actions` and `run_write_action`, or turn on the full toolset.
- **REST:** `POST /v1/dm/update-campaign`
- **CLI:** `xholic dm update-campaign`
- **Scope:** `write`
- **Plan:** Pro plan or higher
- **Cost:** Free.
- **Rate limit bucket:** `write`

## Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string (uuid) | Yes | DM campaign id. |
| `name` | string ≤ 80 chars | Yes |  |
| `deliveryMode` | "AUTOMATIC" \\| "ASSISTED" | Yes |  |
| `contactListId` | string (uuid) or null | Yes |  |
| `steps` | array of object (min 1, max 3) | Yes | In order from position 1. Step 1 has delayDays 0; follow-ups wait at least a day. Each variant's template is the message: [first], [name], list fields like [first_line], {Hi\|Hey} spintax. |
| `schedule` | object | Yes |  |
| `schedule.timezone` | string ≤ 64 chars | Yes |  |
| `schedule.sendDays` | integer 1–127 | Yes |  |
| `schedule.windowStartMinutes` | integer 0–1439 | Yes |  |
| `schedule.windowEndMinutes` | integer 1–1440 | Yes |  |
| `schedule.dailyLimit` | integer 1–100 | Yes |  |
| `schedule.rampStart` | integer 1–100 or null | Yes |  |
| `schedule.rampStep` | integer 1–50 or null | Yes |  |
| `schedule.minGapSeconds` | integer 75–3600 | Yes |  |
| `schedule.maxGapSeconds` | integer 75–3600 | Yes |  |
| `schedule.startsAt` | string (date-time) or null | Yes |  |
| `rules` | object | Yes |  |
| `rules.recontactAfterDays` | 30 or 60 or 90 or null | No | Default: `null`. |
| `rules.excludeCampaignIds` | array of string (uuid) (max 20) | No | Default: `[]`. |
| `rules.excludeListIds` | array of string (uuid) (max 20) | No | Default: `[]`. |
| `rules.skipFollowers` | boolean | No | Default: `false`. |
| `rules.skipFollowing` | boolean | No | Default: `false`. |
| `rules.skipExistingConversation` | boolean | No | Default: `true`. |
| `account` | string ≤ 64 chars | No | X account to act on, as @handle. Leave it out to use the connection's default account. |
| `dryRun` | boolean | No | Return exactly what would happen without doing it. |
| `idempotencyKey` | string ≤ 200 chars | No | A unique key for this write. Repeating a call with the same key returns the first result instead of acting twice. |
