> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nitsor.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Every page in this documentation carries a `plans` list in its frontmatter and a page-details table naming the plans its content applies to: `All plans`, or a subset of `Community`, `Team` and `Enterprise`. A page limited to some plans also opens with a plan marker naming them.
> Use [Capabilities and limits](/product-status) for capability entitlements and documented availability and limits, and [Plans and billing](/plans-and-billing) for plan pricing and billing policy. Those pages are canonical; do not duplicate their policy values here.
> Reading this documentation grants no product access and no permission. No agent can create or approve a Nitsor release.

# Query recipes

> Worked NQL1 queries for common questions, with what each one selects and where it stops.

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](https://docs.nitsor.com/guides/discover-query-fields.md) explains what a field's definition allows.

> [!NOTE]
>
> The dashboard, public HTTP API and Python SDK do not accept or evaluate NQL1 queries, and do not list query fields. [Capabilities and limits](https://docs.nitsor.com/product-status.md#limits) records this boundary.

## Scans with an accepted Crack label

**Intent.** Scans where one label is a Crack and that same label is accepted.

```text nql1 wrap theme={"system"}
label(class:Crack AND reviewState:accepted)
```

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:

```text nql1 wrap theme={"system"}
label.class:Crack AND label.reviewState:accepted
```

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](https://docs.nitsor.com/reference/query-language.md#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.

```text nql1 wrap theme={"system"}
taxonomy["defect-types"]:ANY(crack, porosity)
```

```text nql1 wrap theme={"system"}
taxonomy["defect-types"]:ALL(crack, porosity)
```

| Defect list | `ANY(crack, porosity)` | `ALL(crack, porosity)` |
| - | - | - |
| `[crack]` | yes | no |
| `[crack, porosity]` | yes | yes |
| `[porosity, inclusion]` | yes | no |
| `[inclusion]` | no | no |
| empty, or no answer | no | no |

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".

```text nql1 theme={"system"}
NOT has:meta.partNumber
```

| Filter | no part number | `blade-a` | `blade-b` |
| - | - | - | - |
| `NOT has:meta.partNumber` | yes | no | no |
| `meta.partNumber:!=blade-a` | no | no | yes |
| `NOT meta.partNumber:blade-a` | yes | no | yes |

`!=` 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.

```text Refused query wrap theme={"system"}
meta.partNumber:null
```

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](https://docs.nitsor.com/guides/discover-query-fields.md#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.

```text nql1 theme={"system"}
source.spacing.z:<=0.5
```

```text nql1 theme={"system"}
source.spacing.z:[0.25 TO 0.5}
```

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.

```text Refused query wrap theme={"system"}
source.spacing.z:<=0.5mm
```

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.

```text nql1 wrap theme={"system"}
source.registered:[2026-09-01 TO 2026-09-30]
```

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`.

```text nql1 theme={"system"}
source.registered:[now-7d TO now]
```

```text nql1 wrap theme={"system"}
source.registered:[now-7d/d TO now/d]
```

```text nql1 wrap theme={"system"}
source.registered:[now-7d/d TO now/d}
```

Language illustration

## Rolling and calendar seven-day windows



| Window | From (UTC) | To (UTC) | Covers |
| - | - | - | - |
| Rolling | 2026-09-27 05:30, included | 2026-10-04 05:30, included | exactly 168 hours |
| Whole days | 2026-09-27 00:00, included | 2026-10-05 00:00, excluded | the seven days before today, and today |
| Before today | 2026-09-27 00:00, included | 2026-10-04 00:00, excluded | the seven whole days before today |

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`.

```text nql1 theme={"system"}
meta["machine.name"]:scanner-01
```

```text nql1 theme={"system"}
meta.machine.name:scanner-01
```

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.

```text nql1 theme={"system"}
study.uid:"1.2.840.10008"
```

```text Refused query wrap theme={"system"}
study.uid:1.2.840.10008
```

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.

```text nql1 wrap theme={"system"}
task.state:ANY(queued, assigned) AND task.stage:review
```

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

> [card] [Query language for agents](https://docs.nitsor.com/reference/query-agents.md)
>
> Build the same queries as JSON trees and envelopes, and handle each outcome.

> [card] [Query language (NQL1)](https://docs.nitsor.com/reference/query-language.md)
>
> Every rule these recipes rely on.

[examples.json](https://docs.nitsor.com/assets/nql1/examples.json) carries example queries as trees.

<table aria-label="Page details">
  <tbody>
    <tr><th scope="row">Plans</th><td>All plans</td></tr>
    <tr><th scope="row">Last verified</th><td>2026-10-25</td></tr>
  </tbody>
</table>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.