---
title: Error codes
description: Every error string the platform API, the agent-login flow, the OAuth decision endpoint, and the sherbase Data API gateway can return, by HTTP status and route.
---

# Error codes

Most JSON error bodies carry an `error` field with one of the strings
below. This table covers the platform API (`/api/{platform,sherpage,
sherserve,sherlink,sherbase,agent-login,oauth,mcp}/...`) and the
sherbase Data API gateway (`/db/<slug>/...`). See
[API reference](/api) for the shared 401/403 shapes and the
`WWW-Authenticate` challenge every route uses.

One route doesn't yet fit this table: `POST /api/platform/v1/keys`
(minting a key) puts a human-readable message directly in `error` for
`name`/`apps` validation failures, rather than one of the stable codes
below — for example `"name must be between 1 and 60 characters"` or
`"apps must be a non-empty array of page, serve, link, base"`. Match on
HTTP status (`400`) and read `error` as prose here, not as a code.
Stable codes for this route's validation failures are planned.

| Code | HTTP | Where it occurs | What to do |
| --- | --- | --- | --- |
| `invalid_api_key` | 401 | Any `/v1/...` route — missing, unrecognized, or revoked credential | Send a valid `shb_...` key or sherlock bearer token in `Authorization: Bearer`. |
| `invalid_token` | 401 | Any `/v1/...` route — a JWT-shaped credential that failed verification | Check `error_description` (`token expired`, `unknown signing key`, `token issuer or audience mismatch`, `not an OAuth-obtained token`, `unknown or deleted client`, `client registry unavailable`, `malformed bearer token`) and re-authenticate accordingly. |
| `insufficient_scope` | 403 | Any route — credential is valid but lacks the tier, `apps` scope, or project access the route requires | Mint or use a key/client with the right scope, or get a project grant — see [keys](/concepts/keys). |
| `not_activated` | 403 | Any `/v1/...` route, the hosted MCP, and `POST /api/account/keys` — the credential is valid but the account has not activated | Activate at `dash.shebang.pro`, then retry. See [activation](/getting-started/activation). |
| `captcha_failed` | 400 | `POST /api/account/activate` — the Turnstile token was missing, expired, or rejected | Reload the page and submit again; each token is single-use. |
| `rate_limited` | 429 | `POST /api/account/activate` — more than five attempts in a rolling 15 minutes | Wait 15 minutes. |
| `invalid_phone` | 400 | `POST /api/account/activate` — the number did not parse | Enter a valid number; Singapore numbers may be typed with or without `+65`. |
| `not_eligible` | 400 | `POST /api/account/activate` — the number is not on the invitation list, or is already bound to another account | Check with Braven which number your invitation used. |
| `already_activated` | 409 | `POST /api/account/activate` — the account already has a number bound | Nothing to do. |
| `invalid_json` | 400 | Most `POST`/`PATCH` routes across every service — request body isn't valid JSON | Send a valid JSON body. |
| `invalid_body` | 400 | Platform routes (`email`, `keys`, `projects`, `oauth-clients`), `POST /api/account/activate` — body is JSON but missing/wrong-typed required fields | Match the documented request-body shape for that route. |
| `invalid_project` | 400 | `POST`/`PATCH /api/platform/v1/projects...` — a project field fails validation (missing string field, bad `color`, bad `oauth_client_id`/`status`) | Use the curated `color` palette and the documented field types. |
| `invalid_slug` | 400 | `POST`/`PATCH /api/platform/v1/projects...`, `POST /api/sherbase/v1/databases` — slug fails the format check | Use 3–30 lowercase letters/numbers/underscores. |
| `slug_taken` | 409 | `POST`/`PATCH /api/platform/v1/projects...` — slug already used by another project | Pick a different slug. |
| `slug_conflict` | 409 | `POST /api/sherpage/v1/pages`, `POST /api/sherserve/v1/objects` — slug already used by another page/object | Pick a different slug. |
| `alias_conflict` | 409 | `PATCH`/`DELETE` on a sherpage page, sherserve object, or sherlink link — the requested short-link alias is taken | Pick a different alias. |
| `password_required` | 400 | Setting `access: "password"` on a page, object, or link with no `password` given | Supply a `password`. |
| `public_id_exhausted` | 500 | `POST /api/sherpage/v1/pages`, `POST /api/sherserve/v1/objects` — the random public-id space couldn't find a free id | Retry the call. |
| `profile_handle_missing` | 500 | Any sherpage/sherserve route that needs the caller's profile handle and finds none | Server-side data issue — retry, and report if it persists. |
| `storage_cleanup_failed` | 500 | `DELETE` on a sherpage page, sherserve object, or sherlink link — the record was removed but its stored files weren't | The resource is gone from listings; retry isn't meaningful — report if seen. |
| `link_cleanup_failed` | 500 | `DELETE` on a sherpage page, sherserve object, or sherlink link — the record was removed but its short link wasn't | Same as `storage_cleanup_failed` — report if seen. |
| `invalid_multipart_body` | 400 | `POST /api/sherserve/v1/objects` — malformed multipart upload | Fix the multipart body (a valid file part with a filename). |
| `invalid_resource_type` | 400 | `POST /api/platform/v1/projects/{id}/assign` and `/unassign` | Name one of the documented resource types. |
| `client_already_attached` | 409 | `PATCH /api/platform/v1/projects/{id}` — the named OAuth client already has a home project | Detach it from its current project first, or pick a different client. |
| `project_not_empty` | 409 | `DELETE /api/platform/v1/projects/{id}` — the project still holds resources | Move or delete its resources first. |
| `last_project` | 409 | `DELETE /api/platform/v1/projects/{id}` — this is the account's only remaining project | An account must keep at least one project. |
| `cannot_revoke_current_key` | 400 | `DELETE /api/platform/v1/keys/{id}` — the target is the key authenticating this call | Revoke it from a different key, or from the dashboard's account page. |
| `forbidden` | 403 | `/api/platform/v1/oauth-clients` — caller isn't a master key | Use a master key. |
| `invalid_email_input` | 400 | `POST /api/platform/v1/email` — bad recipient, subject, or body | Fix the email fields. |
| `email_quota_exceeded` | 429 | `POST /api/platform/v1/email` — over 10 sends/account/24h | Wait until the returned `retry_after_hours` elapses — see [limits](/reference/limits). |
| `send_failed` | 502 | `POST /api/platform/v1/email` — the upstream send provider rejected it | Check the error's `message`; retry later. |
| `data_api_disabled` | 409 | `POST /api/sherbase/v1/databases/{slug}/reload` — no gateway instance registered for this slug | Enable the Data API on this database first. |
| `data_api_not_enabled` | 400 | `POST /api/sherbase/v1/databases/{slug}/secret` — Data API was never enabled | Call the `enable` route first. |
| `not_found` | 404 | Any `{slug}`/`{id}` route — no matching resource for this credential | Check the identifier and that this credential can reach it. |
| `mfa_required` | 403 | `POST /api/oauth/decision`, `POST /api/agent-login/decision` — second factor not yet satisfied this session | Complete the account's second factor, then retry. |

## Device login (`/api/agent-login/...`), RFC 8628 shapes

These use `error` the same way but follow RFC 8628's device-flow error
set. See [device login](/getting-started/device-flow-login).

| Code | HTTP | Where it occurs | What to do |
| --- | --- | --- | --- |
| `invalid_request` | 400 | `device_authorization`, `token` — malformed body or missing required field | Fix the request body. |
| `rate_limited` | 429 | `device_authorization`, `token` — too many requests from one client IP | Back off and retry. |
| `unsupported_grant_type` | 400 | `token` — `grant_type` isn't the device-code grant | Use `urn:ietf:params:oauth:grant-type:device_code`. |
| `authorization_pending` | 400 | `token` — the user hasn't approved yet | Keep polling at the returned `interval`. |
| `slow_down` | 400 | `token` — polling faster than the given `interval` | Poll at the larger `interval` this response returns. |
| `access_denied` | 400 | `token` — the user denied the request | Start the device flow again. |
| `expired_token` | 400 | `token` — the device code's 10-minute lifetime elapsed | Start the device flow again. |
| `invalid_grant` | 400 | `token` — the device code is unknown or already claimed | Start the device flow again. |

## Uncaught server errors

| Code | HTTP | Where it occurs | What to do |
| --- | --- | --- | --- |
| `internal_error` | 500 | Any route's catch-all handler for an unexpected server-side failure | Retry; report if it persists. |

## The sherbase Data API gateway (`/db/<slug>/...`)

The gateway answers every error Supabase-shaped (`{message, code, hint,
details}`), with `code` set to the HTTP status as a string rather than a
named error like the routes above:

| HTTP | Message | What it means |
| --- | --- | --- |
| 401 | `No API key found in request` / `Invalid API key` | Missing or unrecognized `apikey`/Bearer credential for this database. |
| 404 | `database not found` / `Not found` | Unknown slug, or a path that isn't `/rest/v1/*` or `/auth/v1/*`. |
| 503 | `database starting` | The database's PostgREST instance is still coming up — retry shortly. |
| 502 | `upstream unavailable` | PostgREST or sherlock didn't respond — retry. |
| 500 | `internal error` | Unexpected gateway failure — report if it persists. |

## Next

[Limits](/reference/limits) — the numeric caps behind `email_quota_exceeded`
and a few of the errors above.
