Skip to content

Errors

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
{
"request_id": "req_01J9…",
"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/errors/permission/scope.insufficient"
}
}
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 429 A plan limit is reached (sites, environments, storage) rather than a throughput one. Also 429, but backing off won’t help — raise the limit or free capacity.
api_error 500 An unexpected error on our side. Safe to retry idempotent requests with backoff; quote the request_id if it persists.

The code field is the precise, stable identifier for a condition. The current catalog:

code HTTP Meaning
scope.insufficient 403 The key can see the resource but is missing the required scope.
site.not_found 404 The site doesn’t exist — or is outside your key’s visibility.
artifact.not_found 404 No such build artifact.
function.not_found 404 No such function.
api_key.not_found 404 No such API key.
job.not_found 404 No such job.
webhook_endpoint.not_found 404 No such webhook endpoint.
webhook_delivery.not_found 404 No such webhook delivery.
resource.not_found 404 Generic not-found for other resources.
idempotency.key_reuse 409 An Idempotency-Key was reused with a different request body.
idempotency.in_progress 409 The original request under this key is still in flight.
binding.unsupported 400 A function declared a binding the platform doesn’t offer.
language.unsupported 400 A function declared a language the platform can’t build.
capability.unsupported 409 The route exists, but this site’s runtime can’t perform it.

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 follows the pattern https://docs.managed.dev/errors/<type>/<code> — for example https://docs.managed.dev/errors/permission/scope.insufficient. Each entry lists what triggers the code and how to fix it, so an integration can surface a “learn more” link straight from the payload. The error codes reference collects the full catalog.