---
title: Change what runs on Meta
description: Build or copy a paused campaign on a Meta advertiser, then start it, stop it, or change its budget, its bid, its schedule or its name, from a conversation. Each change runs in the background, and nothing spends until you activate it.
---

You ask the agent to build, copy or change a campaign on one of your Meta advertisers. AdCrunch sends the change to Meta. Each change runs in the background, and each create and each copy arrives paused.

## Before you start

- Connect Meta with write access, and share a Facebook Page when Meta asks for assets. [Connect Meta](/connect/meta) shows the steps.
- To make a change, the token of your client must hold `mutation:write`. The reads of this job need `observe:read` only: the Pages, the Pixels, and the current state of your campaigns, ad sets, ads and creatives. So you can plan on a token that cannot change anything. [Auth & scopes](/mcp/auth) lists each scope.
- The agent finds the advertiser with [`list_advertisers`](/mcp/tools/list-advertisers).

## How a change runs

Eleven tools make a change on Meta: [`meta_create_campaign`](/mcp/tools/meta-create-campaign), [`meta_create_adset`](/mcp/tools/meta-create-adset), [`meta_create_creative`](/mcp/tools/meta-create-creative), [`meta_create_ad`](/mcp/tools/meta-create-ad), [`meta_copy_campaign`](/mcp/tools/meta-copy-campaign), [`meta_copy_adset`](/mcp/tools/meta-copy-adset), [`meta_set_status`](/mcp/tools/meta-set-status), [`meta_update_budget`](/mcp/tools/meta-update-budget), [`meta_update_bidding`](/mcp/tools/meta-update-bidding), [`meta_update_schedule`](/mcp/tools/meta-update-schedule) and [`meta_rename`](/mcp/tools/meta-rename). Each one works in two steps.

1. **The tool starts the change.** First it checks the scope of the token, the arguments, the advertiser, a usable connection and the write access. When one check fails, the call fails at once, and nothing starts. When all checks pass, the tool answers a `workflowId`. At that time, the change has not reached Meta.
2. **[`get_mutation_status`](/mcp/tools/get-mutation-status) reports the outcome.** The agent gives it the `workflowId`, and it answers one of three states.

| State | What it means | What the agent does |
| --- | --- | --- |
| `running` | The change has not ended. | It asks again. |
| `complete` | The change ended. `result` tells how. | It reads `result`. |
| `errored` | The run itself stopped. AdCrunch cannot tell whether Meta applied the change. | It tells you. Look at the object in Meta Ads Manager before you ask for the change again. |

`complete` does not always mean that Meta applied the change. When `result.ok` is true, Meta applied it. A create then gives the Meta id of the new object. A copy gives the Meta id of the copy and the `budget` that the copy carries. A change of status, of budget, of bid, of schedule or of name gives `success`. A change of status, of budget, of bid, of schedule or of name also gives `priorValue`, the value that it replaced. A change of schedule can also give `deliveryStartsAgain`. When `result.ok` is false, the change did not apply, and `result.error` holds the code:

| `result.error` | Why the change did not apply |
| --- | --- |
| `budget_cap_exceeded` | The budget is above the safety cap. |
| `wrong_level` | The budget or the bid strategy is on the wrong level of the campaign. The message tells which level holds it. |
| `invalid_request` | A rule of AdCrunch refused the change before it reached Meta. The message names the rule. |
| `provider_error` | Meta refused the change. The message holds the words of Meta. |
| `provider_not_connected` | The Meta connection became unusable after the change started: a person removed it, it expired, or Meta refused it. Connect Meta again. |
| `missing_write_access` | The Meta connection lost its write access after the change started. Connect Meta again. |
| `not_found` | The object, or the source of a copy, is not in the ad account of the advertiser, or the advertiser left your organization after the change started. |

A refused change is a finished change, so the agent does not send it again by itself. It tells you the reason, and you decide what to change. [Errors](/mcp/errors) describes the failure of a tool call.

:::warning[Nothing spends until you say so]

Each create arrives paused: the campaign, the ad set and the ad. Each copy arrives paused too. No tool can create an active object. Meta delivers an ad only when the ad, its ad set and its campaign are all active. So spend starts only when you ask the agent to activate all three.

:::

## Build a campaign

### 1. Find the Page and the Pixel

> Which Facebook Page and which Pixel can my Northwind advertiser use?

The agent calls [`meta_list_pages`](/mcp/tools/meta-list-pages) and [`meta_list_pixels`](/mcp/tools/meta-list-pixels), and it asks you which ones to use. Each ad speaks from a Page. An ad set that optimizes for conversions needs a Pixel.

The list of Pages comes from your Meta connection, so it is the same for each advertiser of that connection. A long list comes one part at a time. The agent reads each part. So you see all the Pages and all the Pixels. An empty list of Pages means that you shared no Page when you connected Meta. Connect Meta again and share a Page. Prefer a Pixel that received an event recently. A Pixel that never received an event is usually absent from your site.

### 2. Create the campaign

> Create a sales campaign called Spring Prospecting on Northwind, with a daily budget of 50 euros.

The agent calls [`meta_create_campaign`](/mcp/tools/meta-create-campaign), then `get_mutation_status`, and it gives you the id of the new campaign. The campaign is paused.

A budget is in whole units of the account currency: 50 is 50.00 euros, and 10.5 is 10.50. A budget on the campaign turns on Advantage campaign budget, so Meta shares that budget across the ad sets.

When each ad set has its own budget, for example one ad set for each Line Item of a Campaign Plan, ask for a campaign with no budget. Each ad set then gets its budget in step 3. You can also let the ad sets share up to 20 % of their daily budgets. Sharing works with daily budgets only. It needs a bid strategy, and it locks that bid strategy for the life of the campaign. You cannot turn sharing on later.

If the campaign is about employment, housing, credit, politics, gambling or financial products, Meta requires you to declare that category. The agent must ask you, and not guess.

### 3. Create the ad set

> Add an ad set to Spring Prospecting that targets France and Belgium, ages 25 to 54, and optimizes for purchases on the Northwind Web Pixel.

The agent calls [`meta_create_adset`](/mcp/tools/meta-create-adset). The campaign holds the budget, so the ad set holds none. The ad set is paused. Under a campaign with no budget, the ad set holds exactly one budget.

You can also tell how Meta bids for the ad set: a bid strategy, with the one amount that it takes. A bid cap and a cost cap are in whole units of the account currency. A bid is per optimization event, for example per purchase, and per 1,000 impressions when the goal is impressions or reach. A ROAS goal is a ratio: 1.5 means purchase value of 1.5 times the spend. When the campaign holds the bid strategy, the ad set gives the same strategy, with its own amount.

The agent asks you for the youngest age to target, because many advertisers must exclude people under 18. A goal that optimizes for conversions needs a Pixel and an event. Without both, AdCrunch refuses the change with `invalid_request`. Meta decides which goals each objective allows, and Meta gives its refusal in its own words.

### 4. Create the creative

> Make a creative from the spring trail image, from the Northwind Outdoor Page, with a Shop now button to example.com/spring.

The agent calls [`meta_create_creative`](/mcp/tools/meta-create-creative). First, you upload the image or the video to AdCrunch as an asset, and the agent registers the asset to this advertiser with [`asset_register`](/mcp/tools/asset-register). For an asset that has no registration to this advertiser, AdCrunch refuses the change with `invalid_request`, and the message tells the next step. The kind of the asset decides the format of the creative. For a video that you uploaded a short time ago, the change waits until Meta finishes the processing. If the processing does not finish in that time, AdCrunch refuses the change with `invalid_request`, and you can ask again later.

### 5. Create the ad

> Put that creative in the new ad set.

The agent calls [`meta_create_ad`](/mcp/tools/meta-create-ad) with the id of the ad set and the id of the creative. The ad is paused. An ad can also use a creative that already runs in another ad.

### 6. Review and activate

> Activate Spring Prospecting, its ad set and its ad.

The agent calls [`meta_set_status`](/mcp/tools/meta-set-status) with `ACTIVE` on each of the three. When all three are active, Meta starts delivery, and spend starts.

The id that `get_mutation_status` gives is the handle of each new object. AdCrunch refreshes its stored copy in the background, so a new object can be absent from the stored lists, such as [`list_campaigns`](/mcp/tools/list-campaigns), for a short time. The `meta_list_*` tools ask Meta, so they show the new object at once.

## Copy what works

Copy a campaign or an ad set that works, to scale it or to test a change on it. Meta makes one new object with the settings of the source, and adds " - Copy" to its name. To give the copy a full name, the agent renames it with [`meta_rename`](/mcp/tools/meta-rename). The copy is shallow: it holds the one object that you copy, and none of the objects under it. It arrives paused.

> Copy the FR BE 25-54 ad set into the Summer Scale campaign, from June 1 to June 30.

The agent calls [`meta_copy_adset`](/mcp/tools/meta-copy-adset). Without a campaign, the copy stays in the campaign of the source. A new start time and a new end time are optional. Without them, the copy keeps the times of the source. A copy of an ad set that has ended starts when Meta makes it, with the duration of the source.

> Copy Spring Prospecting.

The agent calls [`meta_copy_campaign`](/mcp/tools/meta-copy-campaign). The copy stays in the ad account of the source, with no ad set. The agent then copies or creates each ad set into it.

> Put the hero ad of the source ad set into the copy.

An ad has no copy tool. The agent reads the `creativeId` of the source ad with [`meta_list_ads`](/mcp/tools/meta-list-ads), and calls [`meta_create_ad`](/mcp/tools/meta-create-ad) with that creative and the new ad set. The new ad keeps the post of the source, with its likes and comments.

`get_mutation_status` gives the id of the copy and the budget that it carries. That budget is what the copy can spend after you activate it:

| `budget` | What the copy carries |
| --- | --- |
| `{ "dailyBudget": 50 }` | A daily budget, in whole units of the account currency. |
| `{ "lifetimeBudget": 500 }` | A lifetime budget, in whole units of the account currency. |
| `"campaign"` | An ad set copy with no budget of its own. It spends the budget of its campaign. |
| `"adset"` | A campaign copy with no budget. Each ad set carries its own budget, and the copy has no ad set yet. |

Under ad set budgets, an ad set copy adds a second budget of the same size to its campaign. To change the budget of a copy, call `meta_update_budget` on the paused copy before you activate it.

Before each copy, AdCrunch reads the source on Meta. When the source is not in the ad account of the advertiser, AdCrunch refuses the copy with `not_found`. AdCrunch does not check the campaign that receives an ad set copy: Meta decides whether that campaign accepts it.

A copy starts its own learning phase. An ad that goes into a live ad set makes that ad set enter the learning phase again. Meta refuses some copies: an Advantage+ shopping campaign and an Advantage+ app campaign cannot be copied, and a copy that targets the EU needs the default payor and the default beneficiary of the ad account. Meta's refusal reaches you in its own words, with `provider_error`.

## Change what already runs

### Read the current state first

> What is the status and the daily budget of Spring Prospecting and of its ad sets now?

The agent calls [`meta_list_campaigns`](/mcp/tools/meta-list-campaigns) and [`meta_list_adsets`](/mcp/tools/meta-list-adsets). They ask Meta now, and they do not read the copy that AdCrunch stores. So you see a change that someone made in Meta Ads Manager a minute ago. [`meta_list_ads`](/mcp/tools/meta-list-ads) and [`meta_list_creatives`](/mcp/tools/meta-list-creatives) read the ads and the creatives in the same way.

Read before you change. A change acts on the object as Meta has it now, and this read shows you that object.

### Make the change

> Pause the FR BE 25-54 ad set.

The agent calls [`meta_set_status`](/mcp/tools/meta-set-status) with `PAUSED`. `ACTIVE` starts delivery again.

> Raise the daily budget of Spring Prospecting to 150 euros.

The agent calls [`meta_update_budget`](/mcp/tools/meta-update-budget). AdCrunch reads the object on Meta to find where the budget is. With Advantage campaign budget, the campaign holds the budget. Otherwise, each ad set holds its own budget. For a change on the wrong level, AdCrunch refuses the change with `wrong_level`, and the message tells which level to use.

A daily budget is not a hard limit for one day. Meta can spend up to 175% of it on one day, and up to 7 times it in one week.

> Move the FR BE 25-54 ad set to a cost per result goal of 12 euros.

The agent calls [`meta_update_bidding`](/mcp/tools/meta-update-bidding) with a bid strategy and the one amount that the strategy takes. To change only the amount, it sends the same strategy with the new amount. [`meta_list_adsets`](/mcp/tools/meta-list-adsets) and [`meta_list_campaigns`](/mcp/tools/meta-list-campaigns) show the bid that Meta has now.

The bid strategy sits where the budget sits. With Advantage campaign budget, the campaign holds the strategy. A change on the campaign sets it, and a bid cap or a cost cap on the campaign applies one amount to each ad set. A change on one ad set then gives the strategy of the campaign and the amount for that ad set. Without a campaign budget, each ad set holds its own strategy. For a change on the wrong level, AdCrunch refuses the change with `wrong_level`. A ROAS goal sits on each ad set, so AdCrunch refuses a ROAS goal on a campaign with `invalid_request`.

A bid change can make an ad set spend its full budget, never more: the budget is the only spend ceiling. A change of strategy starts the learning phase again. Meta advises at most 2 or 3 bid or budget changes a day, and 15 minutes between a budget change and a bid change. Meta refuses a change of strategy on a campaign with ad set budget sharing, on a campaign budget with more than 70 ad sets, and on a campaign budget on ROAS goal. AdCrunch does not check these three first, and the words of Meta come back to you.

> Make the FR BE 25-54 ad set run until the end of December.

The agent calls [`meta_update_schedule`](/mcp/tools/meta-update-schedule) with the new end time. It can also change the start time, or remove the end time, so that the ad set runs with no end. A time is an ISO 8601 timestamp, such as `2026-12-31T23:59:00+01:00`. AdCrunch refuses an end time that is not in the future, and no end on an ad set with a lifetime budget, with `invalid_request`.

A later end time adds days of spend. With no end, the ad set spends until you pause it, up to 7 times its daily budget in one week. With a lifetime budget, the total spend stays within the lifetime budget. An active ad set that has ended delivers again when it gets a new end time or no end. Its result then holds `deliveryStartsAgain`.

To change a daily budget to a lifetime budget, first set an end time, then set the lifetime budget. To change a lifetime budget to a daily budget, first set the daily budget, then remove the end time. Meta refuses a lifetime budget with no end time.

> Rename the FR BE 25-54 - Copy ad set to FR BE 25-54 — Summer.

The agent calls [`meta_rename`](/mcp/tools/meta-rename) with the new name. The new name replaces the whole name, and the rename changes nothing else. It works on a campaign, an ad set or an ad.

Before each change of an object that exists, AdCrunch reads the object on Meta. When the object is not in the ad account of the advertiser, AdCrunch refuses the change with `not_found`, and nothing reaches Meta. The same read keeps the value that the change replaces: `get_mutation_status` gives it as `priorValue`, and the Activity page shows "old → new".

> Put the budget of Spring Prospecting back.

To undo a change, the agent sends the same change again with the `priorValue`. No other tool undoes a change.

## Guardrails

- **A create or a copy arrives paused.** No tool, and no argument, creates an active object.
- **No tool deletes.** `ARCHIVED` retires an object, and it is almost permanent: an archived object cannot deliver again. Use it only for an object that must not exist, such as a duplicate. To stop delivery, use `PAUSED`.
- **AdCrunch does not deduplicate a create.** If you ask for the same create two times, you get two objects. Wait for the outcome before you ask again.
- **A budget has a safety cap.** Above the cap, AdCrunch refuses the change with `budget_cap_exceeded`, and the message gives the cap in the account currency. The cap is 1,000,000 in a currency with cents, such as EUR or USD. It applies to a new campaign, a new ad set and a budget change.
- **A budget is exact.** A budget has no more decimals than Meta counts in the account currency. AdCrunch refuses an amount with more decimals, and never rounds it.
- **A change acts only on an object of the advertiser.** AdCrunch reads the object on Meta before each change of status, of budget, of bid, of schedule or of name, and the source before each copy. It refuses an object of another ad account with `not_found`.
- **The budget is the only limit of spend.** A change of bid or of schedule has no cap of its own. A bid can make an ad set spend its full budget, never more. A later end, or no end, adds days of spend within the budget.
- **The record keeps the value that a change replaced.** For a change of status, of budget, of bid, of schedule or of name, `get_mutation_status` gives the old value as `priorValue`, and the Activity page shows "old → new". To put a value back, send the same change with it.
- **The console records each change.** Its Activity page lists each change, the advertiser, the person who asked, and the outcome.
- **Each tool follows the same rules.** [What to expect](/mcp/what-to-expect) states how a change is queued and then confirmed, and why a create arrives paused.
