Claude Telemetry Reference — Request & Response Parameters, Element by Element
Scenario: LiteLLM proxy (hosted in Azure) receives OpenAI-format requests from clients and forwards them to Claude 3P (third-party) — Claude served via Amazon Bedrock. You want to investigate user problems — were requests successful, what errors/status codes came back, and which parameters you must capture in logs to answer those questions.
Companion page: Logging & extraction tutorial · Last updated: 2026-08-05
Table of Contents
- The Two Wire Formats (Why There Are Two Sets of Parameters)
- Leg A — Client → LiteLLM: Request Parameters (OpenAI format)
- Leg B — LiteLLM → Bedrock: Request Envelope + Claude Body
- Claude Messages API Request Body — Element by Element
- Claude Response Body — Element by Element
- Bedrock Response Metadata (Headers)
- Leg A Response — What the Client Sees (OpenAI format)
- Status Codes & Errors at Every Layer
- "Success Illusions" — 200s That Are Actually Problems
- Symptom → Signal: Diagnosing User Problems
- Capture Checklist — What to Log, by Priority
- Reference Log Record Schema (JSONL)
1. The Two Wire Formats
One user request crosses two different API formats. When you read logs you must know which leg you're looking at, because field names differ for the same concept:
Client ──(OpenAI format)──▶ LiteLLM (Azure) ──(Bedrock envelope + Anthropic Messages format)──▶ Claude on Bedrock
◀─(OpenAI format)── ◀──────(Anthropic Messages response)──────
| Concept | OpenAI format (Leg A) | Anthropic/Bedrock format (Leg B) |
|---|---|---|
| Prompt token count | usage.prompt_tokens |
usage.input_tokens |
| Completion token count | usage.completion_tokens |
usage.output_tokens |
| Why generation stopped | choices[0].finish_reason |
stop_reason |
| Response text | choices[0].message.content |
content[] blocks |
| Model | model |
model (+ Bedrock modelId in the URL) |
| Request ID | id (chatcmpl-...) |
id (msg_...) + x-amzn-RequestId header |
LiteLLM translates between them. Capture both sides where possible — translation is where subtle bugs live (dropped parameters, remapped finish reasons).
Note on "Claude 3P": 3P means third-party — Claude consumed via a partner platform (here: Amazon Bedrock) instead of Anthropic's first-party (1P) API. On 3P, auth is the platform's (SigV4/IAM on Bedrock), model IDs carry a platform prefix (
anthropic.claude-...), quotas are per AWS account+region, and many errors are platform-layer errors that never reached the Claude model. One extra gotcha: model snapshots are deprecated per-platform on their own schedules — a deployment pinned to an old snapshot eventually getsValidationException/404 on every request, a classic "all users suddenly failing" root cause.
2. Leg A — Client → LiteLLM: Request (OpenAI format)
What your users' applications send. Endpoint: POST /v1/chat/completions on the proxy.
| Parameter | Type | Required | What it does | Why it matters for investigation |
|---|---|---|---|---|
model |
string | ✅ | LiteLLM alias (e.g. claude-sonnet) — mapped by config.yaml to a real Bedrock model ID |
The alias can be re-pointed silently. A "model got worse" complaint may be an alias change, not the model. Always compare with the response model. |
messages |
array | ✅ | Conversation: [{role: system|user|assistant|tool, content}] |
The actual user input. Needed to reproduce any reported problem. Malformed role sequences cause 400s. |
max_tokens / max_completion_tokens |
int | Anthropic requires it | Output cap | Too low ⇒ truncated answers (finish_reason: length) — one of the most common "the model cut off" complaints. |
temperature |
float 0–2 | ❌ | Sampling randomness | OpenAI range is 0–2, Anthropic is 0–1 — LiteLLM rescales. Note: rejected entirely on the newest Claude models (4.7+). |
top_p |
float | ❌ | Nucleus sampling | On Claude 4+, sending both temperature and top_p errors — a source of mysterious 400s after model upgrades. |
stream |
bool | ❌ | Server-sent events streaming | Streaming failures look different in logs (connection drops mid-stream, partial usage). Log whether the request streamed. |
stop |
string/array | ❌ | Custom stop sequences | Explains outputs that end abruptly with finish_reason: stop. |
tools / tool_choice |
array/obj | ❌ | Function-calling definitions | Schema errors here are a top cause of 400s; also changes response shape (tool_calls). |
n |
int | ❌ | Number of completions | Anthropic doesn't support n>1; LiteLLM behavior varies — a silent-incompatibility candidate. |
user |
string | ❌ | End-user identifier | Capture it. This is how you slice logs per user when investigating "which users are affected." |
metadata / extra_body |
object | ❌ | Free-form tags (LiteLLM passes into spend logs) | Put run IDs, feature names, app versions here — your join key across systems. |
Header: Authorization: Bearer sk-... |
header | ✅ | LiteLLM virtual key | Identifies the team/app. Hashed in spend logs. 401s at this layer never reached Claude at all. |
Header: x-litellm-tags, custom headers |
header | ❌ | Request tagging | Alternative tagging path if body metadata is inconvenient. |
3. Leg B — LiteLLM → Bedrock: Request Envelope
Bedrock wraps the Claude request in AWS plumbing. Two Bedrock APIs exist; LiteLLM uses the InvokeModel path for bedrock/anthropic.* models (the Converse API is an alternative with its own field names — check your LiteLLM config).
Bedrock envelope (outside the Claude body)
| Element | Where | What it is | Investigation value |
|---|---|---|---|
modelId |
URL path | e.g. anthropic.claude-sonnet-5 or a versioned/inference-profile ARN (us.anthropic....) |
The actual model identity. Wrong/retired/not-enabled model IDs ⇒ ValidationException/AccessDeniedException. |
| AWS region | endpoint hostname | e.g. bedrock-runtime.us-east-1.amazonaws.com |
Quotas, latency, and model availability are per-region. Capacity issues often affect one region only. |
| SigV4 signature | headers | AWS IAM auth (not an Anthropic API key) | 403 here = IAM/credentials problem in the proxy, not a user problem. |
anthropic_version |
body | Must be "bedrock-2023-05-31" for InvokeModel |
Missing/wrong ⇒ immediate ValidationException. |
guardrailIdentifier / guardrailVersion |
headers (optional) | AWS Bedrock Guardrails | If configured, Guardrails can block/mask content — a separate refusal source from Claude itself. |
contentType / accept |
headers | application/json |
Rarely an issue; matters for streaming variant (invoke-with-response-stream). |
4. Claude Request Body — Element by Element
The Anthropic Messages API body (inside the Bedrock envelope). This is the definitive list of what can be sent to Claude and what each element means.
| Parameter | Type | Required | Meaning | Importance for problem investigation |
|---|---|---|---|---|
anthropic_version |
string | ✅ (Bedrock) | API version pin | Wrong value ⇒ 400 before the model runs. |
model |
string | ✅ (first-party; on Bedrock it's the modelId) |
Which Claude serves the request | Highest-value field. Pin exact versions in production; log the one from the response. |
max_tokens |
int | ✅ | Hard cap on generated tokens (on thinking models this includes thinking tokens) | Truncation root cause #1. Compare usage.output_tokens vs max_tokens: if equal, the answer was cut. |
messages |
array | ✅ | Alternating user/assistant turns; content is a string or an array of content blocks |
Malformed histories (first message not user, unpaired tool_result) ⇒ 400 invalid_request_error. |
↳ content block text |
obj | — | {type:"text", text:"..."} |
The prompt text itself — capture for reproduction (with PII policy in mind). |
↳ content block image |
obj | — | base64 or URL image source | Oversized images ⇒ 400/413. Image-bearing requests have very different token counts. |
↳ content block document |
obj | — | PDF input | Page/size limits (e.g. 32 MB, page caps) ⇒ 400/413. |
↳ content block tool_use / tool_result |
obj | — | Prior tool calls and their results in history | Every tool_use.id must have a matching tool_result.tool_use_id — mismatch ⇒ 400. |
system |
string/array | ❌ | System prompt (not part of messages) |
Prompt-version changes here explain behavior shifts. Log a hash of it if content is sensitive. |
temperature |
float 0–1 | ❌ | Randomness | Removed on newest models (400 if sent). Relevant to "output is inconsistent" reports. |
top_p, top_k |
float/int | ❌ | Alternative sampling controls | Same removal caveat; temperature+top_p together errors on Claude 4+. |
stop_sequences |
array | ❌ | Custom strings that halt generation | Explains stop_reason: stop_sequence + which string via response stop_sequence. |
stream |
bool | ❌ | SSE streaming | Streamed errors can arrive mid-stream as an error event after a 200 — log stream aborts separately. |
tools |
array | ❌ | Tool definitions: {name, description, input_schema} |
Invalid JSON Schema ⇒ 400. Tool-heavy traffic has different latency/token profiles. |
tool_choice |
object | ❌ | auto / any / {type:"tool", name} / none |
Forced tool choice changes response shape; on Bedrock, forced tool_choice on newest Sonnet requires thinking disabled. |
thinking |
object | ❌ | Extended/adaptive reasoning config ({type:"enabled", budget_tokens} on 3.7/4.x-era; {type:"adaptive"} on 4.6+) |
Thinking consumes max_tokens budget → truncation; wrong shape for the model generation ⇒ 400. |
metadata.user_id |
string | ❌ | Opaque end-user ID passed to Anthropic | Abuse investigation & per-user tracing on the provider side. |
stop_reason-affecting betas / headers (anthropic-beta) |
header | ❌ | Feature flags (first-party API; limited on Bedrock) | A beta header on an unsupported surface ⇒ 400. |
5. Claude Response Body — Element by Element
The Messages API response — this is your primary telemetry payload.
| Field | Type | Meaning | Importance — what it tells you |
|---|---|---|---|
id |
string | Message ID, msg_... |
Provider-side trace ID. Include when escalating to AWS/Anthropic support. |
type |
string | "message" on success, "error" on failure body |
First branch when parsing logs. |
role |
string | Always "assistant" |
Sanity check only. |
model |
string | The model that actually served the request | ⭐ Compare against requested alias — catches silent re-pointing, fallbacks, and A/B routing. Evals are meaningless without it. |
content |
array | Content blocks produced | See sub-rows. Empty content + a refusal stop reason = blocked request. |
↳ {type:"text", text} |
obj | The answer text | Capture (subject to privacy policy) — required to judge quality later. |
↳ {type:"tool_use", id, name, input} |
obj | Model wants a tool called | Malformed input JSON or wrong tool name = model-side quality issue worth tracking as a rate. |
↳ {type:"thinking", thinking, signature} |
obj | Reasoning block (thinking-enabled models) | Thinking length explains latency & token spend; must be passed back on multi-turn. |
stop_reason |
string | Why generation stopped | ⭐ The single most diagnostic field. See table below. |
stop_sequence |
string/null | Which custom stop sequence fired | Only set when stop_reason: "stop_sequence". |
usage.input_tokens |
int | Prompt tokens billed | Cost & context-pressure tracking; sudden jumps = prompt regression. |
usage.output_tokens |
int | Generated tokens | If == max_tokens ⇒ truncation. Distribution shifts signal behavior change. |
usage.cache_creation_input_tokens |
int | Tokens written to prompt cache | Cache economics; caching support varies by platform. |
usage.cache_read_input_tokens |
int | Tokens served from cache | Cache hit-rate; a drop to 0 = silent cache invalidator (changed prefix). |
stop_reason values — learn these cold
| Value | Meaning | Is it a problem? |
|---|---|---|
end_turn |
Model finished naturally | ✅ Normal |
max_tokens |
Hit the output cap | ⚠️ Truncation — user saw an incomplete answer. Track its rate; alert on spikes. |
stop_sequence |
A custom stop string fired | Usually fine; verify it wasn't accidental |
tool_use |
Model is requesting a tool call | Normal in agentic flows; a problem if your client doesn't handle it |
pause_turn |
Long server-tool turn paused (must be resumed) | ⚠️ If not resumed, users get silently incomplete answers |
refusal |
Safety refusal (Claude 4.5+ surfaces this explicitly) | ⚠️ HTTP 200 but no useful content — invisible unless you log it |
model_context_window_exceeded |
Input exceeded the context window (4.5+) | ⚠️ Conversation too long — needs compaction/trimming |
Streaming responses (if stream: true)
Events arrive as SSE: message_start → content_block_start → content_block_delta* → content_block_stop → message_delta (carries final stop_reason + usage) → message_stop. For telemetry: the final usage and stop_reason are in message_delta, and an error event or dropped connection can occur mid-stream after the 200 status — log stream completion separately from HTTP status.
6. Bedrock Response Metadata
Bedrock adds HTTP headers around the Claude body — free telemetry, capture them:
| Header | Meaning | Value for investigation |
|---|---|---|
| HTTP status code | 200 / 4xx / 5xx | First-line success indicator (but see §9). |
x-amzn-RequestId |
AWS request ID | ⭐ The ID AWS support needs. Join key for CloudTrail / Bedrock invocation logs. |
X-Amzn-Bedrock-Input-Token-Count |
Input tokens | Cross-check against body usage — mismatches indicate translation bugs. |
X-Amzn-Bedrock-Output-Token-Count |
Output tokens | Same. |
X-Amzn-Bedrock-Invocation-Latency |
Model latency in ms, as measured by Bedrock | Splits latency: total client latency − this = network + proxy overhead. Essential for "it's slow" complaints. |
Bonus: if you enable Bedrock Model Invocation Logging (an AWS account setting), Bedrock itself writes full request/response JSON to CloudWatch Logs or S3 — an independent record you can reconcile against LiteLLM's logs.
7. Leg A Response — OpenAI Format
What LiteLLM returns to the client after translating Claude's response:
| Field | Meaning | Notes |
|---|---|---|
id |
chatcmpl-... — LiteLLM-generated |
Different from the Anthropic msg_... ID — log the mapping. |
object |
chat.completion / chat.completion.chunk |
— |
created |
Unix timestamp | — |
model |
Model name as LiteLLM reports it | May be the alias or the resolved model — verify which your version emits. |
choices[0].message.content |
Answer text | — |
choices[0].message.tool_calls |
Translated tool_use blocks |
OpenAI shape: {id, type:"function", function:{name, arguments}} (arguments is a JSON string). |
choices[0].finish_reason |
Translated stop reason | Mapping: end_turn→stop, max_tokens→length, tool_use→tool_calls, stop_sequence→stop. ⚠️ Nuanced reasons (refusal, pause_turn, model_context_window_exceeded) don't survive translation cleanly — capture the raw stop_reason on the Bedrock leg or you lose them. |
usage.prompt_tokens / completion_tokens / total_tokens |
Token counts | Renamed from input_tokens/output_tokens. |
Header x-litellm-* (e.g. x-litellm-call-id, x-litellm-model-id, x-litellm-response-cost) |
LiteLLM trace headers | ⭐ x-litellm-call-id is the join key into LiteLLM spend logs — have clients log it. |
8. Status Codes & Errors at Every Layer
A failed user request can die at three different layers. The status code alone doesn't tell you where — the error body does.
8.1 Errors generated by LiteLLM itself (request never reached Bedrock)
| Status | Cause | Body signature |
|---|---|---|
| 401 | Invalid/expired virtual key | OpenAI-style {"error": {...}} mentioning auth |
| 400 | Budget exceeded, blocked model, invalid request | LiteLLM message (budget/team text is a giveaway) |
| 429 | LiteLLM-configured TPM/RPM limit for the key/team | Rate-limit message referencing key/team limits |
| 500 | Proxy bug, DB down, config error | Stack trace in container logs (Azure Log Analytics) |
| 408/timeout | LiteLLM request_timeout fired |
Timeout message; check proxy timeout config vs long generations |
8.2 Errors from AWS Bedrock (request reached AWS, maybe not the model)
| AWS exception | HTTP | Meaning | Typical root cause |
|---|---|---|---|
ValidationException |
400 | Malformed body / bad modelId / wrong anthropic_version / model access not granted in this region |
Retired model ID; missing model-access grant; translation bug |
AccessDeniedException |
403 | IAM policy or model access denied | Proxy's AWS credentials/role misconfigured |
ResourceNotFoundException |
404 | Model ID doesn't exist in this region | Typo'd/retired model, wrong region |
ThrottlingException |
429 | Bedrock account-level TPM/RPM quota exceeded | Traffic spike; quota too low; no provisioned throughput |
ModelTimeoutException |
408 | Model took too long | Very long generations; retry usually works |
ModelNotReadyException |
429 | On-demand model warming up | Transient; retry |
ServiceQuotaExceededException |
400 | Hard service quota hit | Request a quota increase |
ModelErrorException |
424 | The model itself errored | Provider-side incident |
InternalServerException |
500 | AWS-side failure | Retry with backoff; check AWS health |
ServiceUnavailableException |
503 | Bedrock capacity | Retry with backoff; consider cross-region failover |
8.3 Errors from the Anthropic API itself (for comparison / first-party leg)
Anthropic's error body is always:
{
"type": "error",
"error": { "type": "rate_limit_error", "message": "..." },
"request_id": "req_..."
}
| Status | error.type |
Meaning |
|---|---|---|
| 400 | invalid_request_error |
Bad params (e.g., budget_tokens ≥ max_tokens, bad roles, removed params on newer models) |
| 401 | authentication_error |
Bad API key |
| 403 | permission_error |
Key lacks access to model/feature |
| 404 | not_found_error |
Retired/typo'd model ID — the "everyone broke overnight" failure mode |
| 413 | request_too_large |
Payload too big |
| 429 | rate_limit_error |
RPM/TPM/TPD exceeded — check retry-after header |
| 500 | api_error |
Provider-side error |
| 529 | overloaded_error |
Capacity — back off and retry |
8.4 How LiteLLM maps errors back to clients
LiteLLM catches provider exceptions and re-raises them as OpenAI-style errors with the same status code where possible (ThrottlingException → 429 RateLimitError, etc.). The original provider message is usually embedded in the error text — capture the full error body, not just the code, or you can't tell a LiteLLM 429 (key budget) from a Bedrock 429 (AWS quota). Also log LiteLLM's retry/fallback events: a request that succeeded on attempt 3 to a fallback model looks like plain success unless retries are logged.
9. "Success Illusions"
The cases that make "were all requests successful?" a harder question than counting 200s:
| Looks like | Actually is | How to detect in logs |
|---|---|---|
| HTTP 200 | Truncated answer | stop_reason == "max_tokens" / finish_reason == "length" |
| HTTP 200 | Refusal (no useful content) | stop_reason == "refusal", or refusal-style text with near-zero output tokens |
| HTTP 200 | Guardrails intervention (Bedrock Guardrails masked/blocked) | Guardrail trace fields in Bedrock response; output replaced by canned text |
| HTTP 200 | Empty/whitespace content | content empty or output_tokens ≤ 2 |
| HTTP 200 (stream) | Stream died mid-answer | No message_stop event; client-side abort; missing final usage |
| HTTP 200 | Wrong model served | Response model ≠ expected pin |
| HTTP 200 | Succeeded only after retries/fallback | LiteLLM retry logs; latency outlier with fallback model in response |
| HTTP 200 | Tool call the client never executed | stop_reason == "tool_use" with no follow-up request in logs |
Definition worth adopting: successful request = HTTP 2xx and terminal stop_reason in an accepted set (end_turn, stop_sequence, expected tool_use) and non-empty content and stream completed. Compute this as a derived boolean in your log pipeline and trend it.
10. Symptom → Signal: Diagnosing User Problems
| User complaint | First fields to check | Likely causes |
|---|---|---|
| "It just fails" | HTTP status + error body layer (§8) | Auth (401), quota (429), retired model (400/404) |
| "Answers are cut off" | stop_reason / output_tokens vs max_tokens |
max_tokens too low; thinking eating the budget |
| "It's slow" | X-Amzn-Bedrock-Invocation-Latency vs total latency; retry count; TTFT |
Proxy overhead, retries, long thinking, cold caches, region |
| "It refuses to answer" | stop_reason == refusal; Guardrails trace |
Safety classifiers, Guardrails config, prompt phrasing |
| "Quality got worse" | Response model, system-prompt hash, config-change timeline |
Alias re-point, model snapshot change, prompt edit |
| "Random errors sometimes" | 429/503/529 rate over time by region | Quota exhaustion at peak; provider incidents |
| "Works for me, not for them" | user / key ID slices; per-key budgets & limits |
Per-key budget exhausted or key-level rate limit |
| "Tool calls don't work" | tools schema in request; tool_use.input parse errors; unpaired tool_result |
Schema errors (400), client not handling tool_calls |
11. Capture Checklist
What to actually capture, by priority. P0 = you cannot investigate incidents without it.
P0 — always capture, every request
- Timestamp (request start + end) and computed latency
- HTTP status code + full error body (type, message, which layer)
x-litellm-call-id, Anthropic/Bedrock messageid,x-amzn-RequestId— the three join keys- Requested model (alias) and response
model(actual) stop_reason(raw, from the Bedrock leg — not just the translatedfinish_reason)usage: input_tokens, output_tokens (+ cache tokens if caching)max_tokensrequested (to compute truncation)- Virtual key / team ID (hashed) +
userend-user ID - Streamed? (bool) + stream completed? (bool)
- Retry count / fallback-model-used flag
P1 — strongly recommended
- Request
metadatatags (run ID, app version, feature, prompt-template hash) - AWS region; Bedrock latency header; TTFT for streams
temperature/top_p/thinkingconfig actually sent (sampling params explain variance)- Tool names requested +
tool_choice; whether response containedtool_calls - Cost (LiteLLM
spend)
P2 — payloads (subject to privacy review)
- Full
messagesand responsecontent(or sampled %, or redacted) — required for quality evals and reproduction, but the highest privacy risk. Alternative: store payloads only for errored and flagged requests, sample 1–5% of successes.
12. Reference Log Record Schema
A single JSONL record per request that answers ~95% of investigation questions:
{
"ts_start": "2026-08-05T09:14:03.120Z",
"ts_end": "2026-08-05T09:14:07.891Z",
"latency_ms": 4771,
"ttft_ms": 640,
"litellm_call_id": "d3b9...",
"anthropic_msg_id": "msg_01AB...",
"amzn_request_id": "f1e2...",
"requested_model": "claude-sonnet",
"served_model": "anthropic.claude-sonnet-5",
"region": "us-east-1",
"http_status": 200,
"error_layer": null,
"error_type": null,
"error_message": null,
"stop_reason": "max_tokens",
"finish_reason": "length",
"derived_success": false,
"truncated": true,
"input_tokens": 1834,
"output_tokens": 1024,
"max_tokens": 1024,
"cache_read_input_tokens": 1500,
"cost_usd": 0.0209,
"stream": true,
"stream_completed": true,
"retries": 0,
"fallback_used": false,
"api_key_hash": "a1b2...",
"end_user": "user-4711",
"tags": { "app": "support-bot", "prompt_version": "v12", "env": "prod" },
"messages_ref": "blob://payloads/2026-08-05/d3b9....json"
}
Note the pattern in the last line: metrics inline, payloads referenced (stored separately in blob storage with tighter access control). That keeps your queryable log lean and your PII surface small.
See the companion page for how to extract these logs from Azure (KQL, Python SDK, exports) and how to turn them into evaluation datasets: Claude + LiteLLM on Azure — Logging & Evaluation Tutorial.