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

# Telemetry query API (private readiness preview)

> A neutral query envelope for traces, metrics, and logs. Not yet published in navigation or generated SDKs.

<Warning>
  This page is a private readiness artifact. `POST /v3/telemetry/query` is
  retained for internal consumers but is intentionally absent from the public
  OpenAPI, Mintlify navigation, and released SDKs until the live readiness
  checks in ADR 0004 pass. `POST /v2/reporting` and
  `POST /v3/traces/aggregate` remain supported public APIs.
</Warning>

The private `POST /v2/telemetry/query` route is a compatibility alias for
already-deployed internal callers. New integrations use the canonical v3
route shown below.

`QueryTelemetry` provides one bounded request and response shape for trace
aggregates, registered OTLP metrics, and log aggregates. The `source` selects
the planner; it never accepts table names, SQL, or arbitrary expressions.

## Endpoint and permissions

```http theme={"theme":{"light":"github-light","dark":"github-dark"}}
POST /v3/telemetry/query
Content-Type: application/json
Authorization: Bearer <api-key>
```

Permissions are selected from the request instead of using one new broad
permission:

| Request                                               | Required permission |
| ----------------------------------------------------- | ------------------- |
| `source=TRACES` with only `genai.*` reporting presets | `reporting.view`    |
| `source=TRACES` with trace aggregate computes         | `traces.list`       |
| `source=METRICS`                                      | `reporting.view`    |
| `source=LOGS`                                         | `logs.list`         |

A restricted key with the wrong source permission receives HTTP 403. Missing
authentication receives HTTP 401. Authorization is checked before planner
execution.

## Request schema

| Field             | Type               | Required | Rules                                                                                                                |
| ----------------- | ------------------ | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `source`          | enum               | yes      | `TELEMETRY_SOURCE_TRACES`, `TELEMETRY_SOURCE_METRICS`, or `TELEMETRY_SOURCE_LOGS`                                    |
| `from`, `to`      | RFC 3339 timestamp | yes      | `from` must be before `to` and within the caller's retention window                                                  |
| `compute`         | array              | yes      | 1–10 `{metric, op}` entries; validated by the selected source                                                        |
| `grain`           | enum               | no       | `none`, `auto`, `minute`, `hour`, `day`; empty remains equivalent to `none` for compatibility when `mode` is omitted |
| `mode`            | enum               | no       | `timeseries` or `scalar`; when omitted, `grain=none`/empty is scalar and another grain is timeseries                 |
| `group_by`        | string array       | no       | At most 5 source-owned dimensions                                                                                    |
| `filters`         | object array       | no       | At most 20 `{field, op, values}` filters                                                                             |
| `filter_operator` | enum               | no       | `and` (default) or `or`; tenancy is always ANDed outside this client group                                           |
| `sort`            | enum               | no       | `desc` (default) or `asc`; applies to scalar/top-list rows                                                           |
| `limit`           | integer            | no       | Defaults to 100; maximum 5000; this version does not expose cursor pagination                                        |
| `time_zone`       | string             | no       | Reporting presets apply it. Other sources use UTC and return a warning for non-UTC values.                           |
| `include_totals`  | boolean            | no       | Adds one ungrouped totals row when the source supports the requested compute                                         |

Filter operations use `eq`, `neq`, `in`, `not_in`, `gt`, `gte`, `lt`,
`lte`, `between`, `contains`, `exists`, and `not_exists`. Each source exposes
a smaller field/op allowlist. Reporting presets retain their existing
`eq`/`neq`/`in`/`not_in` behavior rather than accepting a filter the public
Reporting API would reject.

## Metric and operation discovery

Discovery remains source-owned because a single static list would be wrong for
workspace-registered metrics:

| Source            | Discovery                                                                                                           |
| ----------------- | ------------------------------------------------------------------------------------------------------------------- |
| Reporting presets | The `QueryReportRequest.metric` enum and the [Reporting API guide](/docs/ai-studio/observability/reporting-api)     |
| Trace aggregates  | `GET /v3/traces/fields` plus the documented trace compute allowlist                                                 |
| OTLP metrics      | `GET /v2/analytics/metrics`, including each metric's operations, dimensions, grains, type, temporality, and backing |
| Logs              | Log field discovery and the fixed `log_count`, `error_count`, and `error_rate` aggregate operations                 |

Reporting preset operations are canonical: for example `genai.cost:sum`,
`genai.latency.p95:p95`, and `genai.error_rate:rate`. `genai.usage:bundle`
returns all usage sub-fields with keys such as
`genai.usage:request_count`, `genai.usage:total_tokens`, and
`genai.usage:total_cost`.

Registered metrics can advertise `sum`, `avg`, `count`, `min`, `max`,
`increase`, `rate`, and `p50`/`p90`/`p95`/`p99`. The registry decides which
operations are legal for a particular metric. Cumulative monotonic counters
use reset-safe deltas from raw points; histogram quantiles merge bucket vectors
before interpolation.

## Response schema

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "object": "telemetry.query",
  "request": {},
  "data": [
    {
      "timestamp": "2026-07-31T12:00:00Z",
      "group": { "provider": "openai" },
      "metrics": { "genai.cost:sum": 1.25 }
    }
  ],
  "totals": { "metrics": { "genai.cost:sum": 1.25 } },
  "has_more": false,
  "meta": {
    "effective_grain": "hour",
    "warnings": [],
    "row_count": 1,
    "request_id": "req_...",
    "currency": "USD"
  }
}
```

`timestamp` is omitted for scalar rows. `group` is empty for an ungrouped
row. Metric map keys use `<metric>:<op>` so two operations cannot collide.
Bundle presets use `<bundle>:<field>`. `has_more` is always false in this
version; lower the requested limit or time range instead of expecting a page
token.

## Examples

### Reporting-compatible grouped scalar

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl "$ORQ_API_URL/v3/telemetry/query" \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "TELEMETRY_SOURCE_TRACES",
    "from": "2026-07-30T00:00:00Z",
    "to": "2026-07-31T00:00:00Z",
    "compute": [{"metric":"genai.usage","op":"bundle"}],
    "mode": "scalar",
    "grain": "none",
    "group_by": ["provider"],
    "sort": "desc",
    "limit": 10,
    "include_totals": true
  }'
```

### Grouped trace time series

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "source": "TELEMETRY_SOURCE_TRACES",
  "from": "2026-07-30T00:00:00Z",
  "to": "2026-07-31T00:00:00Z",
  "compute": [
    { "metric": "trace_count", "op": "count" },
    { "metric": "duration_ms", "op": "p95" }
  ],
  "mode": "timeseries",
  "grain": "hour",
  "group_by": ["provider"],
  "filters": [{ "field": "status", "op": "neq", "values": ["unset"] }],
  "filter_operator": "and",
  "include_totals": true
}
```

### Registered metric with an OR filter group

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "source": "TELEMETRY_SOURCE_METRICS",
  "from": "2026-07-30T00:00:00Z",
  "to": "2026-07-31T00:00:00Z",
  "compute": [{ "metric": "gen_ai.requests", "op": "rate" }],
  "grain": "minute",
  "group_by": ["provider"],
  "filters": [
    { "field": "provider", "op": "eq", "values": ["openai"] },
    { "field": "provider", "op": "eq", "values": ["anthropic"] }
  ],
  "filter_operator": "or"
}
```

The server compiles the last two filters as a nested OR group and separately
ANDs the authenticated workspace condition. Client filters can never make the
tenant predicate optional.

## Compatibility and migration

No migration is required while this endpoint is private.

| Existing request                                        | Telemetry equivalent                                                                        | Compatibility note                                                                           |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `QueryReport(metric=M, grain=G, group_by=D, filters=F)` | `source=TRACES`, `compute=[{metric:M, op:canonical(M)}]`, same `grain`, `group_by`, filters | Reporting stays supported. The shared reporting planner is cross-validated for mapped cases. |
| `QueryReport(metric=genai.usage)`                       | `compute=[{metric:"genai.usage",op:"bundle"}]`                                              | Telemetry prefixes bundle output keys with `genai.usage:`.                                   |
| `AggregateTraces(compute=C, group_by=D, filters=F)`     | `source=TRACES`, `compute=C`, `mode=scalar`, `grain=none`, same grouping and filters        | AggregateTraces stays supported. Its response aliases are preserved on its own endpoint.     |
| Analytics metric query                                  | `source=METRICS`, one or more registry-valid computes                                       | Discovery remains `/v2/analytics/metrics`.                                                   |
| Log aggregate                                           | `source=LOGS`, log computes and dimensions                                                  | Requires `logs.list`; UTC buckets in this version.                                           |

Reporting presets cannot be mixed with trace aggregates in one request. A
mixed request returns HTTP 400. Scalar/top-list sorting also accepts one
compute only, avoiding an ambiguous sort key.

## Errors and limitations

| Status | Meaning                                                                                                              |
| ------ | -------------------------------------------------------------------------------------------------------------------- |
| 400    | Invalid source metric/op, field, filter, grouping, mode, grain, limit, time range, retention, or mixed compute class |
| 401    | Missing or invalid authentication                                                                                    |
| 403    | The restricted key lacks the source-compatible permission                                                            |
| 415    | Request content type is not JSON                                                                                     |
| 500    | An unexpected executor failure; internal details are not returned                                                    |
| 503    | The reporting backend is temporarily unavailable                                                                     |

Current limitations:

* no cursor pagination; `has_more` remains false;
* no raw SQL or arbitrary metric expressions;
* metrics, logs, and trace aggregates use UTC buckets;
* grouped time series return observed group/bucket rows and do not synthesize
  every missing group/bucket combination;
* the legacy `/v2/analytics/metrics/:name/query` response remains unchanged and
  still rejects `interval` plus `group_by`; grouped metric series use this
  neutral envelope;
* publication, SDK generation, and migration guidance remain blocked on the
  deployed-ingress and restricted-key readiness suite.
