Build This Now
Build This Now
Was ist der Claude Code?Claude Code installierenClaude Code Native InstallerDein erstes Claude Code-Projekt
Subscription BillingMulti-Tenant SaaSAI Chat FeatureRate LimitingStripe WebhooksSemantic SearchRealtime UpdatesDrip Email Sequencesv2.1.122 Release NotesClaude Code Dynamic Workflows: 1.000 Subagents auf einer echten Codebase orchestrierenClaude Code Best PracticesClaude Opus 4.7 Best PracticesClaude Code auf einem VPSGit-IntegrationClaude Code ReviewClaude Code WorktreesClaude Code Remote ControlClaude Code ChannelsChannels, Routines, Teleport, DispatchGeplante Aufgaben mit Claude CodeClaude Code BerechtigungenClaude Code Auto-ModusStripe-Zahlungen mit Claude Code einbauenFeedback-LoopsTodo-WorkflowsClaude Code TasksProjekt-TemplatesClaude Code Preise und Token-NutzungClaude Code Preise: Was du wirklich zahlstClaude Code Ultra ReviewEine Next.js-App mit Claude Code bauenSupabase DatabaseVercel DeepsecTest-Driven DevelopmentSo baust du ein SaaS-MVP mit Claude CodeAuthentifizierung mit Claude Code einbauen (Supabase Auth)Transaktions-E-Mails mit Claude Code einbauen (Resend + React Email)Eine typsichere API mit Claude Code bauen (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 GuideAgentic Commerce: Wie du eine App baust, für die KI-Agents bezahlen können1M ContextUser API KeysAudit LogsCSV Import PipelineDatabase MigrationsProduction Error TrackingFeature FlagsGitHub ActionsHeadless ModeMax Plan vs APICaching and RevalidationIn-App NotificationsOutbound WebhooksPrompt CachingRoles & PermissionsMarketplace PaymentsUsage-Based BillingWas es 2026 kostet, ein SaaS mit Claude Code zu bauenParallel 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.

Hören Sie auf zu konfigurieren. Fangen Sie an zu bauen.

SaaS-Builder-Vorlagen mit KI-Orchestrierung.

Sehen Sie, was wir für Unternehmen bauen →
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

  • Agentic Commerce: Wie du eine App baust, für die KI-Agents bezahlen können
    Ein Guide in einfachem Deutsch zu Agentic Commerce im Jahr 2026: Was x402, ACP und das Machine Payments Protocol tun, plus eine Wochenend-Anleitung, um eine bezahlte API auszuliefern, von der KI-Agents kaufen können.
  • Claude Code Best Practices
    Fünf Gewohnheiten trennen Entwickler, die mit Claude Code liefern: PRDs, modulare CLAUDE.md-Regeln, Custom-Slash-Commands, /clear-Resets und eine System-Evolutions-Denkweise.
  • Claude Code Auto-Modus
    Ein zweites Sonnet-Modell prüft jeden Claude Code-Tool-Aufruf, bevor er ausgeführt wird. Was der Auto-Modus blockiert, was er erlaubt, und die Erlaubnisregeln, die er in deine Einstellungen schreibt.
  • Channels, Routines, Teleport, Dispatch
    Die vier Claude-Code-Features, die Anthropic im März und April 2026 ausgeliefert hat und die die CLI in eine ereignisgesteuerte Koordinationsschicht über Handy, Web und Desktop verwandeln.
  • 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

  • Grundlagen für Agenten
    Fünf Möglichkeiten, spezialisierte Agenten in Claude Code zu erstellen: Aufgaben-Unteragenten, .claude/agents YAML, benutzerdefinierte Slash-Befehle, CLAUDE.md Personas und perspektivische Aufforderungen.
  • Agent-Harness-Engineering
    Der Harness ist jede Schicht rund um deinen KI-Agenten, außer dem Modell selbst. Lern die fünf Steuerungshebel, das Constraint-Paradoxon und warum das Harness-Design die Performance des Agenten mehr bestimmt als das Modell.
  • Agenten-Muster
    Orchestrator, Fan-out, Validierungskette, Spezialistenrouting, Progressive Verfeinerung und Watchdog. Sechs Orchestrierungsformen, um Claude Code Sub-Agenten zu verdrahten.
  • Agent Teams Best Practices
    Bewährte Muster für Claude Code Agent Teams. Kontextreiche Spawn-Prompts, richtig bemessene Aufgaben, Datei-Eigentümerschaft, Delegate-Modus und Fixes für v2.1.33-v2.1.45.

Hören Sie auf zu konfigurieren. Fangen Sie an zu bauen.

SaaS-Builder-Vorlagen mit KI-Orchestrierung.

Sehen Sie, was wir für Unternehmen bauen →

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?

Hören Sie auf zu konfigurieren. Fangen Sie an zu bauen.

SaaS-Builder-Vorlagen mit KI-Orchestrierung.

Sehen Sie, was wir für Unternehmen bauen →