Examples

Charge users for Claude API usage

Updated Oct 25, 2026

These examples bill your users for Claude API calls made with the Anthropic TypeScript SDK. Use meter() to charge for the tokens each request actually uses, or charge() for a flat price per reply. You set the prices; Payfer takes them from the person’s balance and pays you what’s left after its fee.

You’ll need a connected user’s paymentUserId from the quickstart, and both SDKs on your server.

Terminal
npm install payfer @anthropic-ai/sdk
server.ts
import Anthropic from "@anthropic-ai/sdk";
import { Payfer } from "payfer/server";

const anthropic = new Anthropic(); // reads ANTHROPIC_API_KEY
const payfer = new Payfer({ secretKey: process.env.PAYFER_SECRET_KEY! });

Meter Claude API costs per token

Count the input tokens first, then reserve the most the request could cost: that input plus a full max_tokens of output. Payfer checks the person’s balance and allowance before Claude runs, collects the price of the tokens Claude reports, and releases the rest.

server.ts
const MODEL = "claude-opus-5-5";
const MAX_TOKENS = 4000;

// What you charge, in dollars per million tokens, which is also
// microdollars per token. Set these above your Anthropic cost.
const PRICE = { input: 6, output: 30 };

// Payfer amounts are dollar strings with up to 6 decimal places
// (whole microdollars), and at least $0.005.
const toAmount = (micros: number) =>
  (Math.max(Math.ceil(micros), 5000) / 1e6).toFixed(6);

const priceOf = (input: number, output: number) =>
  toAmount(input * PRICE.input + output * PRICE.output);

export async function ask(
  paymentUserId: string,
  question: string,
  operationId: string,
) {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: question },
  ];

  // The most this request can cost: the exact input, plus max_tokens of output.
  const counted = await anthropic.messages.countTokens({
    model: MODEL,
    messages,
  });
  const maxAmount = priceOf(counted.input_tokens, MAX_TOKENS);
  const reservation = { paymentUserId, maxAmount, idempotencyKey: operationId };

  return payfer.meter(reservation, async () => {
    const message = await anthropic.messages.create({
      model: MODEL,
      max_tokens: MAX_TOKENS,
      messages,
    });
    const { input_tokens, output_tokens } = message.usage;
    return { amount: priceOf(input_tokens, output_tokens), value: message };
  });
}

output_tokens includes Claude’s thinking, and max_tokens caps both, so the price you collect never exceeds the reservation. If the person can’t cover the reservation, meter() throws funding_required or allowance_exceeded before Claude is called. If Claude throws, the reservation is released and nothing is charged.

With prompt caching on, Claude reports cached input separately as usage.cache_read_input_tokens and usage.cache_creation_input_tokens. Add them at whatever rate you charge for them.

Charge a flat price per Claude reply

For a simple price per message, charge before calling Claude, so someone without funds never costs you a model call, and refund if Claude fails. Reusing the operation’s idempotency key on a retry returns the same payment instead of charging twice.

server.ts
// A flat price for each reply.
const PRICE_PER_REPLY = "0.01";

export async function reply(
  paymentUserId: string,
  question: string,
  operationId: string,
) {
  // Charge first, so a user without funds never costs you a Claude call.
  const payment = await payfer.charge({
    paymentUserId,
    amount: PRICE_PER_REPLY,
    idempotencyKey: operationId,
  });
  try {
    return await anthropic.messages.create({
      model: "claude-opus-5-5",
      max_tokens: 2000,
      messages: [{ role: "user", content: question }],
    });
  } catch (err) {
    // Claude didn't answer, so give the money back.
    await payfer.refund({
      paymentId: payment.id,
      idempotencyKey: operationId + ":refund",
    });
    throw err;
  }
}

Bill streamed Claude responses

Chat interfaces stream. Forward text to your user inside the meter() callback, then price the final message’s usage when the stream ends. This uses MODEL, MAX_TOKENS and priceOf from the per-token example.

server.ts
export async function askStreaming(
  paymentUserId: string,
  question: string,
  operationId: string,
  send: (text: string) => void,
) {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: question },
  ];
  const counted = await anthropic.messages.countTokens({
    model: MODEL,
    messages,
  });
  const maxAmount = priceOf(counted.input_tokens, MAX_TOKENS);
  const reservation = { paymentUserId, maxAmount, idempotencyKey: operationId };

  return payfer.meter(reservation, async () => {
    const stream = anthropic.messages.stream({
      model: MODEL,
      max_tokens: MAX_TOKENS,
      messages,
    });
    // Pass text to your user as it arrives.
    stream.on("text", (text) => send(text));
    const message = await stream.finalMessage();
    const { input_tokens, output_tokens } = message.usage;
    return { amount: priceOf(input_tokens, output_tokens), value: message };
  });
}

Next steps