Docs API

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_site and captaincore_account_domain join 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-logs filters to your accounts. Asking for an account_id you 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 /api is the CLI ingest endpoint. It is authenticated by a shared token compared with hash_equals. See CLI ingest contract.
  • POST /cli/connect accepts either an administrator application password or that same CLI token.
  • POST /missive verifies an HMAC signature in the X-Hook-Signature header against CAPTAINCORE_MISSIVE_API.
  • GET /site/{id}/snapshots/{snapshot_id}-{token}/{name}, GET /quicksaves/{hash}/blueprint and GET /quicksaves/{hash}/artifact are public but token-protected in the path.

POST /login and GET /verify-login are public because they are the login flow itself.