Capability discovery
A managed.dev site carries a runtime — wordpress, 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.
The runtime field
Section titled “The runtime field”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/{id}/capabilities
Section titled “GET /v1/sites/{id}/capabilities”GET /v1/sites/{site_id}/capabilities
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:
{ "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:
{ "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 — the catalog
Section titled “GET /v1/runtimes — the catalog”GET /v1/runtimes
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:
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.
From the SDK and CLI
Section titled “From the SDK and CLI”runtimes, err := client.Capabilities.ListRuntimes(ctx) // the catalog// …caps, err := client.Capabilities.ForSite(ctx, "site_01J7…") // one siteif 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}mf runtimes # the catalogmf capabilities site_01J7… # one site's advertisementTwo ways the API says “no”
Section titled “Two ways the API says “no””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.
Why this is the foundation
Section titled “Why this is the foundation”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.