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

# Anthropic

> Record prompts, models, tokens, and cost for direct Anthropic API calls

Bitfab records messages your code sends to the [Anthropic API](https://docs.anthropic.com/en/api/messages) through the official `anthropic` / `@anthropic-ai/sdk` client: the prompt, the model, token counts, and latency. Anthropic does not report a cost per call, so Bitfab works out the cost from the model and the token counts.

**Canonical signatures:** [TypeScript `wrapAnthropic`](/reference/typescript#wrapanthropic) · [Python `wrap_anthropic`](/reference/python#wrap-anthropic)

## Language Support

| Language | API | Status |
| - | - | - |
| TypeScript | `wrapAnthropic()`, plus automatic capture under `withTrace()` with the build transform | ✅ Supported |
| Python | `wrap_anthropic()`, plus automatic capture under `trace()` | ✅ Supported |

## Under a trace root

Beneath a trace root, every non-streaming `messages.create` call on an Anthropic client is recorded with no wrapper, as its own `llm` span named `Messages.create` (Python's async client: `AsyncMessages.create`). The span attaches under the function that made the call.

```typescript TypeScript theme={null}
import Anthropic from "@anthropic-ai/sdk"
import { Bitfab } from "@bitfab/sdk"

const bitfab = new Bitfab({ apiKey: process.env.BITFAB_API_KEY })
const anthropic = new Anthropic()

async function classify(email: string) {
  const message = await anthropic.messages.create({
    model: "claude-sonnet-4-5",
    max_tokens: 1024,
    system: "You classify email.",
    messages: [{ role: "user", content: email }],
  })
  const [block] = message.content
  return block?.type === "text" ? block.text : undefined
}

export const detect = bitfab.withTrace("inbox-detection", (email: string) =>
  classify(email),
)
```

```python Python theme={null}
from anthropic import Anthropic
from bitfab import Bitfab

bitfab = Bitfab()
anthropic = Anthropic()


def classify(email: str) -> str:
    message = anthropic.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=1024,
        system="You classify email.",
        messages=[{"role": "user", "content": email}],
    )
    return message.content[0].text


@bitfab.trace("inbox-detection", type="llm")
def detect(email: str) -> str:
    return classify(email)
```

The trace shows `detect`, then `classify`, then an `llm` span named `Messages.create` carrying the prompt, tokens, and cost.

In TypeScript, the [build transform](/typescript-sdk#experimental-subtree-tracing) rewrites each `.messages.create(...)` call site in your code, so it works in bundled apps and the SDK never loads or patches `@anthropic-ai/sdk`. The client's own `APIPromise` is handed back, so `.withResponse()` keeps working. In Python, the SDK patches the `anthropic` messages classes when a `trace()` root is set up.

`exclude: ["create"]` on the root skips these calls, and `withNode()` / `node()` on a wrapper (below) configures the one span.

## Wrapping a call

Use `wrapAnthropic()` / `wrap_anthropic()` when you trace with `withSpan()` / `span()` instead of a trace root. It wraps the function that sends the request and writes the prompt and usage into the span that is current when the call starts. It opens no span of its own.

```typescript TypeScript theme={null}
const send = bitfab.wrapAnthropic(
  anthropic.messages.create.bind(anthropic.messages),
)

const classify = bitfab.withSpan(
  "classify-email",
  { type: "llm" },
  async (email: string) => {
    const message = await send({
      model: "claude-sonnet-4-5",
      max_tokens: 1024,
      messages: [{ role: "user", content: email }],
    })
    const [block] = message.content
    return block?.type === "text" ? block.text : undefined
  },
)
```

```python Python theme={null}
send = bitfab.wrap_anthropic(anthropic.messages.create)


@bitfab.span("classify-email", type="llm")
def classify(email: str) -> str:
    message = send(
        model="claude-sonnet-4-5",
        max_tokens=1024,
        messages=[{"role": "user", "content": email}],
    )
    return message.content[0].text
```

Beneath a trace root, a wrapper around a call that is already recorded as `Messages.create` records nothing more. `onResponse` / `on_response` receives each message after it is recorded.

## No duplicate spans

Each model call is recorded once:

* A wrapper around a call the trace root already recorded adds nothing.
* A call the [OpenAI Agents SDK integration](/frameworks/openai-agents) is recording as a generation or response span, or the [LangChain callback handler](/frameworks/langgraph) is recording as an LLM run, is not recorded a second time. This includes a call made inside your own LangChain chat model class or inside your own Agents model class that opens a generation span. A call made alongside a LangChain run in the same trace is still recorded, except beside the first LangChain run in a TypeScript process (see [Limitations](#limitations)).
* In TypeScript, calls made inside framework libraries in `node_modules` are never rewritten by the build transform, so only the framework's own integration records them.

A custom OpenAI Agents SDK model class that calls the Anthropic client directly opens no generation span, so its calls are recorded as `Messages.create` spans under the agent run.

## What gets captured

| Field | Value |
| - | - |
| Prompt | The request's `system` as a `system` turn, then its messages, as a JSON list of `role` and `content` |
| `provider` | `"anthropic"` |
| `model` | The model from the response |
| `inputTokens` | Every prompt token: `usage.input_tokens` plus `usage.cache_read_input_tokens` plus `usage.cache_creation_input_tokens` |
| `cachedInputTokens` | `usage.cache_read_input_tokens` |
| `outputTokens` | `usage.output_tokens` |
| `durationMs` | How long the request took |

The context fields go in one context entry on the span. A key the response does not carry is left out. Bitfab works out the cost from `model` and the token counts, charging cache reads at the cached input rate.

## Limitations

* **Streaming calls are not recorded.** A call with `stream: true` / `stream=True`, or through `messages.stream()`, runs normally and records nothing.
* **Calls outside any trace are not recorded.** A wrapper used outside a span or trace records nothing and never starts a trace of its own.
* **TypeScript automatic capture needs the build transform.** Code the transform does not compile (for example a call made from a dependency) is not rewritten; wrap it instead.
* **TypeScript: calls beside the first LangChain run in a process.** The SDK hooks into LangChain in the background when the first Bitfab LangChain handler or `withTrace()` root is created. If a LangChain run starts before that finishes, calls made inside it are still recorded once, but a call made at the same time outside it (for example in `Promise.all`) is not recorded while that first run is open. Later runs are not affected.
* **Python automatic capture needs Python 3.12+**, the same as `trace()` subtree capture.
* **Cache writes are priced at the input rate.** Anthropic charges more for tokens written to the prompt cache, so calls that write a lot to the cache show a slightly low cost.
* **Only `messages.create`.** The `beta.messages` client and `messages.parse` helpers are not recorded.


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