Errors and corrections
Every failure is JSON with one shape, and the class tells you whose fault it is: the envelope, the key, the spec, or the model. Successful responses may also carry a corrections array when a misspelled field was silently matched. This page lists both so your application can act on them.
The envelope
{"error": {"class": "FieldNotFound", "message": "No field named 'revenu' in this model. Did you mean: Revenue, Net Paid?"}}
class is stable and meant for code; message is for people and may name fields from the model.
Classes and statuses
| class | status | when |
|---|---|---|
Unauthorized | 401 | no key, or a key that is not valid |
Forbidden | 403 | the key may not do this on this project or branch |
NotFound | 404 | no deployment for the project and branch |
Invalid | 400 | neither spec nor expr in the body |
Shorthand | 400 | the expr did not parse |
ContextRequired | 400 | the branch has policies and the request has no context |
DeployError | 400 | deploy or validate failed (not a query route) |
InvalidSpec | 422 | the spec’s shape is wrong |
InvalidValue | 422 | the spec is well-formed but a value fails validation; message prefixed Validation failed: |
FieldNotFound | 422 | a field reference resolved to nothing |
FieldAmbiguous | 422 | a name is both a dimension and a measure |
Unresolvable | 422 | the planner cannot build the query from the model |
SegmentDatasource | 422 | a segment keyed in another datasource; an internal retry signal, normally not surfaced |
SecurityPolicy | 422 | a policy’s context dimension is unreachable from the query |
Unimplemented | 422 | a feature the planner does not plan |
Dialect | 500 | the base dialect failed to load |
Accounts | 500 | the account store failed |
400 means fix the request envelope; 401/403 means fix the key or its grants; 422 means fix the spec or the model; 5xx is ours.
Messages you will meet
Envelope and auth
| class | message |
|---|---|
Unauthorized | an API key is required: Authorization: Bearer <key> |
Unauthorized | this API key is not valid |
Forbidden | query key <name> is not granted <uid>/<branch> |
Forbidden | <email> has no access to <uid> |
Forbidden | <email> has <level> access to <uid>; this needs <level> |
Forbidden | query keys are read only; deploy with your personal key |
NotFound | no deployment for project <uid> branch <branch> |
Invalid | give a spec or an expr |
ContextRequired | this branch has security policies; a context is required to plan |
Field references
| class | message |
|---|---|
FieldNotFound | No field named 'x' in this model. optionally followed by Did you mean: A, B, C? |
FieldAmbiguous | Field 'x' is ambiguous — more than one field answers to it. Use x@d or x@m to pick the dimension or the measure. |
Spec shape (InvalidSpec)
| message | cause |
|---|---|
malformed spec: ... | unknown key, wrong JSON type, or unparseable JSON anywhere in the spec |
projection N needs a field | a non-calculation projection without field |
'x' is not an order_by (asc, desc) | bad order_by |
Unknown decorator type: X | bad decorator type |
unknown attribute 'x' on the <type> decorator of <owner> | attribute not in the decorator vocabulary |
'X' is not a predicate (filter on F) | bad predicate name |
filters must be an array or a logic tree, got ... | bad filters type |
a filter node needs a field or an and/or key, got X | bad tree node |
Filters here are a flat AND list — each entry must name a field; and:/or: groups are not supported in this position. ... | an and/or group inside an array |
Segment 'X' has no definition — ..., Segment 'X' needs at least one key dimension — ..., Unknown segment mode 'x' — use include or exclude, apply_to: no measure projection matches 'x' ..., apply_to: 'x' matches multiple projections — ..., join applies only to expanding segments — ... | segment rules, listed in full on Segments |
Validation (InvalidValue)
All prefixed Validation failed: .
| message | cause |
|---|---|
Alias can't be blank | calculation without an alias |
Sql can't be blank (calculation 'X') | calculation without a formula |
Sql columns like x,y are not permitted in calculations. | bare identifiers in a formula; use [Name]@m / [Name]@d |
Sql could not find a projection with alias X. If you meant to references a measure or dimension used @m or @d to clarify: [My Measure]@m | [Alias] that matches no projection |
Axis the tooltip holds measures only | axis: tip on a dimension |
X can only be applied to dimensions | truncate, extract or customize on a measure |
only date/datetime types can be date truncated | truncate or extract on a non-date dimension |
Grain must be set, Extract must be set, Mode must be moving or running, Transform invalid transform type | decorator missing its required attribute |
Predicate <Name> predicate is not supported for <Field>. | predicate not allowed on that field type |
Predicate top n can only be applied to a dimension field | top_n on a measure |
Filter value can't be blank (<predicate> on <field>) | missing filter value |
Filter value is not a number, Filter value values must be numeric | non-numeric value on a numeric field |
A bad calculation data_type (outside string, integer, decimal, date, date_time, boolean, bigint, binary) and a window size, offset or buckets below 1 are rejected in the same class.
Planner
| class | message |
|---|---|
Unresolvable | At least one projection required |
Unresolvable | No universe can resolve the query within datasource D. The projected fields cannot be joined into one query from the model; see Universe formation. |
Unresolvable | Could not find path for X |
Unresolvable | cannot parse date ... |
Unresolvable | Calculation X references itself through Y |
Unresolvable | Could not resolve segment S: ... |
SecurityPolicy | Security policy context dimension '<Name>' is not reachable in the universe for this query. Cannot safely enforce security policy. |
Unimplemented | not implemented: custom predicate |
Unimplemented | calculations over rule-bearing measures, inclusion sub-plan outside the node's universe |
Shorthand (Shorthand, 400)
| message | cause |
|---|---|
nothing to plan | empty expr |
top needs a count, as in `category top 10 by revenue` | top without an integer |
'...': unknown function f(); see .help | unknown decorator function |
The corrections array
A field reference that matches nothing exactly is scored against every field of the wanted kind by trigram overlap of the normalized text (lower-cased, runs of non-alphanumerics collapsed to one space) against the field’s name, uid and synonyms. Three constants decide what happens:
| constant | value | effect |
|---|---|---|
MIN_LEN | 4 | references shorter than four characters are never corrected |
FLOOR | 0.5 | the best candidate must score at least this |
MARGIN | 0.2 | and lead the runner-up by at least this |
When both hold the candidate is used silently and the response reports it:
{"sql": "...", "datasource": "Warehouse", "datasource_uid": "warehouse", "adapter": "postgres",
"corrections": [{"term": "departmnt", "field_uid": "department", "field_name": "Department", "score": 0.8}]}
| key | meaning |
|---|---|
term | what the request said |
field_uid, field_name | what was used |
score | the overlap, rounded to two decimals |
corrections is omitted when empty. When the floor or the margin is not met, the request fails with FieldNotFound and up to three candidates scoring at least 0.25 in Did you mean: A, B, C?.
zsql prints corrections to stderr before the SQL, one per line:
departmnt → Department (~0.8)
Treat a correction as a warning in an interactive tool (show the user what was substituted) and as an error in a pipeline, where a silently substituted field is a bug waiting to be noticed.
Handling errors in your app
async function plan(body) {
const res = await fetch(`https://app.0sql.io/projects/tpcds/branches/main/sql`, {
method: "POST",
headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
const json = await res.json();
if (!res.ok) {
const { class: cls, message } = json.error;
switch (cls) {
case "FieldNotFound":
case "FieldAmbiguous":
case "InvalidSpec":
case "InvalidValue":
case "Shorthand":
throw new UserFixable(message); // show it; the request can be edited
case "Unresolvable":
case "SecurityPolicy":
case "Unimplemented":
throw new ModelProblem(message); // the model or the policy needs a change
case "ContextRequired":
case "Invalid":
case "Unauthorized":
case "Forbidden":
case "NotFound":
throw new IntegrationBug(cls, message); // your code built the request or key wrong
default:
throw new Error(`${cls}: ${message}`); // 5xx: retry later, then report it
}
}
if (json.corrections?.length) {
log.warn("0sql corrected fields", json.corrections);
}
return json; // { sql, datasource, datasource_uid, adapter, ... }
}
Three habits that pay off:
- Branch on
class, never onmessage. Messages name fields and may change wording. - Surface
FieldNotFoundwith itsDid you meancandidates andFieldAmbiguouswith its@d/@mhint directly to whoever is building the query; they are written to be shown. - Log
correctionswith the request. A rename in the model can turn an exact match into a fuzzy one without any error.