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

# Traces

> The four trace routes: search the list, read one trace's shape, fetch its values, find traces that share a tag.

The four routes on this page are one walk: find a trace in the list, read its shape, fetch the
bytes of the steps you are looking at, then find the traces that share its tags. Everything the
routes share (authentication, `?team=`, the status fields, handles, errors, pagination) is on
[conventions](https://docs.vexlo.ai/api/conventions.md).

Every response body on this page was captured from a server seeded with the spec's conformance
trace: 17 steps, `root_status: error`, one three-attempt `observed_retry` step, on team `fixture`
with environment `prod`. The one exception is the populated `/related` body at the end, captured
on team `demo` of the same server, which holds three `delivery-service` traces that share one
`order_id` tag. `$READ_TOKEN` holds a read token whose team-set includes both teams.

## GET /v1/traces

Lists traces, newest `last_seen_at` first, as summary rows without steps.

| Parameter | Meaning |
| - | - |
| `team`, `environment`, `environment_unset` | The shared filters ([conventions](https://docs.vexlo.ai/api/conventions.md#team-and-environment-filters)). |
| `service=<name>[,<name>...]` | Any of the named services. |
| `since`, `until` | RFC 3339 instants bounding `first_seen_at`, the instant the Vexlo server first saw the trace. |
| `completeness` | One of `running`, `complete`, `partial`, `abandoned`. |
| `root_status` | `ok` or `error`: the root step's outcome. |
| `has_error` | `true` matches any trace with `error_step_count > 0`, including one whose root returned `ok` after a step failed and was recovered. |
| `root_function` | `module.qualname` of the root step's function. |
| `function` | `module.qualname` of any step in the trace. |
| `duration_min_ms`, `duration_max_ms` | Bounds on `duration_ms`. |
| `tag_key`, `tag_value` | Together, the exact pair. `tag_key` alone matches any trace carrying the key. `tag_value` alone searches every tag key and reports the keys that matched in `matched_tag_keys`. |
| `trace_id=<id>[,<id>...]` | Up to 100 ids, looked up directly. Every other filter is ignored when set. |
| `limit`, `cursor` | Pagination, default 50, maximum 500. |

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  "http://127.0.0.1:18200/v1/traces?team=fixture" | jq .
```

```json theme={null}
{
  "items": [
    {
      "trace_id": "0f1a2b3c4d5e6f708192a3b4c5d6e7f8",
      "service": "delivery-service",
      "team": "fixture",
      "environment": "prod",
      "instance_id": "delivery-service-7f9c8d6b5-x2n4q",
      "tags": {
        "order_id": "O-1234"
      },
      "root_function": "delivery.api.AssignDasherHandler",
      "root_status": "error",
      "completeness": "complete",
      "started_at": "2026-09-07T19:30:01.402Z",
      "duration_ms": 121,
      "step_count": 17,
      "error_step_count": 4,
      "records_lost": 0,
      "records_shed": 0,
      "truncated": false,
      "instance_mismatch": false
    }
  ],
  "limit": 50,
  "next_cursor": null
}
```

`environment` is omitted, never `null`, on a trace whose relay daemon sent no
`X-Vexlo-Environment`.
`duration_ms` is omitted until the root attempt's completion record arrives. The last four
fields are the [coverage block](https://docs.vexlo.ai/api/conventions.md#the-coverage-block) flattened into the row.

A `tag_value` search without `tag_key` adds `matched_tag_keys` to each row:

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  "http://127.0.0.1:18200/v1/traces?team=fixture&tag_value=O-1234" \
  | jq '.items[] | {trace_id, tags, matched_tag_keys}'
```

```json theme={null}
{
  "trace_id": "0f1a2b3c4d5e6f708192a3b4c5d6e7f8",
  "tags": {
    "order_id": "O-1234"
  },
  "matched_tag_keys": [
    "order_id"
  ]
}
```

`has_error` and `root_status` answer different questions. The conformance trace has four error
steps and an error root, so `has_error=true&root_status=ok` matches nothing:

```json theme={null}
{"items":[],"limit":50,"next_cursor":null}
```

### Looking up ids

`?trace_id=` is the cheap re-check for ids you already hold, such as rows still showing
`completeness: running`. It returns list rows, never steps. The response echoes `limit`, raised
to the number of ids named when `?limit=` is smaller, and its `next_cursor` is `null`: every
named id resolves in one page. An id outside the token's team-set, or one that does not exist,
is silently absent from `items`:

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  "http://127.0.0.1:18200/v1/traces?trace_id=0f1a2b3c4d5e6f708192a3b4c5d6e7f8,ffffffffffffffffffffffffffffffff&limit=1" \
  | jq '{limit, next_cursor, trace_ids: [.items[].trace_id]}'
```

```json theme={null}
{
  "limit": 2,
  "next_cursor": null,
  "trace_ids": [
    "0f1a2b3c4d5e6f708192a3b4c5d6e7f8"
  ]
}
```

More than 100 ids is `422 too_many_trace_ids`.

## GET /v1/traces/\{id}

Returns one trace's shape: every step with its attempts, timing, status, and handles with
previews, and no value bytes. This route has no widening parameter; bytes come from the values
route below.

| Parameter | Meaning |
| - | - |
| `steps_sort` | `path` (default) returns steps depth-first. `duration_desc` ranks them by `duration_us`. |
| `limit`, `cursor` | Steps per response, default 500, maximum 500. |

The route accepts no `?team=`: the id names one trace, and an id outside the token's team-set is
`404 trace_not_found`. A trace whose `trace-open` has not arrived answers `404` too and is absent
from every listing, `?trace_id=` included: until that record lands the server holds a landing
place for out-of-order records, not an observed request. Read a `404` as "no trace with that id
is visible to you", never as "the request never ran".

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  http://127.0.0.1:18200/v1/traces/0f1a2b3c4d5e6f708192a3b4c5d6e7f8 | jq .
```

```json theme={null}
{
  "trace_id": "0f1a2b3c4d5e6f708192a3b4c5d6e7f8",
  "service": "delivery-service",
  "team": "fixture",
  "environment": "prod",
  "format_version": 2,
  "instance_id": "delivery-service-7f9c8d6b5-x2n4q",
  "sdk_version": "0.4.0",
  "tags": {
    "order_id": "O-1234"
  },
  "started_at": "2026-09-07T19:30:01.402Z",
  "first_seen_at": "2026-10-20T01:40:22.904Z",
  "last_seen_at": "2026-10-20T01:40:22.904Z",
  "root_status": "error",
  "completeness": "complete",
  "step_count": 17,
  "error_step_count": 4,
  "duration_ms": 121,
  "coverage": {
    "records_lost": 0,
    "records_shed": 0,
    "truncated": false,
    "instance_mismatch": false,
    "service_loss_24h": {
      "delivery-service": 0
    }
  },
  "steps": [
    {
      "path": "",
      "parent_path": null,
      "depth": 0,
      "name": "delivery.api.AssignDasherHandler",
      "parent_name": null,
      "attempt_grouping": "single",
      "duration_us": 121750,
      "attempts": [
        {
          "attempt": 0,
          "started_us": 0,
          "duration_us": 121750,
          "status": "error",
          "error_class": "builtins.ValueError",
          "slots": {
            "error": {
              "state": "stored",
              "bytes_count": 104,
              "truncated": false,
              "encoding": "json",
              "preview": "{\"message\":\"no eligible candidate in list\",\"stack\":[\"assign_dash"
            },
            "input": {
              "state": "stored",
              "bytes_count": 70,
              "truncated": false,
              "encoding": "json",
              "preview": "{\"method\":\"POST\",\"order_id\":\"O-1234\",\"path\":\"/delivery/assign-da"
            }
          }
        }
      ]
    },
    {
      "path": "AAAA",
      "parent_path": "",
      "depth": 1,
      "name": "delivery.api.ParseAssignRequest",
      "parent_name": "delivery.api.AssignDasherHandler",
      "attempt_grouping": "single",
      "duration_us": 70,
      "attempts": [
        {
          "attempt": 0,
          "started_us": 30,
          "duration_us": 70,
          "status": "ok",
          "slots": {
            "input": {
              "state": "pending",
              "bytes_count": 87,
              "truncated": false,
              "encoding": "json",
              "preview": "{\"body\":\"{\\\"order_id\\\":\\\"O-1234\\\",\\\"requested_time\\\":\\\"19:30\\\",\\"
            },
            "output": {
              "state": "pending",
              "bytes_count": 64,
              "truncated": false,
              "encoding": "json",
              "preview": "{\"order_id\":\"O-1234\",\"requested_time\":\"19:30\",\"zone\":\"SF-94107\"}"
            }
          }
        }
      ]
    },
... 398 lines omitted
    {
      "path": "AACAAAIAAA",
      "parent_path": "AACAAAI",
      "depth": 3,
      "name": "delivery.dispatch.PickBestFromCandidates",
      "parent_name": "delivery.dispatch.SelectEligibleDasher",
      "attempt_grouping": "observed_retry",
      "duration_us": 2450,
      "attempts": [
        {
          "attempt": 0,
          "started_us": 118500,
          "duration_us": 850,
          "status": "error",
          "error_class": "builtins.ValueError",
          "slots": {
            "error": {
              "state": "stored",
              "bytes_count": 147,
              "truncated": false,
              "encoding": "json",
              "preview": "{\"message\":\"no eligible candidate in list\",\"stack\":[\"select_elig"
            },
            "input": {
              "state": "stored",
              "bytes_count": 116,
              "truncated": false,
              "encoding": "json",
              "preview": "{\"candidate_ids\":[\"D-101\",\"D-102\",\"D-103\",\"D-104\",\"D-105\"],\"radi"
            }
          }
        },
        {
          "attempt": 1,
          "started_us": 119400,
          "duration_us": 810,
          "status": "error",
          "error_class": "builtins.ValueError",
          "slots": {
            "error": {
              "state": "stored",
              "bytes_count": 147,
              "truncated": false,
              "encoding": "json",
              "preview": "{\"message\":\"no eligible candidate in list\",\"stack\":[\"select_elig"
            },
            "input": {
              "state": "stored",
              "bytes_count": 116,
              "truncated": false,
              "encoding": "json",
              "preview": "{\"candidate_ids\":[\"D-101\",\"D-102\",\"D-103\",\"D-104\",\"D-105\"],\"radi"
            }
          }
        },
        {
          "attempt": 2,
          "started_us": 120260,
          "duration_us": 790,
          "status": "error",
          "error_class": "builtins.ValueError",
          "slots": {
            "error": {
              "state": "stored",
              "bytes_count": 147,
              "truncated": false,
              "encoding": "json",
              "preview": "{\"message\":\"no eligible candidate in list\",\"stack\":[\"select_elig"
            },
            "input": {
              "state": "stored",
              "bytes_count": 116,
              "truncated": false,
              "encoding": "json",
              "preview": "{\"candidate_ids\":[\"D-101\",\"D-102\",\"D-103\",\"D-104\",\"D-105\"],\"radi"
            }
          }
        }
      ]
    },
    {
... 65 lines omitted
  ],
  "limit": 500,
  "next_cursor": null
}
```

Each step carries `path`, `parent_path`, and `depth` for rebuilding the tree, `name` and
`parent_name` in `module.qualname` form, `attempt_grouping` (`single`, `declared`, or
`observed_retry`, derived by the server), `duration_us`, and one entry per attempt in `attempts`.
`error_class` appears only on an `error` attempt, and an `in_flight` attempt carries neither it, a
`duration_us`, nor a `slots` key
([conventions](https://docs.vexlo.ai/api/conventions.md#the-three-status-fields)). `slots` holds one handle per slot the
attempt recorded; `PickBestFromCandidates` failed three times, so it has three attempts and no
`output` slot on any of them.

A step's `duration_us` is the sum of its completed attempts' `duration_us`: the attempts of one
step run one after another, so the sum is what the request paid for that step.
`PickBestFromCandidates` reads 2450, its 850, 810, and 790 added up. The field is absent, never
`0`, on a step whose every attempt is still in flight, and on a step with both completed and
in-flight attempts it is a lower bound rather than a final figure.

`format_version` is the version of the record format this trace's emitter wrote. It is a trace-level
field, omitted from the list rows above, which carry no attempts.

The root step's `error` and `input` read `stored`. The `ok` steps below it, such as
`ParseAssignRequest`, read `pending` on both slots although the trace is `complete` and their
values are served by the route below.

### Step paging and sorting

`limit` caps the steps in one response, `cursor` resumes where the previous one ended, and
`steps_sort=duration_desc` reorders them. `next_cursor` is a string exactly when steps past this
page remain, in either sort, and `null` when nothing sorts after the steps the response carries.
The 17-step trace at `limit=3` returns a cursor:

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  "http://127.0.0.1:18200/v1/traces/0f1a2b3c4d5e6f708192a3b4c5d6e7f8?limit=3" \
  | jq '{step_count, limit, next_cursor, steps: [.steps[] | {path, parent_path, depth, name}]}'
```

```json theme={null}
{
  "step_count": 17,
  "limit": 3,
  "next_cursor": "MDAwMXw",
  "steps": [
    {
      "path": "",
      "parent_path": null,
      "depth": 0,
      "name": "delivery.api.AssignDasherHandler"
    },
    {
      "path": "AAAA",
      "parent_path": "",
      "depth": 1,
      "name": "delivery.api.ParseAssignRequest"
    },
    {
      "path": "AAAQ",
      "parent_path": "",
      "depth": 1,
      "name": "delivery.api.ValidateAssignRequest"
    }
  ]
}
```

Passing that cursor back resumes the same ordering at the fourth step:

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  "http://127.0.0.1:18200/v1/traces/0f1a2b3c4d5e6f708192a3b4c5d6e7f8?limit=3&cursor=MDAwMXw" \
  | jq '{step_count, limit, next_cursor, steps: [.steps[] | {path, parent_path, depth, name}]}'
```

```json theme={null}
{
  "step_count": 17,
  "limit": 3,
  "next_cursor": "MDAwMjAwMDF8",
  "steps": [
    {
      "path": "AABA",
      "parent_path": "",
      "depth": 1,
      "name": "delivery.orders.LoadOrderContext"
    },
    {
      "path": "AABAAAA",
      "parent_path": "AABA",
      "depth": 2,
      "name": "delivery.orders.FetchOrderByID"
    },
    {
      "path": "AABAAAI",
      "parent_path": "AABA",
      "depth": 2,
      "name": "delivery.orders.GetMerchantConfig"
    }
  ]
}
```

Either sort returns one row per step, 17 of them for this trace, and each row carries every
attempt of its step. `duration_desc` ranks on the step's own `duration_us`, so the retried
`PickBestFromCandidates` is ranked on the 2450 microseconds its three attempts add up to and sits
ninth, between `SelectEligibleDasher` at 3020 and `ResolveDispatchParams` at 2000. The five
slowest, with 12 rows still behind the cursor:

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  "http://127.0.0.1:18200/v1/traces/0f1a2b3c4d5e6f708192a3b4c5d6e7f8?steps_sort=duration_desc&limit=5" \
  | jq '{limit, next_cursor, steps: [.steps[] | {path, depth, name, attempts: [.attempts[] | {attempt, duration_us, status}]}]}'
```

```json theme={null}
{
  "limit": 5,
  "next_cursor": "MTI2NTB8MDAwMnw",
  "steps": [
    {
      "path": "",
      "depth": 0,
      "name": "delivery.api.AssignDasherHandler",
      "attempts": [
        {
          "attempt": 0,
          "duration_us": 121750,
          "status": "error"
        }
      ]
    },
    {
      "path": "AACA",
      "depth": 1,
      "name": "delivery.dispatch.AssignDasher",
      "attempts": [
        {
          "attempt": 0,
          "duration_us": 106570,
          "status": "error"
        }
      ]
    },
    {
      "path": "AACAAAA",
      "depth": 2,
      "name": "delivery.dispatch.GetDashersAvailability",
      "attempts": [
        {
          "attempt": 0,
          "duration_us": 103460,
          "status": "ok"
        }
      ]
    },
    {
      "path": "AACAAAAAAE",
      "depth": 3,
      "name": "delivery.dispatch.ExecuteAvailabilityQuery",
      "attempts": [
        {
          "attempt": 0,
          "duration_us": 103200,
          "status": "ok"
        }
      ]
    },
    {
      "path": "AABA",
      "depth": 1,
      "name": "delivery.orders.LoadOrderContext",
      "attempts": [
        {
          "attempt": 0,
          "duration_us": 12650,
          "status": "ok"
        }
      ]
    }
  ]
}
```

A walk is exhaustive only over a trace that has stopped receiving records. A cursor is a position
in the ordering, not an instant, so a step recorded behind one already handed out is never
returned. A child ships before the parent it returned into, and a parent's `path` sorts before
its children's, so a parent that returns mid-walk lands behind the cursor: the walk ends with
`next_cursor: null` and without that parent, the root step included. Under `duration_desc` it
lands further forward still, a parent being at least as slow as the children it waited on.

`step_count` is what says whether a walk saw everything. It counts the steps recorded at the
moment of each response, not the ones returned, so compare the distinct `path` values collected
so far against the newest `step_count` seen, and walk again from no cursor while the two
disagree.

Traces are capped at 10,000 steps; a trace that hit the cap reads `"truncated": true` in its
coverage block.

## GET /v1/traces/\{id}/values

Returns the captured bytes of named steps. It is one of the two routes that serve a value; the
other is [`GET /v1/functions/examples?include=values`](https://docs.vexlo.ai/api/functions#get-/v1/functions/examples),
which serves the example calls of one function.

| Parameter | Meaning |
| - | - |
| `path` | A step's `path` from trace detail, repeated up to 8 distinct times. `path=` with an empty value names the root step. |
| `slot` | `input`, `output`, `error`, or `logs`, repeated. Omitted, every present slot of each named step is returned. |

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  "http://127.0.0.1:18200/v1/traces/0f1a2b3c4d5e6f708192a3b4c5d6e7f8/values?path=" | jq .
```

```json theme={null}
{
  "values": [
    {
      "path": "",
      "slot": "error",
      "state": "stored",
      "encoding": "json",
      "value": "{\"message\":\"no eligible candidate in list\",\"stack\":[\"assign_dasher_handler.py:8\",\"assign_dasher.py:20\"]}",
      "bytes_count": 104,
      "truncated": false
    },
    {
      "path": "",
      "slot": "input",
      "state": "stored",
      "encoding": "json",
      "value": "{\"method\":\"POST\",\"order_id\":\"O-1234\",\"path\":\"/delivery/assign-dasher\"}",
      "bytes_count": 70,
      "truncated": false
    }
  ],
  "unknown_paths": []
}
```

`value` is a string in every case. With `encoding: json` it holds JSON text to parse; with
`text` the exact UTF-8 string; with `base64` the raw bytes encoded. `bytes_count` is the length of
the underlying bytes and `truncated` says whether they were cut at the 16 KiB slot bound.

A retried step returns one entry per attempt for each requested slot; the entries carry no
attempt index. `PickBestFromCandidates` at `AACAAAIAAA` raised the same error
three times:

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  "http://127.0.0.1:18200/v1/traces/0f1a2b3c4d5e6f708192a3b4c5d6e7f8/values?path=AACAAAIAAA&slot=error" \
  | jq .
```

```json theme={null}
{
  "values": [
    {
      "path": "AACAAAIAAA",
      "slot": "error",
      "state": "stored",
      "encoding": "json",
      "value": "{\"message\":\"no eligible candidate in list\",\"stack\":[\"select_eligible_dasher.py:44\",\"pick_best_from_candidates.py:12, in PickBestFromCandidates()\"]}",
      "bytes_count": 147,
      "truncated": false
    },
    {
      "path": "AACAAAIAAA",
      "slot": "error",
      "state": "stored",
      "encoding": "json",
      "value": "{\"message\":\"no eligible candidate in list\",\"stack\":[\"select_eligible_dasher.py:44\",\"pick_best_from_candidates.py:12, in PickBestFromCandidates()\"]}",
      "bytes_count": 147,
      "truncated": false
    },
    {
      "path": "AACAAAIAAA",
      "slot": "error",
      "state": "stored",
      "encoding": "json",
      "value": "{\"message\":\"no eligible candidate in list\",\"stack\":[\"select_eligible_dasher.py:44\",\"pick_best_from_candidates.py:12, in PickBestFromCandidates()\"]}",
      "bytes_count": 147,
      "truncated": false
    }
  ],
  "unknown_paths": []
}
```

A path that names no step on this trace is left out of `values` and listed in `unknown_paths`.
A path whose only attempts are in flight is left out of `values` too and is not listed in
`unknown_paths`: the path is known and the step ran, and there is nothing captured to return.
`unknown_paths` stays reserved for a path matching no step on the trace.
Here `AAAA` (`ParseAssignRequest`, an `ok` step) is served with `"state": "pending"` and its
`value` present, and the unknown `ZZZZ` is echoed in the server's normalized spelling:

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  "http://127.0.0.1:18200/v1/traces/0f1a2b3c4d5e6f708192a3b4c5d6e7f8/values?path=AAAA&path=&path=ZZZZ" \
  | jq .
```

```json theme={null}
{
  "values": [
    {
      "path": "AAAA",
      "slot": "input",
      "state": "pending",
      "encoding": "json",
      "value": "{\"body\":\"{\\\"order_id\\\":\\\"O-1234\\\",\\\"requested_time\\\":\\\"19:30\\\",\\\"zone\\\":\\\"SF-94107\\\"}\"}",
      "bytes_count": 87,
      "truncated": false
    },
    {
      "path": "AAAA",
      "slot": "output",
      "state": "pending",
      "encoding": "json",
      "value": "{\"order_id\":\"O-1234\",\"requested_time\":\"19:30\",\"zone\":\"SF-94107\"}",
      "bytes_count": 64,
      "truncated": false
    },
    {
      "path": "",
      "slot": "error",
      "state": "stored",
      "encoding": "json",
      "value": "{\"message\":\"no eligible candidate in list\",\"stack\":[\"assign_dasher_handler.py:8\",\"assign_dasher.py:20\"]}",
      "bytes_count": 104,
      "truncated": false
    },
    {
      "path": "",
      "slot": "input",
      "state": "stored",
      "encoding": "json",
      "value": "{\"method\":\"POST\",\"order_id\":\"O-1234\",\"path\":\"/delivery/assign-dasher\"}",
      "bytes_count": 70,
      "truncated": false
    }
  ],
  "unknown_paths": [
    "ZZZQ"
  ]
}
```

Branch on the presence of `value`. A slot that never formed (`redacted`, `too_large`,
`not_captured`), or that resolves to `expired` at read time, appears with `path`, `slot`, and
`state` only. More than 8 distinct paths is rejected:

```http theme={null}
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
X-Ratelimit-Limit: 20
X-Ratelimit-Remaining: 36
X-Ratelimit-Reset: 1
X-Request-Id: 7f1e45f313a895b4
Date: Mon, 14 Sep 2026 09:00:09 GMT
Content-Length: 81

{"error":{"code":"too_many_paths","message":"more than 8 distinct path values"}}
```

## GET /v1/traces/\{id}/related

Lists the traces in the same team that share a tag with this one within a time window. Sharing a
tag means both traces carry the same key and the same value, and nothing beyond that: it never
means one called the other. The route takes no `?team=` and is always scoped to the trace's own
team.

| Parameter | Meaning |
| - | - |
| `window` | A duration (`90m`, `24h`) or a day count (`7d`) ending now. Default `24h`, maximum `7d`. |
| `limit`, `cursor` | Pagination, default 50, maximum 500, sorted by `started_at` descending. |

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  http://127.0.0.1:18200/v1/traces/0f1a2b3c4d5e6f708192a3b4c5d6e7f8/related | jq .
```

```json theme={null}
{
  "relation": "shares_tag",
  "searched_tag_keys": [
    "order_id"
  ],
  "window": {
    "since": "2026-09-13T09:00:09.291Z",
    "until": "2026-09-14T09:00:09.291Z"
  },
  "items": [],
  "limit": 50,
  "next_cursor": null
}
```

The conformance trace is the only trace on its team, so `items` is empty, and `searched_tag_keys`
still names the keys the search used. An empty result therefore stays distinguishable from a
service that sends no tags or spells a key differently.

Team `demo` on the same server holds three `delivery-service` traces for order `o-5007`, one
each from its `assign`, `quote`, and `track` operations, all tagging `order_id`. Anchored on one
of the three, the route returns the other two:

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  http://127.0.0.1:18200/v1/traces/8e56048f08e23177606dce55013576f6/related | jq .
```

```json theme={null}
{
  "relation": "shares_tag",
  "searched_tag_keys": [
    "order_id"
  ],
  "window": {
    "since": "2026-09-13T09:00:09.323Z",
    "until": "2026-09-14T09:00:09.323Z"
  },
  "items": [
    {
      "trace_id": "b5dfdf36f4e73f1910cc98d89b882e79",
      "service": "delivery-service",
      "environment": "dev",
      "shared_tags": {
        "order_id": "o-5007"
      },
      "started_at": "2026-09-14T08:55:35.186Z",
      "root_status": "ok"
    },
    {
      "trace_id": "c6a3117022649548f691766308a84347",
      "service": "delivery-service",
      "environment": "dev",
      "shared_tags": {
        "order_id": "o-5007"
      },
      "started_at": "2026-09-14T08:55:35.125Z",
      "root_status": "ok"
    }
  ],
  "limit": 50,
  "next_cursor": null
}
```

Each item carries `trace_id`, `service`, `environment` (when the daemon sent one), `shared_tags`
(the pairs both traces carry), `started_at`, and `root_status` once the root attempt's
completion record has arrived.
Both rows name the anchor's own service, because all three runs are that one service: a shared
tag relates requests and says nothing about how many services they crossed.

`window` is measured back from the time of the request, not from the anchor trace's own
`started_at`, and a value above the maximum is applied as 7 days:

```bash theme={null}
curl -s -H "Authorization: Bearer $READ_TOKEN" \
  "http://127.0.0.1:18200/v1/traces/0f1a2b3c4d5e6f708192a3b4c5d6e7f8/related?window=8d" \
  | jq '.window'
```

```json theme={null}
{
  "since": "2026-09-07T09:00:09.303Z",
  "until": "2026-09-14T09:00:09.303Z"
}
```


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