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.
The error object
Section titled “The error object”When a request fails, data is replaced by an error object:
{ "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 to HTTP status
Section titled “Type to HTTP status”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. |
Machine codes
Section titled “Machine codes”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. |
404 vs 403 — existence hiding
Section titled “404 vs 403 — existence hiding”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 answers404— never403. 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 get403 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.
Following doc_url
Section titled “Following doc_url”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.