Token Cost Management

Ma Mahalakshmi V Updated 19 Sep 2026
8 min read ·Lesson 175 of 224

Understanding input and output token costs

Every model call is billed on two separate quantities: the tokens you send (input) and the tokens the model generates (output). Treating these as a single combined "usage" number, without understanding how they differ in both mechanics and price, is one of the most common reasons teams are surprised by their bill.

What a Token Actually Is

A token is a chunk of text — often a word, part of a word, or a punctuation mark — produced by the model's tokenizer. Tokenization is not the same as splitting on whitespace: common words are often a single token, while rare words, numbers, and non-English text can split into several tokens each. As a rough estimate for English prose, one token is approximately four characters, or about three-quarters of a word, but this ratio varies significantly with content — code, JSON, and non-English languages typically tokenize less efficiently (more tokens per character) than plain English prose.

This matters for cost because billing is per token, not per character or per word. Two prompts of the same character length can cost meaningfully different amounts if one is dense English prose and the other is a JSON payload with lots of punctuation and short keys, because the JSON version may tokenize into more tokens for the same number of characters.

Why Input and Output Are Priced Differently

Input tokens and output tokens are priced separately, and output tokens are typically priced several times higher per token than input tokens. This is not an arbitrary business decision — it reflects a real difference in computational cost.

When a model processes input, it can read the entire prompt in parallel and compute its internal representation of that text in a single forward pass across the whole sequence. When a model generates output, it must produce tokens one at a time, sequentially — each new token depends on every token generated before it, so the model runs a separate forward pass per output token. Generating one hundred output tokens is computationally closer to one hundred separate model invocations than to one, whereas processing one hundred input tokens is one invocation. This sequential, autoregressive generation process is fundamentally more expensive per token, and pricing reflects that.

Note: Exact per-token prices change frequently as providers update pricing tiers and release new models. Always confirm current pricing against the provider's official pricing page before using it in a cost estimate; the ratios and mechanics explained here are stable even when the specific numbers are not.

Building a Cost Model in Code

Because input and output tokens are priced separately, any cost calculation needs to keep them separate too. Combining them into a single "total tokens" number before multiplying by a single rate produces an inaccurate estimate whenever the input/output ratio differs from what that blended rate assumed.

from dataclasses import dataclass


@dataclass
class ModelPricing:
    input_cost_per_1k: float   # USD per 1,000 input tokens
    output_cost_per_1k: float  # USD per 1,000 output tokens


PRICING = {
    "gpt-5.6-terra": ModelPricing(input_cost_per_1k=0.003, output_cost_per_1k=0.015),
    "gpt-5.6-terra-mini": ModelPricing(input_cost_per_1k=0.0006, output_cost_per_1k=0.0024),
}


def estimate_cost(model: str, input_tokens: int, output_tokens: int) -> float:
    pricing = PRICING[model]
    input_cost = (input_tokens / 1000) * pricing.input_cost_per_1k
    output_cost = (output_tokens / 1000) * pricing.output_cost_per_1k
    return round(input_cost + output_cost, 6)

Note: The dollar figures in PRICING are illustrative placeholders for teaching purposes, not real published rates. Replace them with your provider's current, exact pricing before using this in a real cost report.

This function keeps input_cost and output_cost as separate intermediate values before summing them, which makes the calculation auditable — you can print either component individually to see which one dominates a given request. This separation becomes essential once you start optimizing: if output_cost is consistently the larger share of your total spend (which is common, since output tokens cost more per token even when there are fewer of them), the highest-leverage optimization is reducing output length — instructing the model to be more concise, or requesting structured output rather than verbose prose — rather than trimming the input prompt.

Why the Same Task Can Cost Very Differently

Consider two ways of asking a model to answer a factual question:

verbose_prompt = [
    {"role": "user", "content": "What is the capital of France? Please explain your reasoning in detail, including historical context."}
]

concise_prompt = [
    {"role": "system", "content": "Answer in one word only."},
    {"role": "user", "content": "What is the capital of France?"}
]

Both prompts have a similar number of input tokens. But the first invites a long, explanatory answer — potentially hundreds of output tokens — while the second, by explicitly constraining the response format, produces a handful of output tokens. Given that output tokens are priced several times higher than input tokens, the second version can cost substantially less per call even though the input side barely changed. This is the central lesson of this section: controlling output length is usually a bigger cost lever than trimming input, because of the output/input price ratio, not just because of raw token counts.

This does not mean input size is irrelevant — a very large input (a long document, extensive conversation history) can still dominate cost even at the lower input rate, simply through volume. The point is that both levers exist, and the price asymmetry means a small reduction in verbose output can offset a much larger amount of input.

Estimating Cost Before You Call the Model

For applications with a fixed or predictable prompt structure, it is useful to estimate token counts before sending a request, both to warn users about potentially expensive operations and to enforce budget limits. The tiktoken library (or the equivalent tokenizer for your model family) lets you count tokens locally without an API call.

import tiktoken


def count_tokens(text: str, model: str = "gpt-5.6-terra") -> int:
    try:
        encoding = tiktoken.encoding_for_model(model)
    except KeyError:
        # Fall back to a general-purpose encoding if the specific
        # model is not registered in the local tiktoken version.
        encoding = tiktoken.get_encoding("cl100k_base")
    return len(encoding.encode(text))


def estimate_request_cost(
    prompt_text: str,
    model: str,
    expected_output_tokens: int,
) -> float:
    input_tokens = count_tokens(prompt_text, model)
    return estimate_cost(model, input_tokens, expected_output_tokens)

The try/except around encoding_for_model matters because tiktoken's local model registry may not immediately include every newly released model name; falling back to a known-good general encoding (cl100k_base) keeps the function working, with a small accuracy trade-off, rather than raising an exception that blocks the whole request pipeline. expected_output_tokens in estimate_request_cost is necessarily a guess (perhaps from a max_tokens setting or historical averages for this feature) since actual output length is not known until generation completes — this function is for pre-flight estimation and budgeting, not for exact billing, which should always come from the usage object returned with the actual response, as shown in Lesson 1.

Testing Cost Calculations

Cost logic is pure arithmetic and should be tested without any model or tokenizer dependency, using known input/output token counts and pricing.

def test_estimate_cost_separates_input_and_output():
    PRICING["test-model"] = ModelPricing(input_cost_per_1k=1.0, output_cost_per_1k=2.0)
    cost = estimate_cost("test-model", input_tokens=1000, output_tokens=500)
    # 1000 input tokens at $1.00/1k = $1.00
    # 500 output tokens at $2.00/1k = $1.00
    assert cost == 2.0, f"expected 2.0, got {cost}"
    print("PASS: cost combines input and output components correctly")


def test_output_heavy_request_costs_more_than_input_heavy():
    PRICING["test-model-2"] = ModelPricing(input_cost_per_1k=1.0, output_cost_per_1k=3.0)
    output_heavy = estimate_cost("test-model-2", input_tokens=100, output_tokens=1000)
    input_heavy = estimate_cost("test-model-2", input_tokens=1000, output_tokens=100)
    assert output_heavy > input_heavy
    print("PASS: output-heavy request costs more given a higher output rate")


test_estimate_cost_separates_input_and_output()
test_output_heavy_request_costs_more_than_input_heavy()

The second test encodes the core insight of this lesson as an executable assertion: given the same total token count split differently between input and output, the output-heavy request costs more, because output_cost_per_1k is higher. Writing this as a test rather than just a prose claim makes the pricing model's behavior verifiable and protects against a future refactor accidentally flattening input and output into a single rate.

Common Mistakes

Reporting only a single blended "tokens used" metric. A blended number hides which side of the request — input or output — is driving cost, which makes it impossible to choose the right optimization (prompt trimming versus output length control).

Assuming token count scales linearly with word count across all content types. Code, JSON, tables, and non-English text tokenize differently than plain English prose. An estimate based on word count alone can be off by a significant margin for these content types.

Forgetting that system prompts and conversation history count as input tokens on every call. In a multi-turn conversation, the entire history is resent as input on each new turn unless you are using a caching or truncation strategy — a long-running conversation's input cost grows with every turn, not just the newest message.

Best Practices

Track input and output cost as separate fields, always. Store input_tokens, output_tokens, input_cost, and output_cost as distinct values in your logs and reports, and only sum them for a final total — never discard the breakdown.

Set explicit output limits where the use case allows it. A max_tokens parameter or an instruction to be concise is often the single cheapest optimization available, given the output price premium.

Re-verify pricing constants periodically. Store per-model pricing as configuration, not hardcoded literals scattered through the codebase, and review it against the provider's published rates whenever you change models or on a regular cadence — since prices and rate structures do change over time.

0 Comments

Reviewed before they appear

No comments yet.

OpenAI SDK
Introduction to the OpenAI SDK Setting Up Python Creating an API Key Your First Call — client.responses.create() and response.output_text Understanding Billing, Credits, and What a Request Costs Why Responses Replaced Chat Completions Anatomy of a Request: model, input, and instructions Anatomy of a Response: The Typed output Array, Not Just Text Roles: User, Assistant, and Developer/System Choosing a Model, and Reading the Models Page Instead of Memorizing Names Instructions vs. Input Writing Prompts That Get Consistent Results Few-Shot Examples Reasoning Models and the reasoning Parameter Debugging a Prompt That Misbehaves Why Streaming Matters for User Experience stream=True and Iterating Over Events Handling the Event Types You Actually Care About Background Mode for Long-Running Jobs Project — Add Live Streaming to Your Chatbot The Problem With Parsing Free Text JSON Schema and Strict Mode Pydantic Models With the SDK's Parse Helpers Handling Refusals and Validation Failures Project — A Resume-to-JSON Extractor Working With input_image input_file, PDFs, and the Files API Image Generation Speech-to-Text and Text-to-Speech Project: A PDF Question-Answering Script What Function Calling Is Defining a Tool Schema The Full Loop Multiple Tools Errors, Timeouts, and Untrusted Arguments Project: A Weather Assistant Web Search File Search and Vector Stores Code Interpreter Remote MCP Servers and Connectors Project: A Research Assistant What an Embedding Is, Without the Maths Generating and Storing Embeddings Similarity Search From Scratch Hosted Vector Stores vs. Rolling Your Own A Small RAG App Over a Folder of Notes Agents vs. a Single API Call — When You Need One pip install openai Giving Agents Tools Handoffs and Multi-Agent Triage Guardrails and Approvals Tracing and Observing What Your Agent Did A Multi-Agent Support Desk Error Codes and What Each One Means Retries, Timeouts, and Backoff Rate Limits and Spend Limits Prompt Caching and Cost Optimisation The Batch API for Bulk Work Async Clients and Concurrency Moderation and Safety Best Practices Designing the App Backend With FastAPI Streaming to a Simple Frontend Deploying and a Cost/Safety Checklist Why Web Search Is Useful for Current Information Using the Web Search Tool with the Responses API Configuring Search Behavior for Application Use Cases Understanding Citations and Source Attribution Where to Go Next Building a Research Assistant with Web Search Combining Web Search with Structured Outputs Handling Conflicting or Low-Quality Web Sources Reducing Unsupported Claims with Grounded Generation Testing Freshness-Sensitive AI Answers Production Considerations for Web-Grounded Applications Understanding File Search and Retrieval-Augmented Generation Creating and Organizing Vector Stores Uploading Documents for Retrieval Connecting Vector Stores to Responses API Requests Designing Document Metadata and Filtering Strategies Building a PDF Question-Answering Application Improving Retrieval Quality With Better Document Preparation Handling Missing Evidence and Retrieval Failures Combining File Search With Web Search Building a Production Knowledge-Base Assistant What the Code Interpreter Tool Is Designed For Running Python-Based Analysis Through the OpenAI SDK Uploading Datasets for Analysis Analyzing CSV and Spreadsheet Data Generating Charts and Data Summaries Handling Generated Files and Downloadable Artifacts Building a Data-Analysis Assistant Combining Code Execution with Structured Outputs Validating Generated Calculations and Results Security and Sandbox Considerations for Code Execution Understanding Multimodal Input with the OpenAI SDK Sending Images to a Model Image Analysis from URLs and Uploaded Files Extracting Text and Information from Screenshots Building an Image-Question-Answering Application Combining Image Input with Structured Output Analyzing Multiple Images in One Request Handling Image Quality and Input Limitations Designing Multimodal Prompts for Reliable Results Building a Practical Vision-Powered Python Application Understanding Speech-to-Text and Text-to-Speech Workflows Transcribing Audio with the OpenAI SDK Working with Uploaded Audio Files Handling Timestamps and Transcription Metadata Building a Meeting Transcription Workflow Generating Spoken Responses from Text Handling Long Audio and Processing Failures Combining Audio with Text and Tool Calling Building an End-to-End Python Voice Application What Embeddings Are and When to Use Them Generating Embeddings With the OpenAI API Preparing Text for Embedding Comparing Vectors With Cosine Similarity Building a Simple Semantic Search Engine in Python Storing Embeddings in a Database Metadata Filtering for Semantic Search Chunking Strategies for Better Retrieval Evaluating Semantic Search Quality Building a Document Similarity Application When Batch Processing Makes Sense Designing Large-Volume AI Processing Pipelines Using Asynchronous Python with the OpenAI SDK Running Concurrent Requests Safely Controlling Concurrency and Avoiding Rate Limits Tracking Batch Job Progress Handling Partial Failures in Bulk Workloads Retrying Failed Items Without Duplicating Successful Work Designing Resumable AI Processing Jobs Building a Production Batch-Processing Pipeline Batch Processing Makes Sense Large-Scale AI Processing Pipelines Async Python with OpenAI SDK Safe Concurrent Requests Concurrency & Rate Limits Batch Progress Tracking Partial Failure Handling Safe Retry Handling Resumable AI Jobs Production Batch Pipeline System–User Data Separation Reusable App Instructions Prompt Templates & Variables Extraction & Classification Prompts Summarization & Transformation Prompts Explicit Output Requirements Prompt Version Management Prompt Testing & Evaluation Reusable Python Prompt Library API Key Security Secure API Key Storage Secure Secret Management Prompt Injection Prevention Trusted vs. Untrusted Content Tool Argument Validation Sensitive Data Handling Secure Logging AI Action Authorization Production AI Security Checklist Why AI Applications Need Evaluation Beyond Unit Tests Unit Testing OpenAI SDK Integration Code Mocking API Responses in Python Tests Testing Structured Outputs Against Schemas Testing Tool-Calling Workflows Building a Small Evaluation Dataset Measuring Accuracy, Consistency, and Failure Rates Regression Testing Prompts and Model Changes Human Evaluation Versus Automated Evaluation Creating a Repeatable Evaluation Pipeline AI Request Monitoring Token Cost Management Usage Metrics Design Reducing Model Calls Prompt & Context Optimization Model Selection & Optimization AI Caching Strategies Interactive Latency Optimization Usage Dashboards & Budget Alerts Performance & Cost Checklist Every API Call Starts Fresh Fixing API Statelessness Server-Side Conversation Memory Limits of Response Chaining What We're Building Conversation Memory Challenges Preparing an OpenAI SDK Application for Deployment Environment-Specific Configuration for Development and Production Deploying a Python AI Service with Docker Container Health Checks and Startup Configuration Managing Secrets in Cloud Deployments Background Workers for Long-Running AI Tasks Queues and Asynchronous Job Architectures Scaling AI Workloads Horizontally Monitoring Production Incidents and Failures Production Deployment Checklist for OpenAI SDK Applications Reusable OpenAI Service Classes AI Client Dependency Injection Typed AI Responses Python Configuration Management AI Request Decorators Centralized AI Error Handling Clean SDK Abstractions Reusable OpenAI Utilities Internal AI Python Libraries SDK Integration Maintenance Production AI Chatbot Document Q&A System Web Research Assistant Customer Support Agent AI Data Analysis Assistant Image Analysis App Meeting Transcription & Summary Semantic Document Search Multi-Tool AI Agent Production OpenAI SDK App Why "It Looked Fine When I Tested It" Isn't Enough Timing Note Status Note Pre-Decision Status Note Current Availability Note
Ask about this post
AI Ask about this post

Ask questions about Token Cost Management and get answers drawn from it.

Signed-in readers only.