Skip to main content
Every query in a code block is valid NQL1 and was parsed with Nitsor’s NQL1 parser, except the blocks titled Refused query, which show a refusal on purpose. Read a query field explains what a field’s definition allows.
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.

Scans with an accepted Crack label

Intent. Scans where one label is a Crack and that same label is accepted.
nql1
Both filters must hold for the same label. A Scan with an accepted Crack label matches. A Scan with a corrected Crack label and a separate accepted Porosity label does not. The flat form is usually not what you want:
nql1
Each flat filter is tested on its own, so one label can satisfy the class and another the review state. Under a label policy that reads corrected labels, a Scan with a corrected Crack label and an accepted Porosity label matches. Use the block form. Where it stops. Nitsor does not evaluate relation blocks such as label(...). Labels are read only if their review state is in the envelope’s label policy. The default policy reads accepted labels only, so the reviewState:accepted filter changes nothing under it; it matters when the policy also reads corrected, draft or rejected labels. Crack is a display name, resolved to a class ID only when it is unambiguous in the pinned Taxonomy; writing the class ID, such as class:crack, avoids the question. Related records has the full rules.

Any of several defects, or all of them

Intent. Scans whose defect list, a multi-select Taxonomy Field, contains crack or porosity; then Scans whose list contains both.
nql1
nql1
To exclude both defects, write NOT taxonomy["defect-types"]:ANY(crack, porosity). It also selects Scans with no answer. A list field does not admit !=, and NOT is not a replacement for it. To require an answer as well, write has:taxonomy["defect-types"] AND NOT taxonomy["defect-types"]:ANY(crack, porosity). Whether an empty list counts as an answer is not fixed, so this cannot separate an empty answer from a missing one. Where it stops. The values are option IDs, which are case-sensitive. The selector uses the Field’s ID as a bracketed key; your Taxonomy’s Field IDs decide the exact selector. Every value across every ANY and ALL in one query counts toward a limit of 32.

Records with no part number

Intent. Records where the part number is missing, and how that differs from “the part number is not blade-a”.
nql1
!= needs a value to compare, so it never matches a missing field. NOT is the plain opposite of the filter after it, so it does. The two are different queries and neither is rewritten into the other.
Refused query
Refused with null_literal. NQL1 has no null; use NOT has:meta.partNumber. Where it stops. meta.partNumber is a synthetic sidecar field that shows the grammar. Fields used in the examples says which names are real.

An exact numeric threshold

Intent. Scans whose Source has a slice spacing of at most 0.5 mm, and Scans whose Source spacing is between 0.25 mm and 0.5 mm, excluding 0.5.
nql1
nql1
The query keeps a numeric literal exactly as decimal text, in the text and in the tree, so 0.5 is never rounded through floating point on its way to evaluation. 0.50 is the same value and is written 0.5 in canonical form. The field’s units are millimetres, and the value carries no unit.
Refused query
Refused with invalid_value. Drop the unit. Where it stops. A missing spacing matches no comparison, so a Scan whose Source has no recorded spacing is in neither result. Add OR NOT has:source.spacing.z if those Scans belong in the answer.

Registered in September

Intent. Scans whose Source was registered on any day of September 2026, in the envelope’s time zone.
nql1
A date-only value is a whole calendar day, so the inclusive upper bound includes all of 30 September. With timeZone set to UTC, the window runs from 2026-09-01T00:00:00.000Z to before 2026-10-01T00:00:00.000Z. [2026-09-01 TO 2026-10-01} is the same window written half-open. The zone moves the window. With timeZone set to Asia/Kolkata, the single day source.registered:2026-09-01 runs from 2026-08-31T18:30:00.000Z to before 2026-09-01T18:30:00.000Z. Where it stops. NQL1 has no calendar months: now-1M is refused with unsupported_time_unit. Write the month’s dates.

The last seven days: rolling or calendar

Intent. Recent registrations, counted three ways: a rolling 168 hours, whole days through today, and whole days before today. All three are evaluated with timeZone set to UTC and asOf at 2026-10-04T05:30:00.000Z.
nql1
nql1
nql1

Language illustration

Rolling and calendar seven-day windows

Rolling and calendar seven-day windowsA day axis from 27 September to 5 October 2026 in UTC, with asOf at 4 October 05:30. The rolling window [now-7d TO now] runs from 27 September 05:30 to 4 October 05:30, exactly 168 hours. The whole-day window [now-7d/d TO now/d] runs from the start of 27 September to the end of 4 October: the seven days before today and today. The window [now-7d/d TO now/d} runs from the start of 27 September to the start of 4 October: the seven whole days before today.asOf 4 Oct, 05:30 UTC[now-7d TO now]Rolling: exactly 168 hours[now-7d/d TO now/d]Whole days: 27 Sep to today, 4 Oct[now-7d/d TO now/d}Before today: 27 Sep to 3 Oct

Evaluated with timeZone UTC and asOf 2026-10-04T05:30:00.000Z. The whole-day window runs past asOf to the end of today. In another zone, /d takes the days in that zone.

now-7d is exactly seven 24-hour periods, whatever the clocks did. /d turns an instant into its calendar day in the envelope’s zone, and the offset is applied before the day is taken. Across a clock change near midnight, a window of whole days can hold one local day more or fewer. Where it stops. The text is the intent; the envelope’s asOf pins one evaluation of it. Evaluating the same text tomorrow, with a new asOf, moves every window by a day. An envelope that keeps its asOf keeps its window, so its pages agree on what “today” means.

A metadata key that contains a dot

Intent. Records whose sidecar key machine.name, a single key with a dot in it, is scanner-01.
nql1
nql1
The first is the key machine.name under meta. The second is three segments, meta, machine and name: a key name inside a key machine. They are different fields, and only the bracketed one is the sidecar key here. A bracketed key that is a plain identifier is the same as the dotted form: meta["machine"] is meta.machine. Where it stops. Keys use double quotes; meta['machine.name'] is refused with invalid_field.

A dotted study UID

Intent. Scans whose Source belongs to one study, identified by its DICOM study UID.
nql1
Refused query
Refused with invalid_value. An unquoted text value may not contain dots or start with a digit, so a UID is always quoted. The same applies to text such as CT-2.5. Where it stops. study.uid is a sensitive field: its values identify a study and are never summarised into facets. Use it to select a known study, not to list them.

Open Tasks in one Stage

Intent. Tasks that are queued or assigned in the review Stage, oldest first. The envelope’s resource is tasks, and its sort is task.created ascending.
nql1
Task state is queued, assigned or completed. task.stage takes the Stage ID from the Task’s workflow version. Where it stops. Nitsor’s internal Task reader, the component that reads Tasks from their indexes, evaluates this shape: Task state equality, ANY over states, or an OR of state equalities, optionally with one Stage and a creation window, sorted by creation time. Other shapes, such as NOT task.state:completed, label(...) blocks or task.waited:>48h, come back unsupported rather than empty. Task facts are live, so the envelope’s branch commit fixes the branch context, not a snapshot of the Tasks. No public interface reaches this reader.

Next step

Query language for agents

Build the same queries as JSON trees and envelopes, and handle each outcome.

Query language (NQL1)

Every rule these recipes rely on.
examples.json carries example queries as trees.
PlansAll plans
Last verified2026-10-25