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

# Claude Agent SDK

> Instrument the Claude Agent SDK with OpenTelemetry hooks to capture traces in Toqan Router, and route Claude calls through the AI Gateway.

> [card] [Observability](#observability)
>
> Instrument your agents with OpenTelemetry using the SDK's hooks system to capture traces for every agent turn and tool use.

> [card] [AI Gateway  Beta](#ai-gateway)
>
> Route your Claude calls through the **AI Gateway** with a single base URL change. Zero vendor lock-in: always run on the best model at the lowest cost for your use case.

## Observability

### Overview

The Claude Agent SDK (`claude-agent-sdk`) drives the Claude CLI as a programmable agent supporting multi-turn conversations, tool use, and MCP servers. Use the SDK's built-in `hooks` system to attach OpenTelemetry instrumentation at key execution points. Every tool invocation becomes a span, and the full agent workflow is captured as a trace in **Toqan Router** without modifying your agent logic.

### Key Benefits

> [card] Full Agent Visibility
>
> Track every agent turn, tool use, and LLM call with detailed traces and analytics

> [card] Zero Code Changes
>
> Attach observability through hooks without modifying your agent logic

> [card] Cost Tracking
>
> Real-time cost and token usage per agent run synced to Toqan Router

> [card] Tool Inspection
>
> Inspect every tool input and output with full execution context

### Prerequisites

* A Toqan Router account and API Key
* Python 3.10 or higher, or Node.js 18 or higher
* [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code/getting-started) installed and authenticated

> [!NOTE]
>
> To set up your API key, see API keys & Endpoints.

### Install Dependencies

> [tab] TypeScript
>
> ```bash npm theme={"theme":{"light":"github-light","dark":"github-dark"}}
> npm install @anthropic-ai/claude-agent-sdk \
>             @arizeai/openinference-instrumentation-claude-agent-sdk \
>             @opentelemetry/sdk-node \
>             @opentelemetry/exporter-trace-otlp-http
> ```
>
> ```bash pnpm theme={"theme":{"light":"github-light","dark":"github-dark"}}
> pnpm add @anthropic-ai/claude-agent-sdk \
>          @arizeai/openinference-instrumentation-claude-agent-sdk \
>          @opentelemetry/sdk-node \
>          @opentelemetry/exporter-trace-otlp-http
> ```
>
> ```bash bun theme={"theme":{"light":"github-light","dark":"github-dark"}}
> bun add @anthropic-ai/claude-agent-sdk \
>         @arizeai/openinference-instrumentation-claude-agent-sdk \
>         @opentelemetry/sdk-node \
>         @opentelemetry/exporter-trace-otlp-http
> ```

> [tab] Python
>
> ```bash uv theme={"theme":{"light":"github-light","dark":"github-dark"}}
> uv add claude-agent-sdk openinference-instrumentation-claude-agent-sdk \
>        opentelemetry-sdk opentelemetry-exporter-otlp
> ```
>
> ```bash pip theme={"theme":{"light":"github-light","dark":"github-dark"}}
> pip install claude-agent-sdk openinference-instrumentation-claude-agent-sdk \
>             opentelemetry-sdk opentelemetry-exporter-otlp
> ```

### Configuration

Set up the OTel tracer provider to export spans to Toqan Router:

> [tab] TypeScript
>
> ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
> import { NodeSDK } from '@opentelemetry/sdk-node';
> import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
> import { ClaudeAgentSDKInstrumentation } from '@arizeai/openinference-instrumentation-claude-agent-sdk';
> import * as ClaudeAgentSDKModule from '@anthropic-ai/claude-agent-sdk';
>
> const ClaudeAgentSDK = { ...ClaudeAgentSDKModule };
> const instrumentation = new ClaudeAgentSDKInstrumentation();
> instrumentation.manuallyInstrument(ClaudeAgentSDK);
>
> const sdk = new NodeSDK({
>   traceExporter: new OTLPTraceExporter({
>     url: 'https://router.toqan.ai/eu/v2/otel/v1/traces',
>     headers: {
>       Authorization: `Bearer ${process.env.TOQAN_API_KEY}`,
>     },
>   }),
>   instrumentations: [instrumentation],
> });
>
> sdk.start();
> ```

> [tab] Python
>
> ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
> import os
> from opentelemetry.sdk.trace import TracerProvider
> from opentelemetry.sdk.trace.export import BatchSpanProcessor
> from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
> from opentelemetry import trace
> from openinference.instrumentation.claude_agent_sdk import ClaudeAgentSDKInstrumentor
>
> tracer_provider = TracerProvider()
> tracer_provider.add_span_processor(
>     BatchSpanProcessor(
>         OTLPSpanExporter(
>             endpoint="https://router.toqan.ai/eu/v2/otel/v1/traces",
>             headers={"Authorization": f"Bearer {os.environ['TOQAN_API_KEY']}"},
>         )
>     )
> )
> trace.set_tracer_provider(tracer_provider)
>
> ClaudeAgentSDKInstrumentor().instrument(tracer_provider=tracer_provider)
> tracer = trace.get_tracer(__name__)
> ```

### Basic Example

> [tab] TypeScript
>
> Spread the imported SDK module into a mutable namespace, then call `ClaudeAgentSDKInstrumentation.manuallyInstrument(...)` before `sdk.start()`. The instrumentation patches every `query()` call, tool use, and subagent into spans automatically.
>
> ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
> import { NodeSDK } from '@opentelemetry/sdk-node';
> import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
> import { ClaudeAgentSDKInstrumentation } from '@arizeai/openinference-instrumentation-claude-agent-sdk';
> import * as ClaudeAgentSDKModule from '@anthropic-ai/claude-agent-sdk';
>
> const ClaudeAgentSDK = { ...ClaudeAgentSDKModule };
> const instrumentation = new ClaudeAgentSDKInstrumentation();
> instrumentation.manuallyInstrument(ClaudeAgentSDK);
>
> const sdk = new NodeSDK({
>   traceExporter: new OTLPTraceExporter({
>     url: 'https://router.toqan.ai/eu/v2/otel/v1/traces',
>     headers: {
>       Authorization: `Bearer ${process.env.TOQAN_API_KEY}`,
>     },
>   }),
>   instrumentations: [instrumentation],
> });
>
> sdk.start();
>
> const prompt = 'What is the capital of France?';
> console.log(`User: ${prompt}\n`);
>
> for await (const message of ClaudeAgentSDK.query({ prompt })) {
>   if ('result' in message && typeof message.result === 'string') {
>     console.log(`Assistant: ${message.result}`);
>   }
> }
>
> await sdk.shutdown();
> console.log('\nTraces exported to Toqan Router. View them at https://router.toqan.ai/eu');
> ```

> [tab] Python
>
> Call `ClaudeAgentSDKInstrumentor().instrument()` before any agent call. The instrumentor wraps every tool invocation, LLM turn, and subagent in spans automatically. Drive the agent via `ClaudeSDKClient` (instead of the standalone `query()` generator) and wrap the call in a root `agent-workflow` span to stamp the prompt and the final cost.
>
> ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
> import asyncio
> import os
> from claude_agent_sdk import (
>     ClaudeAgentOptions,
>     ClaudeSDKClient,
>     AssistantMessage,
>     TextBlock,
>     ResultMessage,
> )
> from openinference.instrumentation.claude_agent_sdk import ClaudeAgentSDKInstrumentor
> from opentelemetry.sdk.trace import TracerProvider
> from opentelemetry.sdk.trace.export import BatchSpanProcessor
> from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
> from opentelemetry import trace
>
> tracer_provider = TracerProvider()
> tracer_provider.add_span_processor(
>     BatchSpanProcessor(
>         OTLPSpanExporter(
>             endpoint="https://router.toqan.ai/eu/v2/otel/v1/traces",
>             headers={"Authorization": f"Bearer {os.environ['TOQAN_API_KEY']}"},
>         )
>     )
> )
> trace.set_tracer_provider(tracer_provider)
>
> ClaudeAgentSDKInstrumentor().instrument(tracer_provider=tracer_provider)
> tracer = trace.get_tracer(__name__)
>
>
> async def main():
>     options = ClaudeAgentOptions(
>         model="sonnet",
>         system_prompt="You are a helpful coding assistant.",
>         max_turns=3,
>         allowed_tools=["Read", "Bash"],
>     )
>
>     prompt = "What Python version is installed on this system?"
>     print(f"User: {prompt}\n")
>
>     with tracer.start_as_current_span("agent-workflow") as span:
>         span.set_attribute("agent.prompt", prompt)
>
>         async with ClaudeSDKClient(options=options) as client:
>             await client.query(prompt)
>             async for message in client.receive_response():
>                 if isinstance(message, AssistantMessage):
>                     for block in message.content:
>                         if isinstance(block, TextBlock):
>                             print(f"Assistant: {block.text}")
>                 elif isinstance(message, ResultMessage):
>                     span.set_attribute("agent.total_cost_usd", message.total_cost_usd or 0)
>                     span.set_attribute("agent.num_turns", message.num_turns)
>                     print(f"\nCost: ${message.total_cost_usd or 0:.6f} | Turns: {message.num_turns}")
>
>     tracer_provider.force_flush()
>     print("\nTraces exported to Toqan Router. View them at https://router.toqan.ai/eu")
>
>
> asyncio.run(main())
> ```

### View Traces

View your traces in the [AI Studio](/docs/ai-studio/observability/quickstart.md) in the **Traces** tab.

> [!NOTE]
>
> Visit your [AI Studio](https://router.toqan.ai/eu) to view real-time analytics and traces.

## AI Gateway

> [!WARNING]
>
> This feature is in Beta. Traces are not supported while using the AI Gateway with the Claude Agent SDK.

### Overview

Route the Claude Agent SDK's model calls through **Toqan Router**'s **AI Gateway** without changing any agent logic. The SDK drives the Claude CLI, which reads `ANTHROPIC_BASE_URL` and `ANTHROPIC_AUTH_TOKEN` from the environment, so pointing those variables at the gateway routes every turn through **Toqan Router**.

> [!NOTE]
>
> For the raw Claude Messages API without the agent runtime, see the [Anthropic SDK](/docs/ai-studio/integrations/frameworks/anthropic.md) integration.

### Configuration

Set the following environment variables before running the agent:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
export ANTHROPIC_BASE_URL="https://router.toqan.ai/eu/v3/anthropic"
export ANTHROPIC_AUTH_TOKEN="$TOQAN_API_KEY"
export ANTHROPIC_API_KEY=""  # set empty so the SDK does not call the Anthropic API directly
export ANTHROPIC_MODEL="anthropic/claude-sonnet-5"  # the anthropic/ prefix is required
```

### Basic Example

With the environment variables set, every agent run routes through the **AI Gateway** with no change to the calling code:

> [tab] TypeScript
>
> ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
> import { query } from '@anthropic-ai/claude-agent-sdk';
>
> const prompt = 'What is the capital of France?';
>
> for await (const message of query({ prompt })) {
>   if ('result' in message && typeof message.result === 'string') {
>     console.log(message.result);
>   }
> }
> ```

> [tab] Python
>
> ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
> import asyncio
> from claude_agent_sdk import (
>     ClaudeSDKClient,
>     AssistantMessage,
>     TextBlock,
> )
>
>
> async def main():
>     async with ClaudeSDKClient() as client:
>         await client.query("What is the capital of France?")
>         async for message in client.receive_response():
>             if isinstance(message, AssistantMessage):
>                 for block in message.content:
>                     if isinstance(block, TextBlock):
>                         print(block.text)
>
>
> asyncio.run(main())
> ```


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