Skip to content

Exec (WP-CLI)

capability-gated

The exec resource runs a WP-CLI command inside an environment and returns a streaming job — output arrives live over the job’s SSE channel. It’s the same WP-CLI you already run over the SSH gateway, reshaped into a first-class, scoped, idempotent API primitive that automation and agents can call safely.

the scope split — this is the important part

Section titled “the scope split — this is the important part”

There are two levels of exec, and they are deliberately not the same scope:

Scope What it grants Isolation
wp.cli:exec WP-CLI with a denylist — arbitrary-code and catastrophic data commands are refused. The safe default for WP automation.
exec:raw Lifts the denylist — the key runs any WP-CLI command unfiltered. Excluded from every wildcard. Granted one key at a time, explicitly.

A key holding only wp.cli:exec cannot run these commands (each returns 403 with a message naming the required scope):

  • arbitrary code: wp eval, wp eval-file, wp shell, wp package …, wp config edit, wp config set
  • catastrophic data: wp db drop, wp db reset, wp db clean, wp db query, wp db import, wp site empty

A key that also holds exec:raw, and any human dashboard session, runs unfiltered. Even a * wildcard key stays denylisted — exec:raw never rides along with anything else. See the security model for why dangerous scopes are isolated.

POST /v1/sites/{siteID}/environments/{envID}/exec  

wp.cli:exec

Returns 202 Accepted with a job and a Location header. Send an Idempotency-Key so a retried command never runs twice.

Parameter Type Required Description
args string[] yes The WP-CLI arguments without the leading wp, e.g. ["plugin", "list", "--status=active"]. 1–64 items.

Command output streams over the returned job’s SSE channel (GET /v1/jobs/{id}/stream) as it’s produced — you don’t wait for the command to finish to see its output.

Update all plugins on staging and watch the output stream live.

  1. Run the command. A wp.cli:exec-scoped key, an idempotency key, a 202 back.

    wp plugin update --all on staging
    curl -X POST https://api.managed.dev/v1/sites/site_01J7.../environments/env_01J8.../exec \
    -H "Authorization: Bearer mfk_live_9aF2..." \
    -H "Forge-Version: 2026-06-23" \
    -H "Idempotency-Key: a91f-22b7-01de" \
    -H "Content-Type: application/json" \
    -d '{ "args": ["plugin", "update", "--all"] }'
    202 response
    HTTP/1.1 202 Accepted
    Location: /v1/jobs/job_01J9...
    {
    "data": {
    "id": "job_01J9...", "type": "job.exec", "status": "queued",
    "progress": 0,
    "created_at": "2026-07-04T18:10:02.004Z",
    "resource": { "type": "job", "id": "env_01J8...",
    "site_id": "site_01J7...", "env_id": "env_01J8..." },
    "links": { "self": "/v1/jobs/job_01J9...", "stream": "/v1/jobs/job_01J9.../stream" }
    },
    "request_id": "req_01J9..."
    }
  2. Tail the output. The job’s SSE stream carries the command output as it runs.

    Live-tail the exec job
    curl -N https://api.managed.dev/v1/jobs/job_01J9.../stream \
    -H "Authorization: Bearer mfk_live_9aF2..."
    job_01J9… — exec stream
    Plugin ‘woocommerce’ updated to 9.1.2
    Plugin ‘wordpress-seo’ updated to 23.5
    ✓ succeeded · 2 updated
  3. Check the terminal status. The job ends succeeded or failed — the exact job status set — and a failed job carries a typed error. Gate the next step of your automation on it.