Error code reference
Every error response is a {"errors": [...]} envelope (SPEC-DATA-0006); each entry carries one of the codes below, plus the domain naming which subsystem produced it. This page is generated from the api_types::code registry – it cannot drift from what the server actually emits.
| Code | HTTP status | Description |
|---|---|---|
ABSTRACT_OBJECT |
422 | A record write targeted an object declared abstract. |
AMBIGUOUS_REFERENCE |
422 | A field reference in a query is ambiguous across joined objects. |
COMPOSITE_NOT_SCALAR |
422 | A bare composite field reference is used where only a scalar is valid. |
COMPOSITE_ROOT_PATH_CONFLICT |
422 | A composite field’s root path conflicts with another selected field or path. |
DECODE_ERROR |
500 | A fetched column could not be decoded as the expected Rust type. |
DISCRIMINATOR_PAIR_REQUIRED |
422 | A polymorphic lookup’s discriminator and id must both be present or both absent. |
DUPLICATE_ELEMENT |
422 | A list-typed value contains a duplicate element where uniqueness is required. |
EMPTY_LIST |
422 | A list-typed value is empty where at least one element is required. |
FIELD_KEY_MISMATCH |
422 | A request body’s field key does not match the URL path’s field key segment. |
FIELD_NOT_IN_GROUP_BY |
422 | A SELECT field is not present in the query’s GROUP BY clause. |
FIELD_NOT_UNIQUE |
400 | An upsert targeted a field not marked as a unique natural key. |
FIELD_WITHOUT_GROUP_BY |
422 | A field is referenced without the GROUP BY clause the query requires. |
FILTER_PREDICATE_VIOLATED |
422 | A row fails a declared filter predicate (e.g. a cart/junction row-level constraint). |
FK_NOT_FOUND |
422 | A lookup value references a record id that does not exist in the target object. |
FORBIDDEN |
403 | The principal is not authorized to perform the requested action. |
INCOMPLETE_DATE_RANGE |
422 | A date-range value is missing its start or end bound. |
INCOMPLETE_TIMESTAMP_RANGE |
422 | A timestamp-range value is missing its start or end bound. |
INCONSISTENT_TO_TYPE |
422 | A polymorphic lookup’s declared type does not match its resolved target’s type. |
INTERNAL |
500 | An unexpected internal error occurred; detail is scrubbed server-side. |
INVALID_AGGREGATE |
422 | A query used an aggregate function outside a valid position or on an invalid type. |
INVALID_DATERANGE_SHAPE |
422 | A date-range JSON value does not match the expected {start, end} shape. |
INVALID_DATE_RANGE |
422 | A date-range value has its start after its end. |
INVALID_FIELD_REF |
422 | A field reference in a query does not resolve to a real field. |
INVALID_FORMAT |
422 | A field value does not match its declared format constraint. |
INVALID_HAVING |
422 | A HAVING clause references a field or expression it may not. |
INVALID_ID |
400 | A record id path parameter does not parse as a UUID. |
INVALID_KEY |
422 | A metadata module/object/field key path parameter failed charset or traversal validation. |
INVALID_MONEY_AMOUNT |
422 | A Money value’s amount is not a valid decimal. |
INVALID_MONEY_CURRENCY |
422 | A Money value’s currency code is not a recognized ISO 4217 code. |
INVALID_MONEY_SHAPE |
422 | A Money JSON value does not match the expected {amount, currency} shape. |
INVALID_NARROWING_TARGET |
422 | A type-narrowing field-type change targets an incompatible type. |
INVALID_QUERY |
400 | A HOQS query failed to parse, validate, or execute. |
INVALID_RELATIONSHIP |
422 | A relationship reference does not resolve to a declared relationship. |
INVALID_SCHEMA |
422 | A query or write references a module/object schema that does not exist. |
INVALID_TARGET_TYPE |
422 | A field-type change’s target type is not a valid narrowing or widening. |
INVALID_TIMESTAMPRANGE_SHAPE |
422 | A timestamp-range JSON value does not match the expected {start, end} shape. |
INVALID_TIMESTAMP_RANGE |
422 | A timestamp-range value has its start after its end. |
INVALID_TRANSITION |
422 | A relationship strategy transition is not supported. |
INVALID_TRAVERSAL |
422 | A query traversal (dot-path) is malformed or crosses an unsupported boundary. |
INVALID_TYPE |
422 | A field value’s JSON type is incompatible with the field’s declared type. |
JSONB_PATH_NOT_IN_SCHEMA |
422 | A JSONB path reference does not resolve against the object’s declared schema. |
JSONB_PATH_QUERY_NOT_GROUPABLE |
422 | A JSONB path query is used in a position that requires a groupable expression. |
JSONB_PATH_QUERY_NOT_IN_INCLUDE |
422 | A JSONB path query is used in a position that requires it be in INCLUDE. |
JSONB_PATH_QUERY_NOT_ORDERABLE |
422 | A JSONB path query is used in a position that requires an orderable expression. |
JSONB_ROOT_PATH_CONFLICT |
422 | A JSONB root path conflicts with another selected field or path. |
JSON_SCHEMA_VIOLATION |
422 | A JSON value fails the field’s declared JSON Schema. |
LIMIT_EXCEEDED |
422 | A query result set or list value exceeds the platform’s configured limit. |
MAX_ITEMS_EXCEEDED |
422 | A list-typed value exceeds the field’s declared maximum item count. |
MIXED_BETWEEN_TYPES |
422 | A BETWEEN clause’s two operands have incompatible types. |
MIXED_IN_LIST_TYPES |
422 | An IN list’s elements have inconsistent types. |
NOT_A_LOOKUP |
422 | A lookup/relationship operation was attempted on a field that is not a lookup. |
NULL_COMPARISON |
422 | A comparison operator was used against a null value where it is not valid. |
NULL_ELEMENT |
422 | A list-typed value contains a null element where nulls are not permitted. |
OBJECT_NOT_FOUND |
404 | The requested module/object combination does not exist in metadata. |
OUT_OF_RANGE |
422 | A numeric or date value falls outside the field’s declared valid range. |
POLYMORPHIC_NARROWING_REQUIRED |
422 | A polymorphic lookup write is missing the type narrowing it requires. |
READ_ONLY |
422 | A write attempted to set a formula/rollup (read-only) field. |
RECORD_NOT_FOUND |
404 | No record with the given id exists (or is not visible to the principal). |
REF_MODULE_NOT_READY |
503 | A Money field referenced a currency the reference module has not loaded yet. |
REQUIRED |
422 | A required field was missing from the record payload. |
RULE_FAILED |
422 | A declared ValidationRule (metadata::ValidationRule) evaluated false or null. Not an intra-doc link: api-types has no dependency on metadata (R1 – this crate stays free of domain-crate deps). |
SEED_RECORD_IMMUTABLE |
403 | A write targeted a seed-declared record outside the seed’s own access tier. |
STALE_CACHED_PLAN |
503 | A cached prepared statement no longer matches the schema it was planned against. |
SYSTEM_FIELD_ALIAS_CONFLICT |
422 | A query alias collides with a system field name. |
TYPE_MISMATCH |
422 | A comparison or bind mismatched the operand’s declared type. |
UNIQUE_VIOLATION |
409 | A PostgreSQL unique-constraint violation (23505) surfaced from a write. |
UNKNOWN_FIELD |
422 | The request referenced a field key not defined on the object. |
VALIDATION_ERROR |
422 | A record write failed field- or object-level validation. |
Metadata
Section titled “Metadata”| Code | HTTP status | Description |
|---|---|---|
INVALID_BRANCH_REF |
400 | A {branchId} path segment failed BranchRef::parse’s validation. |
METADATA_FIELD_NOT_FOUND |
404 | A PUT targeted a field that does not exist on the branch. |
METADATA_INVALID_KEY |
400 | A metadata filename resolved outside the metadata directory. |
METADATA_MODULE_NOT_FOUND |
404 | A PUT targeted a module that does not exist on the branch. |
METADATA_MODULE_NOT_LOADED |
404 | The ontology’s {moduleKey} path segment names a module that is not present in the current metadata snapshot. |
METADATA_OBJECT_NOT_FOUND |
404 | A PUT targeted an object that does not exist on the branch. |
METADATA_RESOURCE_EXISTS |
409 | A POST targeted a resource that already exists on the branch. |
METADATA_SCREEN_NOT_FOUND |
404 | A PUT targeted a screen that does not exist on the branch. |
METADATA_SNAPSHOT_UNAVAILABLE |
503 | The ontology was requested before any apply has ever succeeded. |
METADATA_WRITE_CONFLICT |
409 | A concurrent write reached the target branch first. |
| Code | HTTP status | Description |
|---|---|---|
APPLY_BRANCH_NOT_ALLOWED |
400 | POST /apply was sent a branch field, which only POST /plan and POST /metadata@{branchId}/publish accept. |
APPLY_DDL_FAILED |
500 | A DDL statement failed to execute against PostgreSQL. |
APPLY_DIAGNOSTICS |
422 | One or more metadata diagnostics reached Severity::Error or Blocking. |
APPLY_INTERNAL |
500 | A server-side fault with no author-facing cause. |
APPLY_LOAD_FAILED |
422 | Metadata failed to load or compile, or a plan/apply gate rejected the author’s input. |
APPLY_SCREENS_NEED_HFX |
422 | The metadata carries screen files (screens/<moduleKey>/<screenKey>.hfx), and this server was built without HFX support, so it cannot check or serve them. Plan reports the code and apply refuses the whole source: nothing executes and the published metadata does not change. Each screen file is reported on its own entry, targeting its path. Remove the screens/ directory from the source, or run a server built with HFX support. If the same source also fails for other reasons, this code is returned and the full compile message follows in an APPLY_LOAD_FAILED entry. |
APPLY_SURFACE_ACTIONS_NEED_APPROVAL |
422 | A surface in a module manifest lists an action that needs an install approval: admin.metadata.apply or an admin.iam.* action. Plan and apply refuse it for now, because a module surface holding one would act with the signed-in user’s apply or identity-management authority. Each flagged entry is reported with the surface it belongs to; remove it from the manifest and apply again. |
APPLY_SURFACE_NEEDS_GRANTABLE_ACTION |
422 | A module surface declares no grantable action: its actions is empty, or lists only actions that cannot be granted (record and flux actions). A surface is listed to a person only when they hold one of its grantable actions, so such a surface could never be listed to anyone. Plan and apply refuse it (owner ruling O1, #917); each flagged surface is reported by canonical name. Declare the narrowest grantable action the surface needs, such as runtime.screen.render for a surface that only renders screens (every declared action widens its token’s ceiling), and apply again. The platform’s own surfaces are exempt. |
APPLY_UNKNOWN_SURFACE_ACTIONS |
422 | A surface in a module manifest lists an actions entry that is not a known action. Each unknown entry is reported with the surface it belongs to; correct the manifest and apply again. |
APPLY_UNRECOGNISED_TYPE_DRIFT |
422 | A column’s current type and the type your metadata asks for differ in a way the server cannot explain, so it refused the whole apply and changed nothing. This is almost always a bug in the server’s type comparison, not something you did. First review the two types in the message against your metadata and the live column. If they should match, report it at https://github.com/henceforth-labs/henceforth/issues/1094 with the two types from the message. If one apply has both an unrecognised type drift and an unsafe type narrowing, this code is returned and the message lists both. Escape hatch: applying with destructive: true (--destructive on the CLI) rewrites the column to the type your metadata asks for. The server could not explain the difference, so it cannot tell you what the rewrite does to existing values: data in that column may be converted lossily or lost. The column’s current values are vaulted first unless you also pass vault: false. Use the escape hatch only after confirming the column holds nothing you need. |
| Code | HTTP status | Description |
|---|---|---|
BRANCH_NOT_FOUND |
404 | A named branch was looked up but does not exist. |
INTERNAL |
500 | An error from the underlying libgit2 library. Also: An I/O error during file operations in the worktree. |
INVALID_BRANCH_REF |
400 | A caller-supplied branch name failed charset, length, or path-traversal validation. |
MAIN_IS_NOT_A_CART_BRANCH |
400 | main named where a cart branch was expected. |
METADATA_NOT_IN_CHECKOUT |
409 | The server’s metadata directory is not inside a git working tree. |
WORKTREE_DIRTY |
409 | The metadata working tree has uncommitted edits. |
WORKTREE_LOCKED |
409 | An external process holds a git worktree lock. |
| Code | HTTP status | Description |
|---|---|---|
CHALLENGE_EXPIRED |
401 | The SSH challenge nonce has passed its TTL. |
CLAIM_NAME_REQUIRED |
400 | The signed claim payload’s name was empty. |
CODE_EXPIRED |
401 | The one-time session code (exchange or redeem) has passed its TTL, or the token that minted it has expired. |
CONFIRMATION_REQUIRED |
409 | The session code is live, but this browser has not proven it is already signed in as the code’s principal. Nothing was consumed or issued. The page asks the user, then retries with confirm: true. |
CROSS_SITE_REQUEST |
403 | A state-changing auth request carried Sec-Fetch-Site other than same-origin: a browser sent it on behalf of another site. |
EXPIRED_TOKEN |
401 | The JWT exp claim is in the past. |
FORBIDDEN |
403 | The principal is authenticated but not authorized for the action. |
INSTANCE_ALREADY_CLAIMED |
409 | A claim signature arrived at an instance that is already claimed (the one-way latch is set, including when this very request lost the claim race). The client resolves it by logging in. |
INSTANCE_UNCLAIMED |
503 | The instance is unclaimed and the requested route is not one of the handful served while unclaimed (health, status, the SSH challenge and verify). See server::auth::claim::allowed_while_unclaimed. |
INTERNAL |
500 | An internal error (database, crypto, etc.) occurred; detail is scrubbed. |
INVALID_CHALLENGE |
401 | POST /auth/verify’s challengeId does not resolve to a live challenge. |
INVALID_CODE |
401 | The one-time session code (exchange or redeem) does not resolve to a live code. |
INVALID_CREDENTIALS |
401 | Login credentials (username/password) are incorrect. |
INVALID_PRINCIPAL_NAME |
400 | The signed claim payload’s name failed PG-identifier charset or length rules (server::auth::principal_name::validate_principal_name). |
INVALID_REQUEST |
400 | The request’s query string could not be read: a repeated or missing parameter (returnTo twice, no code). Answered after the per-IP gate and counted as a failed attempt, so it cannot be sent for free. |
INVALID_RETURN_TO |
400 | The session exchange’s or redeem’s returnTo is not the name of a mounted surface. |
INVALID_SIGNATURE |
401 | POST /auth/verify’s signature was refused: it does not verify over the received payload, the algorithm or namespace is not accepted, or the signed payload is malformed or does not answer this challenge (wrong nonce, or a purpose that disagrees with the namespace). The message names which, without distinguishing a wrong key from a wrong message. |
INVALID_TOKEN |
401 | JWT signature verification failed or the token is malformed. |
MISSING_TOKEN |
401 | No Authorization header or Bearer token was present. |
ORIGIN_MISMATCH |
401 | The signed challenge payload names an origin this instance does not accept: not one of its --public-origin values, and not a loopback origin on the bound port from a loopback peer. Answered after the signature verified, so it only ever echoes an origin the key holder signed. See server::public_origin::PublicOrigins::decide. |
PASSWORD_REQUIRED |
400 | POST /auth/set-password’s password field was empty. |
PASSWORD_TOO_SHORT |
400 | POST /auth/set-password’s password field is under the minimum length. |
PRINCIPAL_NAME_TAKEN |
409 | The signed claim payload’s name collides with an existing PG role (on this database or elsewhere on the cluster — roles are cluster-wide). |
RATE_LIMITED |
429 | The per-IP or per-principal auth throttle denied this attempt. |
SECRETS_DECRYPTION_FAILED |
500 | AES-256-GCM decryption of a stored secret failed. |
SSH_KEY_ALREADY_ENROLLED |
409 | A claim could not store the declared key because that key is already enrolled on another principal (a credential left by the retired --dev-cert path, for example): one key belongs to one principal (#1020 R5). Distinct from InstanceAlreadyClaimed: the instance is still unclaimed and a client must not fall back to a login. Names no principal. |
SURFACE_SCOPE_EXCEEDED |
403 | The token was issued to a surface whose declared actions do not include the operation. The message names the surface and the action(s) it would have to declare, and nothing about the user’s own grants. Answered before any record or metadata lookup, so it is the same for a resource that exists and one that does not. Also the answer when a surface’s token tries to mint a credential (a session code, a principal, a password reset or an attached credential): no surface may, whatever it declares. |
THROTTLE_UNAVAILABLE |
503 | The per-IP throttle store could not be reached, so the session exchange or redeem failed closed. Nothing was consumed or issued; retry after the Retry-After interval. |
UNKNOWN_KEY |
401 | POST /auth/verify named a key the server has no use for: a login whose publicKeyFingerprint matches no stored credential, or a claim whose signing key (or the key named inside the signed payload) is not the declared claim key, or when no claim key was declared. |
UNKNOWN_PRINCIPAL |
401 | The token’s sub claim does not resolve to a known principal. |
UNKNOWN_SURFACE |
401 | The token was issued to a surface this server no longer serves: its client_id names no embedded or declared surface. Raised at authentication, before the request’s principal is resolved. It follows an uninstall or a rename of the surface key, never a change of mount path. The client signs in again. |
| Code | HTTP status | Description |
|---|---|---|
CREDENTIAL_NOT_FOUND |
404 | The {id} path parameter does not resolve to a known credential. |
PRINCIPAL_NAME_INVALID_FORMAT |
422 | A principal name failed PG-identifier charset or length rules. |
PRINCIPAL_NAME_REQUIRED |
422 | A principal name was empty or missing from the request body. |
PRINCIPAL_NOT_FOUND |
404 | The {id} path parameter does not resolve to a known principal. |
SELF_DELETION_FORBIDDEN |
422 | A principal attempted to delete their own account. |
SSH_KEY_ALREADY_ENROLLED |
409 | The SSH public key is already enrolled, on this principal or another: one key belongs to at most one principal, once (#1020 R5). Names no principal, so the response cannot be used to find out who holds a key. |
UNKNOWN_CREDENTIAL_TYPE |
422 | A credential-type value is not one this server recognizes. |
| Code | HTTP status | Description |
|---|---|---|
HFX_EMIT |
422 | Valid-per-frontend HFX failed to lower to JavaScript, or raw TSX failed. |
HFX_PARSE |
422 | The HFX frontend rejected the source (lex, parse, validate, resolve). |
Module
Section titled “Module”| Code | HTTP status | Description |
|---|---|---|
MODULE_PACKAGE_INCOMPATIBLE |
422 | The package targets an API contract version this server does not support, or was built against a newer platform release than this server runs. The message names both values. |
MODULE_PACKAGE_INVALID |
422 | The package is not a well-formed .hfmod: not a gzip-compressed tar, an entry that is not a regular file, an unsafe or colliding path, a file missing from or absent in the descriptor’s files, a digest that does not match, or a descriptor or module manifest that is malformed or disagrees with the other. The message names the entry and the rule. |
MODULE_PACKAGE_TOO_LARGE |
413 | The package exceeds a size cap: one file, the whole package, or the number of files. The message names the cap. |
MODULE_SIGNATURE_INVALID |
422 | A first-party package (vendor henceforth or hf, in any casing) is unsigned, signed by a key outside the platform’s first-party key list, or carries a signature that does not verify; or a third-party package carries a signature that does not verify. The entry’s target names which check failed: missing, unlistedKey, invalid or rejectedAlgorithm. |
| Code | HTTP status | Description |
|---|---|---|
API_VERSION_REQUIRED |
404 | The request path matches a route this server served before the /v1 prefix landed (#941); retry it under the versioned prefix. |
APPLY_SETTLING |
503 | A schema apply overlapped this request: it committed while the request was in flight, or just before it, and the server had not finished publishing the new metadata. The request had no effect and is safe to retry, writes included; it is never answered for a write whose commit outcome is unknown. Retry after the Retry-After interval. |
CONFIRMATION_REQUIRED |
428 | The operation changes the instance and asks for confirmation on a terminal; without a terminal, or with --json, it runs only when --yes is given. Nothing was sent. Answered by hf, never by the server. |
FORBIDDEN |
403 | The principal is not authorized to perform the requested action. |
INSTANCE_STANDBY |
503 | Another server process holds this database’s instance lease and is the active instance; this process is a standby and serves only the liveness and readiness probes. Not answered with Retry-After: the standby serves nothing until the active instance exits, which no short interval predicts. Route traffic to the instance whose GET /ready answers 200. |
INSTANCE_STARTING |
503 | This process holds the instance lease but has not finished starting (bootstrap, startup apply, engine start). Safe to retry after the Retry-After interval; GET /ready turns 200 when startup ends. |
INTERNAL |
500 | An unexpected internal error occurred; detail is scrubbed server-side. |
INVALID_QUERY_PARAMETER |
400 | A query-string parameter failed to parse or did not match the expected shape. |
INVALID_REQUEST_BODY |
422 | A JSON request body failed to parse or did not match the expected shape. |
NOT_FOUND |
404 | The requested resource does not exist. |
SERVICE_UNAVAILABLE |
503 | The server is temporarily unable to handle the request. |