Tracing

Group related Gateway requests into one trace with a single HTTP header, no SDK required

Tracing groups the Gateway requests that make up one logical run (an agent loop, a chain, a batch pipeline) into a single trace you can view as a tree. Use it when one user action fans out into several LLM calls and you want to see the whole run in one place: what ran, in what order, what it cost, and where the time went.

Your traffic already flows through Gateway, so there is nothing to install. Add one HTTP header to calls you already make and the requests become spans of a trace. Because tracing is header-based, it works identically on /v1/responses, chat completions, and every SDK-compatible surface, including the OpenAI SDK, the Anthropic SDK, and LangChain pointed at a Gateway base_url.

Trace rollups use the same pipeline that bills you, so the cost you see per trace is the amount you were charged, not an estimate from public price tables.

Trace headers

Four request headers control tracing. Only X-Merge-Trace-Id is required; the rest refine the shape of the trace.

HeaderWhat it doesDefault when absent
X-Merge-Trace-IdGroups every request carrying the same value into one trace. Sending it is what opts the request into tracingRequest is not traced
X-Merge-Parent-Span-IdNests this request under an earlier request in the same trace. The value is the earlier request’s IDSpan sits at the root of the trace
X-Merge-Span-NameHuman-readable label for the span, such as draft or critiqueThe requested model name
X-Merge-Thread-IdGroups many traces into one long-running conversation, such as a chat session that spans hours or daysNo thread grouping

Header values can be up to 128 characters (64 for X-Merge-Span-Name) from the set A-Z a-z 0-9 . _ : -. An invalid value never fails the request: Gateway drops it silently and the request proceeds untraced, so a malformed ID in your header logic costs you a trace, not an outage.

Use a fresh trace ID for every run, for example run- plus a UUID. Reusing a trace ID across unrelated runs merges them into one trace. A trace groups requests made within roughly 24 hours; longer-lived groupings belong in a thread.

Trace your first run

Generate one trace ID per run and send it on every request in the run. Span names are optional but make the tree much easier to read.

$TRACE_ID="run-$(uuidgen)"
$
$curl https://api-gateway.merge.dev/v1/responses \
> -H "Authorization: Bearer YOUR_API_KEY" \
> -H "Content-Type: application/json" \
> -H "X-Merge-Trace-Id: $TRACE_ID" \
> -H "X-Merge-Span-Name: draft" \
> -d '{
> "model": "gpt-5.2",
> "input": "Draft a reply to this support ticket: ..."
> }'
$
$curl https://api-gateway.merge.dev/v1/responses \
> -H "Authorization: Bearer YOUR_API_KEY" \
> -H "Content-Type: application/json" \
> -H "X-Merge-Trace-Id: $TRACE_ID" \
> -H "X-Merge-Span-Name: critique" \
> -d '{
> "model": "claude-sonnet-4-5",
> "input": "Critique this draft reply: ..."
> }'

A request that carries X-Merge-Trace-Id with no parent header becomes a root span, so a single traced call is the trivial one-span trace. Requests without the header are untouched: they appear in Logs as before with no trace fields.

Nest spans under a parent

By default every traced request sits at the root of its trace, which is often all you need. To build a deeper tree, pass an earlier request’s ID as X-Merge-Parent-Span-Id.

Every Gateway response includes an X-Request-ID response header identifying that request. Capture it and pass it as the parent on follow-up calls:

Python
1draft_response = client.chat.completions.with_raw_response.create(
2 model="gpt-5.2",
3 messages=messages,
4 extra_headers={"X-Merge-Span-Name": "draft"},
5)
6draft_request_id = draft_response.headers["x-request-id"]
7
8critique = client.chat.completions.create(
9 model="claude-sonnet-4-5",
10 messages=critique_messages,
11 extra_headers={
12 "X-Merge-Span-Name": "critique",
13 "X-Merge-Parent-Span-Id": draft_request_id,
14 },
15)

If reading response headers is awkward in your setup, supply your own request ID up front: send X-Request-ID with a UUID you generated on the parent request, then reference that same value as X-Merge-Parent-Span-Id on the children.

Gateway does not verify parent references at write time. A span whose parent never arrives still renders in the trace, attached at the root level.

Automatic child spans

Some Gateway features fan one request out into several provider calls. Those internal calls become child spans automatically, with no instrumentation on your side. A traced Fusion request, for example, shows each panel model’s answer and the judge call as children of your request, so you can see what each panel member cost and how long the judge took.

Traces versus threads

Traces and threads answer different questions, and most confusion with the header contract comes from mixing them up. A trace is one run: a single agent loop, chain invocation, or pipeline execution, finished within a day. A thread is the long-lived conversation those runs belong to: a support ticket, a chat session, a user’s ongoing interaction over days.

Trace (X-Merge-Trace-Id)Thread (X-Merge-Thread-Id)
GroupsRequests into one runRuns into one conversation
LifetimeOne run, requests within ~24 hoursUnbounded, hours to days
StructureParent/child tree via X-Merge-Parent-Span-IdFlat, no hierarchy across runs
RollupsCost, tokens, duration, and error status per runNone, purely a filter dimension
FreshnessMint a new ID for every runReuse the same ID for the conversation’s lifetime

A support agent shows the shape: every time the agent processes a new customer message, mint a fresh trace ID for that turn’s work, and send the same thread ID for the life of the ticket. Filtering Logs by the thread ID then shows every request across all of the ticket’s runs, while each trace ID isolates one turn with its own tree and cost rollup.

Rule of thumb: trace ID answers “what happened inside this one operation”, thread ID answers “show me everything for this conversation”.

Viewing traces in Logs

Tracing lives inside Logs. The filter bar accepts Trace ID, Thread ID, and Span name, so a trace, a conversation, or a recurring step (every critique span across runs) is one filter away. This requires only the View logs permission; see Roles and permissions.

Opening a traced request shows its trace fields (trace ID, span name, thread ID) in the request details, and a Requests in this trace panel listing every request in the run: an indented tree built from the parent links, with each request’s model, duration, and a bar showing when it ran on the trace’s timeline. Failures carry a status badge; parallel steps are visible as overlapping bars. Clicking any request in the panel jumps to its full detail, so you can walk a whole run without leaving Logs.

Request detail also includes a breakdown of where time went inside Gateway (routing, security scanning, provider time to first token), and the request and response payloads when payload logging is enabled for your organization.

The trace panel lists the first 100 requests of a run. Larger runs stay fully searchable through the Trace ID filter.

Tracing from coding agents

Coding agents pointed at Gateway via base_url do not send Merge trace headers on their own, but most send a session identifier, and Gateway turns it into a thread automatically. When a request carries no explicit X-Merge-Thread-Id, Gateway derives one from the tool’s own session header:

ToolSession grouping
Claude CodeAutomatic (x-claude-code-session-id is sent on every request)
Codex CLIAutomatic (session-id)
OpenCodeAutomatic (X-Session-Id)
PiSet "compat": { "sendSessionAffinityHeaders": true } on the provider in models.json
Continue.devSend a header yourself via the model’s requestOptions.headers
Factory DroidSend a header yourself via the custom model’s extraHeaders
CursorNot available (requests route through Cursor’s own backend with no header control)

So a Claude Code, Codex, or OpenCode session pointed at Gateway is already one thread: filter Logs by Thread ID with the tool’s session ID and the whole session’s requests appear together, with zero configuration.

To control the grouping yourself, send an explicit header, which always wins over the derived one. For example, grouping all of one developer’s Claude Code sessions into a single stream:

$export ANTHROPIC_BASE_URL="https://api-gateway.merge.dev/v1/anthropic"
$export ANTHROPIC_CUSTOM_HEADERS="X-Merge-Thread-Id: $(whoami)-claude-code"
$claude

An X-Merge-Trace-Id minted at launch (for example cc-$(uuidgen)) instead turns the session into one trace with cost, token, and duration rollups. Agent-set headers are fixed for the whole session and cannot vary per request, so spans from a coding agent sit side by side rather than nesting.

Next steps