Claude API OpenTelemetry Tracing: How to Set It Up (Beta)
TL;DR
The Claude API exports OpenTelemetry traces in beta. Set up SDK spans in TypeScript or Python, add a Console destination for API spans, and count tokens once.
Last updated: October 18, 2026
The Claude API can now send OpenTelemetry traces of your Messages API requests and Claude Managed Agents sessions straight to the observability platform you already run. Anthropic shipped it in beta on October 14, 2026. There are two independent halves: SDK tracing, which you turn on in code by registering a tracer provider, and API tracing, which an org admin turns on by adding a destination in the Claude Console. Run both and a single trace shows your own span, the SDK's client span, and the API's server spans for each model request and server tool underneath it.
If you have been stitching together token counts from response headers and a third-party tracing proxy, this is the first-party version. Below is exactly what each half gives you, setup in TypeScript and Python checked against the official docs, how to verify spans arrive, how to read a Messages API trace versus a Managed Agents trace, and the traps (token double counting and content limits) that will bite you on day one.
What shipped#
From the OpenTelemetry overview, the two sources cover different things:
| What you get | SDK tracing | API tracing |
|---|---|---|
| Messages API and Sessions API requests | Yes | Yes |
| Token usage | Yes | Yes |
| Each model run within a request | No | Yes |
| Managed Agents turns | No | Yes |
| Server tools run by the API | No | Yes |
| Client tools run by the tool runner | Yes | No |
| Tools in a self-hosted sandbox | Yes | No |
| Other API requests, such as file uploads | Yes | No |
| Coverage | Clients using the SDK | Any client, including raw HTTP |
| Setup | Code | Console (or Admin API) |
| Permission needed | None | Admin |
| Platforms | Any (including Bedrock, Google Cloud, Foundry) | Claude Platform, Claude Platform on AWS |
A few facts worth pinning down before you start:
- SDK versions. Native tracing landed in
@anthropic-ai/sdk0.134.0 andanthropicfor Python 1.14.0, both dated October 14, 2026. The current releases are 0.135.0 and 1.15.0. The SDK docs cover seven languages: Python, TypeScript, C#, Go, Java, PHP and Ruby. - It turns itself on. Both changelogs say that if your app already registers a global tracer provider, the SDK creates spans with it after you upgrade, and requests carry trace context headers from your global propagator. If you do not want that yet, pass
openTelemetry: false(TypeScript),open_telemetry=False(Python), or setANTHROPIC_OPEN_TELEMETRY=false. - Schema. Spans use the OpenTelemetry GenAI semantic conventions (
gen_ai.*), pinned to commite57c543bof September 24, 2026, so platforms that already understandgen_ai.*attributes can render them without custom parsing. Anthropic-specific attributes start withanthropic.. The conventions are still in development.
The docs list Arize, Braintrust, Databricks, Datadog, Grafana Cloud, Honeycomb, Langfuse, LangSmith, New Relic, PostHog, Sentry and Splunk as partners, but any backend that accepts OTLP works.
This is separate from Claude Code's own telemetry. For Claude Code, the Agent SDK or Cowork, the docs point to Claude Code monitoring instead.
%% title: How one traced Claude API request nests
flowchart TD
A["Your span: answer_question"] --> B["SDK CLIENT span: anthropic.messages.create"]
B -- "traceparent header" --> C["API SERVER span: anthropic.messages.create"]
C --> D["anthropic.model_request"]
C --> E["Server tool span"]
C --> F["anthropic.model_request"]
A --> G["SDK span: anthropic.custom_tool_use get_weather"]Your exporter ships the SDK spans; the Console destination ships the API spans. Because the SDK sends a W3C traceparent header, the API's SERVER span lands as a child of the SDK's CLIENT span, and your backend stitches them into one tree.
Step 1: SDK tracing in TypeScript#
Upgrade the SDK and, if your app has no OpenTelemetry setup yet, add the Node trace SDK. These commands are from the SDK setup page:
npm install @anthropic-ai/sdk@latest
npm install @opentelemetry/sdk-trace-nodeRegister a tracer provider before the first call you want traced. The SDK picks up the registered provider, so your existing messages.create calls do not change. This example follows the official one, adds the per-client options from the same page, and wraps the call in your own span so the SDK span nests under it:
import Anthropic from "@anthropic-ai/sdk";
import { trace } from "@opentelemetry/api";
import {
ConsoleSpanExporter,
NodeTracerProvider,
SimpleSpanProcessor
} from "@opentelemetry/sdk-trace-node";
// Register before the first traced call. register() also installs the
// context manager, so SDK spans nest under your own spans.
const provider = new NodeTracerProvider({
spanProcessors: [new SimpleSpanProcessor(new ConsoleSpanExporter())]
});
provider.register();
const client = new Anthropic({
openTelemetry: { tracerProvider: provider, traces: { contentMode: "content" } }
});
const tracer = trace.getTracer("my-service");
await tracer.startActiveSpan("answer_question", async (span) => {
try {
const message = await client.messages.create(
{
model: "claude-opus-5-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello, Claude" }]
},
{ openTelemetry: { conversationId: "conv_42" } }
);
console.log(message.content);
} finally {
span.end();
}
});
We type-checked this against @anthropic-ai/sdk 0.135.0, @opentelemetry/sdk-trace-node 2.12.0 and @opentelemetry/api 1.9.1 with tsc --strict. It was not run against the live API. The SDK types contentMode as 'metadata_only' | 'content', so a typo fails at compile time. The console exporter is for seeing spans locally; before production, swap it for an OTLP exporter (for example OTLPTraceExporter from @opentelemetry/exporter-trace-otlp-proto inside a BatchSpanProcessor) pointed at your platform. Pointing it at a local collector first is a cheap sanity check; our local OpenTelemetry traces post walks through that setup.
What the SDK emits: one CLIENT span per API call, named after the method (anthropic.messages.create here). Per the span reference, that span covers the whole request, retries included, and records http.request.resend_count when the SDK resent it. Failed calls carry error.type, such as rate_limit_error, or connection_error and client_timeout_error when the call failed inside the SDK. That is the right shape for debugging the retry behavior we covered in Claude API reliability and error handling.
Step 1b: the same in Python#
pip install --upgrade anthropic
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
This version goes straight to an OTLP exporter and shows the Python-specific way to set a conversation ID:
# trace_claude.py - set up tracing before the first traced call
from anthropic import Anthropic
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
# OTLP/HTTP protobuf exporter. With no endpoint it reads OTEL_EXPORTER_OTLP_ENDPOINT,
# then falls back to http://localhost:4318/v1/traces.
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("support-bot")
# Uses the global provider. metadata_only is the default content mode.
client = Anthropic()
# A client that never emits spans.
quiet_client = Anthropic(open_telemetry=False)
def answer_ticket(ticket_id: str, question: str):
with tracer.start_as_current_span("answer_ticket") as span:
span.set_attribute("app.ticket_id", ticket_id)
# with_options() replaces the whole open_telemetry option,
# so start from the client's resolved settings.
convo = client.with_options(
open_telemetry={**client.open_telemetry, "conversation_id": f"ticket-{ticket_id}"}
)
return convo.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
messages=[{"role": "user", "content": question}],
)
def shutdown_tracing():
# Flush buffered spans before exit.
provider.shutdown()
The with_options detail matters: it replaces the whole open_telemetry option rather than merging it, which is why the example spreads client.open_telemetry first. In anthropic 1.15.0, client.open_telemetry resolves to {'propagation': True, 'traces': {'enabled': True, 'content_mode': 'metadata_only', 'max_content_bytes': 750000}} by default. We checked this file with python -m py_compile and an import run against anthropic 1.15.0, opentelemetry-sdk 1.45.1 and opentelemetry-exporter-otlp-proto-http 1.45.1; no API calls were made.
SDK options#
Each option has the same meaning in every SDK; Python and Ruby use snake_case, TypeScript, Java and PHP use camelCase, and the SDK page lists the C# and Go spellings.
| Option (Python / TypeScript) | Environment variable | Default | Use it to |
|---|---|---|---|
traces.content_mode / traces.contentMode | ANTHROPIC_OPEN_TELEMETRY_TRACES_CONTENT_MODE=content | metadata_only | Include prompts and responses in spans |
traces.max_content_bytes / traces.maxContentBytes | ANTHROPIC_OPEN_TELEMETRY_TRACES_MAX_CONTENT_BYTES | 0.75 MB | Change the content limit per span |
conversation_id / conversationId | none | none | Link several calls into one conversation (gen_ai.conversation.id) |
tracer_provider / tracerProvider | none | the registered provider | Choose which provider gets the spans |
propagation | ANTHROPIC_OPEN_TELEMETRY_PROPAGATION=false | on | Stop sending traceparent and baggage headers |
open_telemetry / openTelemetry | ANTHROPIC_OPEN_TELEMETRY=false | on | Turn spans off for one client |
Three rules worth memorizing. First, passing openTelemetry (or open_telemetry) to the client overrides all the ANTHROPIC_OPEN_TELEMETRY* environment variables as a whole; any nested key you leave out falls back to its default, even if its env var is set. Second, conversation_id is exported in both content modes, so never put personal data or secrets in it. Third, if you omit it, SDK spans for Managed Agents requests to one session thread get that thread's ID automatically.
Step 2: API tracing with a Console destination#
SDK spans stop at the edge of your process. To see inside the request, add a destination; it needs no code change. What you need, per the API setup page:
- The admin role in the Claude Console organization (developers can view destinations, not add them).
- An OTLP/HTTP endpoint that accepts protobuf at an HTTPS URL on port 443, with a hostname rather than an IP address. A self-hosted OpenTelemetry Collector listens on 4318 by default, so put it behind a load balancer or reverse proxy, or configure the receiver for HTTPS on 443.
- Credentials for that endpoint, sent as request headers.
Then:
- Open the OpenTelemetry settings page and click Add destination.
- Provider: pick a listed partner or Custom. Picking a partner makes the Console ask for that platform's details and fill in a content size limit it accepts.
- Name: optional label.
- OTLP endpoint URL: the base URL, for example
https://gateway.example.com/otlp. Do not append/v1/traces; the API adds it. - Request headers: your auth header. The Console does not show the values again after you save.
- Content mode: Metadata only or Full content.
- Workspaces: All (including the Default Workspace and workspaces created later), Selected, or All except.
- Advanced settings (optional): an OTLP traces endpoint URL on the same host if your platform takes traces somewhere other than
/v1/traces, and Maximum content size in MB for Full content. - Save, then click Test in the destination's More actions menu and look for an
anthropic.testspan in your backend.
The Admin API route#
If destinations belong in infrastructure as code, create them through the Admin API with an Admin API key in ANTHROPIC_API_KEY. This version adds the test call to the docs example:
// destination.ts - run with an Admin API key in ANTHROPIC_API_KEY
import Anthropic from "@anthropic-ai/sdk";
const admin = new Anthropic();
const destination = await admin.beta.organization.telemetryDestinations.create({
display_name: "Prod collector",
// Base URL only. The API appends /v1/traces on delivery.
endpoint_url: "https://otel.example.com",
headers: { authorization: `Bearer ${process.env.COLLECTOR_TOKEN}` },
traces: { content_mode: "metadata_only" },
workspaces: { type: "all" }
});
console.log(destination.id); // tdst_...
// Sends one anthropic.test span, once, no retries. Wait 10s between tests.
const check = await admin.beta.organization.telemetryDestinations.test(destination.id);
if (check.result.type === "failure") {
console.error("Test span failed:", check.result.error);
} else {
console.log("Test span delivered");
}
This type-checks against @anthropic-ai/sdk 0.135.0. Per the telemetry destinations API reference, the test endpoint returns 200 either way, with result.type set to success or failure plus an error string such as HTTP error (status 403), and a second test of the same destination within 10 seconds may get a 429. Reserved header names are rejected: host, cookie, user-agent, and anything starting with content-, proxy-, x-forwarded- or anthropic-. Python uses client.beta.organization.telemetry_destinations.create(...) with the same snake_case fields, alongside list, retrieve, update, delete and test.
The raw HTTP version is a POST to https://api.anthropic.com/v1/organizations/telemetry_destinations with the anthropic-beta: telemetry-destinations-2026-08-11 header; the SDK adds that header for you. The ant CLI equivalent is ant --profile admin beta:organization:telemetry-destinations create after ant auth login --profile admin --scope "org:admin".
Nest API spans under your own traces#
The API reads the W3C traceparent header on each request. If you set up SDK tracing and call the SDK inside your own span (as in Step 1), that happens automatically. If the sampled flag in the header is off, the API emits nothing for that request. If your app propagates B3, add the W3C trace context propagator alongside it. With the MCP connector, the API also passes a traceparent in params._meta on each request it sends to your MCP server, so the server can nest its own spans under anthropic.mcp_tool_use.
Step 3: Verify spans arrive#
- Pick a workspace the destination covers and send a request from the Console playground, from your app, or start a Managed Agents session there.
- On the OpenTelemetry settings page, the Delivery column should show Healthy within about a minute. The other states are Failing, No data (no delivery attempted yet) and Disabled.
- In your backend, filter on
service.name=claude-platform.
If it shows Failing, hover over it for the last error:
| Error | Likely cause |
|---|---|
| HTTP 401 or 403 | Wrong auth header |
| HTTP 404 | Wrong path in the endpoint URL or traces endpoint URL (appending /v1/traces yourself is an easy way to get one) |
| DNS resolution failure | Hostname does not resolve |
| TLS failure | Certificate or TLS configuration problem |
| Timeout | Endpoint missed the 5-second limits |
| Connection failure | API could not connect at all |
If spans arrive but have no prompts despite Full content, your organization may have a HIPAA, FERPA or K-12 data-handling restriction, covered below.
Reading a trace: Messages API vs Managed Agents#
The span reference uses two naming rules:
- Request spans are
anthropic.plus the API method path, with dots and withoutbeta.client.beta.sessions.events.send()becomesanthropic.sessions.events.send. - Tool spans are a fixed name, a space, then the tool name:
anthropic.tool_use bash,anthropic.mcp_tool_use <tool>,anthropic.custom_tool_use list_issues.
SDK spans have your app's service.name and scope com.anthropic.sdk.<language>; their request spans are kind CLIENT. API spans have service.name claude-platform and scope com.anthropic.claude-platform; their request spans are kind SERVER. Everything else on both sides is INTERNAL. Since both sides name the request span anthropic.messages.create, kind and service.name are how you tell them apart.
Messages API. Under the API's anthropic.messages.create span you get one anthropic.model_request per model run, a tool span for each server tool or MCP connector tool the API runs, and an anthropic.compaction span for each compaction. If you use the SDK's tool runner, the SDK adds an anthropic.messages.tool_runner span and an anthropic.custom_tool_use <name> span each time it runs one of your tools between requests. With Full content, the server span holds the system prompt in gen_ai.system_instructions, the request messages in gen_ai.input.messages and the response in gen_ai.output.messages.
Managed Agents. A session has one span per Sessions API request plus an anthropic.session.turn span per agent turn. A turn contains anthropic.model_request spans, tool spans and anthropic.session.outcome_evaluation spans. Here is the docs' example of one chat request that runs a server-side bash tool and then a custom tool on your side:
%% title: Managed Agents session spans
flowchart TD
A["POST /chat - your span"] --> B["sessions.events.send CLIENT"]
B --> C["sessions.events.send SERVER"]
C --> D["session.turn 1"]
D --> E["model_request"]
D --> F["tool_use bash"]
D -. "stops: requires_action" .-> H["custom_tool_use list_issues - SDK"]
A --> H
A --> I["sessions.events.send CLIENT"]
I --> J["sessions.events.send SERVER"]
J --> K["session.turn 2"]The turn's anthropic.session.turn.stop_reason attribute tells you why it ended (requires_action here). Sessions outlive single requests, so their spans spread across traces. Group by gen_ai.conversation.id (the session thread ID) to read one thread as a transcript, or by anthropic.session.id to see a whole session across all threads and traces. With Full content, a turn span carries the turn's input and output messages, and a tool span carries the arguments in gen_ai.tool.call.arguments and the result in anthropic.tool_result.content.
Counting tokens without double counting#
This is the section that will save you from a dashboard that shows double your real spend. A single request carries usage on several spans, so the docs define exactly which ones to sum:
- Sum one source per request. When a request has both an SDK span and an API span, sum the API spans (
SERVER), not the SDKCLIENTspan of the same name. - SDK spans only: sum
anthropic.messages.create, and addanthropic.usage.compaction.input_tokensandanthropic.usage.compaction.output_tokensyourself, becausegen_ai.usage.*excludes compaction tokens. - API spans, Messages API: sum
anthropic.messages.createoranthropic.model_request, never both, plus anyanthropic.compactionspans. Do not also add the compaction attributes fromanthropic.messages.create; they are the same tokens. - API spans, Managed Agents: sum
anthropic.model_requestandanthropic.session.outcome_evaluation. - Cache tokens are already in.
gen_ai.usage.input_tokensincludes cache reads and writes, so do not addgen_ai.usage.cache_read.input_tokensorgen_ai.usage.cache_write.input_tokenson top. Use them to measure cache hit rate and the prompt caching savings you think you are getting. - Dedupe. Retried deliveries can duplicate spans; count each trace ID plus span ID once.
%% title: Which spans to sum
flowchart TD
S{"API spans present?"} -- "No, SDK only" --> K["Sum CLIENT messages.create plus compaction attributes"]
S -- "Yes" --> T{"Which API?"}
T -- "Messages API" --> M["Sum messages.create OR model_request plus compaction spans"]
T -- "Managed Agents" --> N["Sum model_request plus outcome_evaluation"]
K --> D["Dedupe on trace ID + span ID"]
M --> D
N --> DSpans are not billing. The docs say plainly that exported spans are not a complete record of usage. Delivery is best effort, and a caller can drop a request's spans by sending traceparent with the sampled flag off. For billing reconciliation or spotting a leaked key, use the Usage API; use traces for attribution and debugging.
Content modes, privacy and limits#
Metadata only (the default everywhere) exports identifiers, timing, status, token counts and text you chose, such as agent and tool names, descriptions and MCP server names. Full content adds the system prompt, input messages and output, plus tool arguments and results on tool spans.
Privacy rules worth knowing before you flip to Full content:
- Organizations with a HIPAA, FERPA or K-12 data-handling restriction get metadata only from destinations regardless of the setting. SDK spans are not affected; they go out through your own exporter.
- With zero data retention you can still use Full content; for Messages API requests the API holds exported content only in memory until delivery. Managed Agents is not eligible for ZDR, and its retention does not change with OpenTelemetry.
- After delivery, the data lives under your observability vendor's retention and access controls. Check those before you export prompts.
Content size. With Full content, each span holds up to 0.75 MB by default, which the docs say fits a 200k-token English context and stays under the 1 MB request limit many platforms and proxies impose. A full 1M-token context needs about 4 MB, so raise the limit only if your backend accepts it, and lower it if a proxy rejects large bodies. Over the limit, content is shortened and anthropic.content.truncated is set to true: request spans drop the system prompt and earliest messages first, tool spans drop blocks from the end, and images and documents appear only as their type.
Destination limits:
| Limit | Value |
|---|---|
| Destinations per organization (including disabled) | 10 |
| Selected workspaces per destination | 1,000 |
| Request headers per destination | 20 |
| Header name length | 128 bytes |
| Header value length | 4,096 bytes |
| All headers combined | 16,384 bytes |
| Endpoint URL length | 2,048 characters |
Gotchas and limits#
Destinations disable themselves. The API disables a destination on a 3xx redirect, on a hostname that resolves to a non-public IP, or after 24 hours of failed deliveries; fix the cause, then click Enable in More actions. The rest of the delivery contract: at least once (dedupe on trace ID plus span ID), best effort (a lost span is never re-sent), gzip first with an uncompressed resend if your endpoint answers 400 or 415, limited retries after timeouts, connection failures, 408, 429 or 5xx, and User-Agent: Claude-Platform on every request. Your endpoint gets 5 seconds for the TLS handshake, 5 seconds to send response headers, and 10 seconds per delivery in total.
TypeScript auto-instrumentation clash. If you use @opentelemetry/instrumentation-undici (included in @opentelemetry/auto-instrumentations-node), set openTelemetry: { propagation: false }. Otherwise a bug in that package adds a second traceparent header and breaks the trace context.
Beta means breaking. The SDK page warns that a new SDK version can rename spans, attributes, options or environment variables during the beta. Pin the SDK version and upgrade on purpose, or your dashboards break silently.
Streams end late. A streamed anthropic.messages.create span ends only when your code reads the whole stream or closes it, so close streams you abandon. In content mode, the response is captured only when you read via messages.stream(). Spans you start while reading a stream are siblings of the SDK span, not children, so wrap the call and the read loop in your own span.
The OpenAI-compatible endpoint is not traced server-side. The API does not emit anthropic.messages.create for POST /v1/chat/completions.
Fallbacks are one span. When a fallback model serves a request, you still get one span, and gen_ai.response.model shows the model that actually answered.
Java version floor. The Java SDK needs opentelemetry-api 1.57.0 or later; with an older transitive version, the SDK stops sending spans to your provider or trace context to the API.
MCP propagation is uneven. For client-side MCP tools, trace context in _meta is sent automatically by Python mcp 2.0+ and the C# ModelContextProtocol package, not by the TypeScript, Go, Java, Ruby or PHP MCP SDKs.
Self-hosted sandboxes. In a Managed Agents self-hosted sandbox, your sandbox runs the tools, so the tool spans come from the SDK. The environment worker in the Python, TypeScript and Go SDKs groups them under one anthropic.environment.work span per work item. Set up SDK tracing in the worker process and allow egress from the sandbox to your collector.
The closest alternative: client-side instrumentation#
Before this, the standard route was a proxy or SDK wrapper from a tracing vendor, which we compared in Langfuse vs Braintrust vs Helicone, or community instrumentation such as Traceloop's OpenLLMetry package opentelemetry-instrumentation-anthropic (0.62.4 at the time of writing).
Those still earn their place. Vendor platforms add evals, prompt management and UI on top. Per its README, OpenLLMetry ships standard OpenTelemetry instrumentations for many LLM providers and vector databases, which matters if you route traffic across vendors and want one schema in one dashboard, and adding it is a two-line change (AnthropicInstrumentor().instrument()) that works with SDK versions that predate native tracing.
Where the native options differ, on documented behavior:
| Client-side wrapper (e.g. OpenLLMetry) | Native SDK tracing | API tracing (Console) | |
|---|---|---|---|
| Multi-provider coverage | Yes | Claude only | Claude only |
| Content capture default | On (disable with TRACELOOP_TRACE_CONTENT=false) | Off (metadata_only) | Off (Metadata only) |
| Each model run inside a request | No | No | Yes |
| Server-run and MCP connector tools | No | No | Yes |
| Managed Agents turns and outcome evaluations | No | No | Yes |
| Works with non-SDK clients | No | No | Yes |
The practical recommendation: keep exactly one client-side tracer. If you already depend on a wrapper for multi-provider consistency, keep it and set open_telemetry=False / ANTHROPIC_OPEN_TELEMETRY=false on the Claude client to avoid duplicate spans, as the docs advise. Either way, add the Console destination if your agent runs on Managed Agents or leans on server tools, because the server-side view is the part no client library can reproduce. If you only call the Messages API from your own code, SDK tracing plus your existing backend may be all you need.
What people are actually saying#
- There is no sizeable Hacker News or Reddit thread on the launch yet as of October 17; the discussion so far is vendor-side.
- Several partner platforms on Anthropic's list already have setup guides live, for example Honeycomb, Grafana Cloud and Sentry, which is a decent sign the span schema is usable out of the box.
- The sharpest counter-case comes from Chainloop: they argue these spans are observability, not evidence. Best-effort delivery means you cannot use them to prove what an agent did for audit or compliance, and they pitch signed session records for that job. That matches Anthropic's own "not a complete record" note.
What to do Monday#
- Upgrade the SDK, register a provider (TypeScript or Python above), and confirm one
anthropic.messages.createspan prints to the console. If you already had a global provider, check what the upgrade started emitting. - Swap the console exporter for your OTLP exporter and keep
metadata_onlyuntil you have checked your backend's retention policy. - Ask an org admin to add a Console destination scoped to one workspace, click Test, confirm Healthy in the Delivery column, then widen it.
- Fix your token dashboard to sum one source per request, using the rules above, before anyone quotes a cost number from it. If you already watch Claude Code token burn, apply the same discipline here.
FAQ#
How do I get OpenTelemetry traces from the Claude API?#
Upgrade the Claude SDK and register an OpenTelemetry tracer provider before your first call for SDK spans. For API spans, an org admin adds a destination in the Claude Console pointing at an OTLP/HTTP endpoint on HTTPS port 443. Set up both to see client and server spans in one trace.
Do I need both SDK and API tracing?#
No, they work independently. SDK tracing alone gives you request spans, token usage and client-side tool runs nested in your app's traces. API tracing alone covers any client, including raw HTTP, and shows each model run, server-run tools and Managed Agents turns. You need both to see one trace that runs from your own span through the API's internals.
Do Claude API traces include my prompts and responses?#
Not by default. Metadata only is the default content mode. Switch a destination to Full content, or set the SDK content mode to content, to include prompts and responses. Organizations with a HIPAA, FERPA or K-12 restriction only get metadata from destinations.
Will Full content break zero data retention?#
Not for the Messages API: the docs say the API holds exported content only in memory until it is delivered. Managed Agents is not ZDR-eligible in the first place, and OpenTelemetry does not change its retention. Once delivered, the content is governed by your backend's retention policy.
Does Claude API OpenTelemetry work on Bedrock or Google Cloud?#
SDK tracing works on any platform the SDK supports, including Amazon Bedrock, Google Cloud and Microsoft Foundry. API tracing through a Console destination is only available on the Claude Platform and Claude Platform on AWS.
Why do my Claude token counts double in my dashboard?#
A request traced by both halves has two anthropic.messages.create spans, one CLIENT and one SERVER. Sum one source per request, and prefer the API spans when both exist. To tell them apart in a trace view, filter by span kind or service.name (claude-platform for API spans). If you still see duplicates, another library is also instrumenting the SDK; turn one of them off.
Does Claude API OpenTelemetry export cost extra?#
The OpenTelemetry docs do not list a fee for SDK tracing or Console destinations. The costs you will see are your observability backend's ingest and storage, which scale with span volume and with Full content payload size, so start in Metadata only and raise the content limit deliberately.
Continue Reading#
- Langfuse vs Braintrust vs Helicone: Agent Tracing Tools in 2026 - what a dedicated tracing platform adds on top of raw spans
- Claude Managed Agents Public Beta: What's Actually Available vs What's Gated - the runtime whose turns these API spans now expose
- Prompt Caching in the Claude API: A Production Guide - read cache attributes correctly once they are in your traces
- Local OpenTelemetry traces - point your exporter at a local collector before you send anything to a vendor
- Who Bought Your LLM Observability Tool? - which tracing backends changed owners, and why vendor-neutral spans matter
- Claude Usage and Cost Analytics API: Attribute Spend by Key and User - monthly cost by key and user to pair with per-request traces
Sources#
- Claude Platform release notes, October 14, 2026
- OpenTelemetry for the Claude API
- Set up OpenTelemetry with the Claude SDK
- Set up OpenTelemetry with the Claude API
- OpenTelemetry span reference
- Telemetry destinations API reference
- anthropic-sdk-typescript CHANGELOG (0.134.0)
- anthropic-sdk-python CHANGELOG (1.14.0)
- OpenTelemetry GenAI semantic conventions
- W3C Trace Context
- Claude Code monitoring
- opentelemetry-instrumentation-anthropic on PyPI
- Traceloop OpenLLMetry repository
Get the next deep dive like this in your inbox
One email a week on Anthropic and the rest of the AI dev stack. Free.
Read next on Claude Code
Langfuse vs Braintrust vs Helicone: Agent Tracing Tools in 2026
Langfuse vs Braintrust vs Helicone, plus LangSmith and Arize Phoenix: 2026 pricing tiers, self-host options, agent-trace features, and OpenTelemetry support.
14 min readClaude Managed Agents Public Beta: What's Actually Available vs What's Gated
Claude Managed Agents public beta reviewed: what ships today, which orchestration features are gated behind a research preview, what it costs, and when to DIY.
8 min readClaude Code Token Burn Is an Observability Problem
Claude Code token burn is an observability problem. Why coding agents need cache-hit telemetry, spend ceilings and repro-grade usage logs, not a quota bar.
8 min readTechnical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.






