Back to Intelligence Hub
CostSeptember 26, 2026

How to Calculate Token Usage Cost Accurately in Python & TypeScript

"Exact formulas, tiktoken usage, per-model pricing tables, Anthropic count_tokens API, and multi-turn cost accumulation patterns — with budget enforcement for production AI agents."

Observyze Engineering
Observyze Engineering
Runtime Systems & AI Reliability
9 min read

The Observyze Engineering team builds production-grade telemetry, circuit breakers, and runtime governance tools for distributed multi-agent systems and LLM pipelines.

Direct Answer — Cost Formula

cost_usd = (prompt_tokens × input_price_per_M / 1_000_000)
         + (completion_tokens × output_price_per_M / 1_000_000)

Use tiktoken (GPT models) or count_tokens API (Anthropic) for pre-call estimation. Always read response.usage post-call for billing accuracy.

Model Pricing Reference (Verified September 2026)

USD per 1 million tokens. Verify against official provider pricing before production billing decisions.

ModelInput $/1MOutput $/1MEncoding
gpt-4o$2.50$10.00o200k_base
gpt-4o-mini$0.15$0.60o200k_base
o3-mini$1.10$4.40o200k_base
claude-3-5-sonnet$3.00$15.00count_tokens API
claude-3-haiku$0.25$1.25count_tokens API
gemini-2.0-flash$0.10$0.40Vertex AI count
gemini-1.5-pro$1.25$5.00Vertex AI count

Python — tiktoken Pre-Call Estimation + Post-Call Tracking

Python — cost_tracker.py
import tiktoken
from openai import OpenAI
from dataclasses import dataclass

PRICING: dict[str, tuple[float, float]] = {
    "gpt-4o": (2.50, 10.00),
    "gpt-4o-mini": (0.15, 0.60),
    "o3-mini": (1.10, 4.40),
}

@dataclass
class CostTracker:
    model: str
    session_cost_usd: float = 0.0

    def estimate_prompt_cost(self, messages: list[dict]) -> float:
        """Pre-call estimation — not billing-accurate."""
        enc = tiktoken.encoding_for_model(self.model)
        tokens = sum(len(enc.encode(m["content"])) for m in messages)
        input_price, _ = PRICING[self.model]
        return tokens * input_price / 1_000_000

    def record_usage(self, usage) -> float:
        """Post-call — billing-accurate from response.usage."""
        input_price, output_price = PRICING[self.model]
        call_cost = (
            usage.prompt_tokens * input_price / 1_000_000
            + usage.completion_tokens * output_price / 1_000_000
        )
        self.session_cost_usd += call_cost
        return call_cost

client = OpenAI()
tracker = CostTracker(model="gpt-4o")

def agent_turn(messages: list[dict], budget_usd: float = 0.10) -> str:
    estimated = tracker.estimate_prompt_cost(messages)
    if tracker.session_cost_usd + estimated > budget_usd:
        raise RuntimeError(f"Budget exceeded: USD {tracker.session_cost_usd:.4f} + ~USD {estimated:.4f}")

    response = client.chat.completions.create(model=tracker.model, messages=messages)
    call_cost = tracker.record_usage(response.usage)
    print(f"Call: USD {call_cost:.4f} | Session: USD {tracker.session_cost_usd:.4f}")
    return response.choices[0].message.content

TypeScript — js-tiktoken + Cost Tracking

TypeScript — cost-tracker.ts
import OpenAI from 'openai'
import { getEncoding } from 'js-tiktoken' // npm i js-tiktoken

const PRICING: Record<string, [number, number]> = {
  'gpt-4o': [2.50, 10.00],
  'gpt-4o-mini': [0.15, 0.60],
}

class CostTracker {
  sessionCostUsd = 0

  estimatePromptCost(messages: OpenAI.ChatCompletionMessageParam[]): number {
    const enc = getEncoding('o200k_base')
    const tokens = messages.reduce((sum, msg) => {
      const text = typeof msg.content === 'string' ? msg.content : ''
      return sum + enc.encode(text).length
    }, 0)
    return (tokens * PRICING['gpt-4o'][0]) / 1_000_000
  }

  recordUsage(usage: OpenAI.CompletionUsage, model: string): number {
    const [inputPrice, outputPrice] = PRICING[model]
    const callCost =
      (usage.prompt_tokens * inputPrice) / 1_000_000 +
      (usage.completion_tokens * outputPrice) / 1_000_000
    this.sessionCostUsd += callCost
    return callCost
  }
}

const client = new OpenAI()
const tracker = new CostTracker()

async function agentTurn(
  messages: OpenAI.ChatCompletionMessageParam[],
  budgetUsd = 0.10
): Promise<string> {
  const estimated = tracker.estimatePromptCost(messages)
  if (tracker.sessionCostUsd + estimated > budgetUsd) {
    throw new Error('Session budget would be exceeded')
  }
  const res = await client.chat.completions.create({ model: 'gpt-4o', messages })
  if (res.usage) tracker.recordUsage(res.usage, 'gpt-4o')
  return res.choices[0].message.content ?? ''
}

Anthropic Claude — count_tokens API (Python)

Claude uses a SentencePiece-based tokenizer. Do not use tiktoken for Anthropic — the token counts will be inaccurate. Use the official count_tokens beta API:

Python — anthropic_count_tokens.py
import anthropic

CLAUDE_PRICING = {
    "claude-3-5-sonnet-20241022": (3.00, 15.00),
    "claude-3-haiku-20240307": (0.25, 1.25),
}

client = anthropic.Anthropic()

def estimate_claude_cost(model: str, messages: list[dict]) -> float:
    # Makes a lightweight API call, returns exact token count
    response = client.beta.messages.count_tokens(
        model=model,
        messages=messages,
        betas=["token-counting-2024-11-01"],
    )
    input_price, _ = CLAUDE_PRICING[model]
    return response.input_tokens * input_price / 1_000_000

⚠️ The Multi-Turn Token Trap

Tracking only completion tokens underestimates cost significantly in multi-turn conversations. The full message history is re-submitted every turn — prompt tokens grow non-linearly:

Turn 1: prompt=500t completion=200t → $0.0013

Turn 2: prompt=750t completion=180t → $0.0019

Turn 5: prompt=1,800t completion=250t → $0.0070

Turn 10: prompt=4,200t completion=300t → $0.0135

Total after 10 turns: ~$0.09 vs $0.02 if only counting completions

Automated Budget Enforcement

For autonomous multi-agent sessions, use Observyze execution budgets to enforce hard ceilings automatically without manual tracking code in every agent:

TypeScript — budget-enforcement.ts
import { ObservyzeClient } from '@observyze/sdk'
import OpenAI from 'openai'

const obs = new ObservyzeClient({
  apiKey: process.env.OBSERVYZE_API_KEY!,
  circuitBreaker: {
    maxExecutionBudgetUsd: 0.50,  // hard ceiling per session
    maxTurns: 20,                  // halt after 20 turns
    maxTokens: 200_000,            // halt at 200K cumulative tokens
  },
})

// All token tracking, cost accumulation, and budget enforcement
// happens automatically — throws BudgetExceededError when exceeded
const openai = obs.wrapOpenAI(new OpenAI())
Technical FAQ

Frequently Asked Questions

Q1.How do you calculate OpenAI token cost in Python?

Use tiktoken to count tokens pre-call, then apply the formula: cost_usd = (prompt_tokens * input_price_per_1m / 1_000_000) + (completion_tokens * output_price_per_1m / 1_000_000). Read actual counts from response.usage after each call for billing accuracy. GPT-4o uses the o200k_base tiktoken encoding.

Q2.Does tiktoken give accurate token counts for gpt-4o?

tiktoken is accurate for text tokens in gpt-4o (o200k_base encoding). It does not account for image tokens in multimodal calls — OpenAI uses a tile-based counting system for images (170 tokens per 512×512 tile plus 85 base tokens). Use response.usage for billing-accurate counts after each call.

Q3.How do you count tokens for Anthropic Claude?

Anthropic provides a count_tokens beta API method: client.beta.messages.count_tokens(model="claude-3-5-sonnet-20241022", messages=messages). This returns the exact input token count before inference. Do not use tiktoken for Anthropic — Claude uses a different tokenizer (SentencePiece-based).

Q4.How do you track cumulative cost across a multi-turn conversation?

Maintain a running cost accumulator in session state. Add each call cost after the API call: session_cost += (response.usage.prompt_tokens * input_price / 1M) + (response.usage.completion_tokens * output_price / 1M). Note that prompt tokens grow with history — the full message array is re-tokenized each turn, so prompt token cost grows non-linearly.

Ready to Govern your Inference?

Request Early Access to evaluate Observyze against a representative AI workflow.