Skip to main content

Tag and filter traces

What you'll do: attach tags and metadata to traces, filter your trace list by tag or metadata, and discover which tags and metadata keys your team is already using.

Tags are short labels (prod, nl, staging) that help you group and filter traces. Metadata is a freeform JSON object ({"env": "prod", "lang": "nl", "userId": "abc123"}) that lets you attach structured context without changing your trace schema. Both are set on creation and merge when you append to an existing trace — tags are union-merged (deduplicated), metadata is shallow-merged (new keys win).

Full runnable script

A standalone script exercising this whole flow lives at scripts/guides/tag-and-filter-traces on GitHub — Python and TypeScript versions.

1. Set tags and metadata when creating a trace​

The fastest way to attach tags is through the gateway headers on every completion call. This works from any HTTP client — no SDK changes needed.

Pass x-trace-tags (comma-separated) and x-trace-metadata (JSON string) as request headers:

curl -X POST "$ACRUXCORE_BASE_URL/chat/completions" \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "x-trace-tags: prod, nl" \
-H 'x-trace-metadata: {"env": "prod", "lang": "nl"}' \
-d '{
"model": "support-model",
"messages": [{"role": "user", "content": "Hello"}]
}'

The trace created by this call will have tags: ["prod", "nl"] and metadata: {"env": "prod", "lang": "nl"}.

2. Filter traces by tag​

Once traces have tags, you can filter them. Tags use AND matching — a query for prod and nl returns only traces that carry both.

Repeat the tags parameter for AND matching:

curl -g -H "Authorization: Bearer $ACRUXCORE_API_KEY" \
"$ACRUXCORE_BASE_URL/traces?tags=prod&tags=nl"
{
"data": [
{
"id": "1b2d4e99-8bfb-49f0-af63-97ae6da301f0",
"name": "support-query",
"status": "ok",
"tags": ["prod", "nl"],
"spanCount": 1,
"startedAt": "2026-07-05T12:25:11.000Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}
URL-encode brackets

curl treats [ and ] in URLs as globbing syntax. Always pass -g to disable that, or URL-encode the brackets (%5B/%5D).

3. Filter traces by metadata​

Metadata filtering uses bracket notation and also AND matching — every supplied key/value pair must be present in the trace's metadata.

curl -g -H "Authorization: Bearer $ACRUXCORE_API_KEY" \
"$ACRUXCORE_BASE_URL/traces?metadata[env]=prod&metadata[lang]=nl"
{
"data": [
{
"id": "1b2d4e99-8bfb-49f0-af63-97ae6da301f0",
"name": "support-query",
"status": "ok",
"tags": ["prod", "nl"],
"spanCount": 1,
"startedAt": "2026-07-05T12:25:11.000Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}

4. Discover which tags and metadata keys exist​

You don't need to remember every tag by hand. The facets endpoint returns all distinct tags and metadata keys your team has used, and a second endpoint returns all distinct values for a single metadata key.

Get all tags and metadata keys:

curl -H "Authorization: Bearer $ACRUXCORE_API_KEY" \
"$ACRUXCORE_BASE_URL/traces/facets"
{
"tags": ["prod", "staging", "nl", "de", "support", "billing"],
"metadataKeys": ["env", "lang", "userId", "sessionId"]
}

Get all values for one metadata key:

curl -H "Authorization: Bearer $ACRUXCORE_API_KEY" \
"$ACRUXCORE_BASE_URL/traces/facets/values?key=env"
{
"values": ["prod", "staging", "dev"]
}

The response carries no key field — only values. A caller that expects the request's key echoed back in the body won't find it there.

5. Search what was actually said​

Tags and metadata only find a trace if someone thought to label it. Often the only thing you remember is the conversation itself — a customer said they wanted to visit London, and you need that run.

q searches the captured request and response as well as trace and span names and attributes:

curl -H "Authorization: Bearer $ACRUXCORE_API_KEY" \
"$ACRUXCORE_BASE_URL/traces?q=london"

When a word appears on both sides of the exchange, q_in picks one:

  • q_in=input — only what was sent to the model.
  • q_in=output — only what the model answered.
  • q_in=name — only trace and span names, no payload text.
curl -H "Authorization: Bearer $ACRUXCORE_API_KEY" \
"$ACRUXCORE_BASE_URL/traces?q=london&q_in=input"
Payload capture is required

Only traces whose payloads were captured can be searched by content. If a search returns nothing you expected, check payload capture is on — see Configure trace payload capture.

6. Filter by prompt​

prompt_version_id narrows to one exact version. prompt_id matches every version of a prompt, which is what you want when several versions have shipped and you are asking how the prompt as a whole behaves:

curl -H "Authorization: Bearer $ACRUXCORE_API_KEY" \
"$ACRUXCORE_BASE_URL/traces?prompt_id=$PROMPT_ID"

A trace matches when at least one of its spans used the prompt. One run can use several prompts, so the same trace can appear under more than one prompt_id.

7. Filter the feedback feed with the same words​

Everything above also filters GET /traces/feedback, the team-wide feed of ratings and comments — the list you work through when deciding which production traffic becomes evaluation data:

curl -H "Authorization: Bearer $ACRUXCORE_API_KEY" \
"$ACRUXCORE_BASE_URL/traces/feedback?prompt_id=$PROMPT_ID&rating=down&has_comment=true"

Four filters there describe the feedback row rather than its trace:

  • rating — up, down, or none.
  • source — user, developer, end_user, api.
  • label — exact match.
  • has_comment — true for rows carrying a written critique.

The same criteria object builds a dataset in one call — see Improve a prompt from feedback.

8. Filter in the dashboard, and save a view​

Everything above is one input box in the dashboard. Type into the filter bar on Traces or Feedback and it suggests the filter you are reaching for:

  • tag: and meta.<key>: suggest values your team has actually used.
  • prompt: searches your prompts by name, so you never need a version id.
  • input:, output: and name: narrow a text search to one part of the trace.
  • status:, rating:, source:, comment: and error_type: suggest their allowed values.
  • error_code: suggests the failure slugs your own tools have declared — see Report a tool failure from your own code.
  • score>, latency>, cost> and tokens> take a number.
  • Anything with no prefix becomes a plain search across names, input and output.

Each committed filter becomes a chip. Backspace on an empty box removes the last one, and Clear all filters removes them all. The filters live in the URL, so a filtered list can be linked and shared, and back/forward work.

You can type several filters in one go — rating:down comment:yes commits both chips at once. A value with a space in it still counts as one filter, because tag alone names nothing: tag:my tag is a single tag called "my tag". If a value is one the filter does not accept, nothing is applied, the text stays in the box, and the bar says what that filter takes.

Save the ones you use often. Press Views → Save current filters, give it a name, and it is one click for everyone on the team from then on. A view stores the filter string exactly as it was, and any member can rename or delete it.

The same bar appears inside Add example → From feedback on a dataset. Filter there, and Add all N matching pulls in every row the criteria select — including rows on pages you never opened.

9. Group traces under a session​

Tags and metadata live on individual traces. When you want several traces to roll up as one conversation — a support ticket, a multi-turn chat, a single agent run — give them a shared session id. All traces carrying the same session id appear together under Observability → Sessions in the dashboard.

A session id is just a string you choose (support-ticket-421, agent-run-abc). It has no tags or metadata of its own — filter your traces by tag first, then the matching traces group under whatever sessions they belong to.

Pass x-session-id as a request header on any gateway call:

curl -X POST "$ACRUXCORE_BASE_URL/chat/completions" \
-H "Authorization: Bearer $ACRUXCORE_API_KEY" \
-H "Content-Type: application/json" \
-H "x-session-id: support-ticket-421" \
-H "x-trace-tags: prod, support" \
-d '{
"model": "support-model",
"messages": [{"role": "user", "content": "Hello"}]
}'

Send the same x-session-id on every call that belongs to the conversation; each trace keeps its own tags and metadata but groups under the one session.

Read it back from a trace with hub.traces.get():

detail = await hub.traces.get(trace_id)
print(detail.trace.session_id) # "support-ticket-421"
const detail = await hub.traces.get(traceId);
console.log(detail.trace.sessionId); // "support-ticket-421"

For deeper coverage of sessions — listing them, filtering traces by session, and reading aggregate stats — see the Using sessions and traces guide.

Merge behavior​

When you append to an existing trace (sending a second request with the same trace id), tags and metadata merge rather than overwrite:

  • Tags are union-merged — if the trace already has ["prod"] and you send ["nl"], the result is ["prod", "nl"]. Duplicates are removed.
  • Metadata is shallow-merged — existing keys stay, new keys are added, and overlapping keys take the new value.

This means you can set base tags once (e.g. prod) and add per-request tags (e.g. support, billing) without losing either.

Summary​

WhatHow
Set tags on a tracex-trace-tags: a, b header (gateway) or tags: [...] in trace input (SDK)
Set metadata on a tracex-trace-metadata: {"k":"v"} header (gateway) or metadata: {...} in trace input (SDK)
Filter by tags?tags=a&tags=b (AND match)
Filter by metadata?metadata[k1]=v1&metadata[k2]=v2 (AND match)
Search the conversation?q=london, narrowed with ?q_in=input|output|name
Filter by prompt?prompt_id=<id> (any version) or ?prompt_version_id=<id> (one version)
Filter by how a run failed?error_type=tool_declared, ?error_code=<your slug>, ?has_warning=true
Filter the feedback feedSame params on GET /traces/feedback, plus rating, source, label, has_comment
Save a filter setViews → Save current filters, or POST /trace-views
Discover tagsGET /traces/facets
Discover declared error codesGET /traces/facets (errorCodes)
Discover metadata valuesGET /traces/facets/values?key=<key>
Group traces under a sessionx-session-id: <id> header (gateway) or sessionId / session_id in trace input (SDK)
SDK filtering gap

The Python and Node.js SDKs forward tags and metadata to the gateway on chat() and runToolLoop() calls, but list_traces() / listTraces() do not yet accept tag or metadata filter parameters. Use the REST API directly for filtering until SDK support is added.