> ## Documentation Index
> Fetch the complete documentation index at: /docs/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.

See [HTTP URLs and authentication](/docs/reference/base-urls.md) and the API reference for complete request and response formats.

**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](/docs/ai-studio/observability/telemetry-api.md).

## Quick start

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

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://router.toqan.ai/eu/v3/traces/query \
  -H "Authorization: Bearer $TOQAN_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"
  }'
```

Rows arrive under `search.data`. Use the query examples below in the SDK's `oql` field or in the request body. API time ranges use RFC 3339 timestamps.

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

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](/docs/ai-studio/observability/traces.md#filter-grammar); the accepted fields and their operators come from `GET /v3/traces/fields` for traces and `GET /v3/logs/fields` for logs.

Status filters use the `success` and `error` spelling; [Trace status values](/docs/ai-studio/observability/telemetry-api.md#trace-status-values) maps them to the Telemetry API and Reporting values.

## 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 | 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**

```text OQL theme={"theme":{"light":"github-light","dark":"github-dark"}}
fetch traces | filter status == "error" | sort end_time desc | limit 50
```

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

```text OQL theme={"theme":{"light":"github-light","dark":"github-dark"}}
fetch traces | filter name contains "responses" | limit 20
```

**Traces in either of two states**

```text OQL theme={"theme":{"light":"github-light","dark":"github-dark"}}
fetch traces | filter status in ("error", "success") | limit 20
```

**Error logs with the newest first**

```text OQL theme={"theme":{"light":"github-light","dark":"github-dark"}}
fetch logs | filter severity_text == "ERROR" | sort timestamp desc | limit 100
```

**Expensive traces**, combining two filters

```text OQL theme={"theme":{"light":"github-light","dark":"github-dark"}}
fetch traces | filter tokens.total > 1000 | filter status == "success" | limit 20
```

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

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.

## 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](/docs/ai-studio/observability/telemetry-api.md) |
| 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.