Build This Now
Build This Now
Qu'est-ce que le code Claude ?Installer Claude CodeL'installateur natif de Claude CodeTon premier projet Claude Code
Subscription BillingMulti-Tenant SaaSAI Chat FeatureRate LimitingStripe WebhooksSemantic SearchRealtime UpdatesDrip Email Sequencesv2.1.122 Release NotesClaude Code Dynamic Workflows : comment orchestrer 1 000 sous-agents sur une vraie codebaseBonnes pratiques Claude CodeMeilleures pratiques pour Claude Opus 4.7Claude Code sur un VPSIntégration GitRevue de code avec Claude CodeLes Worktrees avec Claude CodeClaude Code à distanceClaude Code ChannelsChannels, Routines, Teleport, DispatchTâches planifiées avec Claude CodePermissions Claude CodeLe mode auto de Claude CodeAjouter les paiements Stripe avec Claude CodeFeedback LoopsWorkflows TodoGestion des tâches dans Claude CodeTemplates de projetTarification et utilisation des tokens Claude CodeTarifs de Claude Code : ce que tu vas vraiment payerClaude Code Ultra ReviewConstruire une app Next.js avec Claude CodeSupabase DatabaseVercel DeepsecTest-Driven DevelopmentConstruire un MVP SaaS avec Claude CodeAjouter l'authentification avec Claude Code (Supabase Auth)Ajouter les emails transactionnels avec Claude Code (Resend + React Email)Construire une API type-safe avec Claude Code (oRPC + Zod)File UploadsBackground Jobs (Inngest)Admin DashboardFull-Text SearchClaude Agent SDKKiro Migration GuideClaude Research AgentLeave Grok BuildRoute Subagent ModelsClaude Monorepo SetupCI Repair AgentVisual Regression TestsAgent Cost DashboardCursor Migration GuideCommerce agentique : comment construire une app que les agents IA peuvent payer1M ContextUser API KeysAudit LogsCSV Import PipelineDatabase MigrationsProduction Error TrackingFeature FlagsGitHub ActionsHeadless ModeMax Plan vs APICaching and RevalidationIn-App NotificationsOutbound WebhooksPrompt CachingRoles & PermissionsMarketplace PaymentsUsage-Based BillingCombien coûte la création d'un SaaS avec Claude Code en 2026Parallel AI AgentsCoding Agent Injection
speedy_devvkoen_salo
Blog/Handbook/Workflow/Agent Cost Dashboard

Agent Cost Dashboard

Build a Claude Agent SDK cost dashboard in Next.js 16. Record per-run estimates, token usage, cache activity, and model mix without confusing estimates with billing.

Arrête de tout configurer. Place à la construction.

Des templates SaaS avec orchestration IA.

Découvrez ce que nous construisons pour les entreprises →
speedy_devvWritten by speedy_devvPublished Jul 27, 202612 min readHandbook hubWorkflow index

An Agent SDK cost dashboard records the result of each query() call and turns those estimates into an operational view of run volume, token use, cache activity, and cost trends. The dashboard is useful for engineering optimization, but its dollar totals are estimates and must not be treated as Anthropic billing data.

This guide builds a local version with the Claude Agent SDK and Next.js 16. A runner appends one privacy-conscious record per query to an NDJSON file, and a server-rendered route shows recent usage.

Understand the cost data

The Claude Agent SDK emits a result message when a query finishes. Its total_cost_usd field is the cumulative estimated cost for that one query, including a query that resumes an earlier session. The result can also contain modelUsage, which breaks usage down by model.

Anthropic's official Agent SDK cost tracking guide warns that these values are locally calculated estimates. Pricing changes, unknown models, provider routing, and billing rules can make them differ from an invoice.

Data sourceBest useBilling authority
Agent SDK total_cost_usdCompare individual automated runsNo
Agent SDK modelUsageAnalyze tokens, caching, and model mixNo
Claude Code /usageUnderstand an interactive session or plan allowanceNo
Anthropic Console or Usage and Cost APIReconcile organization chargesYes

That distinction matters for subscription users too. Anthropic's Claude Code cost guide explains that a locally shown session dollar figure does not represent an extra charge when usage is included in a Pro or Max plan.

For a deeper introduction to the query stream and permission options, start with the Claude Agent SDK tutorial. This article focuses on durable cost telemetry around that stream.

Choose a small storage contract

The dashboard needs enough data to compare runs without retaining prompts, source code, or model output. Store a short human-written label, timestamp, duration, status, estimated dollar value, and aggregate token fields.

The finished example uses this structure:

app/
  agent-costs/
    page.tsx
lib/
  agent-costs.ts
scripts/
  run-costed-agent.ts
data/
  agent-costs.ndjson

NDJSON places one JSON object on each line and is easy to inspect locally. For concurrent production workloads, replace it with a database and protect the dashboard with authentication.

Install the Agent SDK and tsx, then add commands for the runner and dashboard. These commands assume the project already uses Next.js 16 and TypeScript.

npm install @anthropic-ai/claude-agent-sdk
npm install --save-dev tsx
npm pkg set scripts.agent:costed="tsx scripts/run-costed-agent.ts"
npm pkg set scripts.dev="next dev"

For a local script that you control, create an API key in the Anthropic Console and expose it to the process through your shell or secret manager. Anthropic's Agent SDK overview says products built on the SDK should use API-key or supported cloud-provider authentication, not another person's Claude subscription credentials.

export ANTHROPIC_API_KEY="replace-with-a-console-api-key"

Do not commit the real value. If you later deploy the runner, store the key in the hosting platform's secret manager and keep it outside the agent's tool-visible filesystem.

Store one record per run

Keep file access in a server-only module. The module below defines the record, appends it atomically enough for a single local process, and tolerates a missing or partially invalid data file when the dashboard starts.

Create lib/agent-costs.ts with the complete implementation. The parser checks the fields used by the page rather than trusting arbitrary JSON, and the module must remain in server-side imports because it reads the local file system.

import { appendFile, mkdir, readFile } from "node:fs/promises";
import path from "node:path";

export type AgentCostRecord = {
  id: string;
  timestamp: string;
  label: string;
  status: "success" | "error";
  durationMs: number;
  estimatedCostUsd: number;
  inputTokens: number;
  outputTokens: number;
  cacheReadInputTokens: number;
  cacheCreationInputTokens: number;
  models: string[];
};

const dataDirectory = path.join(process.cwd(), "data");
const dataFile = path.join(dataDirectory, "agent-costs.ndjson");

function isAgentCostRecord(value: unknown): value is AgentCostRecord {
  if (!value || typeof value !== "object") {
    return false;
  }

  const record = value as Partial<AgentCostRecord>;

  return (
    typeof record.id === "string" &&
    typeof record.timestamp === "string" &&
    typeof record.label === "string" &&
    (record.status === "success" || record.status === "error") &&
    typeof record.durationMs === "number" &&
    typeof record.estimatedCostUsd === "number" &&
    typeof record.inputTokens === "number" &&
    typeof record.outputTokens === "number" &&
    typeof record.cacheReadInputTokens === "number" &&
    typeof record.cacheCreationInputTokens === "number" &&
    Array.isArray(record.models) &&
    record.models.every((model) => typeof model === "string")
  );
}

export async function appendCostRecord(record: AgentCostRecord) {
  await mkdir(dataDirectory, { recursive: true });
  await appendFile(dataFile, `${JSON.stringify(record)}\n`, "utf8");
}

export async function readCostRecords(): Promise<AgentCostRecord[]> {
  try {
    const contents = await readFile(dataFile, "utf8");

    return contents
      .split("\n")
      .filter(Boolean)
      .flatMap((line) => {
        try {
          const parsed: unknown = JSON.parse(line);
          return isAgentCostRecord(parsed) ? [parsed] : [];
        } catch {
          return [];
        }
      })
      .sort((a, b) => b.timestamp.localeCompare(a.timestamp));
  } catch (error) {
    if (
      error instanceof Error &&
      "code" in error &&
      error.code === "ENOENT"
    ) {
      return [];
    }

    throw error;
  }
}

The file contains operational metadata, so add data/agent-costs.ndjson to .gitignore and do not import this module from a Client Component. Keeping run labels generic further reduces the chance of recording a customer name, repository path, or secret.

Capture Agent SDK results

The runner should record exactly one terminal result, even if the query produces many assistant and tool messages. Reading costs from every streamed message and adding them together would overcount a cumulative value.

Create scripts/run-costed-agent.ts with the code below. It totals tokens across the result's model records and uses the result subtype to distinguish successful and failed agent runs.

import { randomUUID } from "node:crypto";
import { query } from "@anthropic-ai/claude-agent-sdk";
import { appendCostRecord } from "../lib/agent-costs";

const prompt = process.argv.slice(2).join(" ").trim();
const label = process.env.AGENT_COST_LABEL ?? "local-project-review";

if (!prompt) {
  console.error(
    'Usage: npm run agent:costed -- "Summarize the authentication flow"',
  );
  process.exit(1);
}

let recorded = false;

for await (const message of query({
  prompt,
  options: {
    model: "sonnet",
    maxTurns: 6,
    allowedTools: ["Read", "Glob", "Grep"],
    permissionMode: "dontAsk",
  },
})) {
  if (message.type !== "result") {
    continue;
  }

  const modelEntries = Object.entries(message.modelUsage ?? {});
  const totals = modelEntries.reduce(
    (sum, [, usage]) => ({
      inputTokens: sum.inputTokens + usage.inputTokens,
      outputTokens: sum.outputTokens + usage.outputTokens,
      cacheReadInputTokens:
        sum.cacheReadInputTokens + usage.cacheReadInputTokens,
      cacheCreationInputTokens:
        sum.cacheCreationInputTokens + usage.cacheCreationInputTokens,
    }),
    {
      inputTokens: 0,
      outputTokens: 0,
      cacheReadInputTokens: 0,
      cacheCreationInputTokens: 0,
    },
  );

  const status = message.subtype === "success" ? "success" : "error";

  await appendCostRecord({
    id: randomUUID(),
    timestamp: new Date().toISOString(),
    label,
    status,
    durationMs: message.duration_ms,
    estimatedCostUsd: message.total_cost_usd,
    models: modelEntries.map(([model]) => model),
    ...totals,
  });

  console.log(
    `${label}: $${message.total_cost_usd.toFixed(4)} estimated, ${status}`,
  );
  process.exitCode = status === "error" ? 1 : 0;
  recorded = true;
  break;
}

if (!recorded) {
  throw new Error("The Agent SDK stream ended without a result message.");
}

The official TypeScript reference defines each ModelUsage entry with input tokens, output tokens, cache-read tokens, cache-creation tokens, estimated cost, and model limits. This example stores aggregate token values plus model names; preserve the full per-model object if the product needs model-specific charts.

permissionMode: "dontAsk" makes unattended execution fail instead of waiting for an approval prompt, while the tool allowlist limits the run to repository reading. Adjust those controls to the actual job rather than giving a cost-measurement script broad write or shell access.

Run a few representative queries to create data. Each invocation appends one terminal record to data/agent-costs.ndjson.

npm run agent:costed -- "Summarize the authentication flow"
AGENT_COST_LABEL=test-audit npm run agent:costed -- "Find untested API routes"

Collect real development work, then compare similar labels over time. Do not run agents merely to fill a chart.

Render the Next.js dashboard

A Server Component can read the local file without exposing file-system code to the browser. Force dynamic rendering so a page refresh reflects records appended after the Next.js process started.

Create app/agent-costs/page.tsx with this complete page. It calculates a seven-day summary and shows the twenty most recent runs in an accessible table.

import { readCostRecords } from "@/lib/agent-costs";

export const dynamic = "force-dynamic";

const money = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
  minimumFractionDigits: 4,
  maximumFractionDigits: 4,
});

const integer = new Intl.NumberFormat("en-US");

function duration(milliseconds: number) {
  if (milliseconds < 1_000) {
    return `${milliseconds} ms`;
  }

  return `${(milliseconds / 1_000).toFixed(1)} s`;
}

export default async function AgentCostsPage() {
  const records = await readCostRecords();
  const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1_000;
  const recent = records.filter(
    (record) => new Date(record.timestamp).getTime() >= sevenDaysAgo,
  );

  const summary = recent.reduce(
    (totals, record) => ({
      runs: totals.runs + 1,
      cost: totals.cost + record.estimatedCostUsd,
      input: totals.input + record.inputTokens,
      output: totals.output + record.outputTokens,
      cacheRead: totals.cacheRead + record.cacheReadInputTokens,
    }),
    { runs: 0, cost: 0, input: 0, output: 0, cacheRead: 0 },
  );

  const average = summary.runs ? summary.cost / summary.runs : 0;

  return (
    <main className="mx-auto min-h-screen max-w-7xl px-6 py-12 text-slate-950">
      <header className="mb-10 max-w-3xl">
        <p className="mb-3 text-sm font-semibold uppercase tracking-widest text-indigo-600">
          Last seven days
        </p>
        <h1 className="text-4xl font-bold tracking-tight sm:text-5xl">
          Agent cost dashboard
        </h1>
        <p className="mt-4 text-lg leading-8 text-slate-600">
          Local Agent SDK estimates for engineering analysis. Reconcile charges
          in the Anthropic Console.
        </p>
      </header>

      <section
        aria-label="Seven day usage summary"
        className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4"
      >
        {[
          ["Estimated cost", money.format(summary.cost)],
          ["Agent runs", integer.format(summary.runs)],
          ["Average per run", money.format(average)],
          ["Cache read tokens", integer.format(summary.cacheRead)],
        ].map(([label, value]) => (
          <article
            key={label}
            className="rounded-2xl border border-slate-200 bg-white p-6 shadow-sm"
          >
            <p className="text-sm font-medium text-slate-500">{label}</p>
            <p className="mt-2 text-2xl font-bold">{value}</p>
          </article>
        ))}
      </section>

      <section className="mt-10 overflow-hidden rounded-2xl border border-slate-200 bg-white">
        <div className="border-b border-slate-200 px-6 py-5">
          <h2 className="text-xl font-bold">Recent runs</h2>
          <p className="mt-1 text-sm text-slate-500">
            Showing up to twenty locally recorded Agent SDK queries.
          </p>
        </div>

        {records.length === 0 ? (
          <p className="px-6 py-12 text-slate-600">
            No runs recorded yet. Use the agent:costed script, then refresh.
          </p>
        ) : (
          <div className="overflow-x-auto">
            <table className="w-full min-w-[900px] text-left text-sm">
              <thead className="bg-slate-50 text-slate-600">
                <tr>
                  {[
                    "Time",
                    "Label",
                    "Status",
                    "Models",
                    "Input",
                    "Output",
                    "Duration",
                    "Estimate",
                  ].map((heading) => (
                    <th key={heading} scope="col" className="px-5 py-3">
                      {heading}
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody className="divide-y divide-slate-100">
                {records.slice(0, 20).map((record) => (
                  <tr key={record.id}>
                    <td className="whitespace-nowrap px-5 py-4 text-slate-600">
                      {new Date(record.timestamp).toLocaleString("en-US", {
                        dateStyle: "medium",
                        timeStyle: "short",
                        timeZone: "UTC",
                      })}
                    </td>
                    <td className="px-5 py-4 font-medium">{record.label}</td>
                    <td className="px-5 py-4">{record.status}</td>
                    <td className="px-5 py-4 text-slate-600">
                      {record.models.join(", ") || "unknown"}
                    </td>
                    <td className="px-5 py-4">
                      {integer.format(record.inputTokens)}
                    </td>
                    <td className="px-5 py-4">
                      {integer.format(record.outputTokens)}
                    </td>
                    <td className="px-5 py-4">
                      {duration(record.durationMs)}
                    </td>
                    <td className="px-5 py-4 font-semibold">
                      {money.format(record.estimatedCostUsd)}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
      </section>

      <p className="mt-5 text-sm leading-6 text-slate-500">
        Estimates can differ from billed usage because pricing and billing
        rules may change. Do not use this page for customer billing or
        automated spending decisions.
      </p>
    </main>
  );
}

The @/lib import assumes the standard Next.js TypeScript alias. The page also assumes Tailwind; use a relative import or CSS Module if the project removed either default.

Start Next.js and open /agent-costs. A refresh will read the latest records because the route is dynamically rendered.

npm run dev

Interpret the numbers

Compare like-for-like tasks first. A lower average across unrelated labels may simply mean the dashboard recorded more small jobs, while a lower average for the same test-audit job can indicate a genuine improvement.

Input tokens include the conversation and context sent to the model. Output tokens reflect generated content. Cache-read input tokens show reused prompt context, which is generally a better sign than repeatedly creating the same cache material. The prompt caching guide explains how stable context and cache boundaries affect that pattern.

Use three practical views:

  • Trend estimated cost and duration by a stable job label.
  • Compare success rate before and after changing prompts or tool permissions.
  • Watch cache-read volume relative to ordinary input for repeated workflows.

Do not impose a hard stop from local dollar estimates. If an automated workflow needs a safety limit, use deterministic controls such as maxTurns, restricted tools, a job timeout, and workload quotas, then reconcile actual charges through Anthropic's authoritative systems.

The dashboard also should not be used to compare Max plan value directly with API usage. Those products have different payment models; the Max plan versus API guide covers that decision.

Extend it safely

Move records to Postgres or another transactional store before multiple processes can append at once. Add a unique run ID, authenticated access, retention policy, and organization boundary before exposing the page to a team.

Keep prompts and responses out of cost telemetry unless there is a clear, reviewed need to retain them. Labels should be controlled categories, not arbitrary user input. Restrict dashboard access because repository names, job categories, model choices, and timing can still reveal operational information.

For interactive Claude Code rather than the Agent SDK, Anthropic supports OpenTelemetry monitoring for cost, token, session, pull request, commit, and activity metrics. It can segment some metrics by session, model, agent, skill, or plugin. Anthropic notes that cost metrics remain estimates, and enabling detailed prompt or tool telemetry can expose sensitive data.

Once the local version answers a real question, add only the dimensions needed for it.

Frequently asked questions

How do I track Claude Agent SDK cost?

Read the single result message emitted at the end of each query() call and store its total_cost_usd value. Read modelUsage from the same result when you need whole-agent-tree token counts, including work performed by subagents. Do not add total_cost_usd from intermediate stream messages because it is already cumulative for the call.

Is total_cost_usd the same as my Anthropic bill?

No. The Agent SDK calculates total_cost_usd locally from a bundled price table. Anthropic says the estimate can drift when prices change, a model is unknown to the installed SDK, or billing rules differ. Reconcile financial records against the Anthropic Console or Usage and Cost API.

Can a Claude agent cost dashboard show usage by model?

Yes. The result message has a modelUsage map keyed by model name. Each entry contains input, output, cache-read, cache-creation, and estimated cost fields. This tutorial aggregates the token fields and keeps the model names; store each full entry if you need per-model totals or charts.

Code Kit is a $29 one-time harness that runs on top of Claude Code. It helps plan, build, evaluate, and test changes with quality gates for zero type errors, zero lint errors, and a clean production build; Claude Code itself requires a paid Anthropic plan.

Posted by @speedy_devv

Continue in Workflow

  • Commerce agentique : comment construire une app que les agents IA peuvent payer
    Un guide en français simple du commerce agentique en 2026 : ce que font x402, ACP et le Machine Payments Protocol, plus un pas-à-pas d'un week-end pour livrer une API payante que les agents IA peuvent acheter.
  • Bonnes pratiques Claude Code
    Cinq habitudes séparent les ingénieurs qui livrent avec Claude Code : les PRDs, les règles CLAUDE.md modulaires, les slash commands personnalisés, les resets /clear, et un état d'esprit d'évolution du système.
  • Le mode auto de Claude Code
    Un second modèle Sonnet examine chaque appel d'outil Claude Code avant qu'il s'exécute. Ce que le mode auto bloque, ce qu'il autorise, et les règles d'autorisation qu'il place dans tes paramètres.
  • Channels, Routines, Teleport, Dispatch
    Les quatre fonctionnalités Claude Code livrées par Anthropic en mars et avril 2026 qui transforment le CLI en une couche de coordination orientée événements, entre téléphone, web et desktop.
  • Claude Code 1M Context in Practice: When Bigger Isn't Better
    The 1M-token context window is GA at flat pricing, but bigger isn't always better. A decision framework, token-cost math, and when to use /compact, subagents, and dynamic workflows instead.
  • How to Build an Admin Dashboard With Claude Code
    Ship an internal admin panel with Claude Code: role-gated routes, a searchable users and orders table with pagination, impersonation-safe RLS, and metrics tiles pulled through a type-safe API.

More from Handbook

  • Principes de base de l'agent
    Cinq façons de construire des agents spécialisés dans le code Claude : Sous-agents de tâches, .claude/agents YAML, commandes slash personnalisées, personas CLAUDE.md, et invites de perspective.
  • L'ingénierie du harness agent
    Le harness, c'est toutes les couches autour de ton agent IA sauf le modèle lui-même. Découvre les cinq leviers de contrôle, le paradoxe des contraintes, et pourquoi le design du harness détermine les performances de l'agent bien plus que le modèle.
  • Patterns d'agents
    Orchestrateur, fan-out, chaîne de validation, routage par spécialiste, raffinement progressif, et watchdog. Six formes d'orchestration pour câbler des sub-agents Claude Code.
  • Meilleures pratiques des équipes d'agents
    Patterns éprouvés pour les équipes d'agents Claude Code. Prompts de création riches en contexte, tâches bien calibrées, propriété des fichiers, mode délégué, et correctifs v2.1.33-v2.1.45.

Arrête de tout configurer. Place à la construction.

Des templates SaaS avec orchestration IA.

Découvrez ce que nous construisons pour les entreprises →

Visual Regression Tests

Build visual regression tests with Claude Code, Next.js 16, and Playwright. Create stable screenshot baselines, review diffs, and block accidental UI changes in CI.

Cursor Migration Guide

Migrate a Cursor project to Claude Code without losing rules, commands, MCP servers, or shared agent instructions. Includes a safe conversion script and a verification checklist.

On this page

Understand the cost data
Choose a small storage contract
Store one record per run
Capture Agent SDK results
Render the Next.js dashboard
Interpret the numbers
Extend it safely
Frequently asked questions
How do I track Claude Agent SDK cost?
Is total_cost_usd the same as my Anthropic bill?
Can a Claude agent cost dashboard show usage by model?

Arrête de tout configurer. Place à la construction.

Des templates SaaS avec orchestration IA.

Découvrez ce que nous construisons pour les entreprises →