# Batch changes

> Create, update and delete up to 500 DNS records of a zone as one versioned, all-or-nothing change with POST records/batch or apply_records_batch.

A **batch** applies creates, updates and deletes of one zone's records as **one** change: one version, one diff, one
rollback. Either every operation is applied or none is. Use it to migrate a zone, to replace a set of records (for
example the mail records of a new provider) or for any change that would leave the zone inconsistent halfway.

The dashboard uses batches too: bulk delete and bulk proxy switch in **DNS › Records**, imports, and the email wizard.

## Request

`POST /v1/orgs/{org}/zones/{zone}/records/batch`, with the `tech` role or higher.

```json
{
  "delete": ["0b9c6c2e-1f7a-4a39-9d1e-2b8f3c5d7e01"],
  "update": [
    {"id": "5a1e2f3b-6c7d-4e8f-9a0b-1c2d3e4f5a6b", "content": "203.0.113.20"}
  ],
  "create": [
    {"type": "MX", "name": "@", "content": "mx1.example.net", "priority": 10},
    {"type": "MX", "name": "@", "content": "mx2.example.net", "priority": 20},
    {"type": "TXT", "name": "@", "content": "v=spf1 include:_spf.example.net -all"}
  ]
}
```

| Field | Content |
|---|---|
| `create` | Records with the same fields as a single create: `type`, `name`, `content`, `ttl`, `priority`, `proxied`, `comment`, `tags` |
| `update` | `id` of the record plus only the fields that change (`"priority": null` clears it) |
| `delete` | Ids of the records to delete |

Rules:

- 1 to 500 operations in total. More: `422 invalid` with “at most 500 operations per batch”.
- Each record can be updated or deleted only once in the same batch.
- Operations run as deletes, then updates, then creates. Each record is validated as it is written, and the
  [coexistence rules](https://developers.openwaf.dev/dns/record-types/#coexistence) are checked on the **final** state of every name the batch
  touches. So one batch can, for example, replace an `A` record with a `CNAME` at the same name.
- As with a single update, switching the proxy of one `A`, `AAAA` or `CNAME` record switches the other records at that
  name that the batch does not change.
- An error names the failing operation in its `field`, for example `create[2].content`.

## Simulate and apply

**API**

```sh
# Simulate: validates everything and returns the diff, applying nothing
curl -X POST "https://dash.openwaf.dev/v1/orgs/$ORG/zones/$ZONE/records/batch?dry_run=1" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Change-Reason: Move mail to the new provider" \
  --data @batch.json

# Apply against the version you simulated
curl -X POST "https://dash.openwaf.dev/v1/orgs/$ORG/zones/$ZONE/records/batch" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Change-Reason: Move mail to the new provider" \
  -H 'If-Match: "41"' \
  --data @batch.json
```
Both answer `200` with `from_version`, `to_version`, `diff`, `warnings` and `records`, the zone's records after the
change. If the zone changed since the version in `If-Match`, the batch is refused with `412 version_conflict`.
**MCP**

Ask your assistant for the change; it calls `apply_records_batch` with `zone`, `create`, `update` and `delete`. The
first call (`confirm: false`) only simulates and returns the diff and `current_version`; after you approve, the same
call with `confirm: true` and `expected_version` applies it. The tool is marked destructive when it deletes records.
**Dashboard**

Select records in **DNS › Records** and use **Delete**, **Proxy on** or **Proxy off**; the dialog lists every record
the change touches and applies it as one version.


## Undo a batch

A batch is one version, so reverting to the version before it undoes all its operations at once. See
[Zone versions](https://developers.openwaf.dev/dns/zone-versions/#rollback).
