Skip to content

Error Codes

AnvilBase uses standard HTTP status codes with structured error bodies. This page maps each code to its meaning and the usual cause.

Error envelopes

Management plane (/api/v1):

{ "error": { "message": "human-readable description", "code": 404 } }

Data plane (/v1):

{ "error": "invalid query", "statusCode": 400, "hint": "...", "details": "..." }

Internal/database details are never leaked — a server-side failure always returns a generic message; the specifics are in the server logs.

Status codes

CodeMeaningTypical cause
200OKsuccessful read/update
201Createdresource created (project, table, secret, user, function…)
202Acceptedasync job queued (e.g. PITR restore)
204No Contentsuccessful delete / no-body success
400Bad Requestinvalid input, failed validation, bad ?confirm= echo, malformed query
401Unauthorizedmissing/invalid/expired credential (management plane fails closed)
403Forbiddenreserved (e.g. suspended project)
404Not Foundresource doesn’t exist or cross-tenant access denied (IDOR-safe)
409Conflictduplicate slug/name/email or unique-constraint violation
429Too Many Requestsrate limit, per-project DB pool exhaustion, or quota exceeded
500Internal Server Errorserver-side failure (details logged, not returned)
503Service Unavailableupstream timeout (e.g. auth service)

Notable specifics

401 (management)

Messages: missing authorization header, invalid platform credential, expired platform token. You’re using the wrong credential type (e.g. a service_role key on /api/v1) or no token. Use the admin token or a PAT — see Management API → Auth.

404 as access control

Cross-tenant access (guessing another project’s UUID) returns 404, not 403 — the resource’s existence isn’t revealed. This is intentional (IDOR-safe). See Multi-Tenancy.

409 (conflict)

  • Project slug already exists.
  • Pending invite already exists for that email on the project.
  • Function name already exists on create.
  • A unique/foreign-key constraint violation on a write.

429 (three distinct causes)

Sub-causeSignal
Rate limitX-RateLimit-Remaining: 0 + Retry-After
DB pool exhausted{"error":{"kind":"db_pool_exhausted","code":429}} + Retry-After: 1
Quota exceededon storage writes / function deploys over the project limit

All three are back-pressure — retry with exponential backoff. See Scaling and Rate limits.

503 (upstream timeout)

{ "error": "upstream_timeout", "service": "auth" }

A backing service (commonly auth) didn’t respond in time. If /health/services shows that service unhealthy, scale it, not the control plane (Scaling).

Handling errors well

  • Retry 429 and 503 with exponential backoff and jitter; respect Retry-After.
  • Don’t retry 400/401/403/404/409 — fix the request.
  • Surface hint/details (data plane) in logs — they usually pinpoint the problem (a constraint, a bad column, an RLS rejection).
  • A write that silently returns zero rows under the anon key is usually RLS (no policy permits it), not an error — see Row Level Security.

Next: Glossary.