Skip to content

Error codes

Every failure a Rebase backend returns uses one envelope and carries a stable code. The code is the thing to branch on: the message is written for a person and may be reworded, the status is shared by a dozen different problems, and the code is neither.

{
"error": {
"message": "Schema drift: table \"posts\" does not exist.",
"code": "SCHEMA_DRIFT",
"details": { "dbCode": "42P01" },
"requestId": "6f1b2f3e-8a0c-4d1b-9c3e-2a5b7c9d1e0f"
}
}
  • message — human-readable. For a 4xx it is the server’s own message; for a 5xx it is deliberately generic, because the underlying text can quote a host, a role or a column name.
  • code — one of the values below. Stable across minor versions.
  • details — optional, and never guaranteed. See the rules below.
  • requestId — present whenever the request passed through the request-ID middleware, which is every route under basePath.

Every request under basePath gets an ID: the caller’s X-Request-ID header when it is a valid UUID v4, otherwise a fresh one. It is echoed back on the response as X-Request-ID, included in the error envelope as requestId, and attached to the server’s log line for that request.

That is the join key. Quote it in a bug report and an operator can find the one log line that explains the failure, carrying the reason the client was never shown.

Sending your own is how a trace survives a hop: a gateway or a job runner that forwards the header gets one ID across every service that handled the request. An invalid value is ignored rather than rejected — a malformed header from a caller is not worth failing a request over — so do not assume the ID you sent is the ID you got. Read the response header.

details is diagnostic, not contractual. Three rules govern it:

  1. Anything a route sets explicitly is always returned. These are the caller’s own mistakes described precisely: which filter field was unknown, which relation is not writable, which value did not fit its type.
  2. Database diagnostics are trimmed in production. When the failure came from Postgres, details.dbCode — the SQLSTATE — is always present: it names the class of problem and reveals nothing about the data. dbMessage, detail and hint are added only when NODE_ENV is not production, because Postgres puts row contents in them. 23505 reports Key (email)=(a@b.c) already exists., which answers “is this person registered?” for any address anyone cares to try.
  3. Never branch on details. Branch on code. What is under details is whatever was useful to a person at that call site, and it changes.
Status What it says about the request
400 Malformed, or asking for something that does not exist in the schema. Fix the request.
401 Not authenticated, or the credential expired. Sign in or refresh.
403 Authenticated, and not allowed. Retrying as the same identity will not help.
404 No such route, collection or row — or a row that row-level security hides.
409 A conflict with existing state: a duplicate, or a concurrent write.
413 415 422 The body is too large, the wrong media type, or semantically rejected.
429 Rate limited. Back off; the message says for how long.
500 The server or its database is wrong, not the caller. Check the logs.
501 The route exists, and this deployment cannot serve it — a feature that is off or unconfigured.
502 503 504 A dependency was unreachable, unconfigured, or too slow.
Code Status Means Do
AAL2_REQUIRED 403 The route needs a second factor and the session has only one. Complete the MFA challenge, then retry.
ALREADY_VERIFIED 400 The address or factor is already verified. Nothing — the desired state is already true.
ANONYMOUS_AUTH_DISABLED 403 Anonymous sign-in is off on this server. Enable it, or sign in with a real identity.
API_KEY_FORBIDDEN 403 An API key was used on a route only people may call. Use a user session.
API_KEY_SELF_MANAGEMENT_FORBIDDEN 403 An API key tried to create, list or revoke API keys. Manage keys as a signed-in admin.
AUTH_MIDDLEWARE_MISSING 500 A guarded route ran with no Rebase auth middleware before it, so the caller’s credential was never looked at. Mount the app through the functions router rather than onto your own server directly.
BOOTSTRAP_ANONYMOUS 403 First-admin bootstrap was attempted by an anonymous caller. Sign in first.
BOOTSTRAP_COMPLETED 403 The first admin already exists. Have an existing admin grant the role.
BOOTSTRAP_NOT_FIRST_USER 403 Bootstrap is only for the very first user, and this is not it. Have an existing admin grant the role.
CAPTCHA_FAILED 400 The provider rejected the CAPTCHA token. Solve a fresh challenge.
CAPTCHA_REQUIRED 400 The route requires a CAPTCHA token and none was sent. Include the token.
CHALLENGE_EXHAUSTED 401 Too many wrong codes against one MFA challenge. Start a new challenge.
EMAIL_EXISTS 409 An account with that address already exists. Sign in, or start a password reset.
EMAIL_NOT_CONFIGURED 503 Magic links or OTP were requested and the server has no mail transport. Configure SMTP, or use another sign-in method.
EMAIL_NOT_VERIFIED 403 The account exists and its address is unverified. Verify the address.
FACTOR_NOT_VERIFIED 400 The MFA factor was enrolled but never confirmed. Confirm the factor.
IDENTITY_ALREADY_LINKED 409 That OAuth identity belongs to another account. Sign in with it, or unlink it there first.
INVALID_ACCOUNT 400 The account is in a state this operation cannot act on. See the message.
INVALID_CHALLENGE 400 The MFA challenge is unknown or expired. Start a new one.
INVALID_CODE 401 The OTP or MFA code is wrong. Retry with the current code.
INVALID_CREDENTIALS 401 Wrong email or password — deliberately not saying which. Retry, or reset the password.
INVALID_TOKEN 400 A verification, reset or magic-link token is malformed or unknown. Request a fresh link.
LAST_ADMIN 403 The change would leave the project with no admin. Promote someone else first.
MFA_REQUIRED 401 The password was right and the account has a verified second factor, so sign-in is only half done. details carries a short-lived token scoped to the MFA challenge — it is not a session. Open a challenge and answer it; the challenge response issues the session.
NO_SESSION 401 No session cookie or refresh token was presented. Normal on a first page load. Sign in.
NOT_ANONYMOUS 400 An upgrade-from-anonymous route was called by a real account. Nothing to upgrade.
OAUTH_ERROR 401 The OAuth provider refused, or returned an error. Retry the flow; the message carries the provider’s reason.
RATE_LIMITED 429 Too many attempts from this caller. Back off; the message says for how long.
REDIRECT_URI_NOT_ALLOWED 400 The redirect target is not on the allow-list. Add it to the provider configuration.
REGISTRATION_DISABLED 403 Self-service sign-up is off. Have an admin create the account.
ROLE_EXISTS 409 That role name is taken. Pick another name.
ROLE_LOOKUP_FAILED 503 Roles could not be read for an admin-gated request. Fails closed rather than trusting the token’s own claim. Retry; check the database.
SELF_DELETE 400 An admin tried to delete their own account. Have another admin do it.
SESSION_REVOKED 401 The session was signed out elsewhere, or every session was revoked. Sign in again.
SETUP_REQUIRED 403 The project has no admin yet, so this route is not available. Complete first-admin setup.
TOKEN_ALREADY_USED 401 A one-time token was replayed. Request a fresh one.
TOKEN_EXPIRED 401 The token is past its lifetime. Request a fresh one.
USER_NOT_FOUND 404 No account with that id. Check the id.
WEAK_PASSWORD 400 The password does not meet the configured policy. Choose a stronger one.
Code Status Means Do
AGGREGATE_NOT_SUPPORTED 501 This driver cannot compute the requested aggregate. Use a driver that can, or compute it in the client.
BRANCHING_UNSUPPORTED A database branch was requested over the Studio websocket on the managed development database (PGlite), where a branch is the parent and nothing would be isolated. The refusal is the same one rebase db branch prints. Point DATABASE_URL at a Postgres of your own (rebase dev --docker starts one) and branch there.
BATCH_TOO_LARGE 400 POST /api/data/_batch carries more operations than the per-batch limit (1000 by default). One batch is one transaction and holds its locks for the whole of it. Send it in chunks; the message names the limit and your count.
BATCH_UNSUPPORTED 400 This backend’s driver cannot write across collections atomically, and a loop of single writes would be neither atomic nor one round trip. Send the writes as separate requests, or as per-collection /bulk calls.
BULK_TOO_LARGE 400 The bulk body exceeds the configured item limit. Split the request.
BULK_UNSUPPORTED 400 This collection or driver does not support bulk writes. Write the rows one at a time.
CALLBACK_REJECTED 400 A collection callback refused the write. A throw from beforeSave/beforeDelete/after* is a 400 carrying the author’s own message; a beforeDelete that returns false is a 403. details.stage names which callback, details.path the collection. Read the message — it was written by this project, not by Rebase.
CURSOR_WITH_OFFSET 400 ?after= was combined with ?offset= or ?page=. A cursor already says where the page starts, so an offset on top of it silently skips that many rows past the cursor — a gap the caller cannot see in the response. Use one or the other.
DISTINCT_NOT_APPLICABLE 400 ?distinct=true was combined with a search or vector query. Both attach a per-row score, so no two rows are ever equal and DISTINCT would collapse nothing — it would look like it worked and change nothing. Drop one of the two.
DISTINCT_ORDER_BY_NOT_SELECTED 400 A distinct read is ordered by a column it does not return. A SELECT DISTINCT can only be ordered by columns in its select list, or the rows it collapses have no defined order. details.fields names them. Add those fields to ?fields=, or drop them from ?orderBy=.
DB_PERMISSION_DENIED 500 Postgres refused the statement (42501): either a row-level-security policy denying this role, or a missing GRANT. See Troubleshooting.
FIELD_NOT_READABLE 400 A filter, orderBy, fields, aggregate select or groupBy names a field this caller’s roles cannot read (access.read). A field no response can carry has to be one no query can interrogate, or the value is readable one predicate at a time. details.violations names each field. Drop the field from the query, or acquire the role. See Field access.
FIELD_NOT_WRITABLE 400 The body sets a field this caller’s roles cannot write (access.write). Refused rather than dropped: a write that discards a field would report success for an edit that did not happen. details.violations names each field. Remove the field, or acquire the role. A field nobody may write answers VALIDATION_EXCLUDED_FIELDS instead.
IDEMPOTENCY_KEY_IN_PROGRESS 409 An earlier request with the same Idempotency-Key is still running. Retry once it finishes.
IDEMPOTENCY_KEY_REUSED 422 The same Idempotency-Key arrived with a different body. Use a new key, or send the original body.
INVALID_AGGREGATE_FUNCTION 400 ?select= named a function that is not count, sum, avg, min or max. Use one of those; the message lists them.
INVALID_AGGREGATE_SELECT 400 An ?select= entry is not fn(field), or a function other than count() was given no field. Write sum(total), count(), avg(score).
INVALID_BATCH_BODY 400 A /_batch operation is missing op, collection, values or id, names a collection this backend does not serve, or reuses a ref name. See the message; it names the operation by index.
INVALID_BATCH_REF 400 A { "$ref": "<name>.<field>" } names no earlier operation, points forward, or asks for a field the referenced row does not have. Only backward references resolve. Name the operation with ref before referencing it.
INVALID_BULK_BODY 400 The bulk body is not the expected shape. Send the documented items array.
INVALID_CONFLICT_TARGET 400 An upsert’s on_conflict / onConflict names columns carrying no uniqueness guarantee, or names them without upsert: true. Postgres would otherwise answer 42P10 from inside a transaction that has already done work. Declare validation: { unique: true } or a unique index; the message lists the targets that do exist.
INVALID_DELETED_PARAM 400 ?deleted= is neither include nor only. Refused rather than ignored: a mistyped ?deleted=true that quietly hid every deleted row would look like it worked and answer the opposite question. Send include (live and deleted) or only (deleted alone). Omit it for live rows only.
INVALID_DISTINCT 400 ?distinct= is not true or false. Send one of those; 1 and 0 are accepted too.
INVALID_FIELD_OPERATION 400 A $inc / $push / $pull / $merge was used on a property type it is not defined on, with an operand of the wrong shape, with two operators on one field, misspelled, or on a create — where there is no stored value to operate on. See Writing over REST; the message names the field.
INVALID_FILTER_FIELD 400 The filter names a property this collection does not have. Check the spelling against the collection.
INVALID_FILTER_OPERATOR 400 The operator is not one this property type supports. See Querying data.
INVALID_FILTER_VALUE 400 A filter value cannot be read as the type of the column it was compared against: ?id=eq.abc on an integer key, a label that is not in the enum, a timestamp that is not one, a number past the type’s range. details.dbCode carries the SQLSTATE. Send a value of the column’s type.
INVALID_HARD_PARAM 400 ?hard= is not true or false. Anything else is refused rather than read as “no” — a typo that soft-deletes when the caller asked to purge leaves them believing the data is gone. Send true or false.
INVALID_INCLUDE 400 ?include= is malformed: not a valid path list, or nested past the maximum depth. Answered at the boundary rather than escaping the driver as a 500. See the message; it names the offending path.
INVALID_INPUT 400 The body failed validation. See the message.
INVALID_LIMIT A realtime subscription asked for a limit outside the allowed range. Delivered as a WebSocket ERROR frame, not an HTTP response. Lower the limit.
INVALID_LOGICAL_GROUP 400 An ?or= / ?and= group is malformed or nested past the allowed depth. See the message; it shows the flattening rule.
INVALID_OFFSET 400 ?offset= is not a whole number of 0 or more. Send a non-negative integer.
INVALID_ORDER_BY 400 ?orderBy= is not field, field:desc, or a JSON array of { field, direction }. See the message; it shows all three spellings.
INVALID_PAGE 400 ?page= is not a whole number of 1 or more. Pages are 1-based, so ?page=0 is a mistake rather than the first page. Send 1 or higher, or use ?offset=.
INVALID_PARAM 400 A query parameter is malformed. See the message.
INVALID_VECTOR 400 ?vector= is not a JSON array of numbers. Send [0.1,0.2,0.3].
INVALID_VECTOR_DISTANCE 400 ?vector_distance= is not cosine, l2 or inner_product. Use one of those three.
INVALID_VECTOR_THRESHOLD 400 ?vector_threshold= is not a number. Send a number.
INVALID_WHERE 400 ?where= is not a JSON object mapping fields to conditions. Send {"status":["==","active"]}.
MISSING_AGGREGATE_SELECT 400 The aggregate route was called with no ?select=. Add one, e.g. ?select=count().
NO_COLLECTIONS 404 The project serves no collections: none declared in code, and no tables to derive them from. Create tables — a migration, SQL, or a collection file plus rebase db push — and restart.
NOT_FOUND 404 No row with that id in that collection — or one that row-level security hides from this caller. Check the id, then the collection’s securityRules.
UNKNOWN_RELATION 400 ?include= names something that is not a relation on the collection. The same code answers 404 when a nested URL path names one instead, e.g. /api/data/authors/1/posts where authors declares none — there the URL names nothing, so it is a not-found rather than a malformed request. Check the relation’s name — the message lists the ones the collection has. A back-reference has to be declared on the parent to be traversable.
ORDER_BY_FIELD_NOT_SORTABLE 400 The sort names a property that is not sortable. Sort on a column-backed property.
PAYLOAD_TOO_LARGE 413 The body exceeds the configured limit. Send less, or raise the limit.
READ_ONLY_TRANSACTION 409 An afterRead callback tried to write. A request-scoped read runs in a READ ONLY transaction, so neither the callback nor anything it calls may write. Move the write out of the read: a background job, or rebase.dataAsAdmin from a cron job or a custom function.
RELATION_HAS_NO_PIVOT 400 The write carried a link payload, but the path does not reach its target through a manyToMany that declares through.properties — so there is no junction row to put it on. Declare through.properties on the relation, or drop the payload from the write. See Relations.
RELATION_MISCONFIGURED 500 A relation does not resolve against the registered schema. The operation is refused rather than skipped: dropping it would report success for a write that never happened, or emptiness for rows that exist. Run rebase schema generate if the generated schema is older than the database.
RELATION_NOT_UNLINKABLE 400 The relation cannot be unlinked from this side. Write from the owning side.
RELATION_NOT_WRITABLE 400 The nested path is not a writable relation. See Relations.
RELATION_PIVOT_UNSUPPORTED 400 The relation declares junction columns, but this data source cannot write them. The link itself still works; only the payload on it does not. Check the driver’s capabilities.
RELATION_SOURCE_KEY_EMPTY 400 A relation write had no source key to hang the link on. Save the parent row first.
SCHEMA_DRIFT 500 A table or column the code expects does not exist in the database. rebase db push in development; redeploy on a managed tenant.
SCORE_CURSOR_UNSUPPORTED 400 startAfter was combined with orderBy: "_score". Relevance is computed per query rather than stored, so it cannot key a cursor. Page relevance with limit/offset, or order by a column.
TENANT_IMMUTABLE 400 A write would move a row from one tenant to another. A row cannot change tenant. details.violations names the field. Create the row in the other tenant and delete this one, or write with a role in tenant.bypassRoles.
TENANT_MISMATCH 400 The write names a tenant this caller does not belong to; the database would refuse it as well. Write into a tenant the caller belongs to, or authenticate as one that belongs to it.
TENANT_REQUIRED 400 The collection is tenant-scoped and the tenant cannot be inferred: the request carries none, or the caller belongs to several. Send the tenant field explicitly, or authenticate as a caller belonging to exactly one.
UNKNOWN_FIELD 400 ?fields= names a field the collection does not have. Check the spelling; the message lists the valid fields.
UNKNOWN_AGGREGATE_FIELD 400 An aggregate or groupBy names a field the collection does not have. Check the spelling; the message lists the valid fields.
UNKNOWN_FILTER_FIELD 400 The filter names a field this collection — or a relation’s target — does not have. Check the spelling; the message lists the valid fields.
UNKNOWN_FILTER_OPERATOR 400 The filter names an operator that does not exist. The message lists every operator.
UNKNOWN_ORDER_BY_FIELD 400 The sort names a field this collection does not have. Check the spelling; the message lists the valid fields.
PRECONDITION_FAILED 412 An If-Match named a version of the row that is no longer current: somebody wrote to it between the read and this write. Nothing was written. Re-read the row, re-apply the change, and send the new ETag.
UNKNOWN_RESPONSE_FIELD 400 ?fields= asks for a field the collection does not have. Check the spelling; the message lists the known fields.
UNKNOWN_VECTOR_PROPERTY 400 A vector search named a property that is not a vector on this collection. The message lists the collection’s vector properties.
UNSUPPORTED_MEDIA_TYPE 415 The Content-Type is not one this route accepts. Send the type the route documents.
UNSUPPORTED_RELATION_FILTER 400 The filter crosses a via relation, whose join path is authored one way only, so there is nothing to correlate a subquery back to. Filter from the owning side.
UNSUPPORTED_RELATION_FILTER_OPERATOR 400 The operator is not defined for that field: a relation with no column on this row is filtered by membership, and case-insensitive matching only applies to text. See the message; it lists what the field accepts.
VALIDATION_CONSTRAINT 400 A value broke a validation rule the property declares — a length, a range, a pattern, a required field. See the message; it names each violation.
VALIDATION_EXCLUDED_FIELDS 400 The body writes a column marked excludeFromApi, or access: { write: [] } — the same rule, two spellings. Those are the server’s to set: a password hash, a verification token. Unlike FIELD_NOT_WRITABLE this is the same answer for every caller, admin included. Remove the field. See Field access.
VALIDATION_INVALID_VALUE 400 A value does not fit its property type. See the message; it names the property.
VALIDATION_UNKNOWN_FIELDS 400 The body names a field the collection does not have — including an id argument on a collection keyed on something else. Check the spelling; the message lists the known fields.
WRITE_DENIED 403 A security rule or row-level-security policy refused the write. Check the collection’s securityRules.

PG_<SQLSTATE> — a constraint the database refused

Section titled “PG_<SQLSTATE> — a constraint the database refused”

A write that Postgres rejects for a reason that is the caller’s data answers with the SQLSTATE in the code: PG_23505, PG_23503, and so on. That is a family, not a list — Postgres defines hundreds of SQLSTATEs — but only two classes ever reach it, because only those two are the caller’s fault:

  • class 23, integrity constraint violation: a duplicate, a foreign key that points at nothing, a NOT NULL column left empty;
  • class 22, data exception: a value the column’s type cannot hold.

Everything else — a dropped connection, a missing column, a privilege problem — is the server’s, and stays a 500. So code.startsWith("PG_") is a safe test for “the row I sent was wrong”, and the four below are the ones a client actually meets. details.dbCode carries the same SQLSTATE for all of them, and the message names the constraint.

Code Status Means Do
PG_22P02 400 A value could not be read as the column’s type — the write-side twin of INVALID_FILTER_VALUE. Send a value of the column’s type.
PG_23502 400 A NOT NULL column was left empty. Send the field, or give the column a default.
PG_23503 400 A foreign key points at a row that does not exist. Create the target row first, or fix the id.
PG_23505 409 A unique constraint was violated. The message names the constraint. Use a different value, or update the existing row.
Code Status Means Do
INVALID_STORAGE_BUCKET 400 The bucket name is malformed. Check the name.
INVALID_STORAGE_KEY 400 The object key is malformed, or escapes its prefix. Check the key.
INVALID_TRANSFORM_OPTIONS 400 The image-transform parameters are out of range or contradictory. See Storage.
STORAGE_FILE_TOO_LARGE 413 The upload exceeds the maxSize the target property declares. Enforced on the server, not only in the browser. details carries the property, the limit and the actual size. Upload a smaller file, or raise maxSize on the property.
STORAGE_FILE_TYPE_REFUSED 400 The upload’s type is not in the property’s acceptedFiles. details carries the property, the accepted list and the content type sent. Upload an accepted type, or widen acceptedFiles.
STORAGE_NOT_CONFIGURED 503 No storage backend is configured on this server. Configure S3, GCS, or local storage.
STORAGE_SOURCE_NOT_CONFIGURED 501 The storage source is declared but has no credentials here. Set that source’s environment variables.
STORAGE_WRITE_FAILED 502 The storage backend refused or dropped the write. Check its own logs and credentials.
TRANSFORM_OVERLOADED 503 Too many image transforms are in flight. Retry; consider a CDN in front.
UNKNOWN_STORAGE_SOURCE 400 The request named a storage source (?storageId=) this project does not declare. A bucket this deployment does not serve is the same code at 404 — the store is what is missing, and details names the buckets and the sources that do exist. Both used to come back as “file not found”, identical to a key that is simply absent. Declare the source in config/resources.ts, or check GET /api/storage/sources.
Code Status Means Do
FUNCTION_NOT_FOUND 404 No function of that name is served — or one is, and its own routes do not cover the path after it. A signed-in caller is also told what is served; an anonymous one is not, because that list is an inventory of every custom endpoint. When a file of that name failed to load, the message says so: that is the difference between a typo and a broken deploy. Check the name against GET /api/functions, or the boot log for a file that did not load.
FUNCTION_TIMEOUT 504 The handler exceeded its timeout. It is still running; it cannot be cancelled from here. Give outbound calls an AbortSignal, or raise REBASE_FUNCTIONS_TIMEOUT_MS.
FUNCTIONS_UPSTREAM_UNREACHABLE 502 This process proxies functions to another one, which did not answer. Check that the functions unit is running.

These say a feature is off or unconfigured rather than that the request was wrong. Each is also reported on the corresponding /status route with a 200, so a panel can grey the feature out instead of showing an error.

Code Status Means Do
ADMIN_SURFACE_UNAVAILABLE 501 An admin-only surface was called on a server with no authentication configured, so nothing can tell an admin from a stranger. Set auth.jwtSecret, or pass an AuthAdapter.
CONTRACT_UNAVAILABLE 404 The project contract is served only when authentication is configured — it describes every table and relation. Configure auth. /meta/schema-version is always served.
DEV_MAILBOX_UNAVAILABLE 501 No development mailbox is active. Mail is captured only when SMTP_HOST is unset and NODE_ENV is not production. Unset SMTP_HOST in development, or read the real inbox.
INVALID_CHANGE 400 The proposed schema change is not well-formed. See the message.
SCHEMA_CHANGE_FAILED 400 Applying a planned schema change failed for a reason more specific codes do not cover. See the message; it is the underlying failure verbatim.
SCHEMA_CHANGE_UNAPPLICABLE 400 The change is valid and cannot be applied to the schema as it stands. See the message.
SCHEMA_EDIT_DIRTY_TREE 409 The repository has uncommitted changes, so the edit could not be applied safely. Commit or stash, then retry.
SCHEMA_EDIT_REFUSED 400 The schema editor refused the edit. See the message; it is the editor’s own refusal.
SCHEMA_EDIT_REQUIRES_A_PERSON 403 An API key or other machine principal tried to apply a schema change. Sign in as a user, or set liveSchema.allowMachineApply.
SCHEMA_EDITING_NO_COLLECTIONS_DIR 501 Live schema editing needs collectionsDir or liveSchema.repository, and this server was started with neither. Configure one.
SCHEMA_EDITING_NO_REPOSITORY 503 Planning works; there is no repository to commit the change to. Configure liveSchema.repository.
SCHEMA_EDITING_UNSUPPORTED 503 This driver cannot plan schema changes. Live editing is available on Postgres.
SCHEMA_EDITOR_BAAS_MODE 501 Collections are introspected from the database here, so there are no source files to edit. Change the schema with a migration.
SCHEMA_EDITOR_DISABLED 501 The schema editor is turned off for this server. Turn it on with schemaEditor.
SCHEMA_EDITOR_MISSING_DEPENDENCY 501 The schema editor needs ts-morph, which is not installed. pnpm add -D ts-morph@28.0.0.
SCHEMA_EDITOR_NO_COLLECTIONS_DIR 501 The server has no collectionsDir, so the editor has nothing to write to. Set collectionsDir.
SCHEMA_EDITOR_PRODUCTION 501 The editor is off under NODE_ENV=production: a deployed server’s files are rebuilt from your repository on every deploy, so an edit here would be discarded. Edit collections in development and deploy.

A route uses one of these when nothing more specific applies.

Code Status Means Do
BAD_REQUEST 400 Malformed, and nothing more specific applies. See the message.
UNAUTHORIZED 401 Not authenticated, or the credential was rejected. Sign in or refresh.
FORBIDDEN 403 Authenticated, and not allowed. Retrying as the same identity will not help.
CONFLICT 409 A conflict with existing state. See the message.
INTERNAL_ERROR 500 Something on the server failed. The message is generic on purpose. Quote the requestId; the reason is in the logs.
NOT_CONFIGURED 503 A dependency this route needs is not configured on this server. See the message.
SERVICE_UNAVAILABLE 503 A dependency was unreachable. Retry; check the logs.

pnpm verify:docs fails when a code the server can raise is missing from these tables, when a table lists a code nothing can raise, when a stated status disagrees with the source, or when a code family like PG_<SQLSTATE> has no row for a SQLSTATE callers meet. The stage is tooling/scripts/docs-verify/check-error-codes.mjs.

It checks itself first. The scan reads codes out of TypeScript rather than out of a running server, so its blind spots are silent by construction: it once could not see a code passed through a one-line wrapper, or one written after a message containing a ), and reported “every code the server can raise is documented” over a page missing seventeen of them. So the stage runs a fixture of exactly those shapes before it reads this page, and refuses to report anything if it cannot see them.