Skip to content

Requests & responses

Every managed.dev endpoint speaks JSON over HTTPS and returns the same predictable envelope. Learn the envelope once and every resource in the API reads the same way: a data payload, a request_id you can quote to support, and — for lists — a pagination block.

Every endpoint lives under a single versioned base:

Base URL
https://api.managed.dev/v1

The /v1 prefix is the URL-major version. It only ever changes for a breaking change to the shape of a request or response; additive changes are pinned with a dated header instead. See versioning for the full policy.

The API accepts and returns application/json, exclusively over TLS — plain HTTP requests are refused, not redirected. Send your credentials in the Authorization header on every call:

A minimal request
GET /v1/sites HTTP/1.1
Host: api.managed.dev
Authorization: Bearer mfk_live_9aF2…
Accept: application/json
Forge-Version: 2026-06-23
  • Authorization: Bearer … — your API key. See API keys.
  • Accept: application/json — optional; JSON is the only representation.
  • Content-Type: application/json — required on any request with a body (POST, PATCH, PUT).

Pin your integration to a dated behavior snapshot by sending the Forge-Version header with every request:

Forge-Version: 2026-06-23

The header is optional. Omit it and you get the latest behavior, which can shift additively under your feet; send an unknown date and the request fails with a 400. Whatever version served your request is echoed back in the Forge-Version response header on every response, so you can always see which snapshot you got. Pinning is strongly recommended for anything beyond exploration — it is how you keep CI runs deterministic. The full model is described in versioning.

Alongside the body, every response carries a small set of standard headers:

Header Meaning
Forge-Version The dated version that served the request — your pin, or the latest when you didn’t send one.
X-Forge-Stability The stability of the API surface as a whole. Currently preview on every response while the API is in developer preview.
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset Your standing in the matched rate-limit bucket.

Every successful response is wrapped in one of two shapes. There is no bare array and no top-level resource object — data always holds the payload.

A request that addresses one object returns it under data, alongside the request_id:

GET /v1/sites/site_01J7…
{
"data": {
"id": "site_01J7…",
"slug": "acme-marketing",
"domain": "acme-marketing.com",
"profile": "bedrock",
"php_version": "8.3",
"runtime": "wordpress",
"region": "us-east",
"status": "running",
"created_at": "2026-06-20T14:02:55.901Z"
},
"request_id": "req_01J9…"
}

A list endpoint returns an array under data plus a pagination block:

GET /v1/sites
{
"data": [
{ "id": "site_01J7…", "domain": "acme-marketing.com", "runtime": "wordpress" },
{ "id": "site_01J7…", "domain": "docs.acme.com", "runtime": "static" }
],
"pagination": {
"next_cursor": "eyJ0…",
"has_more": true
},
"request_id": "req_01J9…"
}

next_cursor and has_more drive cursor pagination — the only pagination style the API uses. See pagination for the loop.

Every response, success or error, carries a request_id like req_01J9…. It uniquely identifies that single call in our logs. Capture it (the SDKs expose it on every response object) and quote it in any support ticket — it lets us find exactly what happened without you having to reconstruct the request.

Errors share the envelope but replace data with an error object:

An error response
{
"request_id": "req_01J9…",
"error": {
"type": "not_found",
"code": "site.not_found",
"message": "site not found",
"param": "site_id",
"doc_url": "https://docs.managed.dev/errors/not_found/site.not_found"
}
}

The type maps to the HTTP status; the code is stable and machine-readable. Full catalog and handling guidance live in errors.