# How to Purge Page Cache on Many Sites at Once with the xCloud Public API and MCP

> Purge the WordPress page cache on up to 100 sites in one call with POST /api/v1/sites/bulk/cache-purge or MCP: dry run, confirm, then poll each site's result.

You can purge the WordPress page cache on 1 to 100 sites with one request: `POST /api/v1/sites/bulk/cache-purge` from a script, or the matching tool in any AI agent connected to xCloud MCP. A dry run first shows which sites xCloud can purge, and nothing runs until you confirm. You then read each site's result from `GET /api/v1/sites/bulk-operations/{uuid}`.

Use it after a theme or plugin deploy across many client sites, or when one change has to show up everywhere at once. Bulk page cache purge is new in xCloud v2.9.7.4.

## What the bulk purge clears

The bulk purge clears the **server page cache** of each WordPress site: the Nginx page cache on Nginx servers, and the LiteSpeed page cache on OpenLiteSpeed servers. This is the same cache that **Purge Cache** clears on a site's **Caching** page in the dashboard.

On some older Nginx servers the API cannot purge the page cache yet. Those sites show `not_eligible`, so purge them from the site's **Caching** page.

It does **not** clear:

- Cloudflare, including [Edge Full Page Cache](/docs/enable-the-edge-full-page-cache/)
- Redis Object Cache or [Object Cache Pro](/docs/integrate-object-cache-pro-with-xcloud/)
- Sites that are not WordPress sites

A site can be purged when it is a provisioned WordPress site on a provisioned, connected Nginx or OpenLiteSpeed server. Any other site in the list is **skipped** and the rest still run.

## Prerequisites

- An xCloud API token with `write:sites` to preview and submit, and `read:sites` to read the results. See [How to Access the xCloud API](/docs/how-to-access-the-xcloud-api/) to create one.
- A team role of **Team Admin**, **Server Admin** or **Site Admin**, with the permission to manage site caching.
- For MCP, an AI agent connected to xCloud MCP. See [How to Connect xCloud MCP to Your AI Agent](/docs/how-to-connect-xcloud-mcp-to-ai-agent/).
- The UUIDs of the sites to purge. `GET /api/v1/sites` lists them, or use the [fleet overview](/docs/get-fleet-overview-with-xcloud-public-api-and-mcp/).

## Step 1: Preview the purge with a dry run

Send the site UUIDs with `"dry_run": true`. A dry run checks every site and changes nothing: no purge runs and no operation is created.

```bash
export XCLOUD_TOKEN='YOUR_API_TOKEN'

curl -X POST 'https://app.xcloud.host/api/v1/sites/bulk/cache-purge' \
  --header "Authorization: Bearer $XCLOUD_TOKEN" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{
    "sites": [
      "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
      "7e8f9a0b-1c2d-4e3f-8a4b-5c6d7e8f9a0b"
    ],
    "dry_run": true
  }'
```

**Expected result:** a `200` response. Each site shows `queued` (it would be purged) or `skipped` with a reason:

```json
{
  "success": true,
  "message": "Dry run complete. The payload is valid and nothing was created.",
  "data": {
    "dry_run": true,
    "would_create": {
      "uuid": null,
      "status": "dry_run",
      "items": [
        { "site_uuid": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f", "status": "queued", "reason": null, "task_uuid": null, "error": null },
        { "site_uuid": "7e8f9a0b-1c2d-4e3f-8a4b-5c6d7e8f9a0b", "status": "skipped", "reason": "not_eligible", "task_uuid": null, "error": null }
      ],
      "next_actions": []
    },
    "warnings": []
  }
}
```

| `reason` | Meaning |
|---|---|
| `null` | The site can be purged |
| `not_eligible` | The site is not a provisioned WordPress site on a connected Nginx or OpenLiteSpeed server, or it is on an older Nginx server where the API cannot purge the page cache yet |
| `not_found` | The UUID does not exist, or you do not have access to that site |

The `sites` list takes 1 to 100 UUIDs, each one only once. A longer list, a repeated UUID or a value that is not a UUID is refused with `422`, and nothing runs.

## Step 2: Submit the purge

Send the same list with `"confirm": true` instead of `dry_run`. Add an `Idempotency-Key` header so the request is safe to retry: if the connection drops and you send it again with the same key, xCloud returns the purge that already exists instead of starting a second one.

```bash
curl -X POST 'https://app.xcloud.host/api/v1/sites/bulk/cache-purge' \
  --header "Authorization: Bearer $XCLOUD_TOKEN" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: deploy-2026-10-30-cache' \
  --data '{
    "sites": [
      "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
      "7e8f9a0b-1c2d-4e3f-8a4b-5c6d7e8f9a0b"
    ],
    "confirm": true
  }'
```

**Expected result:** a `202` response with the operation `uuid` and a `next_actions` entry that tells you where to read progress:

```json
{
  "success": true,
  "message": "Bulk page-cache purge admitted.",
  "data": {
    "uuid": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
    "status": "running",
    "items": [
      { "site_uuid": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f", "status": "queued", "reason": null, "task_uuid": null, "error": null },
      { "site_uuid": "7e8f9a0b-1c2d-4e3f-8a4b-5c6d7e8f9a0b", "status": "skipped", "reason": "not_eligible", "task_uuid": null, "error": null }
    ],
    "next_actions": [
      { "action": "poll", "method": "GET", "url": "/api/v1/sites/bulk-operations/5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e" }
    ]
  }
}
```

A `202` means xCloud accepted the request, not that the caches are already clear. Without `"confirm": true` (and without `"dry_run": true`) the request is refused with `422`.

To purge sites in another team granted to your token, add the `X-Team-Id` header. See [Multi-Team API Tokens and MCP Access](/docs/multi-team-api-tokens-and-mcp-access/).

## Step 3: Poll each site's result

Read the operation with the URL from `next_actions`. This call needs `read:sites` and changes nothing.

```bash
curl 'https://app.xcloud.host/api/v1/sites/bulk-operations/5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e' \
  --header "Authorization: Bearer $XCLOUD_TOKEN" \
  --header 'Accept: application/json'
```

**Expected result:** the same shape as Step 2, with each site's current status. Poll every few seconds until the operation `status` is no longer `running`.

| Site `status` | Meaning |
|---|---|
| `queued` | Waiting for its turn on its server |
| `running` | The purge is running on the server |
| `done` | The purge finished successfully |
| `failed` | The purge did not finish, or it did not start in time. `error` says which |
| `skipped` | Not purged. `reason` says why |

The operation `status` is `completed` when no site failed (sites that were skipped count as not failed), `completed_with_failures` when some failed and some finished, and `failed` when sites failed and none finished. An operation where every site was skipped is also `completed`, so check each site's `status` instead of trusting the operation status alone.

xCloud purges **one site at a time on each server**, and different servers run side by side. Sites that have not started within **five minutes** of the request are marked `failed` and nothing ran on them.

If you lose access to a site after submitting, that site shows `skipped` with `not_found` in the results.

## Step 4: Retry or check failed sites

The `error` text of a `failed` site tells you what to do next:

| `error` | What to do |
|---|---|
| `Not started before the five-minute deadline; nothing ran. Retry with a new Idempotency-Key.` | Submit those sites again with a **new** key |
| `Admission stopped before this purge started; nothing ran. Retry with a new Idempotency-Key.` | Submit those sites again with a **new** key |
| `Purge completion was not confirmed. Check the site before retrying.` | Open a page of the site first. The purge may have run. Submit again only if the page is still out of date |

xCloud never retries a failed purge on its own. Send only the failed site UUIDs in the new request.

On **OpenLiteSpeed** sites, open a page after the purge to confirm visitors see the new version.

## Purge with an AI agent over MCP

In an MCP client connected to xCloud, ask in plain language:

```text
Purge the page cache on all my WordPress sites on the server docs-demo.
Do a dry run first and show me which sites will be skipped, then wait for my OK.
```

The agent calls the `sites_bulk_cache-purge` tool. A dry run needs no confirmation. The real purge is marked as a destructive tool, so most MCP clients ask you to approve it before it runs. Check your client's approval setting. On the compact MCP profile, the agent runs the `sites.bulk.cache-purge` operation through `xcloud_execute_destructive`, and reads the result with `sites.bulk-operations.show` through `xcloud_execute_read`, which changes nothing.

**Expected result:** the agent lists the sites it will purge and skip, waits for your approval if your client asks for it, then reports each site's final status.

## Troubleshooting

| Response | Cause | Fix |
|---|---|---|
| `401` | No bearer token, or the token was revoked | Send `Authorization: Bearer <token>` with an active token |
| `403` on submit | The token lacks `write:sites`, or your role cannot manage site caching | Use a token with `write:sites` and ask the team owner for caching access |
| `403` on poll | The token lacks `read:sites` | Use a token with `read:sites` |
| `404` on poll | The operation UUID is wrong or belongs to another team | Use the URL from `next_actions` with the same team |
| `409` | A request with the same `Idempotency-Key` is still being accepted | Wait a moment and send it again with the same key |
| `422` | Invalid list, missing `confirm`, or the `Idempotency-Key` was already used with a different body | Fix the body, or use a new key for a different list |
| `429` | More than 30 purge requests in a minute | Wait a minute. Put more sites in one request instead of many small ones |

## Frequently asked questions

### Does the bulk purge clear Cloudflare, Edge Full Page Cache or Redis?

No. It clears only the server page cache of each WordPress site, the same cache the Purge Cache button on the site's Caching page clears. Cloudflare and Edge Full Page Cache, Redis Object Cache and Object Cache Pro are not touched.

### Why does a site show skipped with the reason not_eligible?

The site is not a provisioned WordPress site on a connected Nginx or OpenLiteSpeed server that xCloud can purge from the API. Some older Nginx servers also show not_eligible, because the API cannot purge their page cache yet. Check that the site and server are provisioned and connected. If the site looks healthy, purge it from its Caching page in the dashboard, or contact support.

### Is it safe to send the same purge request twice?

Yes, when you send an Idempotency-Key header. A repeated request with the same key and the same body returns the operation that already exists instead of starting a second purge. The same key with a different body is refused with 422.

### Why does polling return 403 when submitting worked?

Submitting needs the write:sites permission, but reading the result needs read:sites. Create a token that has both permissions if one script submits and polls.

## Next steps

- Read the full request and response format in the [xCloud Public API reference](https://app.xcloud.host/api/v1/docs), and authentication and rate limits in the [xCloud API documentation overview](/docs/xcloud-api/).
- Set up page caching for a site with [xSpeed Cache](/docs/configure-xspeed-cache-settings-xcloud/).

If you run into any issues with bulk page cache purge, feel free to reach out to our [support team](/docs/access-built-in-support-portal-in-xcloud/) for help.
