---
title: campaign_plan_update
description: 'Edit a campaign plan. Any real change returns an approved plan to draft.'
---

Updates a **Campaign Plan**'s own fields. Its Line Items are edited with [`line_item_update`](/mcp/tools/line-item-update).

:::warning[A Campaign Plan is not a provider campaign]

It is AdCrunch's own planning document — written before anything is bought, and possibly executed into several campaigns, or none. For campaigns that exist on Meta or Google, use [`list_entities`](/mcp/tools/list-entities) and [`get_entity`](/mcp/tools/get-entity).

:::

## Input

Omit a field to leave it alone; send `null` to clear it. The two are deliberately different — "I only moved the end date" must not erase the rationale.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `campaign_plan_name` | `string` | yes | The plan slug. |
| `base_revision` | `integer` | yes | The revision you last read via `campaign_plan_get`. |
| `name`, `description` | `string` | no |  |
| `slug` | `string` | no | A new handle — a deliberate rename. |
| `brand_name` | `string \| null` | no | Re-point the plan; `null` unlinks it. |
| `currency` | `string` | no | Refused once any Line Item carries a budget. |
| `total_budget` | `number \| null` | no | In whole units of `currency`: `40000` is €40,000. Never negative. `null` means "no declared constraint", not zero. |
| `start_date` / `end_date` | `YYYY-MM-DD \| null` | no |  |
| `rationale`, `measurement` | `string \| null` | no |  |

:::warning[Editing cancels an approval]

An approval says somebody agreed to _particular content_. Change that content and the plan returns to `draft`, so the label never outlives what it referred to. A write that changes nothing leaves the approval standing.

:::

:::info[The currency freezes at the first budget]

Re-denominating a costed plan would silently reinterpret every number in it — `40000` going from €40,000 to $40,000 without a figure moving on screen. Nothing is ever converted: a converted budget is a different plan.

:::

:::info[Re-pointing the brand can be refused]

If a Line Item still names a persona of the current brand, the change is refused and names those lines. Clear their personas first — they are not cleared for you, because only you know which reading you meant.

:::

Requires the `campaign_plan:write` scope.

## Reference

Update a Campaign Plan. Omit a field to leave it alone; send null to clear it. Pass base_revision from campaign_plan_get. Any real change returns an approved plan to draft — an approval refers to particular content and cannot outlive it; a write that changes nothing leaves the approval standing. The currency is refused once any Line Item carries a budget, because re-denominating a costed plan would silently reinterpret every number in it. A Campaign Plan is AdCrunch's own planning document — it is **not** a campaign on Meta or Google. For those, use list_entities and get_entity.

### Input

| Argument | Type | Required | Description |
| --- | --- | --- | --- |
| `base_revision` | integer | yes | The revision you last read via campaign_plan_get. Guards against writing over a change somebody else made since. |
| `brand_name` | string or null | no | Re-point the plan at another brand by slug; null unlinks it. Refused while a Line Item still names a persona of the current brand. |
| `campaign_plan_name` | string | yes | The Campaign Plan slug — its stable per-organization handle. |
| `currency` | string, 3 characters | no | ISO 4217. Refused once any Line Item carries a budget — nothing is ever converted. |
| `description` | string | no |  |
| `end_date` | string or null | no | null makes the plan always-on, which also flips every inheriting line from a total to a daily rate. |
| `measurement` | string or null | no |  |
| `name` | string | no |  |
| `rationale` | string or null | no |  |
| `slug` | string | no | A new slug handle — a deliberate rename. |
| `start_date` | string or null | no |  |
| `total_budget` | number, at least 0 or null | no | null means "no declared constraint", not zero. |

### Failure codes

A failed call has `isError` set, and `structuredContent.error` holds one of these codes. [Errors](/mcp/errors) describes the shape of a failed call.

- `not_found`
- `invalid_slug`
- `invalid_currency`
- `invalid_amount`
- `invalid_window`
- `persona_not_in_brand`
- `currency_frozen`
- `revision_mismatch`
- `slug_conflict`
- `forbidden`
- `invalid_request`
- `internal_error`

### Scope

The token must hold `campaign_plan:write`. [Auth & scopes](/mcp/auth) lists each scope.

### Annotations

A client reads these hints. A hint that the tool does not declare has the default value of the MCP specification.

- **Writes.** The tool can change data.
- **Destructive.** The tool can make a change that you cannot undo. A client can ask you to confirm before it calls the tool.
- **Not idempotent.** A second call with the same arguments can change more.
- **Closed world.** The tool reads and writes the data of AdCrunch only.
