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

# Integrate with Anthropic

> Add MarginFront usage tracking to your Anthropic Claude agent

# Integrate MarginFront with Anthropic

This recipe shows how to add MarginFront usage tracking to an app that calls the Anthropic API (Claude models). Same pattern as the [OpenAI recipe](./openai), but Anthropic names its fields slightly differently.

**What this does for you:** You call Claude. MarginFront records the model, token count, and customer. You see costs on your dashboard. If you have a pricing plan, your customer gets billed automatically.

***

## Prerequisites

1. A MarginFront API key (`mf_sk_...`). Get one from **Build > API keys** in the dashboard.
2. An Anthropic API key.

> **You don't need to create the agent or signal first.** When you fire your first event with a new `agentCode` or `signalName`, MarginFront creates them automatically. The same goes for `customerExternalId`. You can rename and enrich any of them in the dashboard later.

```bash theme={null}
npm install @marginfront/sdk @anthropic-ai/sdk express
```

***

## Complete working example

Copy-paste and run. Receives a question from a customer, asks Claude for an answer, sends the answer back, and tells MarginFront what happened.

```typescript theme={null}
import express from "express";
import Anthropic from "@anthropic-ai/sdk";
import { MarginFrontClient } from "@marginfront/sdk";

const app = express();
app.use(express.json());

// Set up clients once at startup
const anthropic = new Anthropic(); // reads ANTHROPIC_API_KEY from environment
const mf = new MarginFrontClient(process.env.MF_API_SECRET_KEY!);

app.post("/research", async (req, res) => {
  const { customerId, question } = req.body;

  // --- Step 1: Call Claude ---
  const response = await anthropic.messages.create({
    model: "claude-sonnet-4-20250514",
    max_tokens: 1024,
    messages: [{ role: "user", content: question }],
  });

  // --- Step 2: Send the answer to your customer ---
  const answer =
    response.content[0].type === "text" ? response.content[0].text : "";
  res.json({ answer });

  // --- Step 3: Track the usage in MarginFront (AFTER responding) ---
  // Runs after the response is sent. If MarginFront is unreachable,
  // the SDK queues the event and retries. Your customer never waits.
  await mf.usage.record({
    customerExternalId: customerId, // who used it
    agentCode: "research-bot", // which product did the work
    signalName: "analyses", // what you're measuring
    model: response.model, // 'claude-sonnet-4-20250514' -- from Anthropic's response
    modelProvider: "anthropic", // tells MarginFront which pricing table to check
    inputTokens: response.usage.input_tokens, // Anthropic calls it input_tokens
    outputTokens: response.usage.output_tokens, // Anthropic calls it output_tokens
  });
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});
```

***

## Where the data comes from: field mapping

| What MarginFront needs | Where to get it from Anthropic               | Example value                |
| ---------------------- | -------------------------------------------- | ---------------------------- |
| `model`                | `response.model`                             | `"claude-sonnet-4-20250514"` |
| `inputTokens`          | `response.usage.input_tokens`                | `1024`                       |
| `outputTokens`         | `response.usage.output_tokens`               | `512`                        |
| `cacheReadTokens`      | `response.usage.cache_read_input_tokens`     | `15626`                      |
| `cacheWriteTokens`     | `response.usage.cache_creation_input_tokens` | `23488`                      |

The two cache fields are optional — only send them if you use prompt caching. See [Prompt caching](#prompt-caching) below.

### The naming difference from OpenAI

This is the one thing that trips people up:

| Provider  | Input tokens field    | Output tokens field       |
| --------- | --------------------- | ------------------------- |
| OpenAI    | `usage.prompt_tokens` | `usage.completion_tokens` |
| Anthropic | `usage.input_tokens`  | `usage.output_tokens`     |

MarginFront always uses `inputTokens` and `outputTokens` (camelCase). You just need to read from the right field on the provider side.

***

## Prompt caching

If you use Claude prompt caching, pass the cache token counts so MarginFront prices cached traffic at the cheaper cache rate instead of folding it into the full input rate. Both fields are optional — leave them off and cache contributes nothing, exactly as before.

Anthropic returns two cache counts on `response.usage`:

| MarginFront field  | Anthropic field                              | What it is                          |
| ------------------ | -------------------------------------------- | ----------------------------------- |
| `cacheReadTokens`  | `response.usage.cache_read_input_tokens`     | Tokens served from the cache (hits) |
| `cacheWriteTokens` | `response.usage.cache_creation_input_tokens` | Tokens written into the cache       |

```typescript theme={null}
const response = await anthropic.messages.create({
  model: "claude-sonnet-4-20250514",
  max_tokens: 1024,
  messages: [{ role: "user", content: question }],
});

await mf.usage.record({
  customerExternalId: customerId,
  agentCode: "research-bot",
  signalName: "analyses",
  model: response.model,
  modelProvider: "anthropic",
  inputTokens: response.usage.input_tokens,
  outputTokens: response.usage.output_tokens,
  cacheReadTokens: response.usage.cache_read_input_tokens, // cache hits
  cacheWriteTokens: response.usage.cache_creation_input_tokens, // cache writes
});
```

Read-back of these counts depends on your Claude API version having prompt caching enabled. If a response has no cached tokens, the fields come back as `0` — pass them through as-is or omit them.

***

## What happens if MarginFront is down?

Same as OpenAI -- nothing bad. The SDK runs in **fire-and-forget mode** by default:

* **MarginFront unreachable?** Event goes into a local retry buffer. Retries automatically.
* **Validation error?** Warning logged, event dropped. Your customer still got their answer.
* **Your server crashes?** That one event is lost. Acceptable for most use cases.

**MarginFront never blocks your agent.** Your customer always gets their answer.

***

## Streaming responses

Anthropic's streaming works differently from OpenAI. The `message_stop` event includes the final message with usage data:

```typescript theme={null}
const stream = await anthropic.messages.stream({
  model: "claude-sonnet-4-20250514",
  max_tokens: 1024,
  messages: [{ role: "user", content: question }],
});

// Collect chunks as they arrive
let fullContent = "";
stream.on("text", (text) => {
  fullContent += text;
  // Send each chunk to the customer as it arrives
});

// Wait for the stream to finish to get usage data
const finalMessage = await stream.finalMessage();

await mf.usage.record({
  customerExternalId: customerId,
  agentCode: "research-bot",
  signalName: "analyses",
  model: finalMessage.model,
  modelProvider: "anthropic",
  inputTokens: finalMessage.usage.input_tokens,
  outputTokens: finalMessage.usage.output_tokens,
});
```

Unlike OpenAI, you don't need to pass any special option to get usage data -- Anthropic always includes it in the final message.

***

## Extended thinking (Claude models with thinking enabled)

If you use Claude's extended thinking feature, the input and output token counts in `response.usage` already include thinking tokens. You don't need to do anything special -- just pass them as-is:

```typescript theme={null}
const response = await anthropic.messages.create({
  model: "claude-sonnet-4-20250514",
  max_tokens: 16000,
  thinking: {
    type: "enabled",
    budget_tokens: 10000,
  },
  messages: [{ role: "user", content: question }],
});

// Usage already includes thinking tokens -- no extra work needed
await mf.usage.record({
  customerExternalId: customerId,
  agentCode: "research-bot",
  signalName: "deep-analyses",
  model: response.model,
  modelProvider: "anthropic",
  inputTokens: response.usage.input_tokens,
  outputTokens: response.usage.output_tokens,
});
```

***

## Using multiple Claude models

If your agent picks different models for different tasks (e.g., Haiku for fast summaries, Sonnet for detailed analysis), just pass the model that was actually used:

```typescript theme={null}
const modelToUse = needsDeepAnalysis
  ? "claude-sonnet-4-20250514"
  : "claude-haiku-4-20250514";

const response = await anthropic.messages.create({
  model: modelToUse,
  max_tokens: 1024,
  messages: [{ role: "user", content: question }],
});

// response.model reflects whichever model was used
await mf.usage.record({
  customerExternalId: customerId,
  agentCode: "research-bot",
  signalName: "analyses",
  model: response.model,
  modelProvider: "anthropic",
  inputTokens: response.usage.input_tokens,
  outputTokens: response.usage.output_tokens,
});
```

MarginFront looks up the cost per model automatically. A Haiku call costs less than a Sonnet call, and your dashboard shows the difference.

***

## Error handling (if you want more control)

Turn off fire-and-forget to catch tracking errors:

```typescript theme={null}
const mf = new MarginFrontClient(process.env.MF_API_SECRET_KEY!, {
  fireAndForget: false,
});

try {
  await mf.usage.record({
    customerExternalId: customerId,
    agentCode: "research-bot",
    signalName: "analyses",
    model: response.model,
    modelProvider: "anthropic",
    inputTokens: response.usage.input_tokens,
    outputTokens: response.usage.output_tokens,
  });
} catch (error) {
  // Log it, but don't crash your request handler
  console.error("MarginFront tracking failed:", error);
}
```

***

## Auto-provisioning note

If you log an event for a `customerExternalId` or `agentCode` that MarginFront hasn't seen before, it will auto-create a minimal customer or agent record. Handy for prototyping, but can mask typos. If events show up under an unexpected name, check your IDs in the dashboard.

***

## Reading back what you earned

You sent events. Now you want numbers: revenue, cost, margin, MRR. Three canonical paths, all point to the same data.

### Via SDK (inside your app)

```typescript theme={null}
import { MarginFrontClient } from "@marginfront/sdk";
const mf = new MarginFrontClient(process.env.MF_API_SECRET_KEY!);

// Last 30 days of revenue + cost + margin for one customer
const end = new Date();
const start = new Date(end.getTime() - 30 * 24 * 60 * 60 * 1000);

const revenue = await mf.analytics.revenue({
  startDate: start.toISOString(),
  endDate: end.toISOString(),
  customerId: "cus_123",
});

const cost = await mf.analytics.costBreakdown({
  startDate: start.toISOString(),
  endDate: end.toISOString(),
  customerId: "cus_123",
});

const mrr = await mf.analytics.mrr({ customerId: "cus_123" });
```

### Via REST (from any language or `curl`)

```bash theme={null}
curl "https://api.marginfront.com/v1/analytics/revenue?customerId=cus_123&startDate=2026-03-25&endDate=2026-04-24" \
  -H "Authorization: Bearer $MF_API_SECRET_KEY"

curl "https://api.marginfront.com/v1/analytics/cost?customerId=cus_123&startDate=2026-03-25&endDate=2026-04-24" \
  -H "Authorization: Bearer $MF_API_SECRET_KEY"

curl "https://api.marginfront.com/v1/analytics/mrr?customerId=cus_123" \
  -H "Authorization: Bearer $MF_API_SECRET_KEY"
```

### Via MCP (from an AI agent like Claude, Cursor, or Copilot)

If you've connected MarginFront's MCP server to your AI coding assistant, the agent can call canonical analytics tools directly:

```
Tool: get_customer_revenue
Args: { customerExternalId: "acme-001", startDate: "2026-03-25", endDate: "2026-04-24" }

Tool: get_cost_metrics
Args: { startDate: "2026-03-25", endDate: "2026-04-24", customerExternalId: "acme-001" }

Tool: get_mrr
Args: { customerExternalId: "acme-001" }
```

All three paths return the same canonical numbers. Pick whichever fits your workflow.
