Skip to content
esc

Search pages and sections. Try a tool name, an error code, a route.

↑↓ navigate↵ openesc close

API reference · Basics

Errors

Every refusal has the same shape and comes from a closed catalogue. This page is that catalogue, then the order in which the API checks a request.

The shape#

json
{ "error": { "code": "…", "message": "…", "hint": "…" } }

code is stable and machine-readable; message says what happened; hint says what to do next — the exact problem, the valid values, the tool that helps. Two codes add fields: every 5xx carries request_id, and 428 confirmation_required carries confirmation_token and will_destroy.

The catalogue#

HTTPcodeWhenThe hint tells you
400validation_errorInvalid body, a platform column in the body, a wrong type, a missing required field (name on a key, preset on a table), limit above 200, an upload without a declared length, a value outside a closed listThe faulty field and the expected shape or values
400unknown_columnA filter, sort or body names a column that does not existThe available columns
400invalid_filterUnknown operator or broken filter syntaxThe nine operators and an example
401unauthorizedKey absent, invalid, revoked or replaced by a rotation (three distinct messages); a session is required and missing; a signed-in human is required and the account session is missing or invalid; the session is valid but not re-authenticated within 5 minutes (DELETE /v1/auth/me); the wrong key class for this routeWhich key or session to provide
402quota_exceededA quota is reached: the app limit of the account (Free: 2, Builder: 20, Studio: 100), the seat cap of an app (Free: 3, Builder and Studio: 1 000), the row cap of a form table (10 000), account file storage, account database size, requests of the UTC month — and any write on a project that is in read-only modeThe quota, its figure as an integer, and how to free it or upgrade
403forbiddenThe resource exists and you may know it, but the action is not allowed: editing a journal row, a member acting as a space admin, an account key limited to some projects on an account route or on POST /v1/projects, touching a platform column or a reserved table in SQL, revoking or rotating the key presented in the request, a function key on the generic rotate route, rotating a key that has no live value (revoked, or replaced by a rotation), not being an active admin of the GitHub organization you connectThe rule and what to do instead
404not_foundThe table, row, space, project, user, file or GitHub repository does not exist — or you are not allowed to read it. The two cases are indistinguishable by designFor an authenticated caller, the list of tables; otherwise neutral. On a GitHub repository the hint names the fix when the repository is not covered by your installation
404unknown_routeUnknown path under a served versionThe expected form (/v1/<resource>) — never the list of mounted routes
404unknown_versionUnknown version prefix (/v2/…)The only served version, /v1
405method_not_allowedReserved. Today an unsupported method on a known path answers 404 unknown_route—
408timeoutThe request took more than 10 sReduce limit, add a filter
409conflictWhat is asked already exists or no longer applies: an Idempotency-Key reused with a different body; CREATE TABLE of an existing table; adding a member who is already one; an operation still in flight; a preset switch to a spaces preset on a table that has rows; an invitation on a project with no app URL; a GitHub installation already connected to another account, recorded as removed on GitHub, or suspended on GitHub; a project still provisioning on export or deleteWhat to do instead
413payload_too_largeA size limit: request payload 256 KB, row 64 KB (32 KB on a form table), batch 100 rows (10 on a form table), file 25 MB, function module 1 MB, secret value 5 KB, 126 variables on a function, 126 secret names on a project, the site bounds, an OAuth gate body over 32 KBThe figure
428confirmation_requiredA guarded gesture presented without a confirmation token, or with a token that no longer counts (expired, already used, issued for another subject). The body describes what would be destroyed or become invisible and carries a fresh confirmation_tokenWhat would change, and how to confirm
429rate_limited300 requests per minute per key; 10 creations per minute per IP on a form table; the OAuth gates of the remote connector (30 per 10 s on register and token, 10 per 10 s on authorize, per IP); and, on any management route that calls the platform's administration API, an administration budget that does not cover the gesture — its account share, its owner-session share, or the share common to every account (limits): nothing runs, and the idempotency key stays free. Applying a shared secret when no administration call can go out answers the same way. On POST /v1/auth/magic-link: more than 20 requests an hour from one IP, or today's sign-in emails used up — the app's, or all apps' — under the same body. On a function's host: beyond the calls from one address in a minute or in a UTC day, or beyond the function's calls in a UTC day — refused before its code runsThe delay, also in the Retry-After header — per key, the period (60 s), and on an administration budget, the period (300 s), never a remainder; on the magic link, what remains of the hour or of the UTC day, rounded up to the minute; on a function's host, what remains of the address's minute, or the time to 00:00 UTC rounded up to the minute — 5 s when the function's counter is saturated
500internalA bug on our sideRetry; if it persists, read the logs with the request_id
501not_implementedReserved. Not emitted today—
502internalA third party refused — retrying identically will not change anythingThat it is on our side, plus the request_id, and nothing else
503internalA third party did not answer, or a configuration is missing — transient; and, on any route that calls the platform's administration API, the platform limiting requests or failing on its side, or our own administration budget running out mid-gesture — the gesture went past what it had reserved, and what was already done is kept; and, on the magic link, the counter shared by all apps cannot count — nothing is sent; and, on revoking or rotating a project key, its cut could not be recorded in the project's database — a revoked key is revoked at the key service all the same, a rotation issued nothing; repeat the call until it succeedsRetry in a few seconds, plus the request_id — and when the platform limits requests, Retry-After: 60; when it is our administration budget, Retry-After: 300. Each is the period, never a remainder. The body never names a missing variable

Seventeen distinct codes on nineteen rows: internal serves 500, 502 and 503. The 429 of the OAuth gates on mcp.manybases.com is the one refusal outside this envelope: it uses the OAuth error format (error: too_many_requests) and carries no request id.

Why 428 and not 403#

A guarded gesture is legitimate; it only lacks a precondition. The first call answers 428 with a summary of the consequence and a single-use token valid 5 minutes; the second call, same body plus confirmation_token, executes. The token is bound to the account, the project, the exact subject and the state of the target: if anything moved in between, the summary you read is stale and the API answers 428 again with a fresh token — never 409. The gestures that confirm are listed in The human boundary.

The order of checks#

A request is refused by the first check that fails, always in this order. Knowing it tells you what a refusal does and does not say.

  1. Routing — does the host address a served project? If not, the same neutral 404 as step 5.
  2. Transport and constant sizes — a body over 256 KB (25 MB on an upload, 12 MB on a site deployment) → 413. A CORS preflight is answered here with 204.
  3. Key authenticity → 401.
  4. Route resolution — path, method, version → 404 unknown_route / 404 unknown_version.
  5. Key class — is this key allowed on this route? → 401.
  6. Authorization, existence and ownership → 401 or the neutral 404.
  7. Human elevation, when the route requires a signed-in human → 401, definitive, no token issued. On the account routes that require it even to read, this step runs before step 6.
  8. Two-step confirmation, when the gesture is guarded → 428.
  9. Sizes derived from the target — row 64/32 KB, batch 100/10 → 413.
  10. Quota → 402. On a project in read-only mode, this refusal comes out as soon as step 6 is cleared, before elevation and confirmation.
  11. Rate → 429. A management gesture reserves its administration budget here, after every earlier check and before any effect — so this refusal never hides the 402 of step 10 or the 428 of step 8.
  12. Validation → 400.
  13. Idempotency → 409 on a replay with a different body or an operation in flight.

Then the request executes. A call to a deployed function is counted first: beyond its call limits it answers 429 rate_limited and its code does not run. Two things can still fail during execution: a deployed function that exhausts its CPU or subrequest budget answers 503 internal; an uncaught exception in a deployed function answers 500 internal with a message that says the deployed code failed.

Two consequences worth knowing. A 404 never tells you whether something exists: a project of another account, a project outside your key's scope and a project that was never created all answer the same bytes. And a refusal that executed nothing never enters the idempotency store, so a 428 can be retried freely.

Reading a refusal from a browser#

The SDK never throws: every call returns { data, error }, and error is a ManybasesError carrying status, code, message, hint, requestId, retryAfter and, on a 428, confirmationToken and willDestroy. status: 0 means the server never answered (network, DNS, or a blocked cross-origin request). See SDK errors. An agent sees the same fields rendered as text — see Errors for agents.