Skip to content

Capability discovery

Preview A managed.dev site carries a runtimewordpress, static, and, on the roadmap, drupal and more. 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, domains, 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 map for one site — which dynamic-layer features its runtime supports, and for each, the actions you can take:

GET /v1/sites/site_01J7…/capabilities — a WordPress site
{
"data": {
"runtime": "wordpress",
"runtime_version": "6.8",
"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 }
}
},
"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 },
"database": { "supported": false },
"magic_link": { "supported": false },
"build": { "supported": true },
"clone_content": { "supported": true, "selectors": ["files"] }
}
},
"request_id": "req_01J9…"
}

The pattern for a client is: read capabilities, then drive your UI and tool selection from supported and actions — show the “install plugin” action only where components.plugins.supported is true and "install" is in its actions.

GET /v1/runtimes

Where …/capabilities answers “what can this site do?”, /v1/runtimes answers “what do runtimes do in general?” It’s a static catalog — each runtime mapped to its default capabilities and component kinds — 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", "component_kinds": ["plugin","theme","user","content","cron"], "default_capabilities": ["components.plugins","database","exec","magic_link"] },
{ "runtime": "static", "component_kinds": [], "default_capabilities": ["build","clone_content"] }
],
"request_id": "req_01J9…"
}

See runtimes & capabilities reference for the complete catalog.

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 not_found The capability is structurally absent for this runtime — it doesn’t exist here, so the API hides it (a scoped key couldn’t even hold the scope for it). A plugins route on a runtime that has no concept of plugins.
409 capability.unsupported The route exists, but this runtime can’t perform it. POST …/components { kind: "plugin" } against a static site.

The distinction matters when you write error handling: a 404 says “don’t offer this feature for this runtime at all”; a 409 capability.unsupported says “this is a real feature, just not one this particular site can do.” 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.