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

> Query traces, logs, and usage metrics through one endpoint: one request shape, one filter dialect, and one response shape per source.

The **Telemetry API** answers usage, cost, latency, trace, and log questions from one endpoint. Every request names a `source`, a time range, and one or more `compute` entries, and every source answers with the same row shape.

<Note>
  The **Telemetry API** is the recommended query surface for new integrations: one envelope covers usage metrics, trace aggregates, and logs. The [Reporting API](/ai-studio/observability/reporting-api) and the trace aggregate endpoints remain supported for existing callers. For usage and cost questions, prefer this endpoint.
</Note>

<CardGroup cols={2}>
  <Card title="Usage and cost" icon="dollar-sign" href="#examples-by-use-case">
    Group genai metrics by model, provider, project, agent, or tool.
  </Card>

  <Card title="Trace aggregates" icon="chart-line" href="#sources-and-metrics">
    Count traces and spans, and take latency percentiles or token and cost totals.
  </Card>

  <Card title="Log volume" icon="scroll" href="#sources-and-metrics">
    Count log records and error records, grouped by severity or service.
  </Card>

  <Card title="Discover the surface" icon="compass" href="#discover-what-is-available">
    List the metrics, operations, and dimensions the caller can reach.
  </Card>
</CardGroup>

## Endpoint

<Badge color="blue">POST</Badge> `https://my.orq.ai/v3/telemetry/query`

* All requests require a `Bearer` token. See [API Keys](/ai-studio/organization/api-keys) for how to generate one.
* The workspace and project scope come from the API key.

<CardGroup cols={3}>
  <Card title="Query telemetry" icon="code" href="/reference/telemetry/query-telemetry">
    Full schema for `POST /v3/telemetry/query`.
  </Card>

  <Card title="List capabilities" icon="compass" href="/reference/telemetry/list-telemetry-capabilities">
    `GET /v3/telemetry/capabilities`.
  </Card>

  <Card title="List facet values" icon="list" href="/reference/telemetry/list-telemetry-facet-values">
    `POST /v3/telemetry/facet-values`.
  </Card>
</CardGroup>

## Quickstart

Count requests per model over the last day:

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  FROM=$(date -u -v-1d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d "1 day ago" +%Y-%m-%dT%H:%M:%SZ)
  TO=$(date -u +%Y-%m-%dT%H:%M:%SZ)
  curl -X POST "https://my.orq.ai/v3/telemetry/query" \
    -H "Authorization: Bearer $ORQ_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"source\": \"TELEMETRY_SOURCE_TRACES\",\"from\": \"$FROM\",\"to\": \"$TO\",\"compute\": [{\"metric\": \"genai.requests\", \"op\": \"count\"}],\"grain\": \"hour\",\"group_by\": [\"model\"]}"
  ```

  ```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.telemetry.query({
    source: "TELEMETRY_SOURCE_TRACES",
    from: new Date(Date.now() - 24 * 60 * 60 * 1000),
    to: new Date(),
    compute: [{ metric: "genai.requests", op: "count" }],
    grain: "hour",
    groupBy: ["model"],
  });

  for (const row of result.data ?? []) {
    console.log(row.timestamp, row.group?.model, row.metrics?.["genai.requests:count"]);
  }
  ```

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

  from orq_ai_sdk import Orq

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

  result = orq.telemetry.query(
      source="TELEMETRY_SOURCE_TRACES",
      from_=(datetime.now(timezone.utc) - timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%SZ"),
      to=datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
      compute=[{"metric": "genai.requests", "op": "count"}],
      grain="hour",
      group_by=["model"],
  )

  for row in result.data or []:
      print(row.timestamp, row.group.get("model"), row.metrics.get("genai.requests:count"))
  ```
</CodeGroup>

## Request

| Field | Type | Description |
| - | - | - |
| `source` | string | `TELEMETRY_SOURCE_TRACES`, `TELEMETRY_SOURCE_METRICS`, or `TELEMETRY_SOURCE_LOGS`. Required. |
| `compute` | array | One to ten `{ "metric": "…", "op": "…" }` entries. Required. |
| `from`, `to` | timestamp | RFC 3339 bounds. `from` is inclusive and `to` is exclusive on the traces and logs sources; the metrics source includes a data point recorded exactly at `to`. Required. |
| `grain` | string | `none`, `auto`, `minute`, `hour`, or `day`. `none` and an omitted field return one row per group instead of a time series. |
| `interval_seconds` | integer | Explicit bucket width, 1 to 86400, taking precedence over `grain`. Supported on raw trace metrics and on the metrics source. Rejected for genai metrics, which take `grain`, and ignored on the logs source. |
| `mode` | string | `timeseries` buckets by grain; `scalar` returns one row per group. Omitted, the grain selects the shape. A grouped scalar genai query accepts exactly one `compute` entry. |
| `group_by` | array | Up to five dimensions to break the rows down by. |
| `filters` | array | Up to twenty `{ "field": "…", "op": "…", "values": ["…"] }` entries. |
| `filter_operator` | string | `and` or `or` between filters. Defaults to `and`. |
| `sort` | string | `desc` or `asc` for scalar and top-list rows. Defaults to `desc`. |
| `limit` | integer | Scalar queries: maximum rows, default 100. Time-series queries: maximum distinct groups, and exceeding it fails the query instead of truncating timelines. |
| `time_zone` | string | Places bucket boundaries in the zone for genai metrics. Every other source buckets in UTC and returns a `meta.warnings` entry when a zone other than `UTC` is sent. |
| `include_totals` | boolean | Adds a `totals` row covering the whole window. |
| `selected_range_seconds` | integer | The span the client originally selected, so `grain: "auto"` stays stable while a live window grows. Read by raw trace metrics only; every other source resolves the bucket from `from` and `to` alone. |

`auto` resolves the bucket width from the requested range, and the ladder depends on the source:

| Source | `auto` ladder |
| - | - |
| traces, raw trace metrics | 1s up to 5 minutes, 5s up to 15 minutes, 15s up to 30 minutes, 30s up to an hour, then minute, 5m, 10m, 2h, 6h, 12h and day |
| traces, genai metrics | `minute` up to 6 hours, `hour` up to 7 days, `day` beyond |
| metrics | `minute` up to 48 hours, `hour` up to 14 days, `day` beyond |
| logs | `minute` up to 6 hours, `hour` up to 7 days, `day` beyond |

## Response

Every source answers with the same envelope:

```json Envelope theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "object": "telemetry.query",
  "request": { },
  "data": [
    {
      "timestamp": "2026-05-01T00:00:00Z",
      "group": { "model": "openai/gpt-5.4-mini" },
      "metrics": { "trace_count:count": 128 }
    }
  ],
  "totals": {
    "group": { },
    "metrics": { "trace_count:count": 4021 }
  },
  "meta": {
    "effective_grain": "hour",
    "warnings": [],
    "row_count": 24,
    "request_id": "req_...",
    "currency": ""
  },
  "has_more": false
}
```

* `metrics` keys are the requested metric and operation joined by a colon, for example `trace_count:count` or `genai.cost:sum`.
* `timestamp` is present on bucketed rows only.
* `totals` appears only when the request sets `include_totals: true`, and carries no timestamp and an empty `group`.
* `meta.effective_grain` reports the bucket actually applied, including widths `interval_seconds` or `auto` resolved to. `meta.currency` is `USD` only when every `compute` entry is `genai.cost`, and empty on every other query.
* `meta.warnings` explains a value the source ignored, for example a `time_zone` sent to a source that buckets in UTC.

## Sources and metrics

`TELEMETRY_SOURCE_TRACES` covers raw trace aggregates and the genai usage metrics. The two cannot be mixed in one request.

| Metric | Operations | Source |
| - | - | - |
| `trace_count` | `count` | traces |
| `span_count` | `count` | traces |
| `duration_ms` | `avg`, `p50`, `p95`, `p99`, `min`, `max` | traces |
| `tokens.total`, `tokens.prompt`, `tokens.completion` | `sum`, `avg` on the total; `sum` on the breakdowns | traces |
| `cost.total`, `cost.input`, `cost.output` | `sum`, `avg` on the total; `sum` on the breakdowns | traces |
| `error_count` | `count` | traces |
| `error_rate` | `ratio` | traces |
| `genai.requests` | `count` | traces |
| `genai.tokens` | `sum` | traces |
| `genai.cost` | `sum` | traces |
| `genai.errors` | `count` | traces |
| `genai.error_rate` | `rate` | traces |
| `genai.latency.avg` | `avg` | traces |
| `genai.latency.p50`, `genai.latency.p95`, `genai.latency.p99` | `p50`, `p95`, `p99`, one per metric | traces |
| `genai.ttft.runs` | `count` | traces |
| `genai.ttft.avg` | `avg` | traces |
| `genai.ttft.p50`, `genai.ttft.p95` | `p50`, `p95`, one per metric | traces |
| `genai.evaluator.runs`, `genai.evaluator.errors`, `genai.guardrail.runs`, `genai.guardrail.triggered` | `count` | traces |
| `genai.evaluator.pass_rate`, `genai.evaluator.fail_rate`, `genai.evaluator.error_rate`, `genai.guardrail.block_rate` | `rate` | traces |
| `genai.evaluator.score.avg` | `avg` | traces |
| `log_count` | `count` | logs |
| `error_count`, `error_rate` | `count`, `ratio` | logs |

`TELEMETRY_SOURCE_METRICS` covers the custom metrics registered in the workspace. `TELEMETRY_SOURCE_LOGS` covers log records only.

`genai.usage` is a bundle: request it with `op: "bundle"`, and it answers every field in one row under `genai.usage:<field>` keys. Its fields are not queryable metrics on their own, and the compute cannot be mixed with any other in one request.

## Discover what is available

Capabilities list what a key can reach, so a client never hard-codes the vocabulary:

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl "https://my.orq.ai/v3/telemetry/capabilities?source=TELEMETRY_SOURCE_TRACES" \
    -H "Authorization: Bearer $ORQ_API_KEY"
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const capabilities = await orq.telemetry.listCapabilities();

  for (const source of capabilities.sources ?? []) {
    console.log(source.source, source.metrics?.map((metric) => metric.name));
  }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  capabilities = orq.telemetry.list_capabilities()

  for source in capabilities.sources or []:
      print(source.source, [metric.name for metric in source.metrics or []])
  ```
</CodeGroup>

Each metric entry carries its label, unit, operations, default operation, query modes, result kinds, and the dimensions it can group by, including whether each dimension is groupable and which filter operators it accepts.

Facet values list the distinct values a field takes in the workspace, so a filter can be built from real data. Only the traces source is supported: the metrics and logs sources answer `501`.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  FROM=$(date -u -v-7d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d "7 days ago" +%Y-%m-%dT%H:%M:%SZ)
  TO=$(date -u +%Y-%m-%dT%H:%M:%SZ)
  curl -X POST "https://my.orq.ai/v3/telemetry/facet-values" \
    -H "Authorization: Bearer $ORQ_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"source\": \"TELEMETRY_SOURCE_TRACES\",\"metric\": \"trace_count\",\"field\": \"model\",\"from\": \"$FROM\",\"to\": \"$TO\"}"
  ```
</CodeGroup>

## Examples by use case

<AccordionGroup>
  <Accordion title="Spend: cost by model">
    `genai.cost` sums what the window recorded, `group_by` splits it per model, and `include_totals` adds the window total:

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "source": "TELEMETRY_SOURCE_TRACES",
      "from": "2026-05-01T00:00:00Z",
      "to": "2026-05-08T00:00:00Z",
      "compute": [{ "metric": "genai.cost", "op": "sum" }],
      "grain": "day",
      "group_by": ["model"],
      "include_totals": true
    }
    ```
  </Accordion>

  <Accordion title="Errors: failing traces by Agent">
    Count only the failing traces. `status` takes `ok`, `error`, or `unset`:

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "source": "TELEMETRY_SOURCE_TRACES",
      "from": "2026-05-01T00:00:00Z",
      "to": "2026-05-08T00:00:00Z",
      "compute": [{ "metric": "trace_count", "op": "count" }],
      "grain": "day",
      "group_by": ["agent_name"],
      "filters": [{ "field": "status", "op": "eq", "values": ["error"] }]
    }
    ```
  </Accordion>

  <Accordion title="Latency: p95 by Tool">
    A scalar top list: no grain, one row per tool, ordered by the metric:

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "source": "TELEMETRY_SOURCE_TRACES",
      "from": "2026-05-01T00:00:00Z",
      "to": "2026-05-08T00:00:00Z",
      "compute": [{ "metric": "duration_ms", "op": "p95" }],
      "mode": "scalar",
      "group_by": ["tool_name"],
      "sort": "desc",
      "limit": 10
    }
    ```
  </Accordion>

  <Accordion title="Logs: volume by severity">
    The logs source groups by the OpenTelemetry fields it stores, and `severity_text` carries values such as `INFO`, `WARN`, or `ERROR`:

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "source": "TELEMETRY_SOURCE_LOGS",
      "from": "2026-05-01T00:00:00Z",
      "to": "2026-05-02T00:00:00Z",
      "compute": [{ "metric": "log_count", "op": "count" }],
      "grain": "hour",
      "group_by": ["severity_text"]
    }
    ```
  </Accordion>
</AccordionGroup>

The same shape answers the entity questions: swap `group_by` for `provider`, `product`, `base_model`, `project_id`, `tool_name`, or `agent_name` on traces, `service_name` or `host_name` on logs, and `agent`, `tool`, or `project` on the genai usage metrics. The ttft, evaluator, and guardrail metrics accept no entity dimension beyond `project`. Dynamic dimensions are addressable by prefix: `attributes.*` and `metadata.*` on traces, `attribute.*`, `resource.*`, and `scope.*` on logs.

## Errors

A rejected query returns a JSON envelope with a numeric `code`, a human-readable `message`, and a `details` array (the 401 and 403 responses carry `code` and `message` only):

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "code": 3,
  "message": "invalid filter: unsupported aggregate op \"sum\" for metric \"trace_count\"",
  "details": []
}
```

| HTTP | Code | Reason |
| - | - | - |
| 400 | 3 | Unknown metric, an operation the metric does not accept, a dimension that cannot be grouped, genai and raw computes in one request, more than one compute on a grouped scalar genai query, `interval_seconds` on a genai metric, an unusable `time_zone`, a `minute` or `hour` grain past its rollup retention, or a query that exceeds a budget. |
| 501 | 12 | Facet values requested for a source other than traces. |
| 401 | 16 | Missing or forged bearer token. |
| 403 | 7 | API key lacks the domain behind the requested source: `traces-read`, `reporting-read`, or `logs-read`. |
| 415 | 3 | Request body is not JSON. |
| 500 | 13 | Unexpected backend failure. Contact support if it persists. |

<Tip>
  Every successful query response includes `meta.request_id`. Include it in support tickets for fast log correlation.
</Tip>

Budget rejections carry an `ErrorInfo` detail naming the resource that ran out (`range_days`, `buckets`, `rows`, `series`, or `metric_values`) and its limit; on every source except genai metrics, a bucket overflow also reports the amount required.

## Limits

| Limit | Value |
| - | - |
| Query window | 90 days; every source except raw trace metrics also honours the workspace retention period. On genai metrics an explicit `minute` grain also requires `from` within 14 days, and `hour` within 90 days |
| `compute` entries | 10 |
| `group_by` | 5 dimensions |
| `filters` | 20 entries, 100 values each |
| `limit` | 5000 (default 100) |
| Time-series result | 5000 rows, 5000 buckets, 50000 metric values |
| `interval_seconds` | 1 to 86400 |
| Execution | 30 seconds |
