Skip to content

Capability discovery

A managed.dev site carries a runtimewordpress, static, or wasm, with drupal and node on the roadmap. Rather than make you hard-code what each runtime can do, the API lets a site tell you: a machine-readable capability map you read before you act. This is what lets one integration work across every runtime, and what lets new runtimes inherit the whole platform without you shipping new code.

Every site exposes a runtime attribute. The foundational resources — sites, environments, deployments, backups, jobs, observability — are defined without reference to any CMS, so they work identically whichever runtime a site runs. What differs between a WordPress site and a static site isn’t the resource model; it’s the set of capabilities each advertises.

GET /v1/sites/{site_id}/capabilities  

sites:read

Returns the capability advertisement for one site — which dynamic-layer features its runtime supports, and for each, extra detail where it’s useful (the actions a component kind allows, a database engine, exec shells, clone selectors, a cron kind). Every runtime reports the same capability keys, so a client always sees an explicit supported: false rather than a missing entry:

GET /v1/sites/site_01J7…/capabilities — a WordPress site
{
"data": {
"runtime": "wordpress",
"capabilities": {
"components.plugins": { "supported": true, "actions": ["list","install","activate","update","delete"] },
"components.themes": { "supported": true, "actions": ["list","activate","update","delete"] },
"components.users": { "supported": true },
"components.content": { "supported": true },
"cron": { "supported": true, "kind": "wp-cron" },
"database": { "supported": true, "engine": "mysql" },
"exec": { "supported": true, "shells": ["wp-cli","bash"] },
"magic_link": { "supported": true },
"clone_content": { "supported": true, "selectors": ["db","files"] },
"build": { "supported": true },
"functions": { "supported": false }
}
},
"request_id": "req_01J9…"
}

For a static site, the CMS-shaped entries report supported: false:

The same call against a static site
{
"data": {
"runtime": "static",
"capabilities": {
"components.plugins": { "supported": false },
"components.themes": { "supported": false },
"components.users": { "supported": false },
"components.content": { "supported": false },
"cron": { "supported": false },
"database": { "supported": false },
"exec": { "supported": false },
"magic_link": { "supported": false },
"clone_content": { "supported": true, "selectors": ["files"] },
"build": { "supported": true },
"functions": { "supported": false }
}
},
"request_id": "req_01J9…"
}

The pattern for a client is: read capabilities, then drive your UI and tool selection from supported and actions — show a “run WP-CLI” action only where exec.supported is true.

GET /v1/runtimes  

sites:read

Where …/capabilities answers “what can this site do?”, /v1/runtimes answers “what do runtimes do in general?” It returns the full catalog — each runtime with its default capability map, in the same shape — useful for building a runtime picker or validating a plan before any site exists:

GET /v1/runtimes
curl https://api.managed.dev/v1/runtimes \
-H "Authorization: Bearer mfk_live_…" \
-H "Forge-Version: 2026-06-23"
{
"data": [
{ "runtime": "wordpress", "capabilities": { "components.plugins": { "supported": true, "actions": ["list","install","activate","update","delete"] }, /* … */ } },
{ "runtime": "static", "capabilities": { "build": { "supported": true }, "clone_content": { "supported": true, "selectors": ["files"] }, /* … */ } },
{ "runtime": "wasm", "capabilities": { "build": { "supported": true }, "functions": { "supported": true, "actions": ["deploy","rollback","routes","secrets"] }, /* … */ } }
],
"request_id": "req_01J9…"
}

See runtimes & capabilities reference for the complete catalog.

Discover, then act
runtimes, err := client.Capabilities.ListRuntimes(ctx) // the catalog
// …
caps, err := client.Capabilities.ForSite(ctx, "site_01J7…") // one site
if err != nil {
return err
}
// Capabilities is a map of capability key → capability object.
if c, ok := caps.Capabilities["exec"].(map[string]any); ok && c["supported"] == true {
// safe to call Environments.ExecWPCLI on this site
}

When you call a capability-gated route, a refusal can mean one of two distinct things — and the status code tells you which:

Status Meaning Example
404 site.not_found The resource is missing — the site (or environment) doesn’t exist, or is hidden from your key. Any call against a site id your key can’t see.
409 capability.unsupported The site exists and the route exists, but this runtime can’t perform it. POST …/exec against a static site.

The distinction matters when you write error handling: a 404 says “check the id and your key’s visibility”; a 409 with code capability.unsupported says “this is a real feature, just not one this particular site can do — stop offering it here.” Both are different from a plain permissions 403 — see errors.

Capability discovery is what makes managed.dev runtime-agnostic by construction. Adding a runtime requires no change to the core API — only a new catalog entry and the agent’s capability probe. Every client that discovers capabilities rather than hard-coding them picks up the new runtime for free. That’s the whole trick: a static site and a WordPress site are the same resource type with different advertised capabilities.