---
title: campaign_plan_approve
description: 'Approve a plan, validating every draft line on it and opening the execution gate.'
---

Approves a **Campaign Plan**. This is the human act in the plan's lifecycle — and the gate a Line Item must pass before it can be executed.

:::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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `campaign_plan_name` | `string` | yes | The plan slug. |
| `base_revision` | `integer` | yes | The revision you last read. This is what makes it an approval **of the content you read**. |

## Output

`{ campaignPlan: { status: 'approved', … }, lineItemsValidated: 4 }`

:::warning[It cascades over every draft line]

Approving validates **all** of them, including one somebody else was still writing. When only one line is ready, use [`line_item_validate`](/mcp/tools/line-item-validate) instead — it exists precisely so your approval need not reach another person's half-written row.

:::

:::info[It blocks on nothing, and it spends nothing]

Unbounded, over-allocated and incomplete plans are all approvable: execution carries its own checks, applied where the information is complete, and putting them here too would only let the two drift. Approving creates no provider object — executing goes through the mutation tools and needs `mutation:write`.

:::

:::tip[A line added afterwards starts as a draft]

And adding it returns the plan to `draft`. That hole is the whole reason a line carries a state of its own — otherwise a line of spend could be slipped into a signed plan and be immediately executable.

:::

Requires the `campaign_plan:write` scope.

## Reference

Approve a Campaign Plan, which validates every draft Line Item on it and opens the execution gate — a line cannot be executed until it is validated. This blocks on nothing: unbounded, over-allocated and half-written plans are all approvable, because execution carries its own checks and approval is the human act. It creates nothing and spends nothing. Pass base_revision from campaign_plan_get, so the approval is of the content you read. A line added afterwards starts draft and returns the plan to draft. 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. |
| `campaign_plan_name` | string | yes | The Campaign Plan slug — its stable per-organization handle. |

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