---
title: Make a change
description: Change what runs on an advertiser with the Mutations API — start a Mutation, poll it until it ends, and read the history of each change.
---

A change to a live advertiser is **asynchronous**. A provider write is slow, and it can fail halfway. So the API never does the provider write inside your request. It records your request as a **Mutation**, and it answers the id of that Mutation. The change runs after the answer. You poll the Mutation until the change ends.

Not every provider accepts a change. See [What each provider supports](/connect/providers).

## Start a Mutation

Send the advertiser and one action to `POST /mutations`. This request pauses a campaign:

```bash
curl -X POST https://api.adcrunch.dev/mutations \
  -H "Authorization: Bearer $ADCRUNCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "advertiserId": "acc_1203456789012345",
    "action": {
      "kind": "meta_set_status",
      "level": "campaign",
      "id": "120210000000000",
      "status": "PAUSED"
    }
  }'
```

The answer is the id of the Mutation, and not the result of the change:

```json
{ "workflowId": "b3d1f0c4-6a2e-4a1f-9f77-2c0d1e5a8b94" }
```

Before AdCrunch starts the change, it checks three things: the body, the owner of the advertiser, and write access. A request that fails a check starts nothing. It also adds no row to the history.

| Status | `error` | Cause |
| --- | --- | --- |
| `400` | `invalid_request` | The body does not match the schema. |
| `403` | `missing_write_access` | The connection of the advertiser has no write access. |
| `404` | `not_found` | The advertiser does not exist, or it belongs to a different organization. |

## Poll until it ends

Read the Mutation with its id:

```bash
curl https://api.adcrunch.dev/mutations/b3d1f0c4-6a2e-4a1f-9f77-2c0d1e5a8b94 \
  -H "Authorization: Bearer $ADCRUNCH_API_KEY"
```

`status` has one of three values:

| `status` | Meaning |
| --- | --- |
| `running` | The change has not ended. Wait a few seconds, then poll again. |
| `complete` | AdCrunch reached the provider and has its answer. **Read `result` to learn what that answer was.** |
| `errored` | The change ran into a failure that AdCrunch did not expect. It cannot say whether the provider applied the change, so read the object at the provider before you send the request again. |

:::warning[`complete` is not the same as "it worked"]

A provider that refuses a change also answers `complete`. The refusal is in `result`, as `ok: false` with the reason. Branch on `result.ok`, and never on `status` alone.

:::

For a create that succeeds, `result` holds the id of the new object.

To poll, you need `mutation:write`, the permission that started the change.

## The actions

Each action has a `kind`. The name of the kind starts with its provider.

| `kind` | What it does |
| --- | --- |
| `meta_set_status` | Sets the status of a campaign, an ad set, or an ad: `ACTIVE`, `PAUSED`, or `ARCHIVED`. |
| `meta_update_budget` | Sets the daily budget or the lifetime budget of a campaign or an ad set. |
| `meta_create_campaign` | Creates a campaign. |
| `meta_create_adset` | Creates an ad set in a campaign. |
| `meta_create_creative` | Creates a creative from an Asset that you registered to this advertiser. |
| `meta_create_ad` | Creates an ad from an ad set and a creative. |

[Start a mutation](/api/mutations/start-mutation) shows each field of each action.

To build an ad, create the objects in this order: the campaign, the ad set, the creative, and the ad. Each step takes the new id from the `result` of the step before it. The creative takes the `ast_` id of an Asset that you registered to the same advertiser. See [Upload a file](/api/upload-a-file).

A creative also needs the id of a Facebook Page. An ad set that optimizes for a conversion also needs a pixel. The REST API has no operation that lists Pages or pixels. Over MCP, the tools `meta_list_pages` and `meta_list_pixels` list them.

## Rules for each write

:::warning[A create never starts spend]

Each campaign, ad set, and ad that you create starts `PAUSED`. To start delivery, send `meta_set_status` with `ACTIVE` in a separate request. `ACTIVE` starts spend.

:::

- **The API deletes nothing.** To retire an object, send `meta_set_status` with `ARCHIVED`. On Meta, you cannot make an archived object active again from this API.
- **A budget is in whole units of the currency of the advertiser.** `10.5` is 10.50. A budget that you read from Observe is in the same unit. See [Conventions](/api/conventions#money).
- **An action takes only the fields that its schema names.** No field sends a value to the provider unchanged.

## Read the history

Each Mutation stays in the history of your organization. The history shows the advertiser, the change, who asked for it, and how it ended. `GET /mutations` lists the history, and needs `mutation:read`:

```bash
curl https://api.adcrunch.dev/mutations \
  -H "Authorization: Bearer $ADCRUNCH_API_KEY"
```

The answer holds `data`, at most `limit` rows (100 by default). When more rows exist, it also holds `nextCursor`. Send that value back as `cursor` to read the next page. The console shows the same history on the **Activity** page.
