How to Fix Vulnerable Plugins, Themes and Core on Many Sites with the xCloud Public API and MCP

Updated November 5, 2026 · 8 min read

Summarize with:
Share:

You can update only the vulnerable plugins, themes and WordPress core on 1 to 100 sites with one request: POST /api/v1/vulnerabilities/fix from a script, or the matching tool in any AI agent connected to xCloud MCP. xCloud backs up each site first, then updates just the items with an open vulnerability. A dry run shows the plan, and nothing runs until you confirm. You then read each finding’s result from GET /api/v1/sites/bulk-operations/{uuid}.

Use it when a vulnerability report lands for a plugin you run on many client sites, or to clear every critical finding across your team in one pass. Other plugins with updates waiting are not touched.

What the bulk fix updates

The bulk fix works from the same list as GET /api/v1/vulnerabilities, the team-wide list of open vulnerabilities found by each server’s Vulnerability Scan (Wordfence) and by Site Security PRO (Patchstack). For each site with a match, xCloud:

  1. Takes a backup of the site (on by default).
  2. Updates each vulnerable plugin, theme and WordPress core to the newest version WordPress offers.
  3. Checks the site again and reports each finding as updated, resolved or needs_manual_update.

A site can be fixed when it is a provisioned WordPress site (not a Docker app) on a connected server, and the server’s Vulnerability Scan covers it. Any other site is skipped and the rest still run.

Prerequisites

Step 1: Choose which findings to fix

The request takes the same filters as GET /api/v1/vulnerabilities, so you can list the findings first and send the same filters to fix them:

Field Values Default
severity all, critical, high, medium, low, unknown all
source all, wordfence, patchstack all
include_ignored true or false. Ignored findings are left out unless this is true false
sites A list of 1 to 100 site UUIDs, each only once Every site in the team not ruled out as no_findings, including skipped sites that cannot be fixed, up to 100
backup_before_update true or false true

severity matches one level exactly: high fixes high findings only, not critical ones. To fix both, send two requests, or leave severity out.

Without sites, the response lists every site in the team that is not ruled out as no_findings, up to 100. Only sites skipped as no_findings are left out. Sites that cannot be fixed stay in the list as skipped and each one takes one of the 100 places: sites that are not WordPress, Docker apps, sites that are not provisioned (not_eligible), sites on a disconnected server (server_not_connected) and sites you have no permission for (not_found). When the list is cut at 100, the response carries the warning more_sites_have_findings, even if fewer than 100 sites are actually fixed. Running the fix again does not always reach the rest, because the same skipped sites fill the places again. In large teams, send sites in batches of up to 100 to be sure each site is included.

Step 2: Preview the fix with a dry run

Send your filters with "dry_run": true. A dry run checks every site and changes nothing: no backup, no update and no operation is created.

export XCLOUD_TOKEN='YOUR_API_TOKEN'

curl -X POST 'https://app.xcloud.host/api/v1/vulnerabilities/fix' \
  --header "Authorization: Bearer $XCLOUD_TOKEN" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --data '{
    "severity": "critical",
    "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 fixed) or skipped with a reason. A queued site lists the items it will update in slugs and each matching finding in findings:

{
  "success": true,
  "message": "Dry run complete. The payload is valid and nothing was created.",
  "data": {
    "dry_run": true,
    "would_create": {
      "uuid": null,
      "kind": "site.vulnerability_fix",
      "status": "dry_run",
      "items": [
        {
          "site_uuid": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
          "status": "queued",
          "reason": null,
          "task_uuid": null,
          "backup": { "requested": true, "destination": "remote" },
          "warnings": [],
          "slugs": ["contact-form-7"],
          "findings": [
            { "uuid": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d", "slug": "contact-form-7", "source": "wordfence", "severity": "critical", "resolved_slug": "contact-form-7", "from_version": "5.9.2", "status": "queued" }
          ],
          "skipped_findings": [],
          "needs_manual_update": false,
          "error": null
        },
        {
          "site_uuid": "7e8f9a0b-1c2d-4e3f-8a4b-5c6d7e8f9a0b",
          "status": "skipped",
          "reason": "no_findings",
          "task_uuid": null,
          "backup": { "requested": true, "destination": null },
          "warnings": [],
          "slugs": [],
          "findings": [],
          "skipped_findings": [],
          "needs_manual_update": false,
          "error": null
        }
      ],
      "next_actions": []
    },
    "warnings": []
  }
}
reason Meaning
null The site will be fixed
no_findings No open vulnerability on the site matches your filters, the server’s Vulnerability Scan does not cover the site, or none of its findings could be matched to an installed plugin or theme (see skipped_findings)
not_eligible The site is not a provisioned WordPress site, or it is a Docker app
server_not_connected The site’s server is not connected to xCloud
already_fixing A vulnerability fix started on this site in the last 30 minutes is still running
not_found The UUID does not exist, you do not have access to that site, or your team role does not include the vulnerability scan permission for that site

backup.destination shows where the backup goes: remote or local for the site’s backup destination. When it is null and a backup is requested, the site carries the warning no_backup_destination and xCloud takes a local backup instead.

A finding in skipped_findings with the reason slug_unresolved could not be matched to an installed plugin or theme, so it is not updated. Check that item on the site’s Security → Vulnerability Scan page.

Step 3: Submit the fix

Send the same body 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 fix that already exists instead of starting a second one.

curl -X POST 'https://app.xcloud.host/api/v1/vulnerabilities/fix' \
  --header "Authorization: Bearer $XCLOUD_TOKEN" \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: critical-fix-2026-11-05' \
  --data '{
    "severity": "critical",
    "sites": [
      "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e6f",
      "7e8f9a0b-1c2d-4e3f-8a4b-5c6d7e8f9a0b"
    ],
    "confirm": true
  }'

Expected result: a 202 response with the message Bulk vulnerability fix queued., the operation uuid and a next_actions entry that tells you where to read progress:

"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 sites are already updated. Without "confirm": true (and without "dry_run": true) the request is refused with 422 and the message The confirm field is required.

To fix sites in another team granted to your token, add the X-Team-Id header. See Multi-Team API Tokens and MCP Access.

Step 4: Poll each finding’s result

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

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 the dry run, with each site’s and each finding’s current status. Poll every few seconds until the operation status is no longer running. The backup runs first, so a site can stay queued for a while.

Site status Meaning
queued Waiting, or its backup is running
running The updates are running on the site
done The update run finished. Check each finding’s status
failed Nothing was updated, or the run did not finish. error says which
skipped Not fixed. reason says why

Once a site is done, each finding shows what happened:

Finding status Meaning
updated The item now runs a newer version
resolved The vulnerability is no longer open on the site
needs_manual_update WordPress offered no update, so the vulnerable version is still installed

When any finding on a site is needs_manual_update, the site’s needs_manual_update field is true. This is common for premium plugins whose updates need a license that WordPress could not use. Update those items by hand, for example with the vendor’s download, then check the site’s Vulnerability Scan page again.

The operation status is completed when no site failed, completed_with_failures when some failed and some finished, and failed when sites failed and none finished. Skipped sites do not count as failed, so check each site and finding instead of trusting the operation status alone.

Step 5: Handle failed sites

The error text of a failed site tells you what happened:

error What to do
The backup before the fix failed, so nothing was updated. Check the site’s backup destination, then submit that site again with a new Idempotency-Key
The fix did not complete. Check the site events for details. Open the site’s Events page in the dashboard to see the update output. Fix the cause, then submit that site again with a new key

xCloud never retries a failed fix on its own. Send only the failed site UUIDs in the new request. A site with a fix that started in the last 30 minutes and is still running is skipped with already_fixing. A failed fix does not block the site, so you can submit it again right away.

Fix vulnerabilities with an AI agent over MCP

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

Fix all critical vulnerabilities on my WordPress sites.
Do a dry run first and show me which plugins will be updated on each site, then wait for my OK.

The agent calls the vulnerabilities_fix tool. A dry run needs no confirmation. The real fix 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 vulnerabilities.fix 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 items it will update and the sites it will skip, waits for your approval if your client asks for it, then reports each finding’s final status, including any that need a manual update.

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 or permissions do not include vulnerability scans Use a token with write:sites and ask the team owner for the vulnerability scan permission
403 on poll The token lacks read:sites, or the vulnerability scan permission Use a token with read:sites and ask the team owner for the vulnerability scan permission
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 filter or site 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 request
429 More than 30 fix requests in a minute Wait a minute. Put more sites in one request instead of many small ones

Frequently asked questions

Does the bulk fix update every plugin on my sites?

No. It updates only the plugins, themes and WordPress core that have an open vulnerability matching your filters. Other plugins and themes with updates waiting are left alone. Use the Updates Manager in the dashboard for those.

What does needs_manual_update mean?

WordPress offered no update for that item when the fix ran, so the vulnerable version is still installed. This is common for premium plugins whose updates need a license. Update the item by hand, for example from the vendor’s site, then check the site’s Vulnerability Scan page again.

Is a backup taken before the update?

Yes, by default. xCloud backs up each site before it updates anything, to the site’s backup destination when it has one, or as a local backup when it has none. If that backup fails, nothing on that site is updated. Send backup_before_update: false to skip it.

Why is a site skipped with no_findings?

xCloud has no open vulnerability for that site that matches your filters. The site may have no vulnerabilities, the vulnerable item may be ignored, or the server’s Vulnerability Scan may not cover the site, so xCloud has no findings for it. A site is also skipped with no_findings when none of its findings could be matched to an installed plugin or theme; those findings are listed in skipped_findings.

Next steps

If you run into any issues with the bulk vulnerability fix, feel free to reach out to our support team for help.