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

# Trace and log selection with OQL

> Write one pipeline query for traces and logs, with fetch, filter, sort and limit stages, and run it from the API, the CLI, or AI Studio.

**Use Cases**

* Running the same selection from the API, the CLI, and **AI Studio**.
* Combining several conditions in one expression instead of building a `filters` array.
* Handing a saved investigation to a teammate as a single line of text.

***

## Overview

OQL is a pipeline language for selecting **Traces** and **Logs**. A query names the signal, then adds stages separated by `|`:

```txt theme={"theme":{"light":"github-light","dark":"github-dark"}}
fetch <signal> | filter <expr> | sort <field> <order> | limit <n>
```

`fetch` comes first and is required. `filter` may repeat, and every filter is combined with `and`. A repeated `sort` or `limit` stage takes the last value. OQL compiles onto the same planner and field registry as the structured `filters` array, so a field or an operator that works there works in OQL too. There is no aggregation stage: calculated rows and time series come from the [Telemetry API](/ai-studio/observability/telemetry-api).

## Quick start

Run a trace query over a time window. The API takes RFC 3339 timestamps only.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST https://my.orq.ai/v3/traces/query \
    -H "Authorization: Bearer $ORQ_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "from": "2026-10-01T00:00:00Z",
      "to": "2026-10-02T00:00:00Z",
      "oql": "fetch traces | filter status == \"error\" | limit 20"
    }'
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import { Orq } from "@orq-ai/node";

  const orq = new Orq({ apiKey: process.env.ORQ_API_KEY });

  const result = await orq.traces.query({
    from: new Date("2026-10-01T00:00:00Z"),
    to: new Date("2026-10-02T00:00:00Z"),
    oql: 'fetch traces | filter status == "error" | limit 20',
  });

  console.log(result.search?.data);
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import datetime
  import os

  from orq_ai_sdk import Orq

  orq = Orq(api_key=os.environ["ORQ_API_KEY"])

  result = orq.traces.query(
      from_=datetime.datetime(2026, 10, 1, tzinfo=datetime.timezone.utc),
      to=datetime.datetime(2026, 10, 2, tzinfo=datetime.timezone.utc),
      oql='fetch traces | filter status == "error" | limit 20',
  )

  print(result.search.data)
  ```

  ```sh CLI theme={"theme":{"light":"github-light","dark":"github-dark"}}
  orq traces query-oql --from 24h --to now \
    --oql 'fetch traces | filter status == "error" | limit 20'
  ```
</CodeGroup>

Rows arrive under `search.data`. The CLI examples below need the [**Orq CLI**](/reference/cli) installed and authenticated. It accepts relative windows such as `24h` or `7d` and bare dates, and normalizes them to RFC 3339 before sending, so it needs no timestamps of its own.

## Stages

| Stage | Form | Rules |
| - | - | - |
| `fetch` | `fetch traces`, `fetch logs` | Required, first. Any other head is rejected. |
| `filter` | `filter <field> <operator> <value>` | Optional, repeatable. Every filter is combined with `and`, so contradictory filters return no rows. |
| `sort` | `sort <field> <asc\|desc>` | Optional, repeatable. Each signal locks the field it accepts, and a repeated stage takes the last value. |
| `limit` | `limit <n>` | Optional, repeatable. Takes the last value and overrides the request's `limit` field. |

## Filter expressions

Two forms parse to the same filter. The symbol form covers equality and ordering:

| Written | Operator | Example |
| - | - | - |
| `A == B` | `eq` | `status == "error"` |
| `A != B` | `neq` | `status != "error"` |
| `A > B` | `gt` | `tokens.total > 100` |
| `A >= B` | `gte` | `tokens.total >= 100` |
| `A < B` | `lt` | `tokens.total < 100` |
| `A <= B` | `lte` | `tokens.total <= 100` |

The word form covers the operators that need their own syntax:

| Written | Example |
| - | - |
| `A contains B` | `name contains "responses"` |
| `A in (B, C)` | `status in ("error", "success")` |
| `A not_in (B, C)` | `status not_in ("error")` |
| `A between (B, C)` | `start_time between ("2026-10-01T00:00:00Z", "2026-10-02T00:00:00Z")` |
| `A exists` | `tokens.total exists` |
| `A not_exists` | `tokens.total not_exists` |

Lists take parentheses or square brackets and are comma separated. Values may be quoted with `"` or `'`, which lets a value contain spaces; a quote inside a value needs the other quote character.

Which operators a field accepts depends on its type, not on the parser. A valid expression on the wrong field type is still rejected:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{ "code": 3, "message": "invalid filter: operator \"exists\" is not allowed for \"status\"", "details": [] }
```

The type-to-operator table is in [Filter grammar](/ai-studio/observability/traces#filter-grammar); the accepted fields and their operators come from `GET /v3/traces/fields` for traces and `GET /v3/logs/fields` for logs.

## Rules per signal

| | Traces | Logs |
| - | - | - |
| Head | `fetch traces` | `fetch logs` |
| Endpoint | `POST /v3/traces/query` | `POST /v3/logs/query` |
| Sort | `sort end_time desc` only | `sort timestamp desc` only |
| Pipeline `limit` | Up to 200 | Up to 1000 |
| Timestamps | RFC 3339 in the request body, relative values through the CLI | same |
| Selector fields | `oql` only; `filters` and `query` are rejected as unknown fields | `oql` only; `filters` and `query` are accepted but ignored, so a request that sends them silently drops them |

## Examples

**Failing traces in the last day**

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
orq traces query-oql --from 24h --to now \
  --oql 'fetch traces | filter status == "error" | sort end_time desc | limit 50'
```

**Traces whose name matches a deployment**, by the trace name

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
orq traces query-oql --from 7d --to now \
  --oql 'fetch traces | filter name contains "responses" | limit 20'
```

**Traces in either of two states**

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
orq traces query-oql --from 7d --to now \
  --oql 'fetch traces | filter status in ("error", "success") | limit 20'
```

**Error logs with the newest first**

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
orq logs query --from 24h --to now \
  --oql 'fetch logs | filter severity_text == "ERROR" | sort timestamp desc | limit 100'
```

**Expensive traces**, combining two filters

```sh theme={"theme":{"light":"github-light","dark":"github-dark"}}
orq traces query-oql --from 7d --to now \
  --oql 'fetch traces | filter tokens.total > 1000 | filter status == "success" | limit 20'
```

**Error logs from the SDK**, with the client from the Quick start

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const result = await orq.logs.query({
    from: new Date("2026-10-01T00:00:00Z"),
    to: new Date("2026-10-02T00:00:00Z"),
    oql: 'fetch logs | filter severity_text == "ERROR" | sort timestamp desc | limit 100',
  });
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  result = orq.logs.query(
      from_=datetime.datetime(2026, 10, 1, tzinfo=datetime.timezone.utc),
      to=datetime.datetime(2026, 10, 2, tzinfo=datetime.timezone.utc),
      oql='fetch logs | filter severity_text == "ERROR" | sort timestamp desc | limit 100',
  )
  ```
</CodeGroup>

OQL written in **AI Studio** reaches the same endpoint, so the rules above still apply. The query bar in **Traces** and **Logs** switches between a builder and OQL, and the Insights report form accepts the same dialect for `filter` stages only: `sort` and `limit` are rejected there. Field suggestions come from the workspace field registry.

<Frame caption="The **Traces** query bar in OQL mode, with a query that selects traces in either the error or success state.">
  <img src="https://mintcdn.com/orqai/t22NyyWihidZmXMA/images/oql-query-bar.png?fit=max&auto=format&n=t22NyyWihidZmXMA&q=85&s=d4aa44f76a5d5d4ffe2517be83de767a" alt="Trace query bar with the Builder and OQL tabs, OQL selected, and the editor holding a fetch traces query that filters on two status values and limits the result to 100 rows." width="1096" height="155" data-path="images/oql-query-bar.png" />
</Frame>

## Errors

| Message | Cause |
| - | - |
| `invalid oql: query must start with fetch traces` | Head missing or naming another signal; the message echoes the head the signal wants, so a log query reads `fetch logs` |
| `invalid oql: unsupported command "..."` | A stage other than `filter`, `sort`, `limit` |
| `invalid oql: unterminated quote` | A `"` or `'` that opens and is never closed |
| `invalid oql: malformed list` | A list without its closing `)` or `]` |
| `invalid oql: between expects exactly two values` | `between` with one value or three |
| `invalid oql: limit cannot exceed 200` | A trace query above the pipeline cap |
| `validation error: limit: must be greater than or equal to 1 and less than or equal to 1000` | A log query above the pipeline cap |
| `invalid sort: only end_time desc is supported` | Any other trace sort |
| `invalid sort: only timestamp desc is supported` | Any other log sort |
| `invalid filter: unknown field "..."` | A trace query naming a field the registry does not know. A log query answers `filter[0]: unknown field "..."` |
| `invalid filter: operator "..." is not allowed for "..."` | A trace query applying an operator outside the field's list, which `GET /v3/traces/fields` publishes |

A malformed query is rejected before the planner runs, so a bad request never returns partial rows.

## Limitations

| Limitation | Impact | Workaround |
| - | - | - |
| No aggregation | `summarize`, `group by`, and counts are not part of the language | Use the [Telemetry API](/ai-studio/observability/telemetry-api) |
| One sort field | Each signal accepts one sort field: `end_time desc` for traces, `timestamp desc` for logs | Pick the ordering that matters |
| Fields come from the registry | A custom span attribute exists only once the workspace has recorded it | Check the field list before writing the query |
| Traces take no structured selector | `filters` and `query` are unknown fields on `/v3/traces/query` | Express every condition as a `filter` stage |
| The pipeline text is case-insensitive on commands | `FETCH` and `fetch` are the same | Pick one spelling per query for readability |


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