Skip to main content

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, 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 Developer Zone > API Keys in the dashboard.
  2. An Anthropic API key.
  3. An agent and signal already created in the dashboard (or they’ll be auto-created on first event).
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.
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 needsWhere to get it from AnthropicExample value
modelresponse.model"claude-sonnet-4-20250514"
inputTokensresponse.usage.input_tokens1024
outputTokensresponse.usage.output_tokens512

The naming difference from OpenAI

This is the one thing that trips people up:
ProviderInput tokens fieldOutput tokens field
OpenAIusage.prompt_tokensusage.completion_tokens
Anthropicusage.input_tokensusage.output_tokens
MarginFront always uses inputTokens and outputTokens (camelCase). You just need to read from the right field on the provider side.

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:
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:
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:
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:
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.