Skip to main content
This page is for agents and the people who build them. Query language (NQL1) defines what the text means; this page assumes it.
The dashboard, public HTTP API and Python SDK do not accept or evaluate NQL1 queries, and do not list query fields. Capabilities and limits records this boundary.
An agent can use this page to write, check, store and explain queries. To check a query, validate its tree against the downloadable schemas, then apply the remaining rules on this page and in the language reference. What validation covers lists those rules in order.

Downloads

The schemas check structure only, and they leave out some of the structural rules Nitsor applies, so a document that passes them can still be refused. What validation covers lists the rules they omit. The example selectors and values illustrate syntax and do not establish that a field exists, is visible to you, or can be evaluated.

One query, three forms

The same query as a person states it, as NQL1 text, and as the tree an agent exchanges. It illustrates the language and cannot run: Nitsor does not evaluate relation blocks, and the example keeps one because it is the clearest way to show the relation node. Capabilities and limits records this.
Scans with at least one accepted Crack label, whose Source was registered from the start of the day a week ago through the end of today, in the envelope’s time zone.A week ago is exactly 7 × 24 hours before asOf. That usually gives the seven days before today and today, but a clock change during the week can move the first day by one. Dates, durations and time zones has the rule.
Text and tree convert both ways without loss. Store and compare canonical text or the tree, never a spelling a person typed.

The query tree

A query is { "ql": "nql1", "expression": ... }. expression is null for the empty query, which means the whole authorised, selected scope, or one node. Every object is strict: a property the contract does not define is refused. A field is an array of strings. The first is an identifier that is not a keyword or has; each later one is any non-empty string. meta["machine.name"] is ["meta", "machine.name"], and meta.machine.name is ["meta", "machine", "name"].

Scalars

Units are m, h, d and w.

Numbers are strings

A number scalar carries its value as decimal text, so no value loses precision in a JSON parser that reads numbers as floating point. The text must already be canonical: no trailing fractional zeros, no leading zeros, no -0, no exponent. "1.5" is accepted and "1.50" is refused with invalid_ast. Two integers in the tree are JSON numbers: a relative date’s offset.amount, which is never 0 (write "offset": null instead), and a duration’s amount, which is never negative.
query.json
This is source.spacing.z:<=0.5 AND task.waited:>48h.

Only canonical trees are accepted

A tree is accepted only if it is exactly the tree Nitsor’s NQL1 parser produces from its own canonical text. In practice: A data refusal carries a path: the keys and indexes from the top of the document to the offending value, such as ["expression", "children", 8].

What validation covers

Schema validation is the first structural check, not the last. A query passes through these layers in order, and each one refuses on its own terms: A document that passes the downloadable schemas meets the constraints written in them, and no more. Nitsor’s validator applies further rules at the Schema layer that the downloads leave out. It refuses a query that breaks one with invalid_ast, and an envelope with invalid_envelope, before any later layer runs. These rules include: JSON Schema also does not check the bounded-copy admission before the Schema layer, such as plain data or the total bytes of all strings, or any layer after it. An agent that validates locally with the schemas should still expect every refusal in this table.

Trees sent as data

A tree or envelope received as JSON is copied into a bounded snapshot before any schema runs, so hostile nesting is refused rather than recursed into. These bounds sit well above any valid query’s size: For data, depth_exceeded can come from this nesting bound or from the tree depth limit of 8 that the Limits layer applies. Both carry a path and the same message, so the refusal does not say which bound was reached; compare the document with both bounds.

The envelope

The envelope holds the expression and everything that fixes its universe. The same expression means the same predicate in every envelope. Identifiers in scope and version are strings of 1 to 200 characters. Write every property, defaults included, so the envelope reads the same everywhere it is stored. A complete envelope over Tasks, with synthetic identifiers:
envelope.json
Its expression is task.state:ANY(queued, assigned) AND task.created:>=now-7d/d: queued or assigned Tasks created on or after the start of 18 October in Berlin (2026-10-17T22:00:00.000Z), oldest first. An envelope can be structurally valid and still unsupported. "resource": "tasks" with "version": { "kind": "datasetVersion", ... } passes the schema, but Task facts are live, so Nitsor has no plan for Tasks frozen in a Dataset Version.

Relative intent and pinned evaluation

now-7d/d in query text is intent: “since the start of the day a week ago”, whenever it runs. An envelope’s asOf turns that intent into one evaluation. Every now in that envelope means the same instant, so bounds stay fixed while the results are paged.
  • Store the text or tree as the intent. Do not replace relative dates with the instants they resolved to, unless the intent really was a fixed window.
  • Treat an envelope as the record of one evaluation. Running the query again means a new asOf, and a new evaluation with a fresh cursor.
  • Do not read an old asOf as the present. An envelope from yesterday answers yesterday’s “today”.
Pinning asOf fixes the time bounds. It does not freeze the data. version decides where labels and Sources are read, and a commit or Dataset Version pins them. Task facts such as state, Stage and time waited are live workflow facts: a branch commit in the envelope pins the branch context, not a historical snapshot of the Tasks. Two evaluations of the same Tasks envelope can return different Tasks, and a queued Task’s time waited is measured up to asOf.

Outcomes and what to do

A query ends in one of five outcomes, and each calls for a different response. Confusing them is the most common way an agent reports something false. An unexpected execution or service failure is an error, not an outcome. Never report it as an empty, partial or complete result. No public interface returns these outcomes. They are the classes Nitsor’s query evaluation keeps apart, and an agent that reasons about NQL1 should keep them apart too.

Refused

The text or data is not valid NQL1. The refusal carries a code, and a span for text or a path for a tree or envelope. Repair the query using the code, change one thing at a time, and check it again before you use it. Never resend it unchanged. Repairable refusals maps the common codes to their fixes.

Unavailable

A field cannot be used. A field that does not exist and a field you may not see give the same answer, by design. Do not try spelling variants to find out which, and do not infer from a refusal that a field, a value or a count exists. Use a field whose definition you have read, such as one in Fields used in the examples, or ask the person you work for which field to use and to arrange access.

Unsupported

The query is valid and its fields are available, but Nitsor has no plan to evaluate that shape over that resource, scope or version. A query checked against an older registry revision is also unsupported until it is checked again against the current one. Report the limit. Do not change the query’s meaning to get a plan: replacing NOT field:value with field:!=value, or splitting a label(...) block into flat filters, asks a different question.

Partial

Evaluation stopped at a budget before it examined everything. The rows returned are real, and the count is a lower bound or unknown. Continue from the cursor with the same envelope, or report the result as incomplete. Never report a partial result as the total, and never report an empty partial page as no match.

Complete

Evaluation was not cut short by a budget. Only a first page that examined every candidate carries an exact count, and only an empty one proves that nothing matches. Later pages carry no total, because the records can change between pages.

Repairable refusals

These codes mean the text is wrong in a way an agent can fix without asking anyone: Two codes come from the envelope’s zone, not the text. For calendar_day_not_contiguous, use timestamps for that day. time_zone_unavailable means the zone is on the pinned tzdata 2026c list but the runtime evaluating the query cannot resolve it. Report that, or ask the person you work for whether UTC or another listed zone fits; a different zone moves every day boundary. Dates, durations and time zones has the zone rules.

Refusal codes

Every code NQL1 returns, grouped by where it arises. errors.json has the same list for machines.
The query_too_long message mentions saved views. That is the catalog’s wording, and no saved-view interface accepts NQL1. Shorten the query instead.

Next step

Read a field’s definition before you choose fields.

Read a query field

See which filters a field admits, what its values mean and whether Nitsor can evaluate a query on it.

Query recipes

Start from worked queries for common questions.
PlansAll plans
Last verified2026-10-25