---
title: "JQL"
description: "Reference for querying, aggregating, and presenting Judgment trace, span, and session data."
sidebar:
  label: "JQL"
seo:
  title: "JQL Reference | Judgment Docs"
  description: "Build JQL queries for live and offline traces, spans, and sessions with the Python and TypeScript Judgeval SDKs."
---

JQL is Judgment's query-builder reference for live and offline traces, spans,
and sessions. Build queries in Python or TypeScript by chaining methods; the
Judgeval SDK validates and runs the resulting expression against your project.
Use JQL to:

- **Filter and search** live or offline traces and spans, plus sessions — for
  example, find traces slower than 10 seconds or spans that errored.
- **Aggregate and summarize** your data — for example, total cost by model or
  error rate over the last 7 days.
- **Build tables and charts** from results for dashboards and reports.
- **Query from a coding agent** so it can pull your project data on your behalf.

If you know SQL, the shape is familiar: the source is your `FROM`, `.where()`
is `WHERE`, `.summarize(...)` with `by` is `GROUP BY` plus aggregates,
`.sort()` is `ORDER BY`, and `.take()` is `LIMIT`.

> **Info**
>
> The JQL SDK is rolling out behind an organization feature flag. If JQL has
> not been enabled for your organization, contact Judgment to request access.



## Quick start

### Configure the client

> **Info**
>
> Make sure you have Judgeval installed and your `JUDGMENT_API_KEY` and
> `JUDGMENT_ORG_ID` set in your environment. See [Get
> started](/documentation/quickstart) for installation and credential setup.

Select a project when you create the client:

**Python**

```python
from judgeval import Judgeval

client = Judgeval(project_name="my-project")
```

**TypeScript**

```typescript
import { Judgeval } from "judgeval";

const client = await Judgeval.create({
  projectName: "my-project",
});
```

Your API key and `JUDGMENT_ORG_ID` identify you and your organization, and the
`project_name` you pass to the client selects the project to query. You never
put an organization ID, project ID, credentials, trace IDs, or session IDs inside
a query itself. Organization and project scope come from the client; optional
trace or session scope is a method option when the query runs.

### Run a query

This query returns the 20 most recent traces slower than 10 seconds — a filter
that works for any project:

**Python**

```python
from judgeval import Judgeval
from judgeval.jql import gte, traces

client = Judgeval(project_name="my-project")

# duration is in nanoseconds, so 10 seconds is 10_000_000_000
query = traces().where(gte("duration", 10_000_000_000)).last("7d").recent(20)
result = client.query(query)
```

**TypeScript**

```typescript
import { Judgeval } from "judgeval";
import { gte, traces } from "judgeval/jql";

const client = await Judgeval.create({
  projectName: "my-project",
});

// duration is in nanoseconds, so 10 seconds is 10_000_000_000
const query = traces().where(gte("duration", 10_000_000_000)).last("7d").recent(20);
const result = await client.query(query);
```

### Narrow a query to traces or sessions

Pass trace or session scope outside the JQL query object when you want every part
of a query—including span rows—to stay within specific traces. The two options are
mutually exclusive:

**Python**

```python
from judgeval.jql import spans

trace_result = client.query(
    spans().rows(),
    trace_ids=["trace-123"],
)

session_result = client.query(
    spans().rows(),
    session_ids=["session-123"],
)
```

**TypeScript**

```typescript
import { spans } from "judgeval/jql";

const traceResult = await client.query(spans().rows(), {
  traceIds: ["trace-123"],
});

const sessionResult = await client.query(spans().rows(), {
  sessionIds: ["session-123"],
});
```

Trace IDs narrow the query directly. Judgment resolves session IDs within the
authenticated organization and project, then narrows the query to their traces.
If none resolve, the request fails instead of falling back to the whole project.
Both options work with `present` and `discover`.

### Query offline traces

Use `offline_traces()` and `offline_spans()` to query data captured by an
`OfflineTracer`. Offline data is stored separately from live monitoring data, so
the live `traces()` and `spans()` sources do not return it:

**Python**

```python
from judgeval.jql import offline_spans, offline_traces

offline_trace_result = client.query(
    offline_traces().last("7d").rows(limit=20)
)

offline_span_result = client.query(
    offline_spans().last("7d").rows(limit=20)
)
```

**TypeScript**

```typescript
import { offline_spans, offline_traces } from "judgeval/jql";

const offlineTraceResult = await client.query(
  offline_traces().last("7d").rows({ limit: 20 }),
);

const offlineSpanResult = await client.query(
  offline_spans().last("7d").rows({ limit: 20 }),
);
```

Offline sources support the same direct result shapes as their live twins, such
as rows, IDs, counts, aggregates, and trends. They do not expose sessions,
judge scores, or cross-grain relations.

## Query structure

Every JQL query reads top to bottom as a builder chain: pick a source, narrow
it down, describe the result you want, then run it. This query does all four —
it finds error spans from the last 7 days and returns the 10 costliest models:

**Python**

```python
from judgeval.jql import agg_expr, spans, status

query = (
    spans()                                      # source
    .where(status("error"))                     # filter
    .last("7d")                                 # time bound
    .pipe()                                     # result pipeline
    .summarize(
        {"total_cost": agg_expr("sum", "cost")},
        by="model",
    )
    .sort("total_cost desc")
    .take(10)
)

result = client.query(query)                    # execution
```

**TypeScript**

```typescript
import { aggExpr, spans, status } from "judgeval/jql";

const query = spans()                           // source
  .where(status("error"))                      // filter
  .last("7d")                                  // time bound
  .pipe()                                      // result pipeline
  .summarize("model", {
    total_cost: aggExpr({ func: "sum", field: "cost" }),
  })
  .sort("total_cost desc")
  .take(10);

const result = await client.query(query);       // execution
```

Each step in the chain maps to a group of builder methods:

| Step | What it does | Builder methods |
| --- | --- | --- |
| Source | Choose the grain to query — the kind of row you get back. | `traces()`, `spans()`, `sessions()`, `offline_traces()`, or `offline_spans()` |
| Scope | Filter rows and bound the time range. | `.where()`, `.last()`, `.since()`, or `.between()` |
| Result | Select rows directly or build a multi-stage pipeline. | `.rows()`, `.ids()`, `.count()`, `.agg()`, or `.pipe()` |
| Execution | Send the query to Judgment and get results back. | `client.query()`, `client.present()`, or `client.discover()` |

Python and TypeScript use different method spellings where required, but emit
the same canonical JSON. For example, a query that returns trace IDs for a
session emits:

```json
{
  "op": "query",
  "source": "traces",
  "filter": { "op": "eq", "field": "session", "value": "session-123" },
  "select": { "op": "ids" }
}
```

## Sources and fields

Choose the source that matches the grain of the result you need:

| Source | Use it for |
| --- | --- |
| `traces()` | Trace-level status, sessions, duration, and trace attributes. |
| `spans()` | Individual operations, model calls, span names, cost, and span attributes. |
| `sessions()` | Groups of related traces and session-level analysis. |
| `offline_traces()` | Trace-level data captured by an `OfflineTracer`. |
| `offline_spans()` | Individual spans captured by an `OfflineTracer`. |

Field availability depends on the source. Discover valid fields before building
a dynamic query or when you are unsure which grain contains a value:

**Python**

```python
trace_fields = client.discover("fields", source="traces")
span_fields = client.discover("fields", source="spans")
```

**TypeScript**

```typescript
const traceFields = await client.discover("fields", {
  source: "traces",
});
const spanFields = await client.discover("fields", {
  source: "spans",
});
```

Do not silently change sources when a field is unavailable: changing grain can
change the meaning and cardinality of the result.

## Filters and time bounds

Add filters with `.where()`. Calling `.where()` more than once combines the
conditions with AND. To express other logic, compose filters explicitly with
these combinators:

| Combinator | Matches when | Logic | TypeScript | Python |
| --- | --- | --- | --- | --- |
| `all` | Every condition is true. | AND | `all(...)` | `all_(...)` |
| `any` | At least one condition is true. | OR | `any(...)` | `any_(...)` |
| `not` | The condition is false. | NOT | `not(...)` | `not_(...)` |

`all`, `any`, and `not` are reserved words in Python, so the Python SDK spells
them `all_`, `any_`, and `not_`.

**Python**

```python
from judgeval.jql import all_, gte, status, traces

slow_errors = (
    traces()
    .where(
        all_(
            status("error"),
            gte("duration", 1_000_000_000),
        )
    )
    .last("7d")
    .count()
)
```

**TypeScript**

```typescript
import { all, gte, status, traces } from "judgeval/jql";

const slowErrors = traces()
  .where(
    all(
      status("error"),
      gte("duration", 1_000_000_000),
    ),
  )
  .last("7d")
  .count();
```

Bound production queries with one of these time methods before selecting a
result, so a query does not scan unnecessary project history:

| Method | Meaning | Example |
| --- | --- | --- |
| `.last(window)` | A trailing duration. | `.last("7d")` |
| `.since(timestamp)` | Everything after a timestamp. | `.since("2026-01-01T00:00:00Z")` |
| `.between(start, end)` | A closed time range. | `.between(start, end)` |

## Select terminals

A non-pipeline query ends with exactly one select terminal:

| Terminal | Result |
| --- | --- |
| `.rows(...)` | Matching rows with optional explicit fields and a limit. |
| `.ids()` | Matching trace, span, or session IDs. |
| `.count(by?)` | A total count or counts grouped by a field. |
| `.recent(n)` | The `n` most recent rows. |
| `.top(n, by)` | The `n` largest rows by a numeric field. |
| `.ranked(...)` | Positional rows globally or within a group. |
| `.agg(...)` | A scalar aggregate such as sum, average, or quantile. |
| `.trend(...)` | Count or rate values over time buckets. |

The following examples select explicit rows, count errors, and aggregate cost:

**Python**

```python
from judgeval.jql import spans, status

rows = spans().last("7d").rows(
    fields=["span_id", "trace_id", "span_name", "model", "cost"],
    limit=100,
)
error_count = spans().where(status("error")).last("7d").count()
total_cost = spans().last("7d").agg("sum", "cost")
```

**TypeScript**

```typescript
import { spans, status } from "judgeval/jql";

const rows = spans().last("7d").rows({
  fields: ["span_id", "trace_id", "span_name", "model", "cost"],
  limit: 100,
});
const errorCount = spans()
  .where(status("error"))
  .last("7d")
  .count();
const totalCost = spans()
  .last("7d")
  .agg({ func: "sum", field: "cost" });
```

Pass a completed builder to `client.query(...)` to execute it.

## Aggregations and pipelines

Use `.pipe()` instead of a select terminal when the result needs multiple
ordered transformations.

| Stage | Purpose |
| --- | --- |
| `.where(...)` | Filter the current rows, including after aggregation. |
| `.pick(...)` | Keep positional rows within groups. |
| `.derive(...)` | Add computed columns without changing row count. |
| `.summarize(...)` | Collapse rows into grouped aggregate results. |
| `.sort(...)` | Order the current rows. |
| `.take(...)` | Keep a bounded number of rows, optionally after an offset. |

Stages execute in method-call order. This example summarizes cost by model,
sorts the result, and keeps ten rows:

**Python**

```python
from judgeval.jql import agg_expr, spans

spend_by_model = (
    spans()
    .last("7d")
    .pipe()
    .summarize(
        {"total_cost": agg_expr("sum", "cost")},
        by="model",
    )
    .sort("total_cost desc")
    .take(10)
)

result = client.query(spend_by_model)
```

**TypeScript**

```typescript
import { aggExpr, spans } from "judgeval/jql";

const spendByModel = spans()
  .last("7d")
  .pipe()
  .summarize("model", {
    total_cost: aggExpr({ func: "sum", field: "cost" }),
  })
  .sort("total_cost desc")
  .take(10);

const result = await client.query(spendByModel);
```

## Presentation specifications

Wrap a query or pipeline with `.table()` or `.chart()` to describe a
presentation, then execute it with `client.present(...)`.

`client.present(...)` returns normalized presentation metadata and a typed data
frame. It does not render HTML, an image, or a UI component. Render the returned
data with your application's table or chart library.

**Python**

```python
from judgeval.jql import agg_expr, spans

spend_by_model = (
    spans()
    .last("7d")
    .pipe()
    .summarize(
        {"total_cost": agg_expr("sum", "cost")},
        by="model",
    )
    .sort("total_cost desc")
    .take(10)
)

table_result = client.present(
    spend_by_model.table(
        title="Spend by model",
        columns=[
            {"key": "model", "label": "Model"},
            {
                "key": "total_cost",
                "label": "Cost",
                "format": "currency_usd",
            },
        ],
    )
)

chart_result = client.present(
    spend_by_model.chart(
        chart_type="bar",
        title="Spend by model",
        x_axis={"key": "model", "label": "Model"},
        y_axis={
            "key": "total_cost",
            "label": "Cost",
            "format": "currency_usd",
        },
    )
)
```

**TypeScript**

```typescript
import { aggExpr, spans } from "judgeval/jql";

const spendByModel = spans()
  .last("7d")
  .pipe()
  .summarize("model", {
    total_cost: aggExpr({ func: "sum", field: "cost" }),
  })
  .sort("total_cost desc")
  .take(10);

const tableResult = await client.present(
  spendByModel.table({
    title: "Spend by model",
    columns: [
      { key: "model", label: "Model" },
      {
        key: "total_cost",
        label: "Cost",
        format: "currency_usd",
      },
    ],
  }),
);

const chartResult = await client.present(
  spendByModel.chart({
    chart_type: "bar",
    title: "Spend by model",
    x_axis: { key: "model", label: "Model" },
    y_axis: {
      key: "total_cost",
      label: "Cost",
      format: "currency_usd",
    },
  }),
);
```

## Discovery

Discovery returns project-scoped catalog values that help construct valid
queries. Only use kinds present in the generated `DiscoveryKind` contract.

| Kind | Returns |
| --- | --- |
| `fields` | Fields available for a required `source`. |
| `models` | Model names observed in project facts. |
| `span_names` | Span names observed in project facts. |
| `judges` | Current judge names and configuration. |
| `behaviors` | Behavior names and values. |
| `citations` | Spans cited by a judge decision. |
| `rules` | Rule configurations. |
| `judge_prompts` | Judge prompt configurations. |
| `judge_config` | Judge configuration details. |

**Python**

```python
fields = client.discover("fields", source="traces")
models = client.discover("models", time={"last": "7d"}, limit=100)
judges = client.discover("judges", limit=100)
```

**TypeScript**

```typescript
const fields = await client.discover("fields", {
  source: "traces",
});
const models = await client.discover("models", {
  time: { last: "7d" },
  limit: 100,
});
const judges = await client.discover("judges", { limit: 100 });
```

## Responses and errors

Query and discovery responses contain exactly these public fields:

| Field | Type | Description |
| --- | --- | --- |
| `query_id` | string | Identifier for the query execution. |
| `rows` | array or `null` | Result rows, when the operation returns rows. |
| `row_count` | number or `null` | Result row count, when provided by the operation. |
| `elapsed_ms` | number | Server execution time in milliseconds. |

Presentation responses contain exactly these public fields:

| Field | Type | Description |
| --- | --- | --- |
| `query_id` | string | Identifier for the presentation execution. |
| `presentation` | `Any` (Python) / `unknown` (TypeScript) | Presentation metadata returned by the server. |
| `frame` | `Any \| None` (Python) / `unknown \| null` (TypeScript) | Presentation data returned by the server. |
| `elapsed_ms` | number | Server execution time in milliseconds. |

Python responses are typed mappings, so access fields as `result["rows"]`.
TypeScript responses use normal property access, such as `result.rows`.

TypeScript raises `JudgevalAPIError`; Python raises `JudgmentAPIError`. Both
preserve the public error code, an actionable hint when supplied, and a numeric
retry-after value when available.

**Python**

```python
from judgeval.exceptions import JudgmentAPIError

try:
    result = client.query(traces().last("7d").count())
except JudgmentAPIError as error:
    print(error.code)
    print(error.hint)

    if error.retry_after_seconds is not None:
        print(f"Retry after {error.retry_after_seconds} seconds")
```

**TypeScript**

```typescript
import { JudgevalAPIError } from "judgeval";

try {
  const result = await client.query(traces().last("7d").count());
} catch (error) {
  if (!(error instanceof JudgevalAPIError)) throw error;

  console.error(error.code);
  console.error(error.hint);

  if (error.retryAfterSeconds !== undefined) {
    console.error(`Retry after ${error.retryAfterSeconds} seconds`);
  }
}
```

Do not match undocumented error-code strings or assume a retry-after value is
always present.

## Tenant safety

- The API key authenticates the user.
- `JUDGMENT_ORG_ID` selects an organization that the authenticated user must
  belong to.
- The configured project must belong to that organization.
- JQL query payloads use supported query operators and values. They must not
  contain organization IDs, project IDs, API keys, or raw SQL.

Keep credentials in environment variables or your secret manager. Never place
them in a builder, log them, or commit them with query examples.

## API reference

Use the runtime-specific SDK reference when you need the complete function
inventory, accepted parameters, return fields, or an example for a particular
builder.

<CardGroup>
  <Card href="/sdk-reference/python/jql" title="Python JQL reference">
    Complete Python signatures for query builders, filters, pipelines,
    presentations, discovery, responses, and errors.
  </Card>
  <Card href="/sdk-reference/typescript/jql" title="TypeScript JQL reference">
    Complete TypeScript signatures for query builders, filters, pipelines,
    presentations, discovery, responses, and errors.
  </Card>
</CardGroup>
