Skip to content

Errors

Preview Errors use the same envelope as success responses, so you parse them the same way. Every error carries a stable type (which maps to the HTTP status), a machine-readable code, a human message, and a link to the exact docs entry — plus the request_id you can quote to support.

When a request fails, data is replaced by an error object:

An error response
{
"error": {
"type": "permission",
"code": "scope.insufficient",
"message": "the api key is missing the required scope: sites:write",
"param": "scope",
"doc_url": "https://docs.managed.dev/reference/error-codes/#scope.insufficient"
},
"request_id": "req_01J9…"
}
Field Type Description
type string The broad error class. Maps 1:1 to the HTTP status (table below). Branch your handling on this.
code string A stable, dotted identifier like site.not_found. Use this for precise, machine-driven handling — it never changes for a given condition.
message string A human-readable explanation. Safe to log; not meant for end-user display verbatim.
param string The request field that caused the error, when one applies (e.g. site_id, scope). Omitted otherwise.
doc_url string A deep link to the matching entry in error codes.

The top-level request_id is always present — see requests & responses.

type HTTP When you’ll see it
invalid_request 400 Malformed body, a bad or missing parameter, a value that fails validation.
authentication 401 Missing, malformed, expired, or revoked credentials.
permission 403 You’re authenticated and can see the resource, but your key is missing a required scope, or your role can’t perform the action.
not_found 404 The resource doesn’t exist — or exists but is outside what your credentials can see (see below).
conflict 409 The request fights current state: a duplicate, a precondition that doesn’t hold, or a capability this runtime can’t perform (capability.unsupported).
rate_limit 429 You’ve exceeded a rate limit. Honor Retry-After.
quota_exceeded 402 / 429 A plan limit is reached (sites, environments, storage). 402 when it’s a billing ceiling, 429 when it’s a throughput ceiling.
api_error 500 An unexpected error on our side. Safe to retry idempotent requests with backoff; quote the request_id if it persists.

managed.dev deliberately separates “this doesn’t exist for you” from “you can’t do this.” The rule:

  • A resource you can’t see → 404 not_found. If a key’s resource constraint excludes a site, or the site simply belongs to another team, the API answers 404 — never 403. It does not leak the existence of resources you have no business knowing about. This is the same existence-hiding guarantee the dashboard enforces.
  • A scope you’re missing on a resource you can see → 403 permission. If you can already enumerate a site but your key lacks, say, sites:write, you get 403 scope.insufficient. There’s nothing to hide here — you could already list it — so the API tells you plainly which scope you need.

The ordering guarantees you only ever receive a 403 for something you were entitled to know exists. If you get a surprising 404 where you expected a permissions error, check your key’s scopes and resource constraint first — the resource is likely outside your key’s visibility, not just outside its permissions.

Every error’s doc_url anchors into error codes at the exact code. The reference lists each code with what triggers it and how to fix it, so an integration can surface a “learn more” link straight from the payload.