Chelabase

Error codes

Every error code, its status and meaning, for the management, login and data APIs.

Every error response is JSON with the same outer shape:

{ "error": { "code": "name_taken", "message": "a branch with this name exists" } }

code is stable and snake_case; match on it. message is for people and can change. A few codes add keys inside error (listed below). The HTTP status says the class of failure; the code says which one.

The data API adds detail, hint and sqlstate to errors that come from Postgres. See Data API errors.

Management, login and gateway codes

These come from the management API, the login API (the dashboard sign-in and app users) and the gateway. Where a code has two statuses, each row says when.

Request and authentication

StatusCodeMeaning
400invalid_requestA path id isn't a valid UUID, the body isn't well-formed JSON, or the query string is malformed.
415invalid_requestThe request isn't Content-Type: application/json.
422invalid_requestThe body is JSON but has the wrong fields, a field is missing or out of range, or a name is invalid.
401unauthorizedNo credential, or a bad token or key.
409tenant_requiredSign-in (/auth/v1/sign-in) for a member of several tenants with no ?tenant=. Sent only after correct credentials; error.tenants lists the choices.
403forbiddenThe tenant_id in the body isn't the caller's tenant.
404not_foundThe resource doesn't exist, or belongs to another tenant (both answer the same). The gateway also answers not_found for an unknown host or route.
405method_not_allowedThe gateway has no handler for this method on this path; the Allow header lists the methods it has.
413too_largeThe body is over the limit (2 MB on the management API, 8 KiB on the dashboard login endpoints, 64 KiB on the app-user endpoints).
429rate_limitedToo many attempts on a login endpoint.
500internalUnexpected; details are only in the server logs.
502unavailableA service behind the API answered something unexpected, or failed.
503unavailableA service behind the API (the compute, the user service, the identity service) can't be reached right now.
504timeoutA service didn't answer in time. The gateway also answers timeout with 408 when a request times out.

Members, access and invites

Any project route can answer the first four rows. See Access per route for which level each route needs.

StatusCodeMeaning
403forbiddenThe caller's grant is too low, or the Postgres role asked for isn't in their effective set. error.required names what is needed: a tenant, project or branch level (tenant admin, developer, admin), instance admin, owner, role:<name>, or connect (the member's database role may not connect to that Postgres database). A project the caller has no grant on answers 404 not_found instead.
403no_access_yetThe caller is a member with no grants anywhere. A tenant admin must grant them a project.
403pending_approvalThe caller signed up and waits for a tenant admin to approve them.
403member_suspendedA tenant admin suspended the caller's membership. Every route refuses them except GET /v1/me; a tenant admin can reinstate them.
403member_offboardedThe caller was offboarded: removed or declined, which ends their sessions and bans their account. Every route refuses them except GET /v1/me, until they accept a new invite.
409personal_login_existsThe caller already has a live personal login on this database. Reset its password, or delete it and create a new one to change the name.
403not_available_in_platform_modeThe feature (or self-sign-up) isn't designed for platform mode yet.
403sign_up_closedOpen sign-up is off and the tenant already has members.
403setup_code_invalidThe instance has no member yet and its first sign-up needs the setup code that chelabase-init secrets printed; it was missing or wrong. No account is created.
409last_tenant_adminThe change would leave the tenant without an active tenant admin (demoting or removing the last one).
409link_not_allowedA link invite (delivery: "link") for someone who already has a password or a verified email, or whom another tenant holds. Send it by email instead; error.email_available says whether email delivery works now.
409signup_pendingThe person signed up and waits for approval. Approve or decline the sign-up instead of inviting them; error.member_id names the member.
409invite_closedThe invite was revoked, accepted or replaced since. Reload the list.
410invite_expiredThe invite's code has expired. Ask for a new invite.
410invite_revokedThe invite was revoked.
503invite_incompleteThe code was accepted but a later step failed. error.accept_ticket lets the page retry without the spent code.
503member_role_reconcile_failedThe caller's Postgres role on this branch couldn't be prepared right now. Comes with Retry-After; try again.
503compute_tls_failedChelabase couldn't open a verified TLS connection to this branch's compute, or the compute refused the connection's address or client certificate. Comes with Retry-After; try again. If it persists, the operator should check the compute CA and the orchestrator's client certificate.

Two more refusals use general codes. A grant or override below Admin that lists an owner-level, LOGIN or reserved role, or a role whose chain reaches one, is 422 invalid_request, and so is a grant at any level (Admin too) whose role chain holds ADMIN on another role; the message names the role, its chain and the database (error.role, error.chain, error.database_id), and invites and approvals also name the grant's project (error.project_id). A Developer who acts as such a role, or creates a data key bound to one, gets 403 forbidden with required: "admin".

Projects and databases

StatusCodeMeaning
409name_takenA database or branch with this name exists.
409project_deletingThe project is being deleted.
409database_not_readyThe database isn't ready, or is being deleted.
409already_deletingThe database is already being deleted.
409data_api_disabledThe Postgres database isn't served by the data API; enable it first.
409data_api_not_enabledThe data API is off for the Postgres database on a branch.
409conflictThe change conflicts with the current state (for example the main database can't be removed from the data API, or the worker refused the change; the message says why).
502unavailableThe worker failed to apply the change.
503worker_unavailableThe worker can't be reached.
503not_appliedThe change is stored but not applied yet; it will be retried.

Branches

StatusCodeMeaning
409branch_not_readyThe branch isn't ready, or is busy or being deleted.
409backup_branchA restore backup can't take this operation (no compute, can't be protected or rewritten).
409main_branchMain can't be deleted and has no parent to reset to.
409has_childrenThe branch (or one of its backups) has child branches.
409branch_protectedThe branch is protected. On a branch route (reset, restore, delete): unprotect it first. From the table API, and the SQL editor's guard refusal on a protected branch (Postgres error 42501 with hint chelabase:branch_protected): schema changes there need an Admin; ask one, or make the change on a branch of your own and merge it.
409branch_cap_reachedThe database has max_branches.
409credentials_unavailableThe branch has no endpoint, or its owner credentials are missing.
500credentials_unavailableStored credentials can't be opened with the current secrets key.
409owner_password_unknownThe owner's password was set in SQL; reset it first.
409branch_merge_busyA schema merge holds this branch as its source or target (it is rehearsing, applying, finishing, or failed half applied in its online phase), or a rehearsal's throwaway branch still hangs off it, so reset, restore and delete are refused. error.merge_id names the merge; error.rehearsal_branch_id names the throwaway branch.
409review_changedThe branch's review (code owned by roles other than the owner or a superuser that can run in someone else's session) differs from the hash the request carries: protecting without review_hash while the list isn't empty, or with a stale one, and a stale hash on acknowledge or fix. error.review carries the current review (its items and hash).

Table API

StatusCodeMeaning
403permission_deniedPostgres refused a statement for the role you act as (SQLSTATE 42501); the same code as the data API's.
409has_dependentsA drop has dependents and the request doesn't confirm a cascade (dry run or not); nothing was changed. error.dependents lists what a cascade would also drop and its hash, to send as cascade.dependents_hash.
409dependents_changedThe objects a cascade would drop changed since the dry run.
409index_build_failedA concurrent index build failed after creating its index; that invalid index was dropped.
409lock_timeoutA lock the change needs was held past lock_timeout_ms.
409conflictExisting rows break a unique, foreign-key or exclusion constraint (23505, 23503, 23P01), or a deadlock or serialization failure (40P01, 40001; safe to retry).
400constraint_violationRows break NOT NULL or a check, or a value is out of range or can't be read as its type (23502, 23514, class 22). Not the app users' 422 constraint_violation, which is another endpoint's.
400schema_change_failedPostgres refused a table API statement (any SQLSTATE not mapped to another code); the error carries sqlstate, message, detail, hint and position.
409privileges_not_appliedA privilege change ran, but the privileges read back afterwards differ from the requested end state (for example a grant Postgres skipped because the role running it couldn't give it). Nothing of the change was kept.
409privileges_changed_since_readAn object's privileges, or a function's signature, changed since the privileges read the request was built from. Read them again and retry.
422exposes_data_api"All rows" was refused because the data API can reach the table through that role; error.chain shows how.

Schema merges

Merge routes also answer the general codes above: not_found for an unknown merge (or another tenant's, or one of another database), branch_not_ready on a start, rehearse, apply and retry (a branch of the merge isn't ready), backup_branch and invalid_request on a start (a step id with :, ., a control character or phase1 is a 422 invalid_request on a save), and conflict with 409 when the merge is in a state that can't take the request (for example editing a merge that is applying, or exporting a plan before there is one). See Merging schema between branches.

StatusCodeMeaning
409merge_open_existsThe branch and its parent already have an open merge for this Postgres database. Raised on start, and on recompute when a newer open merge exists. error.merge_id names it.
409merge_busyAnother merge is changing the target branch: applying, online, finishing, or failed half applied on it (error.merge_id). Raised on start, apply and retry. A start also answers it, with Retry-After: 1, when the pair's open merge finished a moment earlier: try again.
409merge_version_conflictversion in a save, a rehearse or an apply isn't the merge's. error.current carries the current merge.
409merge_not_rehearsedApply needs a rehearsed merge, and this one is in another state, or changed while the request ran.
409merge_staleThe merge is stale: a schema changed since the merge was computed (found by the rehearsal, or by the apply's check of the target). Recompute it.
409merge_conflicts_openA conflict has no resolution; the message lists their ids. Raised on rehearse and apply.
409merge_drops_unacknowledgedA step that loses data, gives a role BYPASSRLS or owner-level, or renames a role, has no acknowledgement; the message lists the items.
409merge_confirm_requiredThe target is protected, and apply needs confirm: true.
409branch_merge_busySee Branches.
nonemerge_base_unavailableNot an HTTP error. It is the start of the error of a merge left as a draft with no analysis because its base can't be read (outside the history window and not a branch point the storage layer keeps, or stored by another model version): the text is merge_base_unavailable: followed by the reason. Recompute with rebaseline: true.

A merge that fails while running (failed, stale) shows its reason in the merge's own error and failed_phase, not as an HTTP error. The worker starts the error with a code where it knows the cause: merge_stale: (the target changed; nothing of the plan's transaction was applied), merge_target_changed: and merge_target_gone: (the plan's transaction is no longer on the target, or the target is gone: failed with no phase), merge_half_applied: (a recompute found the plan's transaction on the target: failed in online), and merge_resolution_refused: (a saved choice was refused: a draft). An apply interrupted before its transaction committed returns to rehearsed with an error that starts "interrupted before the plan's transaction committed". Merging schema lists them.

Login roles and data API keys

StatusCodeMeaning
409role_existsA login role with this name exists.
409role_cap_reachedThe branch has its maximum number of login roles.
409role_in_useData API keys still use the role. error.keys lists them as {id, name}.
422invalid_role_nameThe name is empty, too long (over 63 bytes), has a NUL byte, or doesn't fit the naming rules.
422reserved_role_nameThe name is reserved, or the role is the owner, which can't be deleted.
422role_attribute_not_allowedAn attribute the roles API doesn't take: a connection limit under -1. Other attributes outside the allowed set (LOGIN, owner-level, INHERIT, CONNECTION LIMIT, VALID UNTIL, BYPASSRLS, memberships) are refused as invalid_request unknown fields.
422role_membership_not_allowedA membership in a role Chelabase keeps for itself (the owner, authenticator, anon, authenticated, neon_superuser, cloud_admin, chelabase_…, pg_…), in the role itself, in a role that doesn't exist or the owner can't administer, or in a group that is itself a member of one of those, at any depth.
422invalid_successorA role delete's successor can't receive the role's objects: it's the role itself, the owner, a reserved role (pg_…, cb_m_…, chelabase_… other than chelabase_orphaned, the platform's own), a personal login, owner-level (directly or through a membership), a role the owner can't SET ROLE to, or one that doesn't exist. Omit successor to hand them to chelabase_orphaned.
409exposure_unconfirmedA role delete to a named successor would leave SECURITY DEFINER routines or views without security_invoker, granted to other roles, running with the successor's rights, and confirm isn't the current token. error.exposures lists them as {database, kind, identity, grantees} and error.confirm carries the current token (also answered by GET …/roles/{role}/delete-preview): review them, then delete again with ?confirm=<token>. chelabase_orphaned never needs one.
403role_refusedThe worker refused the role (on the SQL editor and console routes).
422role_refusedThe worker refused the role when a key is created (it doesn't exist, or it's owner-level: superuser, CREATEROLE, CREATEDB, REPLICATION or a neon_superuser member; the message says which). BYPASSRLS alone is allowed.
500role_privilege_mismatchThe role's privileges in the database don't match what Chelabase recorded.
409public_key_existsThe branch already has a public key.
409public_key_not_revocableA public key can only be rotated.

App users

StatusCodeMeaning
404app_users_disabledApp users were never enabled for the project.
409app_users_disabledApp users aren't enabled (management API and login API).
409app_users_has_usersThe users table has users, so app users can't be disabled.
409app_users_table_existsThe users table exists already.
409app_users_location_fixedThe users table's location can't change.
409email_takenAnother user has this email. On sign-up this happens only when email verification is off.
401invalid_credentialsWrong email or password, or the current password is wrong.
401refresh_token_invalidThe refresh token is invalid.
401refresh_token_reusedThe refresh token was used already; the session is revoked.
401user_token_invalidNo user token, or an invalid one.
403email_not_verifiedThe email isn't verified.
403user_inactiveThe user is deactivated or locked.
410sign_in_expiredStudio's sign-in (not an app-user flow): the sign-in has expired or is unknown.
400, 422invalid_codeThe verification or reset code is wrong or expired. The dashboard login API answers 400; the app-user API answers 422.
400, 422weak_passwordThe password doesn't meet the policy. Same split: 400 on the dashboard login API, 422 on the app-user API and the management API.
422unknown_columnSign-up data names a column the users table doesn't have or sign-up can't set.
422constraint_violationThe users table refused the row. error.constraint names the constraint.
503identity_unavailableThe identity service that holds the project's users can't be reached. Try again. When a deactivate or delete answers this, the user's sessions were already revoked.

SQL editor

StatusCodeMeaning
409sql_confirm_requiredThe guard held statements back. error.statements lists them; send again with confirm: true to run them.
409run_id_in_useYou have a run with this id in flight.
429sql_run_limitYou have 4 runs in flight, or the Postgres database has 4. Comes with Retry-After.
422schema_too_largeThe schema is over 8 MiB; use keyword completion.
409saved_query_name_takenYou have a saved query with that name.
409saved_query_conflictversion is stale. error.current carries the current row.
409saved_query_capYou already keep 500 saved queries (the cap is per user and database).

Data API codes on the gateway

The gateway authenticates data API requests before they reach the data API, and answers with the data API's error body. These codes come from the gateway, with the statuses below.

StatusCodeMeaning
401unauthorizedThe key is missing, invalid or revoked, or an endpoint that needs a secret key got another kind (or a public one got a secret key).
401user_token_invalidThe Chelabase-User token is missing, malformed or invalid.
401user_token_expiredThe Chelabase-User token has expired.
401user_session_revokedThe user's session was revoked.
400user_token_with_secret_keyA Chelabase-User token came with a secret key.
429rate_limitedMore than 20 lookups per second of keys the gateway doesn't know, from one IP address.

A bad user token never falls back to the key's own role.

Data API errors

The data API (POST /data/v1/query, GET /data/v1/schema) answers with:

{
  "error": {
    "code": "conflict",
    "message": "duplicate key value violates unique constraint \"users_email_key\"",
    "detail": "Key (email)=(a@example.com) already exists.",
    "sqlstate": "23505"
  }
}

detail, hint and sqlstate are present only on a database error that Chelabase maps to a code, and carry Postgres's own text. Everything else has code and message only. A 503 and a 500 never show Postgres's text; the 500 message carries a request id and the details go to the log with it.

StatusCodeMeaning
400invalid_queryThe body isn't a valid query or doesn't plan: syntax error, bad filter, wrong types, empty select or returning, an update or delete with neither where nor all.
400unknown_columnA column that doesn't exist or that the key's role can't reach.
400invalid_valuePostgres rejected a value: not-null (23502) or check (23514) violation, or any type or value error (class 22).
400raisedA function raised an exception (RAISE EXCEPTION, P0001).
400user_token_with_secret_keyA Chelabase-User token came with a secret key.
401unauthorizedThe key is missing, unknown or revoked, or its branch is gone.
401user_token_invalidThe Chelabase-User token is invalid.
401user_token_expiredThe Chelabase-User token has expired.
401user_session_revokedThe user's session was revoked.
403permission_deniedPostgres refused the role (42501); the same code as the table API's.
403role_not_allowedA secret key's role is owner-level or has system privileges: superuser, CREATEROLE, CREATEDB, REPLICATION, or a member (at any depth) of a superuser role, neon_superuser, chelabase_admin or the pg_*_server_* roles: what key creation refuses. Take those away, or use another role (BYPASSRLS alone is allowed).
404unknown_tableA table or view that doesn't exist or that the key's role can't reach.
404unknown_functionA function that doesn't exist, can't be called through the API, or that the key's role can't execute.
404unknown_databaseThe database doesn't exist, isn't served, or isn't on the key's allow-list. One text for all three, so a key can't discover database names.
404unknown_roleStudio's console only: the chosen role doesn't exist (any more).
409conflictA unique (23505), foreign-key (23503) or exclusion (23P01) violation.
409retryA serialization failure or deadlock (40001, 40P01). Safe to run again.
413too_largeThe body is over 1 MiB, or the response is over the response limit (CHELABASE_REST_MAX_RESPONSE_BYTES, default 16 MiB).
429rate_limitedSee the gateway table above.
500internalUnexpected. The message includes a request id; check the logs.
503unavailableThe database can't be reached right now. See below.
504timeoutA statement ran past its timeout (57014), or the transaction timeout passed (25P04). Both follow statement_timeout_ms.

On a data API route the gateway also answers an unknown path (404) or a wrong method (405) with invalid_query, and the 405 carries Allow.

503 and Retry-After

Every 503 from the data API carries Retry-After: 1 when the gateway answers it, 2 when the data API service does. Causes:

  • the branch's database can't be reached, refuses the connection, or is out of connections or resources (Postgres classes 08, 28 and 53, and 57P01 to 57P03);
  • no connection to the branch in time, or too many connections in use;
  • the service can't look up the branch, or the branch isn't ready;
  • the key's role can't be used right now (dropped, or its grant is gone until the bootstrap runs again);
  • the data API's schema is missing; it will be reinstalled;
  • the gateway can't check the key or the user's session.

The 503 message is fixed text.

A response that didn't come from the data API (a proxy error page) has no error body from it. The gateway then builds one from the failure: 502 is unavailable, 504 and 408 are timeout, 400 is invalid_request, 413 is too_large, anything else is internal.

For how to send queries and handle these errors, see the Data API guide.

On this page