Running commands and jobs
Execute WP-CLI and shell code across environments, choose between synchronous and queued execution, then poll, stream or cancel the resulting job.
CaptainCore does not touch customer servers from WordPress. It hands work to the CaptainCore CLI, which connects over SSH and reports back. Three routes start that work: POST /run/code for freeform code, POST /sites/cli for named built-in commands, and POST /sites/bulk-tools for one tool across many environments.
All three behave the same way with respect to timing, and all three are permission checked per target. Targets you cannot reach are skipped silently. If nothing remains, you get 403.
Quick commands, synchronous
If your request has no X-WP-Nonce header, which is the normal case for curl plus an application password, the command runs inline and you get the output back. The ceiling is five minutes.
curl -X POST -u user:app-password \
-H "Content-Type: application/json" \
-d '{
"code": "wp option get home",
"environments": [{"site_id": 135, "environment": "production"}]
}' \
https://your-manager.example.com/wp-json/captaincore/v1/run/code
{"status": "completed", "response": "https://example.com\n"}
Long-running commands, queued
Add "async": true for anything that might exceed five minutes: backups, migrations, bulk updates. The call returns immediately with a token.
curl -X POST -u user:app-password \
-H "Content-Type: application/json" \
-d '{
"code": "wp plugin update --all",
"environments": [{"site_id": 135, "environment": "production"}],
"async": true
}' \
https://your-manager.example.com/wp-json/captaincore/v1/run/code
{"status": "queued", "token": "nwaBFBISZEsT"}
async works on /run/code, /sites/cli and /sites/bulk-tools. When the request carries a nonce, meaning it came from the dashboard, these routes always return a token regardless of async.
POST /run/code parameters
| Field | Type | Required | Description |
|---|---|---|---|
code |
string | Yes | The script to run. Empty gives 400 missing_code. |
environments |
array | Yes | Targets, formats below |
async |
boolean | No | API requests only, queue instead of waiting |
Three target formats are accepted, and you can mix them in one array.
Integer environment ids:
{ "code": "wp option get home", "environments": [3365, 3358] }
Objects carrying environment_id (the misspelling enviroment_id is also accepted, for backward compatibility):
{ "code": "wp option get home", "environments": [{"environment_id": 3365}] }
Objects carrying site_id plus an environment name, case insensitive:
{ "code": "wp option get home", "environments": [{"site_id": 135, "environment": "production"}] }
If no target survives the permission check, the response is 403 invalid_targets.
POST /sites/cli named commands
Use this when you want one of CaptainCore’s own operations rather than arbitrary code.
| Field | Type | Required | Description |
|---|---|---|---|
post_id |
integer or array | Yes | Site id, or ids for a bulk run |
command |
string | Yes | One of the named commands |
environment |
string | No | Production, Staging or Both, capitalized. Only Staging appends -staging. Empty means production. |
value |
mixed | No | Command specific |
async |
boolean | No | Queue instead of waiting |
background |
boolean | No | Force background dispatch |
commit / hash |
string | No | Quicksave hash |
version |
string | No | Rollback version |
addon_type |
string | No | plugin or theme, for rollback |
filters |
array | No | Snapshot filters: database, themes, plugins, uploads |
arguments |
object | No | For manage: { "value": "command", "input": "wp ..." } |
An unknown command returns 400 with Unknown command.
Frequently used commands:
| Command | What it does |
|---|---|
sync-data |
Refresh plugins, themes, users and usage |
update-wp |
Apply core, plugin and theme updates |
backup |
Full backup |
quick_backup |
Generate a quicksave |
snapshot |
Build a downloadable snapshot, value is the notify email |
launch |
Launch the site onto a domain, value is the domain |
migrate |
Overwrite the site from a backup zip URL |
copy |
Copy this site onto another CaptainCore site, value is the destination site id |
production-to-staging / staging-to-production |
Environment deploys |
reset-permissions |
Reset filesystem permissions |
activate / deactivate |
Leave or enter maintenance mode |
recipe |
Run a saved recipe, value is the recipe id |
rollback / quicksave_rollback / quicksave_file_restore |
Roll back a component, a whole quicksave, or one file |
remove |
Destructive CLI site delete |
curl -X POST -u user:app-password \
-H "Content-Type: application/json" \
-d '{"post_id":135,"command":"reset-permissions","environment":"Production"}' \
https://your-manager.example.com/wp-json/captaincore/v1/sites/cli
Some foreground commands return the raw CLI output as a string rather than the {status, response} wrapper, so handle both.
Jobs
Job tokens are stored against the user who created them, in captaincore_job_tokens. The token in the path matches [a-zA-Z0-9]+, no hyphens.
Poll a job
GET /my-jobs/{token}
The lookup is by token and current user id, so an unknown token or someone else’s token returns 404.
curl -u user:app-password \
https://your-manager.example.com/wp-json/captaincore/v1/my-jobs/nwaBFBISZEsT
While running:
{"status": "started", "token": "nwaBFBISZEsT"}
If the CLI reports progress, it is passed through untouched:
{"status": "started", "token": "nwaBFBISZEsT", "progress": {"phase": "copy", "percent": 40}}
When finished:
{"status": "completed", "response": "...command output...", "token": "nwaBFBISZEsT"}
Stream a job
GET /my-jobs/{token}/stream
Same ownership check, then the CLI output is proxied as Server-Sent Events with Content-Type: text/event-stream.
curl -N -u user:app-password \
https://your-manager.example.com/wp-json/captaincore/v1/my-jobs/nwaBFBISZEsT/stream
Browser EventSource cannot set an Authorization header, so the dashboard uses the login cookie plus ?_wpnonce= on this route instead.
Cancel a job
DELETE /my-jobs/{token}
curl -X DELETE -u user:app-password \
https://your-manager.example.com/wp-json/captaincore/v1/my-jobs/nwaBFBISZEsT
{"status": "cancelled"}
Administrator job views
Administrators get a wider view of what the CLI server is doing:
| Method | Path | Description |
|---|---|---|
| GET | /jobs/{id} |
Any job by id, not just your own |
| GET | /running/ |
Currently running commands (any logged-in user) |
| GET | /progress/ |
Progress feed |
| GET | /progress/{pid} |
Progress for one process |
| DELETE | /progress/{pid} |
Kill a process |
| POST | /listen-processes |
Subscribe to the process stream |
Scheduled scripts
To run code later rather than now, schedule it. Scripts are stored in captaincore_scripts against an environment.
| Method | Path | Description |
|---|---|---|
| POST | /scripts/schedule |
Schedule code on one environment |
| POST | /scripts/{script_id} |
Update a scheduled script |
| DELETE | /scripts/{script_id} |
Cancel it |
curl -X POST -u user:app-password \
-H "Content-Type: application/json" \
-d '{
"environment_id": 3365,
"code": "wp plugin update --all",
"run_at": {"date": "2026-09-10", "time": "02:00", "timezone": "America/New_York"}
}' \
https://your-manager.example.com/wp-json/captaincore/v1/scripts/schedule
Recipes are the reusable equivalent: named scripts stored in captaincore_recipes and run with POST /sites/cli using command: recipe. See Security and operations.