Skip to content

Mapping Request ID Tracing Between Providers for Support Tickets

9 min read · updated August 11, 2026

Every support ticket about a bad inference starts with “can you give us the request ID”. During a migration you are filing more of those tickets than usual, against an API whose identifier is in a different place from the one you are used to.

Where the identifier lives

There is no cross-provider convention, so this is a lookup rather than a rule. As documented at the time of writing:

  • OpenAI. The x-request-id response header. The TypeScript and Python SDKs surface it as a _request_id property on the response object; .withResponse() returns { data, request_id } together, and .asResponse() hands you the raw Response so you can read any header.
  • Anthropic. The request-id header — note the hyphen and the absence of the x- prefix — with values like req_018EeWyXxfu5pfWkrYcMdjWG. The Python and TypeScript SDKs expose _request_id; the C#, Go, Java and PHP SDKs expose it through their raw-response accessors. Anthropic also puts the value in the error body as a top-level request_id field, which is the one genuinely useful difference on this list. Its errors and request IDs documentation is the primary reference.
  • Azure OpenAI. The apim-request-id header, which is the value that correlates a call to its entry in the service’s own diagnostic logging, alongside the standard Microsoft x-ms- correlation headers.
  • Amazon Bedrock. The AWS request ID, carried in x-amzn-RequestId and surfaced by the AWS SDKs on the response’s metadata object rather than as a field on the model output.
  • Google Gemini. Not a header at all. The GenerateContentResponse body carries a responseId field, documented as identifying each response, next to usageMetadata and modelVersion.

Treat every one of these as an opaque string. Do not parse them, do not validate a prefix, and give the column enough width — a schema that assumed a 36-character UUID because the first provider issued one is a truncation bug waiting for the second.

It is hardest to get exactly when you need it

You want the identifier for the calls that went wrong, and the calls that went wrong are the ones where your logging is least careful. Two patterns cause most of the loss.

The first is logging from the parsed body. If the identifier is header-only, a client that logs response.usage and response.model on success and logs str(exception) on failure has thrown the header away on precisely the failing path. The SDK exception objects generally carry the identifier or the raw response; catch the SDK’s typed error class and pull it off explicitly rather than stringifying.

The second is streaming. The identifier that lives in a header is available the moment response headers arrive, which is before any content. The identifier that lives in the body arrives inside the payload. So a stream that stalls and is abandoned by a client timeout has a header identifier available and a body identifier that never came. If you log identifiers when a call completes, every stalled stream — the exact population you are filing tickets about — is logged with a null. Log the identifier when you first have it and update the record on completion.

Internal retries throw identifiers away

The official SDKs retry transient failures automatically with exponential backoff. That means one call in your code can be several calls on the wire, each with its own identifier, and the value you end up logging belongs to the last attempt — the one that succeeded. When you open a ticket saying “we see intermittent 5xx”, the identifier you attach is from a request that worked.

There are two ways out and they are both deliberate choices. Either turn the SDK’s retry count down to zero and own the retry loop yourself, capturing the identifier per attempt — which also gives you control over backoff and jitter — or use whatever per-attempt hook the client exposes to record the raw response of every attempt. Doing neither is fine right up to the day you need the failing attempt, which during a cutover is usually week one.

Platform-hosted models issue two

When a model vendor’s model is served through a cloud platform, there are two organisations that could look up your request and they index different identifiers. Anthropic documents this explicitly for its models on AWS: responses carry both an AWS request ID in x-amzn-requestid, which is the one indexed in CloudTrail and the one to quote to the platform, and an Anthropic request-id, which is the one to quote to the model vendor. Quote the wrong one and the desk you contacted cannot find anything, which reads to you as an unhelpful support team and is actually a namespace error.

The general rule: an identifier is only meaningful to the party that issued it. Log both, label them by issuer rather than calling them both request_id, and record which support route each one belongs to next to the code that emits it. This is also the point at which which party owes you a response at all stops being obvious.

Carry your own identifier as well

Provider identifiers cannot be your primary key, because a logical operation may involve a retry, a fallback to a second provider, and a summarisation call after it. Generate one identifier per logical operation before you dispatch anything — a UUID or ULID — put it in every log line, and store provider identifiers as a list of attributes against it. Where the provider offers a passthrough metadata field on the request, check its current reference and attach yours there too, so the correlation exists on both sides rather than only in your logs.

log.info("llm.call", extra={
    "call_id": call_id,              # yours, generated before dispatch
    "attempt": attempt,              # 1-based; one row per wire request
    "provider": "provider-a",
    "provider_request_id": rid,      # opaque string, may be null
    "id_issuer": "provider-a",       # who can look this up
    "model": model_id,
    "config_hash": resolved.hash,
})

One row per wire attempt, not one per logical call. It costs a few bytes and it is the difference between a ticket that says “this request failed” and one that says “these three identifiers, in this order, over four seconds”. What to log covers the rest of the record.