Authentication
How to authenticate against the CaptainCore REST API with application passwords, how the dashboard authenticates, and what non-admin scoping means.
Everything the CaptainCore dashboard does goes through the REST API under /wp-json/captaincore/v1/. The same routes are available to you from the command line, so anything the interface can do is scriptable.
Base URL:
https://your-manager.example.com/wp-json/captaincore/v1
Application passwords over Basic Auth
CaptainCore uses standard WordPress application passwords over HTTP Basic Auth. Generate one from Profile, API Access in the dashboard, or through the /me/application-passwords routes. The plaintext password is shown once, at creation.
curl -u user:app-password \
https://your-manager.example.com/wp-json/captaincore/v1/sites
The username is your WordPress login. You can confirm it with GET /me. Application passwords contain spaces, so quote the -u argument if your shell needs it.
The routes for managing your own passwords:
| Method | Path | Description |
|---|---|---|
| GET | /me/application-passwords |
List your application passwords |
| POST | /me/application-passwords |
Create one, returns the plaintext once |
| DELETE | /me/application-passwords/{uuid} |
Revoke one |
| POST | /me/application-password |
Create the dashboard’s own password |
| POST | /me/application-password/rotate |
Replace it |
| DELETE | /me/application-password |
Revoke it |
The X-WP-Nonce path
The dashboard at /account is a same-origin single page application. It authenticates with the logged-in WordPress cookie plus an X-WP-Nonce header, not with an application password.
That header is more than an auth detail. CaptainCore looks at it to decide whether a request came from a browser or from a script:
- Nonce header absent (curl plus an application password): long-running commands run synchronously, up to five minutes, unless you pass
"async": true. - Nonce header present (the dashboard): the same commands return a job token so the interface can stream progress.
Browser EventSource cannot send Basic Auth, so the SSE stream route accepts a cookie plus ?_wpnonce= instead. See Running commands and jobs.
Permission model
Three permission callbacks cover almost every route.
| Callback | Meaning |
|---|---|
captaincore_permission_check |
Any logged-in user. The handler then scopes results to the caller’s accounts. |
captaincore_admin_permission_check |
Logged in and CaptainCore\User::is_admin(). |
__return_true |
Public. Used only by token-protected or signature-verified routes. |
Some routes use an inline closure instead, most often current_user_can( 'manage_options' ) for fleet-wide security operations, or an ownership check for process logs and DNS records.
Account scoping for non-admins
An application password authenticates you as a WordPress user with exactly the access a logged-in session would have. Scoping happens inside each handler, not at the auth layer:
- Sites and domains are limited to the accounts you belong to, through the
captaincore_account_user,captaincore_account_siteandcaptaincore_account_domainjoin tables. - Job tokens are looked up by token and current user id, so someone else’s token returns 404.
- Recipes and process log notes you did not create cannot be edited or deleted unless you are an administrator.
GET /activity-logsfilters to your accounts. Asking for anaccount_idyou do not belong to returns 403. If you belong to no accounts, you get an empty result rather than the whole table.- Envato purchases are scoped to the provider rows you own.
Account membership carries a level: full-billing, full, sites-only or domains-only. The level decides whether you see sites, domains, billing and invites within that account.
Administrator-only fleet tooling includes the users directory, global configuration and defaults, SSH keys, archives, security operations, provider credentials and hard site deletes.
Conventions
Requests and responses are JSON. Send Content-Type: application/json on POST, PUT and DELETE bodies.
Identifiers you will see repeatedly:
| Identifier | What it is | Where to get it |
|---|---|---|
site_id |
CaptainCore site id | GET /sites |
site |
Short alphanumeric slug, for example example |
GET /sites, field site |
environment_id |
CaptainCore environment id | GET /sites/{id}/environments |
environment |
Environment name | See the casing note below |
domain_id |
CaptainCore domain id | GET /domains |
remote_id |
Constellix zone id | GET /domains, field remote_id |
account_id |
CaptainCore account id | GET /accounts |
Environment casing is not uniform. The database, the dashboard and POST /sites/cli use Production, Staging or Both, capitalized. The CLI only appends -staging when the value is exactly Staging. Most URL path segments, including logs, files and the performance monitor, expect lowercase production or staging. Match whatever the endpoint’s own documentation shows.
Path prefixes are not uniform either. Older routes use singular /site/{id}/... (backups list, snapshots list, captures list, archived logs). Newer ones use /sites/{id}/....
Errors. Permission failures come back as REST errors, typically 403 with a code of permission_denied or token_invalid. Some handlers instead return HTTP 200 with { "errors": [ "..." ] } in the body, so check for that key rather than relying on the status code alone.
Pagination. There is no global pagination scheme. Most list routes return the full set. The routes that do paginate say so explicitly, notably GET /activity-logs (page, per_page, capped at 200) and the Mailgun log routes, which page through a page_url supplied by the provider.
The API documentation endpoint
The full user-facing API reference ships inside the plugin and is served to authenticated users:
GET /me/api-docs
GET /me/api-docs?format=html
Without format=html this is not JSON. It responds with Content-Type: text/markdown and a Content-Disposition: attachment; filename="captaincore-api-docs.md" header, with {your-site} substituted for the host you called. With format=html you get { "html": "..." } rendered through Parsedown.
curl -u user:app-password \
-o captaincore-api-docs.md \
https://your-manager.example.com/wp-json/captaincore/v1/me/api-docs
The same file lives in the repository at api-docs.md.
Routes that do not use user auth
Four routes deliberately sit outside the logged-in model:
POST /apiis the CLI ingest endpoint. It is authenticated by a shared token compared withhash_equals. See CLI ingest contract.POST /cli/connectaccepts either an administrator application password or that same CLI token.POST /missiveverifies an HMAC signature in theX-Hook-Signatureheader againstCAPTAINCORE_MISSIVE_API.GET /site/{id}/snapshots/{snapshot_id}-{token}/{name},GET /quicksaves/{hash}/blueprintandGET /quicksaves/{hash}/artifactare public but token-protected in the path.
POST /login and GET /verify-login are public because they are the login flow itself.