---
title: campaign_plan_update
description: 'Edit a Campaign Plan. A real change returns an approved Campaign Plan to draft.'
---

Ask the agent to change the fields of a Campaign Plan: its name, its brand, its envelope, its dates, its rationale or its measurement. The agent sends only what changes.

> "Move the end of the spring sale Campaign Plan to 30 June, and raise the envelope to €7,500."

The agent reads the Campaign Plan, then calls `campaign_plan_update` with the revision of the Campaign Plan that it read. You see the Campaign Plan as it is now. When the Campaign Plan was approved, a real change returns it to draft. When another change came first, AdCrunch refuses the write, and the agent reads the Campaign Plan again. The currency cannot change after a Line Item has a budget. To change a Line Item, the agent calls [`line_item_update`](/mcp/tools/line-item-update).

[Plan a campaign](/mcp/tools/plan-a-campaign) walks the whole job.

## 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. |

### Output

A successful call returns this object in `structuredContent`.

| Field | Type | Always present | Description |
| --- | --- | --- | --- |
| `campaignPlan` | object | yes | The Campaign Plan as it is now, with its Line Items in full. |
| `campaignPlan.allocation` | object | yes | The money of the Campaign Plan. AdCrunch calculates it on each read and does not store it. |
| `campaignPlan.allocation.allocated` | number or null | yes | The sum of the Line Item budgets, in whole units of the currency. `null` when the Line Items with a budget do not share a unit. |
| `campaignPlan.allocation.comparable` | boolean | yes | `false` when the Line Items with a budget mix totals and daily rates. Then no sum exists: say "not comparable", and do not add the budgets. |
| `campaignPlan.allocation.unallocated` | number or null | yes | The envelope less the allocated sum. It can be negative. `null` when the envelope is not set, when the Line Items do not share a unit, or when the unit is `daily`. |
| `campaignPlan.allocation.unit` | one of `total`, `daily` or null | yes | The unit that the Line Items with a budget share. `null` when no Line Item has a budget, or when the Line Items do not share a unit. |
| `campaignPlan.brandId` | string or null | yes | The Brand that the Campaign Plan is for, `brd_…`, or `null`. |
| `campaignPlan.createdAt` | number | yes | Milliseconds since the Unix epoch, UTC. |
| `campaignPlan.createdBy` | string | yes | The user who created the Campaign Plan, `usr_…`. |
| `campaignPlan.currency` | string | yes | ISO 4217. Each amount of the Campaign Plan and of its Line Items is in whole units of this currency. It cannot change after a Line Item has a budget. |
| `campaignPlan.description` | string | yes | One sentence that tells the Campaign Plan apart in a list. |
| `campaignPlan.endDate` | string or null | yes | The last day of the Campaign Plan, or `null`. With no end date, the Campaign Plan is always on, and the budget of a Line Item with no end date of its own is a daily rate. |
| `campaignPlan.id` | string | yes | The id of the Campaign Plan, `cpl_…`. |
| `campaignPlan.lineItems` | array of object | yes | Each Line Item of the Campaign Plan, in full. |
| `campaignPlan.lineItems[].advertiserId` | string or null | yes | The ad account that the Line Item runs through, `acc_…`, or `null`. |
| `campaignPlan.lineItems[].ageMax` | number or null | yes | The highest age, or `null` for no upper bound. |
| `campaignPlan.lineItems[].ageMin` | number or null | yes | The lowest age, or `null` for no lower bound. |
| `campaignPlan.lineItems[].budget` | number or null | yes | The amount in whole units of the currency of the Campaign Plan, or `null` when it is not set. `unit` tells how to read it. |
| `campaignPlan.lineItems[].campaignPlanId` | string | yes | The id of the Campaign Plan that holds the Line Item, `cpl_…`. |
| `campaignPlan.lineItems[].channel` | one of `meta`, `tiktok`, `snapchat`, `google_search`, `google_pmax`, `google_display`, `youtube`, `programmatic_display`, `linkedin`, `x` | yes | What the Line Item buys, in planning words. A channel is not a provider. |
| `campaignPlan.lineItems[].countries` | array of string | yes | ISO 3166-1 alpha-2 codes. An empty list means that the geography is not decided. |
| `campaignPlan.lineItems[].createdAt` | number | yes | Milliseconds since the Unix epoch, UTC. |
| `campaignPlan.lineItems[].createdBy` | string | yes | The user who added the Line Item, `usr_…`. |
| `campaignPlan.lineItems[].effectiveEndDate` | string or null | yes | The end that applies: the end date of the Line Item, else the end date of the Campaign Plan. `null` when neither has one. |
| `campaignPlan.lineItems[].effectiveStartDate` | string or null | yes | The start that applies: the start date of the Line Item, else the start date of the Campaign Plan. `null` when neither has one. |
| `campaignPlan.lineItems[].endDate` | string or null | yes | The end date of the Line Item itself, or `null` when the Line Item uses the end date of the Campaign Plan. |
| `campaignPlan.lineItems[].executable` | boolean | yes | `true` when AdCrunch can create objects for this channel today. It is not a promise that a create succeeds. |
| `campaignPlan.lineItems[].executions` | array of object | yes | The provider objects that the Line Item created, as `line_item_record_execution` recorded them. The list does not tell whether the Line Item is complete. |
| `campaignPlan.lineItems[].executions[].advertiserId` | string | yes | The ad account that holds the object, `acc_…`. |
| `campaignPlan.lineItems[].executions[].createdAt` | number | yes | When AdCrunch recorded the object, in milliseconds since the Unix epoch, UTC. |
| `campaignPlan.lineItems[].executions[].entityId` | string | yes | The id of the object on the provider, with no prefix. `get_entity` and `list_entities` use the same id. |
| `campaignPlan.lineItems[].executions[].entityType` | string | yes | The word of the provider for the level of the object, such as `campaign`, `adset` or `ad`. |
| `campaignPlan.lineItems[].executions[].id` | string | yes | The id of the record, `exe_…`. |
| `campaignPlan.lineItems[].executions[].mutationWorkflowId` | string or null | yes | The `workflowId` of the change that created the object, or `null` when the record names none. |
| `campaignPlan.lineItems[].executions[].provider` | one of `meta`, `gads`, `tiktok`, `x`, `openai`, `snapchat`, `dv360` | yes | The provider of the object. |
| `campaignPlan.lineItems[].gender` | one of `all`, `men`, `women` or null | yes | The gender of the audience, or `null` when it is not decided. `null` executes as `all`. |
| `campaignPlan.lineItems[].id` | string | yes | The id of the Line Item, `lni_…`. |
| `campaignPlan.lineItems[].objective` | one of `awareness`, `engagement`, `leads`, `sales`, `traffic`, `app_promotion` | yes | What the Line Item is for. |
| `campaignPlan.lineItems[].personaId` | string or null | yes | The Persona that explains the audience, `psn_…`, or `null`. The countries, the ages and the gender are what execute. |
| `campaignPlan.lineItems[].provider` | one of `meta`, `gads`, `tiktok`, `x`, `openai`, `snapchat`, `dv360` or null | yes | The provider that sells the channel, or `null` when AdCrunch has no integration for it. |
| `campaignPlan.lineItems[].revision` | number | yes | The revision of the Line Item. It increments when the Line Item changes. Send it as `base_revision` to `line_item_update`, `line_item_delete` or `line_item_validate`. |
| `campaignPlan.lineItems[].startDate` | string or null | yes | The start date of the Line Item itself, or `null` when the Line Item uses the start date of the Campaign Plan. |
| `campaignPlan.lineItems[].status` | one of `draft`, `validated` | yes | `validated` when a person cleared the Line Item to run, by `line_item_validate` or by the approval of the Campaign Plan. A real change to the Line Item returns it to `draft`. |
| `campaignPlan.lineItems[].unit` | one of `total`, `daily` | yes | How to read `budget`. `total` when the Line Item has an effective end date: the budget is for the whole period. `daily` when it has none: the budget is for each day. |
| `campaignPlan.lineItems[].updatedAt` | number or null | yes | When the Line Item last changed, or `null` when it never changed. |
| `campaignPlan.measurement` | string or null | yes | How the result is judged, or `null`. |
| `campaignPlan.name` | string | yes | The display name. |
| `campaignPlan.rationale` | string or null | yes | The argument for the split of the money, or `null`. A reviewer approves it. |
| `campaignPlan.revision` | number | yes | The revision of the Campaign Plan. It increments when the Campaign Plan changes. Send it as `base_revision` to `campaign_plan_update`, `campaign_plan_delete` or `campaign_plan_approve`. A Line Item has a revision of its own. |
| `campaignPlan.slug` | string | yes | The slug of the Campaign Plan. Each tool takes it as `campaign_plan_name`. |
| `campaignPlan.startDate` | string or null | yes | The first day of the Campaign Plan, or `null`. |
| `campaignPlan.status` | one of `draft`, `approved` | yes | `approved` when a person approved the content at this revision. A real change to the Campaign Plan, or a Line Item that is added or deleted, returns it to `draft`. |
| `campaignPlan.totalBudget` | number or null | yes | The envelope: the money that you have, in whole units of `currency`. `null` means no declared limit, not zero. |
| `campaignPlan.updatedAt` | number or null | yes | When the Campaign Plan last changed, or `null` when it never changed. |

### 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.

### Example

The arguments:

```json
{
  "base_revision": 3,
  "campaign_plan_name": "spring-sale",
  "end_date": "2026-06-30",
  "total_budget": 7500
}
```

The result, in `structuredContent`:

```json
{
  "campaignPlan": {
    "allocation": {
      "allocated": 2500,
      "comparable": true,
      "unallocated": 5000,
      "unit": "total"
    },
    "brandId": "brd_6a9c33",
    "createdAt": 1768435200000,
    "createdBy": "usr_5d2b71",
    "currency": "EUR",
    "description": "The spring sale of Acme, on Meta, in France and Belgium.",
    "endDate": "2026-06-30",
    "id": "cpl_4e1a08",
    "lineItems": [
      {
        "advertiserId": "acc_1203456789012345",
        "ageMax": 44,
        "ageMin": 25,
        "budget": 2500,
        "campaignPlanId": "cpl_4e1a08",
        "channel": "meta",
        "countries": [
          "FR",
          "BE"
        ],
        "createdAt": 1768435200000,
        "createdBy": "usr_5d2b71",
        "effectiveEndDate": "2026-06-30",
        "effectiveStartDate": "2026-03-01",
        "endDate": null,
        "executable": true,
        "executions": [
          {
            "advertiserId": "acc_1203456789012345",
            "createdAt": 1772409600000,
            "entityId": "120210000000000",
            "entityType": "campaign",
            "id": "exe_7a4c12",
            "mutationWorkflowId": "b3d1f0c4-6a2e-4a1f-9f77-2c0d1e5a8b94",
            "provider": "meta"
          }
        ],
        "gender": "all",
        "id": "lni_2d9e77",
        "objective": "sales",
        "personaId": "psn_8c4a20",
        "provider": "meta",
        "revision": 2,
        "startDate": null,
        "status": "validated",
        "unit": "total",
        "updatedAt": 1769040000000
      }
    ],
    "measurement": "Revenue in the window, against the spring of 2025.",
    "name": "Spring Sale 2026",
    "rationale": "Meta carries the reach. One Line Item keeps the budget in one place.",
    "revision": 4,
    "slug": "spring-sale",
    "startDate": "2026-03-01",
    "status": "draft",
    "totalBudget": 7500,
    "updatedAt": 1769644800000
  }
}
```
