> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bronto.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Claude Platform observability with OpenTelemetry

> Send OpenTelemetry traces of Claude Messages API requests, tool use, and Managed Agents sessions to Bronto, from the Claude API, the Claude SDK, or both in one trace.

The [Claude Platform](https://platform.claude.com/docs/en/manage-claude/opentelemetry) can export OpenTelemetry traces of your Messages API requests, tool use, and [Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview) sessions. The spans follow the [OpenTelemetry GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md), so token usage, models, tool calls, and (optionally) prompts and responses arrive in Bronto as searchable `gen_ai.*` attributes, with no Bronto-specific SDK.

Spans come from two independent sources. You can use either one or both:

| Source | What it traces | Setup | Who can set it up |
| - | - | - | - |
| **API tracing** | Every Messages API request and Managed Agents session in the covered workspaces, from any client (SDK, `curl`, the Console playground), including each model request, server tools such as web search and code execution, MCP connector tools, and Managed Agents turns | Add a destination in the Claude Console. No code changes. | Claude Console organization admin |
| **SDK tracing** | The client side: each SDK request, the tool runner, your own (client) tools, tools in a self-hosted sandbox, and other API calls such as file uploads | Register an OpenTelemetry tracer provider in your app | Anyone |

With both, the SDK propagates the W3C `traceparent` header and the API spans nest under your application's spans, so one user request becomes **one trace** in Bronto, from your own code down to each model call and tool use.

<Note>
  OpenTelemetry export on the Claude Platform is in **beta**. Span names, attributes, and SDK options can change between releases. This page was verified on **October 15, 2026** with the Python SDK `anthropic==1.14.0` and `opentelemetry-sdk==1.45.1`. Pin your SDK version, and inspect a real trace before building version-sensitive dashboards.
</Note>

## Prerequisites

* A Bronto API key with the **Ingestion** role. See [API Keys](/Account-Management/API-Keys#create-a-new-api-key).
* Your Bronto region: `eu` or `us`.
* For API tracing: the **admin** role in your Claude Console organization.
* For SDK tracing: a Claude SDK version with OpenTelemetry support (Python, TypeScript, Go, Java, C#, PHP, or Ruby). The examples below use Python 3.10 or later.

## Send API traces from the Claude Console

A *destination* tells the Claude API where to push spans. The API sends OTLP/HTTP protobuf over HTTPS, which Bronto's ingestion endpoint accepts directly, so you don't need a Collector.

1. In the Claude Console, open **Settings → OpenTelemetry** ([direct link](https://platform.claude.com/settings/telemetry-destinations)) and click **Add destination**.

2. Fill in the form:

   | Field | Value |
   | - | - |
   | **Provider** | `Custom` |
   | **Name** | For example, `Bronto EU` |
   | **OTLP endpoint URL** | `https://ingestion.eu.bronto.io` or `https://ingestion.us.bronto.io` |
   | **Request headers** | Name `x-bronto-api-key`, value your Bronto API key |
   | **Content mode** | `Metadata only` or `Full content`. See [Prompts and responses](#prompts-and-responses). |
   | **Workspaces** | `All`, or only the workspaces you want to trace |

   <Warning>
     Enter the **base** URL without `/v1/traces`. The Claude API appends `/v1/traces` itself, so a URL that already ends in `/v1/traces` fails with an HTTP `404`.
   </Warning>

   <Frame caption="The whole setup in 36 seconds, from the Console dashboard to the saved destination">
     <video src="https://mintcdn.com/bronto/ixyiAACeoNZK6r5-/images/ai-features/claude-console-add-destination.mp4?fit=max&auto=format&n=ixyiAACeoNZK6r5-&q=85&s=9eb13132bcf09d8f88f6f1e052ea99f3" poster="/images/ai-features/claude-console-add-destination.png" aria-label="Adding Bronto as a telemetry destination in the Claude Console: Settings, OpenTelemetry, Add destination with Provider Custom, OTLP endpoint URL https://ingestion.eu.bronto.io, request header x-bronto-api-key and Full content, then saving it" controls playsInline preload="metadata" data-path="images/ai-features/claude-console-add-destination.mp4" />
   </Frame>

3. Click **Add destination**. To send a test span, open the destination's **More actions** menu and click **Test**. An `anthropic.test` span is delivered to Bronto.

4. Send a request from a covered workspace, for example from the Console playground or with `curl`. Within about a minute, the destination's **Delivery** column changes from **No data** to **Healthy**.

All spans from the API carry `service.name = claude-platform`. In [Explore Traces](/tracing/explore-traces), filter on that service to find them. The operation table lists the root span of each trace, so with SDK tracing enabled you'll find most Claude traces under your own service's operations rather than under `claude-platform`.

<Frame>
  <img src="https://mintcdn.com/bronto/ixyiAACeoNZK6r5-/images/ai-features/claude-platform-tracing-overview.png?fit=max&auto=format&n=ixyiAACeoNZK6r5-&q=85&s=9b8a016a7981e034b3a07be4fe3cf41e" alt="Bronto Tracing overview filtered to the claude-platform service and the services of an instrumented app, with latency, errors and requests charts and an operation table" width="1557" height="784" data-path="images/ai-features/claude-platform-tracing-overview.png" />
</Frame>

### Add the destination with the Admin API

To automate the setup, create the destination with an [Admin API key](https://platform.claude.com/docs/en/manage-claude/admin-api-keys) (`sk-ant-admin…`). A regular API key returns `401`.

```bash theme={"dark"}
curl --fail-with-body -sS "https://api.anthropic.com/v1/organizations/telemetry_destinations" \
  -H "x-api-key: $ANTHROPIC_ADMIN_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "anthropic-beta: telemetry-destinations-2026-08-11" \
  -H "content-type: application/json" \
  -d '{
    "endpoint_url": "https://ingestion.<REGION>.bronto.io",
    "headers": {"x-bronto-api-key": "<BRONTO_API_KEY>"},
    "traces": {"content_mode": "metadata_only"},
    "workspaces": {"type": "all"}
  }'
```

Set `content_mode` to `content` for full prompts and responses.

## Send SDK traces from your application

The Claude SDK emits its spans through whichever OpenTelemetry tracer provider your application registers. Point that provider's OTLP exporter at Bronto and the SDK calls themselves stay unchanged.

```bash theme={"dark"}
pip install anthropic opentelemetry-sdk opentelemetry-exporter-otlp-proto-http
```

```bash theme={"dark"}
export OTEL_SERVICE_NAME="support-agent"
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="https://ingestion.<REGION>.bronto.io/v1/traces"   # eu or us
export OTEL_EXPORTER_OTLP_TRACES_HEADERS="x-bronto-api-key=<YOUR_API_KEY>"
export ANTHROPIC_API_KEY="<YOUR_CLAUDE_API_KEY>"
```

```python app.py theme={"dark"}
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Register the tracer provider before the first Claude call.
provider = TracerProvider(resource=Resource.create())  # reads OTEL_SERVICE_NAME
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))  # reads OTEL_EXPORTER_OTLP_TRACES_*
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("support-agent")

from anthropic import Anthropic, beta_tool

client = Anthropic()


@beta_tool
def get_order_status(order_id: str) -> str:
    """Look up the status of an order.

    Args:
        order_id: The order ID, e.g. "A-1001".
    """
    return f"{order_id}: shipped yesterday"


with tracer.start_as_current_span("handle_ticket"):
    runner = client.beta.messages.tool_runner(
        model="claude-haiku-5-5",
        max_tokens=1024,
        tools=[get_order_status],
        messages=[{"role": "user", "content": "Where is my order A-1001?"}],
    )
    for message in runner:
        pass
    print(message.content[-1].text)

provider.shutdown()  # flush spans before a short-lived process exits
```

SDK spans contain metadata only by default. To add prompts and responses to the SDK spans, set `ANTHROPIC_OPEN_TELEMETRY_TRACES_CONTENT_MODE=content`, or pass `open_telemetry={"traces": {"content_mode": "content"}}` to the client.

For TypeScript, Go, Java, C#, PHP, and Ruby, register a tracer provider with an OTLP/HTTP exporter in the same way. The per-language setup is in [Use OpenTelemetry with the Claude SDK](https://platform.claude.com/docs/en/manage-claude/opentelemetry/sdk) and Bronto's [OpenTelemetry language guides](/opentelemetry/overview).

## What you'll see in Bronto

### One trace from your code to the model

With both sources enabled, the example above produces this trace. Spans from your app use your `service.name`; spans from the Claude API use `claude-platform`.

```text theme={"dark"}
handle_ticket                                   support-agent      your span
└─ anthropic.messages.tool_runner               support-agent      SDK
   ├─ anthropic.messages.create      CLIENT     support-agent      SDK, request 1
   │  └─ anthropic.messages.create   SERVER     claude-platform    API
   │     └─ anthropic.model_request             claude-platform    API, one per model run
   ├─ anthropic.custom_tool_use get_order_status support-agent     SDK, your tool
   └─ anthropic.messages.create      CLIENT     support-agent      SDK, request 2 with the tool result
      └─ anthropic.messages.create   SERVER     claude-platform    API
         └─ anthropic.model_request             claude-platform    API
```

This is how a real trace looks in Bronto. It comes from the [On-call Copilot lab](https://github.com/bronto-community/claude-otel-lab), an alert-triage app: an alert webhook (`alertmanager-sim`) calls a FastAPI service (`oncall-copilot`), which runs the Claude tool runner, then hands the incident to a Managed Agents session. The trace has 59 spans across three services.

<Frame>
  <img src="https://mintcdn.com/bronto/ixyiAACeoNZK6r5-/images/ai-features/claude-platform-trace-waterfall-copilot.png?fit=max&auto=format&n=ixyiAACeoNZK6r5-&q=85&s=ac3f4ef5d0e55b0f2edf9829b0b0ad20" alt="Bronto trace waterfall: alertmanager-sim fire_alert, oncall-copilot POST /alerts and triage, the SDK's anthropic.messages.create CLIENT span with the claude-platform anthropic.messages.create and anthropic.model_request spans beneath it, then the app's tool spans" width="1557" height="784" data-path="images/ai-features/claude-platform-trace-waterfall-copilot.png" />
</Frame>

When a request uses server tools such as web search or code execution, the API adds an `anthropic.tool_use <name>` span for each use between the `anthropic.model_request` spans, with the arguments, the result, and an `ERROR` status when the tool fails.

A Managed Agents session adds `anthropic.session.turn` spans under the `anthropic.sessions.events.send` request that started each turn, with the model requests and the sandbox's tool uses (for example `anthropic.tool_use bash`) beneath them. A turn can also start a trace of its own: in our tests, one turn arrived as a root span with no parent. To see a whole session, filter on `$anthropic.session.id` rather than following a single trace.

With API tracing only, each request is its own trace rooted at the `SERVER` span. With SDK tracing only, you get the `CLIENT` and tool spans without the model-run and server-tool detail.

### Span attributes

The attributes worth knowing:

| Attribute | Example | Where |
| - | - | - |
| `gen_ai.provider.name` | `anthropic` | All GenAI spans |
| `gen_ai.operation.name` | `chat`, `execute_tool`, `invoke_agent` | Request, tool, and Managed Agents turn spans |
| `gen_ai.request.model` / `gen_ai.response.model` | `claude-haiku-5-5` | Request and model spans |
| `gen_ai.request.reasoning.level` | `low` | `anthropic.messages.create` (`SERVER`): the request's effort setting |
| `gen_ai.usage.input_tokens` / `gen_ai.usage.output_tokens` | `6250`, `176` | Request and model spans. Input tokens already include prompt-cache reads and writes. |
| `gen_ai.usage.cache_read.input_tokens` / `gen_ai.usage.cache_write.input_tokens` | `4392` | Request and model spans |
| `anthropic.message.stop_reason` | `end_turn`, `tool_use` | `anthropic.messages.create` (`SERVER`) |
| `anthropic.request.id` / `gen_ai.response.id` | `req_…` / `msg_…` | `anthropic.messages.create` (`SERVER`). Quote these to Anthropic support. |
| `anthropic.workspace.id` / `anthropic.organization.id` | `wrkspc_…` | All API spans. Group by workspace to split usage by team or environment. |
| `gen_ai.tool.name` / `gen_ai.tool.call.arguments` | `web_search`, `{"query": "…"}` | Tool spans |
| `anthropic.tool_result.content` / `error.type` | `invalid_tool_input` | Tool spans. A failed server tool shows `ERROR` status and the error code. |
| `anthropic.session.id` | `sesn_…` | Managed Agents spans. Group by this to see a whole session across traces. |
| `gen_ai.conversation.id` | `sthr_…` | Managed Agents session thread, or the `conversation_id` you set in the SDK |
| `gen_ai.agent.name` / `anthropic.session.turn.stop_reason` | `support-agent` / `requires_action` | `anthropic.session.turn` |
| `anthropic.tool.evaluated_permission` | `allow` | Managed Agents tool spans: the permission policy's decision |
| `gen_ai.system_instructions`, `gen_ai.input.messages`, `gen_ai.output.messages` | — | Only with **Full content** |

Select a span in the trace view to see its attributes. With **Full content**, `gen_ai.output.messages` shows exactly what the model returned. Here the model asked for four tool calls in parallel:

<Frame>
  <img src="https://mintcdn.com/bronto/ixyiAACeoNZK6r5-/images/ai-features/claude-platform-span-messages-create.png?fit=max&auto=format&n=ixyiAACeoNZK6r5-&q=85&s=fe5e7a8e4a9300bec8b088ea7a2e4de1" alt="Span attributes of the claude-platform anthropic.messages.create span in Bronto: gen_ai.request.model claude-opus-5-5, gen_ai.request.reasoning.level low, and gen_ai.output.messages expanded to show tool_call parts for get_runbook and get_recent_deploys" width="1557" height="784" data-path="images/ai-features/claude-platform-span-messages-create.png" />
</Frame>

The full list is in Anthropic's [OpenTelemetry span reference](https://platform.claude.com/docs/en/api/opentelemetry/spans).

### Search and aggregate

Bronto indexes span attributes with a `$` prefix, so you can filter on them as field predicates. For example, all model runs on a given model:

```sql theme={"dark"}
"$span.name" = 'anthropic.model_request' AND "$gen_ai.request.model" = 'claude-haiku-5-5'
```

Server tool failures, with the error code:

```sql theme={"dark"}
"$service.name" = 'claude-platform' AND "$span.name" LIKE 'anthropic.tool_use%' AND "$span.status_code" = 'STATUS_CODE_ERROR'
```

In a trace, the **Error** filter narrows the waterfall to the path that leads to a failed span. In this example, a Managed Agents turn ran a `bash` command against a file path that didn't exist. The agent recovered, and its output looked fine, but the span keeps the command and its error output:

<Frame>
  <img src="https://mintcdn.com/bronto/ixyiAACeoNZK6r5-/images/ai-features/claude-platform-span-bash-error.png?fit=max&auto=format&n=ixyiAACeoNZK6r5-&q=85&s=2010f609b1a7d709c51ec9576e3f85a7" alt="Bronto trace view filtered to errors: oncall-copilot postmortem, anthropic.sessions.events.send, anthropic.session.turn and a failed claude-platform anthropic.tool_use bash span, with its tool_result content showing 'No such file or directory'" width="1557" height="784" data-path="images/ai-features/claude-platform-span-bash-error.png" />
</Frame>

Token usage is numeric. **Sum** of `$gen_ai.usage.input_tokens` and `$gen_ai.usage.output_tokens` over `anthropic.model_request` spans, grouped by `$gen_ai.request.model` or `$anthropic.workspace.id`, gives per-model or per-workspace consumption.

<Warning>
  **Count each token once.** The same usage is repeated on several levels of a trace: the SDK `CLIENT` span, the API `SERVER` span, and the `anthropic.model_request` spans beneath it. Sum one level only. For API traces, filter on `anthropic.model_request` (for Managed Agents, also add `anthropic.session.outcome_evaluation`). Delivery is at-least-once, so for exact accounting deduplicate on `$span.trace_id` + `$span.span_id`. For billing-grade numbers, use Anthropic's [Usage API](https://platform.claude.com/docs/en/manage-claude/usage-cost-api#usage-api): exported spans are best effort and aren't a complete usage record.
</Warning>

## Prompts and responses

Each destination and each SDK client has a content mode:

* **Metadata only** (default): identifiers, timing, status, token counts, model names, and the names and descriptions of your agents and tools.
* **Full content**: also the system prompt (`gen_ai.system_instructions`), the messages sent (`gen_ai.input.messages`), the response (`gen_ai.output.messages`), and tool arguments and results. Content follows the GenAI "parts" format, for example `[{"role":"user","parts":[{"type":"text","content":"…"}]}]`. Each span holds up to 0.75 MB of content by default. Longer content is shortened and the span gets `anthropic.content.truncated = true`.

<Note>
  Full content sends prompts and responses to Bronto, where they're stored under your Bronto retention and access controls. Exercise caution when capturing prompts and responses: depending on your application, they may contain sensitive data. Organizations with a HIPAA, FERPA, or K-12 data-handling restriction with Anthropic export metadata only, whatever the destination's content mode.
</Note>

## Troubleshooting

| Symptom | Likely cause |
| - | - |
| Destination **Delivery** shows **Failing** with HTTP `401` / `403` | The header name must be exactly `x-bronto-api-key`, and the key needs the Ingestion role. If you changed the endpoint host, enter the header value again, because the Console doesn't keep header values across a host change. |
| **Failing** with HTTP `404` | The endpoint URL ends in `/v1/traces`. Enter only the base URL, `https://ingestion.<REGION>.bronto.io`. |
| **Healthy**, but no spans in Bronto | Wrong region: an EU key sent to `ingestion.us.bronto.io` (or the reverse) doesn't land in your account. |
| **Disabled** | The API disables a destination after a `3xx` redirect, a hostname that resolves to a non-public IP, or 24 hours of failed deliveries. Fix the cause, then click **Enable** in the destination's **More actions** menu. |
| No API spans for some requests | The request ran in a workspace the destination doesn't cover, or the caller sent a `traceparent` with the sampled flag off. |
| API spans aren't nested under your app's spans | Call the SDK inside one of your own spans, and make sure the W3C trace-context propagator is registered (the default in Python and Node.js). In TypeScript with `@opentelemetry/auto-instrumentations-node`, set `openTelemetry: { propagation: false }` on the client to avoid a duplicate `traceparent` header. |
| No SDK spans | Register the tracer provider before the first Claude call, and call `provider.shutdown()` before a short-lived process exits. Check that `ANTHROPIC_OPEN_TELEMETRY` isn't `false`. |
| Each request appears twice | The SDK `CLIENT` span and the API `SERVER` span share the name `anthropic.messages.create`. Tell them apart by `$span.kind` or `$service.name`. |
| Spans arrive, prompts and responses are empty | The destination or SDK client is in **Metadata only** mode. For SDK spans, set the client's content mode to `content`. |

## References

* [On-call Copilot lab](https://github.com/bronto-community/claude-otel-lab): a runnable example app that produces the traces on this page
* [LLM Observability](/ai-features/llm-observability)
* [Send Traces to Bronto](/tracing/send-traces)
* [Explore Traces](/tracing/explore-traces)
* [OpenTelemetry for the Claude API](https://platform.claude.com/docs/en/manage-claude/opentelemetry)
* [Use OpenTelemetry with the Claude API (destinations)](https://platform.claude.com/docs/en/manage-claude/opentelemetry/api)
* [Use OpenTelemetry with the Claude SDK](https://platform.claude.com/docs/en/manage-claude/opentelemetry/sdk)
* [OpenTelemetry span reference](https://platform.claude.com/docs/en/api/opentelemetry/spans)
* [OpenTelemetry GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-spans.md)

***

For assistance or questions, contact [support@bronto.io](mailto:support@bronto.io).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.