---
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_9f2c1b",
    "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` | The provider accepted the change. `result` holds what the provider returned. For a create, it holds the id of the new object. |
| `errored` | The change did not occur. |

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 minor units.** `1000` is 10.00 for an advertiser that uses euros or dollars. A budget that you read from Observe is in whole units, so convert it first. 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 `mutations`. 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.
