Skip to main content
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. 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.
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 flattened into the row. A tag_value search without tag_key adds matched_tag_keys to each row:
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:

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:
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. 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”.
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). 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:
Passing that cursor back resumes the same ordering at the fourth step:
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:
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, which serves the example calls of one function.
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:
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:
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:

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