New Sign up free, 10 calls on us. Up to $1, no card needed.

Request Headers

Three optional headers the gateway understands: two carry your own trace context through to our logs, one keeps a conversation on the same upstream so prompt caching keeps hitting. Send none of them and nothing changes.

Header What the gateway does Use it for
X-Trace-Id Echoed back on the response and stored on the call, which groups it with the other calls of the trace. Tying a call back to your own tracing system.
X-Span-Id Echoed back on the response and stored on the call; it names the call in the trace’s waterfall. Identifying one span inside that trace.
X-Session-Id Echoed back and stored on the call. Consecutive requests carrying the same value are pinned to the same channel and the same upstream key. Keeping prompt caching warm across a conversation.
X-Request-ID Not taken from you. Your value comes back as X-Client-Request-ID; the X-Request-ID on the response is always the gateway's own UUIDv7. The gateway's identity for the call - quote it in a support ticket.

Sending them

curl https://synthorai.io/v1/chat/completions \
  -H "Authorization: Bearer $SYNTHORAI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Session-Id: conv-8f3a2b" \
  -H "X-Trace-Id: 7c1d9e40f2b84a17" \
  -H "X-Span-Id: 2f9b41c8" \
  -d '{
    "model": "claude-sonnet-5",
    "messages": [{"role": "user", "content": "Hello"}]
  }'

Headers other tools already send

When a call carries none of our three headers, the gateway reads the equivalent header that a coding agent, an SDK or a tracing standard already sends, so those runs are grouped in Traces without any change on your side.

Header Read as Sent by
X-Claude-Code-Session-Id Session Claude Code
X-Litellm-Session-Id Session LiteLLM SDK, OpenHands
Session-Id Session OpenAI Codex CLI
Helicone-Session-Id Session Helicone
X-OpenWebUI-Chat-Id Session Open WebUI
X-Opencode-Session-Id Session OpenCode
baggage: session.id=… Session W3C Baggage (OpenTelemetry)
baggage: gen_ai.conversation.id=… Session W3C Baggage (OpenTelemetry)
baggage: langfuse_session_id=… Session Langfuse SDK
X-Session-Affinity Session OpenClaw, OpenCode, Kilo Code
X-Litellm-Trace-Id Trace LiteLLM SDK
X-Portkey-Trace-Id Trace Portkey
traceparent Trace; its parent id is the span W3C Trace Context (OpenTelemetry)
X-Claude-Code-Prompt-Id Trace Claude Code
X-Interaction-Id Trace GitHub Copilot
X-Codex-Turn-Metadata Trace OpenAI Codex CLI
baggage: langfuse_trace_id=… Trace Langfuse SDK
X-Portkey-Span-Id Span Portkey
X-OpenWebUI-User-Id End user (the _user label) Open WebUI
baggage: user.id=… End user (the _user label) W3C Baggage (OpenTelemetry)
baggage: langfuse_user_id=… End user (the _user label) Langfuse SDK

Our own header always wins when both are present. Session-Id and X-Codex-Turn-Metadata are read only from the Codex CLI, and X-Interaction-Id only from GitHub Copilot, recognised by their User-Agent. A call with no trace id at all gets the gateway’s request id back as X-Trace-Id.

On /v1/chat/completions and /v1/responses the session id can also go in the body, as session_id (with the OpenAI SDK, in extra_body). A header wins over it, and the field is removed before the request goes to the provider.

OpenClaw sends traceparent on every model call, so its runs are grouped as traces as they are; for sessions, turn on compat.sendSessionAffinityHeaders for the model. Open WebUI sends the chat id and the user id once ENABLE_FORWARD_USER_INFO_HEADERS is on; the name and e-mail headers that come with them are not read. An agent that sends none of these (Cursor, Aider, Cline, Continue and most agent frameworks) needs X-Session-Id added to its custom headers.

W3C Trace Context and Baggage

traceparent gives the trace and, as its parent id, the span; an OpenTelemetry HTTP instrumentation adds it on its own. Everything else travels in baggage, under OpenTelemetry’s names: session.id or gen_ai.conversation.id for the session, gen_ai.agent.id and gen_ai.agent.name for the agent, user.id for the end user. Your code has to put them there: an OpenTelemetry SDK does not copy span attributes into baggage. An agent name is kept as the agent’s type and takes letters, digits, ., _ and -. tracestate is not read.

Agents inside a session

A coding agent that starts sub-agents says which agent made each call. The gateway keeps that with the call, so a session shows its cost by agent, and a trace shows which calls were compactions or side calls and which tools ran between two calls. None of these headers changes routing: a sub-agent stays on the upstream of its session.

Header Read as Sent by
X-Agent-Id Agent Synthorai
X-Parent-Agent-Id The agent or session that started it Synthorai
X-Claude-Code-Agent-Id Agent Claude Code
X-Claude-Code-Parent-Agent-Id The agent or session that started it Claude Code
Thread-Id Agent OpenAI Codex CLI
X-Codex-Parent-Thread-Id The agent or session that started it OpenAI Codex CLI
baggage: gen_ai.agent.id=… Agent W3C Baggage (OpenTelemetry)
X-Parent-Session-Id The agent or session that started it OpenCode, Kilo Code
X-Opencode-Parent-Session-Id The agent or session that started it OpenCode
X-Claude-Code-Request-Class Kind of call Claude Code
X-Claude-Code-Agent-Type Kind of call Claude Code
X-Claude-Code-Compaction Kind of call Claude Code
X-Claude-Code-Context-Compacted Kind of call Claude Code
X-Openai-Subagent Kind of call OpenAI Codex CLI
baggage: gen_ai.agent.name=… Kind of call W3C Baggage (OpenTelemetry)
X-Claude-Code-Prev-Tool-Durations Tool run times Claude Code

Claude Code sends the agent ids by default. It sends the kind of call and the tool run times only when CLAUDE_CODE_GATEWAY_HINT_HEADERS=1 is set where it runs.

Kept: the ids, an agent’s type, the kind of call, and tool names with their run times in milliseconds. Not kept: the name you gave an agent, and anything a tool read or wrote.

Label a request

Send X-Synthorai-Metadata with a JSON object of strings to say what a request was for: a feature, an environment, one of your users. The labels show on the request in Logs, filter Logs, Traces and Sessions (metadata.feature = checkout), add up under Analytics, Breakdown, and go out with your exports.

curl https://synthorai.io/v1/chat/completions \
  -H "Authorization: Bearer $SYNTHORAI_API_KEY" \
  -H 'X-Synthorai-Metadata: {"feature":"checkout","env":"prod","_user":"u_123"}' \
  -d '{"model":"claude-sonnet-5","messages":[{"role":"user","content":"Hi"}]}'

Up to 10 labels. A key is 1 to 32 of a-z 0-9 _ . -; a value is a string of up to 128 bytes. _user names the end user the request was made for; any other key starting with an underscore is reserved. A label that breaks a rule is dropped and the request still runs: the response names the dropped keys in X-Synthorai-Metadata-Dropped.

Labels are kept with the request for as long as the request log, and filtering by them covers the last 90 days. Do not put prompt text, personal data or secrets in them; a value that looks like a credential is stored as [redacted]. They are never sent to the provider.

Session affinity

A cache entry lives on one API key at the provider. A channel can hold several keys, and without a hint the gateway spreads calls across them - so the second turn of a conversation can land on a key that has never seen your prefix and pays the write cost again.

Send the same value on every call of one conversation, and a different value for a different conversation. Nothing is stored server-side and nothing expires: the value is hashed to pick the channel and the key, so the same value always resolves the same way.

On the two OpenAI-compatible endpoints - /v1/chat/completions and /v1/responses - you can skip the header entirely: if the request body carries prompt_cache_key, the gateway uses that as the affinity key. On /v1/messages and the Gemini endpoints, send the header.

Affinity is best effort: it only decides which channel is picked among equally preferred ones. Failover, rate-limit cooldown, retries and an explicit routing preference all still win over it. A model's list price is the same on every channel we may pick.

Limits

  • 128 bytes each. Longer values are truncated. A value containing control characters or anything outside printable ASCII is dropped entirely - not echoed, not logged. These values reach a response header and a log line, so this keeps header and log injection out.
  • They stop at the gateway. None of the three is forwarded to the model provider - the outbound request carries only authentication, the content type and provider-specific headers.
  • None of them identifies a call for billing. Billing, deduplication and reconciliation all key on the gateway's own X-Request-ID, never on a value you control.

When something goes wrong

  • Quote the X-Trace-Id you sent, or the X-Request-ID from the response - either one lets us find the exact call.
  • Your trace id is on the call in Logs and in Traces, so you can find it yourself before opening a ticket.
  • To confirm affinity is working, send the same long prefix a few times with one X-Session-Id and watch the cache columns in Logs: the first call writes to the cache, the later ones should read from it. Change the value and the write should come back.

See also: Prompt Caching · Traces and Sessions