Integrations
Every hosting, registrar, DNS, email and analytics integration CaptainCore ships with, what credentials each one needs, and what it unlocks.
Integrations live in two directories. app/Remote/ holds thin HTTP wrappers around a vendor API. app/Providers/ holds the CaptainCore-side logic: reading credentials, importing sites, provisioning, verifying.
Credentials arrive one of two ways.
Provider rows. Most integrations read a row from captaincore_providers with a matching provider slug. The row’s credentials column is a JSON array of {name, value} pairs, and each provider class has a credentials( $record ) helper that pulls one value out by name. You create these through the Providers screen in the dashboard, or POST /providers.
wp-config constants. A few integrations are operator-wide rather than per-account and read a PHP constant instead. Those are noted below.
A provider row with user_id of 0 is the shared house row that everyone can use. A row owned by a user is only usable by that user, an administrator, WP-CLI or cron. Kinsta enforces this explicitly in provider_is_usable(), because provider ids reach that class from site rows and request bodies.
Kinsta
Files: app/Providers/Kinsta.php, app/Remote/Kinsta.php. Provider slug: kinsta.
The most complete integration in the codebase, at roughly 1,600 lines.
| Credential | Required for | Notes |
|---|---|---|
api |
Everything on the public REST API | Bearer token against https://api.kinsta.com. Site create, clone, environments, domains, phpMyAdmin |
token |
Provider verification, site sync, the provisioning action chain | MyKinsta session token, sent as X-Token to https://graphql-router.kinsta.com |
company_id |
Site create, clone, company user listing, provider sync | The Kinsta company UUID |
username |
Site create only | Becomes admin_user on a freshly installed site |
What it unlocks: create a site, clone an existing one, import remote sites, create and connect a staging environment, deploy staging to production and back, push one environment onto another, list and invite company users, manage hostnames on an environment (add, remove, set primary, verify), request final backups before a delete, and single sign-on into phpMyAdmin.
verify() is deliberately cheap: it runs a public KinstaBlog GraphQL query and checks the response shape, which confirms the token credential without touching customer data.
Cloning has one constraint worth knowing: Kinsta cannot clone across companies, so the clone source list is filtered to sites on the same provider connection.
GridPane
Files: app/Providers/GridPane.php, app/Remote/GridPane.php. Provider slug: gridpane.
| Credential | Required for | Notes |
|---|---|---|
api_key |
Everything currently wired up | Bearer token against https://my.gridpane.com/oauth/api/v1 |
What it unlocks: list remote sites, list system users, and import a site with its SFTP connection details filled in. On import the production environment gets address from the server IP, username and password from the matching system user, protocol sftp, port 22, and a home directory of /var/www/{site}/htdocs.
Note the naming inconsistency. Providers\GridPane::api_get() reads a credential named api_key, while Remote\GridPane reads one named token. The Remote\GridPane wrapper is not called from anywhere in the plugin at present, so api_key is the one to set.
Rocket.net
Files: app/Providers/Rocketdotnet.php, app/Remote/Rocketdotnet.php. Provider slug: rocketdotnet.
| Credential | Required for | Notes |
|---|---|---|
token |
All calls | Bearer token against https://api.rocket.net/v1 |
What it unlocks in the Manager today: phpMyAdmin single sign-on for sites whose provider is rocketdotnet. The Remote\Rocketdotnet wrapper implements GET, POST, PUT, PATCH and DELETE and is reachable from WP-CLI (wp captaincore remote rocketdotnet ...) for anything else.
Constellix
File: app/Remote/Constellix.php. Uses constants, not a provider row.
| Constant | Required | Notes |
|---|---|---|
CONSTELLIX_API_KEY |
Yes | Sent as x-cnsdns-apiKey |
CONSTELLIX_SECRET_KEY |
Yes | Used to HMAC-SHA1 the request timestamp into x-cnsdns-hmac |
CAPTAINCORE_CONSTELLIX_VANITY_ID |
No | Vanity nameserver group id used when creating a zone |
CAPTAINCORE_CONSTELLIX_SOA_NAME |
No | Primary nameserver written into the SOA. Only applied when the vanity id is also defined |
What it unlocks: the entire DNS editor. Zone create, delete and export, record create, update and delete, bulk record updates and BIND zone import.
The wrapper rejects any endpoint containing ?, #, a backslash, whitespace or a .. path segment, because record and zone ids arrive from request input and a crafted value could otherwise steer an account-wide key at another tenant’s zone. Query arguments must go through the $parameters array, not be appended to the endpoint string.
Cloudflare
File: app/Remote/Cloudflare.php. No credentials.
A DNS-over-HTTPS resolver against https://cloudflare-dns.com/dns-query, used to read a record type for a domain. Because it is a public resolver, there is nothing to configure. It is currently reachable through wp captaincore remote cloudflare get, and is not called from any REST handler.
Mailgun
Files: app/Providers/Mailgun.php, app/Remote/Mailgun.php. Uses a constant, not a provider row.
| Constant | Required | Notes |
|---|---|---|
MAILGUN_API_KEY |
Yes | Basic auth as api:{key} against https://api.mailgun.net |
Every method checks the constant first and returns an errors object if it is missing, so an unconfigured install degrades rather than fatals.
What it unlocks, split into two features that both run on Mailgun:
Sending. Set up a sending subdomain such as mg.example.com, write the SPF, DKIM, MX and tracking records into the Constellix zone, verify them, deploy the SMTP credentials to a site, then read events, usage and suppression lists (bounces, complaints, unsubscribes).
Email forwarding. Despite the route name activate-forward-email and the presence of a Forward Email wrapper, forwarding is implemented on Mailgun routes. Domain::get_email_forwards(), add_email_forward(), update_email_forward() and delete_email_forward() all read and write v3/routes, building forward("...") actions with a trailing stop(). A catch-all alias gets priority 100, a specific alias priority 0.
The URL host is validated strictly rather than with a substring check, so a URL like https://evil.tld/?x=https://api.mailgun.net cannot borrow the key.
Forward Email
Files: app/Providers/ForwardEmail.php, app/Remote/ForwardEmail.php. Provider slug: forwardemail.
| Credential | Required | Notes |
|---|---|---|
api_key |
For any call | Sent as the Basic Auth username with an empty password, against https://api.forwardemail.net/v1 |
The wrapper also supports alias auth: pass a username and password explicitly to authenticate as one alias rather than the account.
Be precise about its status. The wrapper is complete and reachable from wp captaincore remote forward-email, but no REST handler calls it. Email forwarding in the dashboard runs on Mailgun, as described above.
Spaceship (registrar)
Files: app/Providers/Spaceship.php, app/Remote/Spaceship.php. Provider slug: spaceship.
| Credential | Required | Notes |
|---|---|---|
api_key |
Yes | Sent as X-Api-Key |
api_secret |
Yes | Sent as X-Api-Secret |
Base URL https://spaceship.dev/api/v1. What it unlocks: domain listing (paged 100 at a time, reporting each domain’s lifecycleStatus), and the registrar controls on /domain/{id} such as lock, WHOIS privacy, auth code, contacts and nameservers.
Like Constellix, the endpoint is validated before use, since domain names are interpolated into the path.
Hover.com (registrar)
File: app/Providers/Hoverdotcom.php. Provider slug: hoverdotcom.
| Credential | Required | Notes |
|---|---|---|
username |
Yes | Hover account login |
password |
Yes | Hover account password |
Hover has no public API, so this integration logs in at https://www.hover.com/api/login and caches the session cookie in the captaincore_hovercom_auth transient for 48 hours. Every registrar action refreshes the login if the transient has expired.
What it unlocks: the same registrar controls as Spaceship, plus domain listing with status.
Fathom Analytics
Files: app/Providers/Fathom.php, app/Remote/Fathom.php. Provider slug: fathom.
| Credential | Required | Notes |
|---|---|---|
api_key |
Yes | Bearer token against https://api.usefathom.com/v1 |
What it unlocks: the Stats tab. Aggregate visits grouped by hour, day, month or year, top pages, top referrers, and public or password-protected share links. The per-site tracker id is stored on the environment as details.fathom and set with PUT /sites/{id}/fathom.
Google Web Risk
File: app/Remote/GoogleWebRisk.php. Uses a constant.
| Constant | Required | Notes |
|---|---|---|
GOOGLE_WEB_RISK_API_KEY |
Yes | Without it, get() returns a missing_api_key WP_Error |
Checks a URI against four threat lists: MALWARE, SOCIAL_ENGINEERING, UNWANTED_SOFTWARE and SOCIAL_ENGINEERING_EXTENDED_COVERAGE. All four are used when none are specified.
What it unlocks: wp captaincore web-risk-check walks every production home URL and writes a summary row into captaincore_web_risk_logs, readable at GET /web-risk-logs.
Missive
File: app/Remote/Missive.php. Uses two constants, for two different purposes.
| Constant | Required for | Notes |
|---|---|---|
MISSIVE_API_KEY |
Outbound API calls | Bearer token against https://public.missiveapp.com/v1 |
CAPTAINCORE_MISSIVE_API |
The inbound webhook | HMAC-SHA256 secret used to verify X-Hook-Signature on POST /missive |
What it unlocks: two automations driven by inbound email. An “Email Health Check” reply is matched to a site and token and handed to captaincore email-health response. A Kinsta “Action is required to renew your SSL certificate” message has its verification TXT record extracted, added to the zone, and the result posted back into the Missive conversation as a bot comment.
The webhook is public at the REST layer and rejects anything whose computed signature does not match with hash_equals. Both the site slug and the token are validated by pattern before they reach the CLI.
Envato
File: app/Providers/Envato.php. Provider slug: envato.
| Credential | Required | Notes |
|---|---|---|
token |
Yes | Personal token against https://api.envato.com/v3/market/buyer/list-purchases |
What it unlocks: your purchased themes and plugins become installable from within CaptainCore, through GET /providers/envato/themes, /plugins and the signed /download routes.
Envato purchases are per-account licences, so reads are scoped: a non-administrator only sees rows where user_id matches their own. Note that fetch_themes() and fetch_plugins(), the background refresh, deliberately walk every envato row, because that runs from cron with no user context.
Adding an integration
Two files and one convention:
- Add
app/Remote/YourService.phpwith staticget,post,putanddeletemethods that read credentials and return decoded JSON. Validate the endpoint string if any part of it comes from request input. - Add
app/Providers/YourService.phpwith acredentials( $record = "" )helper reading( new \CaptainCore\Provider( "yourservice" ) )->credentials(). Provider::verify(),deploy_to_staging()anddeploy_to_production()resolve the class by name, asCaptainCore\Providers\{ucfirst($provider)}, so the slug and the class name must line up.
Anything in app/Remote/ is immediately callable from WP-CLI without further wiring:
wp captaincore remote spaceship get domains --take=100 --orderBy=expirationDate
wp captaincore remote kinsta get sites --company=<uuid>
Run wp captaincore remote with an unknown provider to list what is available.