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 verbatim on the response; written to the access log. | Tying a call back to your own tracing system. |
X-Span-Id | Echoed back verbatim on the response; written to the access log. | Identifying one span inside that trace. |
X-Session-Id | Echoed back. 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"}]
}' 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-Idyou sent, or theX-Request-IDfrom the response - either one lets us find the exact call. - Your trace ID also shows on the request in Usage Analytics, so you can find the call yourself before opening a ticket.
- To confirm affinity is working, repeat the same long prefix a few times with one
X-Session-Idand watch the Cache R and Cache W columns in Usage Analytics: 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 · Usage Analytics