Skip to content

Requests & responses

Preview 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

Omit it and you get the latest behavior, which can shift additively under your feet. Pinning is strongly recommended for anything beyond exploration — it is how you keep CI and Terraform runs deterministic. The full model is described in versioning.

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…",
"name": "acme-marketing",
"runtime": "wordpress",
"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…", "name": "acme-marketing", "runtime": "wordpress" },
{ "id": "site_01J7…", "name": "acme-docs", "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
{
"error": {
"type": "not_found",
"code": "site.not_found",
"message": "site not found",
"param": "site_id",
"doc_url": "https://docs.managed.dev/reference/error-codes/#site.not_found"
},
"request_id": "req_01J9…"
}

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