Skip to main content
Lesson 16 of 22 · Responses API: OpenAI Fundamentals

Monitoring costs and usage

Lesson 16 of 22 Updated July 29, 2026 Developer
On this page
  1. Monitoring costs and usage
  2. Understanding billing
  3. Cost calculator
  4. Real-time cost tracker
  5. Client with budget control
  6. Optimizing costs
  7. Key takeaways

Monitoring costs and usage

API costs can spiral out of control quickly if you don’t watch them. This lesson teaches you how to track, estimate, and control your spending in production. The story repeats itself from project to project: a feature goes live on a Thursday, no one checks the dashboard over the weekend, and Monday morning’s bill matches the entire monthly budget. The steps below help you catch cost drift the moment it happens, not thirty days later.

Understanding billing

Costs are calculated per million tokens, with different prices for input and output. This asymmetry has direct consequences for your design choices: sending a long document as input costs significantly less than having the model generate a long one. Every response returns the exact count for both directions, so you never have to estimate.

from openai import OpenAI
client = OpenAI()

# Every response includes usage metrics
response = client.responses.create(
    model="gpt-5.6-terra",
    input="Explain OpenAI API billing."
)

usage = response.usage
print(f"Input tokens: {usage.input_tokens}")
print(f"Output tokens: {usage.output_tokens}")
print(f"Total tokens: {usage.total_tokens}")

# Output costs more than input!

Cost calculator

Converting these counters into dollars requires a pricing grid. Isolate it in a dedicated structure rather than scattering constants throughout your code: when prices change or you add a model, you only need to modify one line. The rates below are indicative and should be verified during every budget review.

from dataclasses import dataclass

@dataclass
class ModelRate:
    input_per_million: float
    output_per_million: float

# Approximate prices in USD per million tokens
# Always check current prices on platform.openai.com/pricing
RATES = {
    "gpt-5.6-luna": ModelRate(input_per_million=0.20, output_per_million=1.20),
    "gpt-5.6-terra": ModelRate(input_per_million=2.0, output_per_million=12.0),
    "gpt-5.6-sol": ModelRate(input_per_million=5.0, output_per_million=30.0),
}

def calculate_cost(model: str, input_tokens: int,
                   output_tokens: int) -> float:
    """Calculates the cost of a call in USD."""
    rate = RATES.get(model)
    if not rate:
        raise ValueError(f"Unknown model: {model}")

    input_cost = (input_tokens / 1_000_000) * rate.input_per_million
    output_cost = (output_tokens / 1_000_000) * rate.output_per_million

    return input_cost + output_cost

# Example
cost = calculate_cost("gpt-5.6-terra", input_tokens=1000, output_tokens=500)
print(f"Cost: ${cost:.6f}")
# Result: Cost: $0.008000

Eight-tenths of a cent per call sounds reassuring until you multiply it by the fifty thousand daily calls of a production chatbot—that’s four hundred dollars a day. Always get into the habit of thinking in monthly volumes, never per call.

Real-time cost tracker

Unit calculations are useless unless they are aggregated. This tracker aggregates spending across three axes—by day, by model, and by number of calls—and compares the day’s total to a budget you set. Alerts are graduated: a warning when less than 20% of the budget remains, followed by a hard alert upon exceeding the limit. This intermediate step is valuable because it gives you time to intervene before service is cut off.

import json
from datetime import datetime, date
from collections import defaultdict

class CostTracker:
    """Real-time API cost tracking."""

    def __init__(self, daily_budget_usd: float = 10.0):
        self.daily_budget = daily_budget_usd
        self.costs_per_day: dict[str, float] = defaultdict(float)
        self.costs_per_model: dict[str, float] = defaultdict(float)
        self.calls_per_day: dict[str, int] = defaultdict(int)
        self.total_tokens = 0

    def record(self, model: str, input_tokens: int,
               output_tokens: int):
        """Records the costs of a call."""
        cost = calculate_cost(model, input_tokens, output_tokens)
        today = date.today().isoformat()

        self.costs_per_day[today] += cost
        self.costs_per_model[model] += cost
        self.calls_per_day[today] += 1
        self.total_tokens += input_tokens + output_tokens

        return cost

    def remaining_budget(self) -> float:
        """Returns the remaining budget for today."""
        today = date.today().isoformat()
        return self.daily_budget - self.costs_per_day[today]

    def budget_alert(self) -> bool:
        """Checks if the budget is close to the limit."""
        remaining = self.remaining_budget()
        if remaining <= 0:
            print("ALERT: Daily budget exceeded!")
            return True
        elif remaining < self.daily_budget * 0.2:
            print(f"WARNING: ${remaining:.2f} remaining on daily budget")
            return True
        return False

    def report(self) -> dict:
        """Generates a cost report."""
        return {
            "total_cost_usd": sum(self.costs_per_day.values()),
            "cost_today_usd": self.costs_per_day.get(
                date.today().isoformat(), 0
            ),
            "remaining_budget_usd": self.remaining_budget(),
            "costs_per_model": dict(self.costs_per_model),
            "total_tokens": self.total_tokens,
        }

# Usage
tracker = CostTracker(daily_budget_usd=5.0)

response = client.responses.create(
    model="gpt-5.6-terra",
    input="Hello!"
)

cost = tracker.record(
    model="gpt-5.6-terra",
    input_tokens=response.usage.input_tokens,
    output_tokens=response.usage.output_tokens
)
print(f"Cost of this call: ${cost:.6f}")
print(json.dumps(tracker.report(), indent=2))

Client with budget control

Monitoring isn’t always enough: faced with a faulty loop calling the API continuously, you need a financial circuit breaker. This client checks the remaining budget before issuing the request and flatly refuses the call if the envelope is exhausted. The service degrades, but in a controlled way without surprise bills. Cost accounting happens afterward, once actual usage is known.

class BudgetControlledClient:
    """Client that refuses calls if the budget is exceeded."""

    def __init__(self, daily_budget: float = 10.0):
        self.client = OpenAI()
        self.tracker = CostTracker(daily_budget_usd=daily_budget)

    def create(self, model: str, input: str, **kwargs) -> object:
        """API call with budget control."""
        # Check budget before the call
        if self.tracker.remaining_budget() <= 0:
            raise RuntimeError(
                "Daily budget exhausted. "
                f"Spent: ${self.tracker.report()['cost_today_usd']:.2f}"
            )

        response = self.client.responses.create(
            model=model,
            input=input,
            **kwargs
        )

        cost = self.tracker.record(
            model=model,
            input_tokens=response.usage.input_tokens,
            output_tokens=response.usage.output_tokens
        )

        # Alert if budget is low
        self.tracker.budget_alert()

        return response

# Usage
budget_client = BudgetControlledClient(daily_budget=2.0)

try:
    response = budget_client.create("gpt-5.6-terra", "Hello!")
    print(response.output_text)
except RuntimeError as e:
    print(f"Blocked: {e}")

Optimizing costs

1. Choosing the right model

The most powerful lever remains routing by complexity. A three-category classification task has no need for the most expensive model: for this task, the bill difference reaches a factor of twenty-five for identical quality. Make complexity an explicit parameter of your function rather than a decision hardcoded into the logic.

# Relative cost per task (approximate, ~300 input tokens / 20 output):
# Simple classification: gpt-5.6-luna ~$0.0001 vs gpt-5.6-sol ~$0.002
# The right model makes a 25x difference in costs!

def optimized_call(prompt: str, complexity: str) -> str:
    """Chooses the most economical suitable model."""
    model = {
        "simple": "gpt-5.6-luna",
        "standard": "gpt-5.6-terra",
        "complex": "gpt-5.6-sol",
    }.get(complexity, "gpt-5.6-terra")

    response = client.responses.create(model=model, input=prompt)
    return response.output_text

2. Caching responses

The second lever is avoiding paying twice for the same response. An FAQ, product catalog, or standard form generates strictly identical prompts dozens of times a day. The cache key combines the model, input, and parameters—including all of them is essential, otherwise a change in temperature would silently return the old response.

import hashlib

class CachedClient:
    """Client with cache to avoid redundant calls."""

    def __init__(self):
        self.client = OpenAI()
        self.cache: dict[str, str] = {}

    def create(self, model: str, input: str, **kwargs) -> str:
        # Create a cache key
        cache_key = hashlib.md5(
            f"{model}:{input}:{json.dumps(kwargs, sort_keys=True)}".encode()
        ).hexdigest()

        if cache_key in self.cache:
            print("Cache hit!")
            return self.cache[cache_key]

        response = self.client.responses.create(
            model=model, input=input, **kwargs
        )

        self.cache[cache_key] = response.output_text
        return response.output_text

3. Limiting output tokens

Finally, cap the output whenever its length is predictable. Since output is the expensive part, asking for a single word while authorizing two thousand tokens leaves the door open for a verbose response that you will pay for but not use.

# Always set max_output_tokens for predictable tasks
response = client.responses.create(
    model="gpt-5.6-terra",
    input="Classify this text: positive, negative, or neutral.",
    max_output_tokens=10  # Expecting a single word
)

Rates as of August 5, 2026 — prices change regularly: before any budget calculation, verify the current grid on the official OpenAI pricing page.

Key takeaways

  • Costs depend on the model AND the direction (input vs output)
  • Implement a cost tracker with budget alerts
  • Use the right model for each task: Luna for volume, Terra by default
  • Caching can drastically reduce costs for repetitive requests
  • Set max_output_tokens when response length is predictable
  • Check the OpenAI dashboard for official consumption tracking