Skip to content
Talk to our solutions team

Error codes

data.svc returns 66 distinct code values on its main API surface, 21 more on the worksheet surface, and one constant code — admin — on the admin plane. This page is the complete list. Branch on code, not on the status: several codes carry a status that is chosen at response time from the underlying error.

Two JSON shapes, and one route class that is not JSON at all.

SurfaceBody
Main API (/data/rest/*, /data/anon/rest/*, /data/schema/*, /data/actions/*, /data/x/*, /data/stateflow*, /data/bulk, /data/cache, /data/contentstore/list){"errors":[{"code":"<slug>","message":"<message>","details":{...}}]}errors is always an array; code and details omitted when empty. GraphQL and the bulk read carry a path on each entry
Worksheets (/data/worksheets/*){"error":{"code":"<area>.<slug>","message":"…","details":{…},"request_id":"…"}}error is an object
Admin plane (/data/admin/data/*, /data/superadmin/*){"errors":[{"code":"admin","message":"<message>"}]} — the constant code admin, no details
Authentication rejection (any protected route)Plain text Unauthorized, Content-Type: text/plain; charset=utf-8

details is populated on these request-level codes:

Codedetails
bulk_failedoperations — the partial per-operation result array
rate_limitedprofile, limit, remaining, retry_after (seconds)
action_method_not_allowedallowed — array of verbs
method_not_allowedallowed — a single verb string, not an array
file_storage_unconfiguredentity
file_downloader_unconfiguredentity, id
search_text_not_supportedentity, audit
search_dsl_not_supportedentity, got — the Go type of the rejected filter — and audit

and on every failure the engine raises (query_failed, create_failed, update_failed, delete_failed, restore_failed, search_query_failed, the bulk read’s per-query errors and the bulk write’s op_failed), where it says where the failure came from:

KeyPresent when
hook, phaseA hook raised it — the hook’s name and the phase it ran in
tierThe access hook refused it
field, rule, code, failures[]Validation refused it — every accumulated failure, not only the first
backend, sqlstate, constraint, table, columnThe database refused the statement; the message is the database’s own sentence, never its DETAIL line
gateThe query compiler’s accessgate or safetyguard refused it
code: reference_restrict, reference, parent_entity, child_entity, countA delete was refused because child rows still reference the row — see relations
code: version_mismatch, expected, current, columnThe row’s concurrency token did not match the one the caller sent — see concurrency
code: precondition_required / precondition_unsupported / precondition_invalid / lock_requires_transactionThe request asked for something the entity or the call cannot honour; the message says what
code: lock_unsupported, backendThe backend has no such lock
code: lock_timeout / deadlock, retryable: trueA lock wait ran out, or the database broke a deadlock — retry the request

The message is the innermost cause alone. The engine’s wrapper chain (exec: execute mutation: postgres: exec: …, hook validate (BeforeValidate): …) is what the service log shows and never reaches a client.

Six codes — query_failed, create_failed, update_failed, delete_failed, restore_failed, search_query_failed — name the operation that failed, not the reason. The status carries the reason, and is derived from the engine error:

Engine errorStatus
Access rule or row-level rule denied the operation403
Row or entity not found404
A unique or foreign-key constraint refused the statement; a delete refused by a reference’s restrict rule; a lock wait that ran out or a broken deadlock409
The row’s concurrency token did not match412
Field, entity or business-constraint validation failed — including a reference to a missing parent422
The entity requires a concurrency token and the write carried none428
A precondition the entity cannot check, or a lock outside a transaction or on a backend without locks400
Anything else500

Classification is by error type first. A substring fallback covers the paths that still return untyped errors: access denied / not authorised / not authorized / forbidden → 403, not found → 404, validation / invalid / is required → 422, everything else → 500.

CodeStatusMeaningLikely cause
no_tenant403No tenant key was resolved into the request contextMissing or unroutable tenant headers, or the tenant is not configured on this deployment
missing_tenant400The :tenant path parameter is required on this superadmin routeSuperadmin URL built without the tenant segment
invalid_tenant400The :tenant value must be the bare tenant name, not a full four-part keyCustomer, product and environment come from the request, not the path
bad_path400A required path segment is empty — entity, id, action name, or entity plus actionRequest against a route shape the handler does not recognise
CodeStatusMeaningLikely cause
bad_json400The body is not valid JSON, or the bulk envelope failed to parseMalformed payload
body_read400The body could not be read off the wireTruncated request, client disconnect, oversized body
bad_params400Action parameters failed to bindUnreadable body, or invalid JSON in an action call
missing_id400An update needs a primary key and none was suppliedPUT/PATCH /data/rest/{entity} with no key in the body and no /id/{id} in the path
id_mismatch422The primary key in the body contradicts the one in the pathThe key is immutable; send it consistently or omit it
bad_expression400The ?expression= program failed to compileSyntax error in the response-shaping expression
bad_precondition400If-Match or the body’s expect is not a concurrency tokenA version that is not a number, a timestamp that is not RFC 3339
precondition_conflict400If-Match and expect are both present and disagreeSend one, or the same token in both
expression_runtime500The expression compiled but failed at evaluation, or exceeded its time ceilingReference to a field absent from the result, or an illegal operation

Expression semantics are on request flags.

CodeStatusMeaningLikely cause
entity_not_found404The entity is not in the tenant’s composed schemaTypo, or the definition was never loaded for this tenant and product
row_not_found404No row with that idWrong id, or the row is soft-deleted and therefore invisible to reads
missing_entity400No entity was supplied, as a path segment or as ?entity=Bulk import/export without ?entity=, or a schema route without the entity segment
stateflow_not_found404No stateflow registered under that nameTypo, or the flow is not in the composed schema
missing_name400The stateflow name path segment is emptyDetail route called with no name

row_not_found after a successful DELETE is expected: delete is soft by default. See traits.

CodeStatusMeaningLikely cause
query_failed400 / 403 / 404 / 409 / 422 / 500A read failed in the engineAccess denial, missing row, invalid filter, a lock outside a transaction, database error
create_failed403 / 404 / 409 / 422 / 500A create failedValidation failure, a reference to a missing parent (reference_missing), unique violation (409), access denial
update_failed400 / 403 / 404 / 409 / 412 / 422 / 428 / 500An update failedfinal / readonly / writeonce violation, validation failure, a stale concurrency token (412), rejected state transition, access denial
delete_failed400 / 403 / 404 / 409 / 412 / 422 / 428 / 500A delete failedChild rows still reference the row (409 reference_restrict), a stale concurrency token (412), access denial
restore_failed403 / 404 / 422 / 500A soft-delete restore failedMost often restore: entity %q has no soft-delete columns — the entity does not carry the trait
search_query_failed403 / 404 / 422 / 500A structured search failedUnfilterable field, bad operator, access denial
search_body_parse_failed400The search body is not decodable JSONMalformed body on POST /data/rest/{entity}/search
search_text_not_supported501Free-text search is not implementedSending query instead of a filter object; no search client is wired
search_dsl_not_supported501A filter supplied as a string is not interpretedSend filter as a JSON object

Operators and filter shapes are on query DSL; the per-rule failure messages are on validations and stateflows.

CodeStatusMeaningLikely cause
empty_envelope400The operations array is empty or absentEnvelope posted with no operations. A body carrying query or queries is a bulk read; for now it is routed there as an alternate to the QUERY verb
bulk_failed422The bulk transaction rolled backAny operation failed; details.operations carries the partial results
op_failedPer-operation code inside the response body, not a statusCarried in the failing operation’s errors[] entry, with path set to [<operation index>] and meta.status set to failed
bad_format400Unsupported import/export format?format= is not csv, json or jsonl (ndjson is accepted as an alias for jsonl)
import_failed500A bulk import failed mid-streamMalformed source rows, type coercion failure, or a database error

The whole envelope runs in one transaction: one failure rolls everything back. The 422 body still carries the per-operation array for diagnostics — treat it as a report, not as a partial success.

The bulk read (QUERY /data/rest) refuses a malformed batch with a 400 before any query runs:

CodeStatusMeaning
query_missing400Neither query nor queries, or an empty queries object
bad_queries400queries is not a JSON object of name → query
too_many_queries400More than 25 queries; details.max, details.got
bad_query_name400A query name that is not an identifier; details.name
reserved_query_name400A query named batch or queries; details.name
duplicate_query_name400A query name used twice; details.name
query_parse_failed400The single query did not parse
query_kind_refused400The single query is a mutation, not a read
query_filter_unparsed400The single query carried a where the parser dropped
query_param_missing400The single query names a $variable that was not supplied

In the batch form the last four are per-query errors[] entries on a 200, with path naming the query. See REST — bulk read.

CodeStatusMeaningLikely cause
action_not_found404No action or query hash registered under that nameTypo, or the definition is not part of the tenant’s composed schema
action_method_not_allowed405The action declares no section for this verbAn Allow header lists the accepted verbs and details.allowed repeats them
action_failed400 / 403 / 404 / 409 / 422 / 500The action executed and erroredSQL error (500), a lock the backend cannot take (400 lock_unsupported), a lock wait that ran out (409 lock_timeout), a constraint violation (409). The status and details follow the engine error, as for the CRUD codes
registry_unavailable500The query-hash registry could not be resolvedRegistry provider not wired, or tenant configuration incomplete

Parameter binding refusals surface as action_failed with messages naming the parameter: unknown parameter, missing required parameter, value not in enum.

These ride in details.code under the operation’s own code, the way validation rule codes do.

details.codeStatusMeaningWhere
reference_missing422A write points a reference at a parent row that does not exist; one entry per bad reference under details.failures[] with its fieldRelations
reference_restrict409A delete was refused because child rows still reference the row; details.reference, parent_entity, child_entity, countRelations
version_mismatch412The row carries another concurrency token; details.expected, current, columnConcurrency
precondition_required428The entity requires a token on every update and deleteConcurrency
precondition_unsupported400A token was sent for an entity with no concurrency strategyConcurrency
precondition_invalid400The token is not the kind the entity’s strategy checksConcurrency
lock_requires_transaction400A row or named lock outside a transactionConcurrency
lock_unsupported400The backend has no such lock; details.backendConcurrency
lock_timeout409The lock wait ran out; details.retryable: trueConcurrency
deadlock409The database broke a deadlock; details.retryable: trueConcurrency
CodeStatusMeaningLikely cause
endpoint_not_found404No endpoint at that path — including one that exists but is disabledA disabled endpoint deliberately answers 404, not 403, so its existence is not disclosed
endpoint_path_missing404The dispatch catch-all matched with an empty pathRequest to the script root with no endpoint segment
method_not_allowed405The endpoint declares one verb and the request used anotherAllow header and details.allowed name the expected verb
unauthenticated401The endpoint requires a principal and none was resolvedMissing or expired token. This 401 is JSON — unlike the middleware’s
forbidden403The endpoint’s auth gate rejected the principalPrincipal lacks what the endpoint requires; also returned for an unknown auth mode in the declaration
bad_body400The body is unreadable, not JSON, or over 1 MiBBody shape does not match the endpoint’s declaration
script_error500The script failed to compile, or threw at runtimeAny uncaught error in the endpoint body; also a file: script whose bytes were never inlined
script_engine_missing500The endpoint registry was built without a script engineDeployment wiring bug. An unsupported language: does not land here — it is refused at registration, so the endpoint answers endpoint_not_found

See scripted endpoints.

CodeStatusMeaningLikely cause
file_storage_unconfigured501No file-storage backend is registeredAny multipart create
file_downloader_unconfigured501No downloader is registeredAny ?download=true read
multipart_parse400The multipart body could not be parsedBad boundary or truncated upload
multipart_empty400Multipart parsed but produced no formUpload with no file and no fields
multipart_bad_payload400A file part targets an undeclared field, targets a non-file field, carries more than one file for a field, or could not be readWrong field names, or an array upload against a single-file field
download_needs_id400Download requires the /id/{id} form?download=true used on a list path
missing_field400Download requires ?field=Field name omitted
field_not_found404The field does not exist on the entityTypo
field_not_file400The field is not file-typedDownload requested against a scalar
field_empty404The file field exists but holds no reference on this rowNothing was ever uploaded
field_unexpected_shape500The stored reference is present but not a stringData written outside the API, or a storage-format change
download_failed500The byte stream could not be fetchedStorage outage, expired credentials, deleted object
contentstore_list_failed500The content-store catalogue could not be readBacking store unavailable
CodeStatusMeaningLikely cause
requires_admin403The route requires admin or superadminRole matching is case-insensitive, and superadmin also satisfies admin
requires_superadmin403The route requires superadmin strictlyPlan apply, retention and lock force-release are superadmin-gated even for tenant admins
rate_limited429The rate-limit bucket for this tenant, user and profile is exhaustedAnonymous traffic is rate-limited too

A rate_limited response carries Retry-After plus X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset.

One further code, authz_denied (403), exists in the catalogue but data.svc never returns it: it is raised only on alias-mounted CRUD routes, and data.svc mounts none. Where it does apply it fires only when an authorizer is registered — with none registered the check fails open and the request proceeds. It is not a substitute for access rules, which deny by default.

Entity-level and row-level denials never carry these codes — they arrive as the CRUD code with a 403 status. See row-level security and field protection.

CodeStatusMeaningLikely cause
export_render_failed500An export failed to renderA schema construct the exporter cannot represent, or a template failure
cache_clear_failed500Clearing the caller’s tenant schema cache failedCache backend unavailable
invalidate_failed500Superadmin cache invalidation for a target tenant failedCache backend unavailable, or the tenant does not resolve
list_tenants_failed500The superadmin tenant listing failedConfiguration client unavailable

See schema export and caching.

CodeStatusMeaningLikely cause
engine_unavailable500The per-tenant engine bundle could not be resolvedTenant configuration not loaded yet, definitions failed to compile, no datastore declared, or the database is unreachable
engine_unexpected_type500The resolved engine does not implement the interface this surface needsDeployment wiring bug

engine_unavailable is the most common boot-order failure. The wrapped message names the failing step. See schema sources and datastores.

GraphQL does not use the JSON error envelope. Field-level failures return 200 with the error in the errors array and the field set to null — including access denials, validation failures and constraint violations. Two transport-level rejections are plain text, not JSON:

ConditionStatusBody
Any verb other than POST405method not allowed
Unparseable request body400invalid JSON body

no_tenant (403) and engine_unavailable (500) are still returned in the JSON envelope, because they are raised before the handler resolves. Introspection is not served: __schema and __type validate but have no resolver, so they come back as cannot resolve field. See GraphQL.

The bulk read (QUERY /data/rest, or POST /data/rest?_method=query) follows the same convention: a per-query failure returns 200 with an errors[] entry carrying path: ["<query name>"] and data.<name> set to null, while the other queries in the batch still return their rows. Only a malformed batch is a 400 — duplicate_query_name, reserved_query_name (batch, queries) or bad_query_name (names follow GraphQL alias rules: ^[_A-Za-z][_0-9A-Za-z]*$).

The worksheet surface has its own catalogue and its own envelope. Statuses come from a fixed table, not from message classification.

CodeStatusMeaning
worksheet.not_found404No worksheet with that id, or it has been evicted
worksheet.expired410The TTL passed; the worksheet is inside the grace window
worksheet.cpet_violation403The worksheet belongs to a different tenant
worksheet.invalid_ttl400Requested TTL is outside the allowed range
worksheet.memory_exceeded507The worksheet exceeded its memory budget
file.not_found404No staged file with that id
file.uri_unreachable502A file referenced by URI could not be fetched
file.uri_access_denied403The remote store refused the fetch
file.format_invalid400Format unrecognised or unparseable; also when the multipart form has no file part
file.size_exceeded413Over the file-size, file-count, row or column limit
file.encoding_error400The character encoding could not be decoded
file.sheet_selection_required400A multi-sheet workbook needs an explicit sheet
table.name_conflict409A table with that name already exists
table.not_found404No such table in the worksheet
table.union_incompatible400Column sets or types do not line up
column.not_found404No such column on the table
query.syntax_error400The SQL failed to parse
query.timeout504The query exceeded its time budget
query.execution_error400The query parsed but failed at execution
invalid_request400Invalid JSON body on a worksheet call
rate_limit_exceeded429Worksheet rate limit; sends Retry-After: 60

Limits and the values behind each code are on worksheet limits.

Every handler under /data/admin/data/* and /data/superadmin/* returns {"errors":[{"code":"admin","message":"<message>"}]} — the same errors[] array as the main API, but the code is always the constant admin and there is no details. Only the auth gates in front of them use per-condition codes, so requires_admin, requires_superadmin, missing_tenant and invalid_tenant do appear on these routes — but nothing the handler itself raises carries anything other than admin. Branch on the message.

StatusMessage shapes
400plan_id query parameter required, plan id missing in path, missing :resource segment, claimer_id is required, confirmations: empty, entity path segment missing, invalid confirm_max_risk %q (use low / medium / high), body parse failures
401Always missing tenant identity
403tenant key missing from request context
404plan %q not found, entity <name> not found in schema
409Applying a plan that was refused at generation time. The plan is stored so the id resolves, but it can never be applied — regenerate it
500Wrapped operational errors; engine resolution failures are prefixed engine_unavailable: inside the message string, not carried as a code

Plan apply is the exception to the table: it answers 207 whenever the result is anything other than fully applied, and carries a partial result object rather than an error body — {plan_id, status, steps_applied, steps_failed, steps_skipped, failed_step?, halted_step?, error?}. It falls back to 500 with the plain {"errors":[…]} shape only when the apply produced no result at all. See migrations.

Three families of identifier look like error codes and are not.

Per-rule validation codes. REQUIRED, READONLY, FINAL, WRITE_ONCE, MIN, MAX, MIN_LENGTH, MAX_LENGTH, LENGTH, PATTERN, the format-checker codes (EMAIL, URL, URL_PROTOCOL, UUID, ULID, CUID, NANOID, SEMVER, PHONE, IP, HOSTNAME, MACADDRESS, PORT, JSON, DATAURI, CREDITCARD, LATITUDE, LONGITUDE, MIMETYPE, FILESIZE_MIN, FILESIZE_MAX, CONTAINS, NOTCONTAINS, STARTSWITH, ENDSWITH, NOTIN, DIVISIBLEBY, BEFORE, AFTER, PAST, FUTURE, PATTERN_UNKNOWN, VALIDATION_UNKNOWN), and the defaults ENTITY_VALIDATE, ENTITY_VALIDATE_ERR, BUSINESS_CONSTRAINT, BUSINESS_CONSTRAINT_ERR, POINTCUT_ERR, POINTCUT_ABORT are the rule’s identity, not a wire code: the response carries the CRUD code, the rule’s message, and the rule’s own code under details.code (and per failure under details.failures[]).

Schema-compile codes. ENTITY_NOT_FOUND, FIELD_NOT_FOUND, TYPE_MISMATCH, INVALID_ENUM_VALUE, RELATION_NOT_FOUND, FIELD_NOT_QUERYABLE, UNSUPPORTED_OPERATION, DUPLICATE_ENTITY, DUPLICATE_FIELD, SHADOW_PRODUCT_FIELD and INVALID_TRANSITION are declared constants with no emitter — nothing in the tree raises one. The schema front-end the deployable service uses reports load failures as plain wrapped errors naming the failing step, not as coded ones. A schema that fails to load never serves traffic, so a client sees nothing from this family either way. Do not branch on these names.

The legacy data-* catalogue. A large set of codes spelled data-<something> exists in the tree and is dead: no handler emits any of them. Do not build client branching on that list.