Azure AI/ML

Tokens, Context Windows, and Cost: How Azure OpenAI Billing Actually Works Before You Burn Your Budget

Your first Azure OpenAI proof-of-concept costs almost nothing — a few hundred rupees, a delighted demo, a green light to “productionise it.” Three months later the bill is a five-figure surprise and nobody can explain why, because nobody can answer the one question every Azure OpenAI cost rests on: what exactly are we paying for, per call? The answer is tokens — not requests, not characters, not API calls — and until you can look at a prompt, roughly count its tokens, estimate the reply, and add the two, the bill feels like weather. This article makes it something you control.

Azure OpenAI Service is Microsoft’s hosted version of OpenAI’s models (the GPT-4o family, GPT-4o mini, GPT-3.5 Turbo, embeddings, and more), running in your Azure subscription with enterprise networking, identity, and regional data residency. It bills almost entirely on tokens — the sub-word chunks a model reads and writes — and meters input tokens (everything you send: system prompt, conversation history, retrieved documents, the question) separately from output tokens (everything generated), usually at a higher rate for output. Two more concepts decide the bill: the context window (the hard ceiling on how many tokens one request may contain, input plus output together) and the deployment model — flexible pay-as-you-go (Standard) versus reserved Provisioned Throughput Units (PTU).

By the end you will read a prompt and see the money in it: why a chatbot that “remembers the whole conversation” gets quadratically more expensive every turn, why retrieval-augmented generation (RAG) can quietly 10× your input bill, when to reach for GPT-4o mini over GPT-4o, what prompt caching does to the input rate, and how to turn “about 50,000 questions a day” into a defensible monthly figure before finance asks. Mental model first; the pricing tables, decision grids, and a token-counting lab follow.

What problem this solves

Most teams ship an Azure OpenAI feature without anyone owning the unit economics, because the SDK call is trivially easy — client.chat.completions.create(...) and you have a working assistant. The cost machinery stays invisible right up until one of three things happens: traffic grows, the prompt grows, or the conversation grows — and all three multiply the same hidden quantity, tokens per call. A team that never built the mental model can’t see the cliff coming.

What breaks is predictable and expensive. Someone stuffs the entire knowledge base into every prompt “to be safe,” paying input on 40,000 tokens to answer a 12-token question. A chatbot keeps the full transcript in the prompt, so by turn 20 every message re-sends the previous nineteen. A team picks GPT-4o for a task GPT-4o mini would nail at a fifteenth of the price. Another leaves max_tokens huge “just in case,” and a chatty model bills 4,000 output tokens where 300 would do. None of these are code bugs — they are bugs in the understanding, and they only show up on the invoice.

Who hits this: every team adopting generative AI on Azure — a startup’s support bot, an enterprise RAG copilot, a data team’s batch summarisation over millions of records. The pattern is identical: the POC is cheap because volume and prompt size are tiny; production is expensive because both grew; the gap is entirely tokens × price × calls. Compute that product on a napkin and Azure OpenAI becomes a line item you can forecast, optimise, and defend.

To frame the whole field before the deep dive, here is what actually drives an Azure OpenAI bill, the lever that controls each driver, and the single most common way each one quietly explodes:

Cost driver What it is The lever you control Most common way it explodes
Input tokens Everything you send: system prompt + history + RAG context + question Trim the prompt; cache the static part; retrieve fewer chunks Stuffing the whole KB into every call (RAG)
Output tokens Everything the model generates (billed higher than input) Set a sane max_tokens; ask for concise answers No cap → a chatty model writes essays
Conversation history Re-sending past turns so the model “remembers” Summarise or window the history Full transcript re-sent every turn (quadratic)
Model choice GPT-4o vs GPT-4o mini vs GPT-3.5 — wildly different rates Route easy tasks to the cheap model Using the flagship for everything
Call volume Requests per day × tokens per call Batch, cache, debounce, rate-limit Traffic grows; per-call cost never reviewed
Deployment type Pay-as-you-go vs reserved PTU capacity Match commitment to steady-state load Buying PTUs for spiky/low traffic

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should be comfortable calling a large language model over an API — sending a prompt, getting a completion — and reading JSON. You do not need machine-learning theory; this is an economics and operations article, not a modelling one. Running az in Cloud Shell helps for the deployment and quota commands, and we define RAG (retrieval-augmented generation) inline.

This sits at the very front of the AI/ML on Azure track — the cost-literacy foundation everything else assumes. New to the models? AI-900: Generative AI & Azure OpenAI Fundamentals is the on-ramp. Once the token economics here make sense, the architecture follow-ons do too: An Enterprise Landing Zone for Azure OpenAI: Networking, Quotas, and Gateways wraps quotas and private networking around a deployment, Azure AI Search for RAG: Vector Indexing, Hybrid Search, Semantic Ranking, and Indexer Pipelines is where your RAG input tokens come from, and An LLM Gateway for Cost, Safety and Observability is how large orgs meter this spend centrally. The broader discipline lives in Azure FinOps and Cost Management: Controlling Cloud Spend at Scale.

A quick map of who in a team owns which token lever, so the conversation has the right people in the room:

Concern What they decide Who usually owns it Cost driver it controls
Prompt design System prompt size, output instructions App / prompt engineer Input + output tokens per call
Retrieval (RAG) How many chunks, how big, re-ranking Search / data team Input tokens (often the biggest)
Model selection GPT-4o vs mini vs 3.5 per route App / architect Per-token rate
Conversation state History windowing / summarisation App team History tokens (quadratic risk)
Deployment & quota Standard vs PTU, TPM limits, regions Platform / FinOps Rate model + blast-radius cap
Budgeting & alerts Cost budgets, anomaly alerts FinOps / finance Catches runaways early

Core concepts

Six mental models make every later pricing decision obvious. Internalise these and the tables that follow are just lookups.

A token is a chunk of text, not a word. Models read tokens — sub-word units from a tokenizer. Common short words are one token (the, and); longer or rarer words split (tokenizationtoken + ization); spaces and punctuation count; non-English text and code tokenize less efficiently. The rule of thumb for English prose is ~4 characters per token, or ~0.75 words per token (~750 words ≈ 1,000 tokens). You pay per token, so this is your napkin-math currency.

You pay for input and output separately, and output usually costs more. Every chat call has two metered halves: prompt (input) tokens — everything you send, concatenated: system prompt, the conversation history you include, retrieved documents, the latest message — and completion (output) tokens the model generates. Azure prices these at different per-1,000-token (per-1K) rates, and output is typically several times more expensive than input. A short question that triggers a long answer is output-dominated; a huge RAG prompt yielding one line is input-dominated. Which half dominates tells you which lever to pull.

The context window is a hard ceiling on input + output combined. Each model’s context window is the maximum tokens a single request can involve, counting prompt and completion together (e.g. 128K for the GPT-4o family). It is a capacity limit, not a billing one, and it bites two ways. Reserved output space eats into it: if the window is 128K and your prompt is 127K, the model has ~1K tokens to answer in. And exceeding it doesn’t silently work — you get an error, or your own code truncates history/context to fit and loses information. The window caps what fits in one call; tokens cap what you pay.

Conversation memory is re-sent text — that’s why chat gets expensive. A model is stateless; to make it “remember,” your code re-sends prior turns in the prompt every message. Turn 2 re-sends turn 1; turn 10 re-sends turns 1–9; turn n pays input on all n−1 prior turns plus the new one. Cumulative input grows with the square of the turn count. The fix: don’t send history forever — window it (last k turns) or summarise older turns.

RAG is “paste documents into the prompt,” and documents are big. Retrieval-augmented generation retrieves relevant chunks from a search index and pastes them into the prompt as context before the question. Those chunks are input tokens. Eight chunks of 800 tokens adds ~6,400 input tokens to every call. RAG is often the single largest line on a copilot’s bill; the lever is retrieval discipline — fewer, smaller, better-ranked chunks beat dumping the corpus.

The deployment type changes the pricing model, not just the price. Standard (pay-as-you-go) charges per token — pay for exactly what you use, no commitment, ideal for variable or low volume. Provisioned Throughput Units (PTU) reserve dedicated capacity for a fixed price regardless of tokens consumed — predictable cost and latency at high steady volume, but you pay whether or not you use it. Choosing wrong wastes money: PTUs for spiky low traffic, or pay-as-you-go for massive steady traffic where reserved would be cheaper and faster.

The vocabulary in one table

Before the deep sections, pin down every moving part. The glossary at the end repeats these for lookup; this table is the mental model side by side:

Concept One-line definition Billed? Why it matters to your bill
Token A sub-word chunk the model reads/writes (~4 chars) Yes — the unit Everything is priced per 1K tokens
Input / prompt tokens Everything you send (system + history + RAG + question) Yes Often the larger half once RAG/history grow
Output / completion tokens Everything the model generates Yes (higher rate) A chatty model with no cap inflates this
Context window Max input+output tokens in one request No (a ceiling) Caps what fits; over it → error/truncation
max_tokens Your cap on output length for a call Indirectly The single easiest output-cost control
System prompt Instructions prepended to every call Yes (as input) Re-sent every call; keep it lean
Conversation history Prior turns re-sent for “memory” Yes (as input) Grows quadratically if unbounded
RAG context Retrieved document chunks pasted in Yes (as input) Frequently the biggest input contributor
Embeddings Vectors for search/RAG indexing Yes (input only, cheap) Cost of building the index, not answering
Prompt caching Discounted re-use of a repeated prompt prefix Yes (input, discounted) Cuts the cost of a large static prefix
PTU Reserved dedicated throughput capacity Yes (flat) Fixed cost; great only at steady high load
TPM / RPM Tokens- and requests-per-minute quota No (a limit) Caps blast radius and throttles spend

What a token really is, and how to count one

You cannot budget what you cannot count. A tokenizer splits text using a fixed vocabulary learned during training (the GPT-4o family uses o200k_base; GPT-3.5/4 used cl100k_base). The rules that surprise people:

The practical rules of thumb for English prose: ~4 characters per token, ~0.75 words per token (so ~750 words ≈ 1,000 tokens), or equivalently ~1.3 tokens per word; a typical A4 page (~500 words) is ~650–700 tokens. Minifying JSON/HTML before you paste it can cut 10–30% of tokens. The only exact count comes from a tokenizer library (tiktoken) or the API’s returned usage. A few examples to calibrate your eye (approximate — verify with tiktoken when it matters):

Text Approx. characters Approx. tokens Note
Hello, world! 13 ~4 Punctuation and the space cost tokens
A 280-character tweet 280 ~70 The ~4-chars rule in action
A 500-word email ~3,000 ~700 Sizing a typical message
One A4 page of prose ~3,000 ~650–700 Sizing a RAG chunk source
A 4,000-word document ~24,000 ~5,300 One mid-size doc nearly fills a 8K window
A minified 1 KB JSON blob ~1,000 ~300–400 Structure/quotes inflate it

The only number you should bill against is the API’s own accounting. Every chat completion response includes a usage object with the exact split — ground truth, and you should log it:

{
  "usage": {
    "prompt_tokens": 1287,
    "completion_tokens": 142,
    "total_tokens": 1429
  }
}

prompt_tokens is the input charge; completion_tokens the output charge; total_tokens their sum, which must stay under the context window. Logging usage per call is the highest-leverage observability move here — it turns the bill into a sum you can group by user, feature, and model.

The anatomy of a billed call

The “prompt” you pay input on is not just the user’s question — it’s the concatenation of several pieces your code assembled. Knowing the parts tells you where to cut. Everything on the input side of a single call, and how each behaves:

Prompt component What it is Billed as Grows with How to shrink it
System prompt Persona/rules instructions Input Stays fixed (but re-sent every call) Tighten wording; cache the prefix
Few-shot examples Sample Q&A pairs to steer the model Input Number/size of examples Fewer, shorter examples; fine-tune instead
Conversation history Prior user/assistant turns Input Turn count (quadratic cumulative) Window to last k; summarise older turns
RAG context Retrieved document chunks Input Chunks × chunk size Retrieve fewer/smaller; re-rank; trim
User question The actual new message Input Usually small Rarely the problem
Model completion The generated answer Output Verbosity / max_tokens Cap max_tokens; ask for brevity

Read that as a priority list. The question is almost never the cost; the system prompt is fixed overhead; the things that grow — and wreck budgets — are conversation history and RAG context on the input side, and an uncapped completion on the output side. Three components, three levers.

A worked decomposition. Suppose a support copilot call looks like this:

Component Tokens Side Share of input
System prompt 350 Input 5%
2 few-shot examples 600 Input 9%
Conversation history (8 turns) 1,900 Input 28%
RAG context (6 chunks) 3,600 Input 53%
User question 40 Input <1%
Input subtotal 6,490 Input 100%
Model answer 220 Output

The user typed 40 tokens, but you billed input on 6,490 — and 81% is history + RAG, the two growable components. Halve RAG to 3 well-ranked chunks and window history to the last 4 turns and input falls to ~3,500 tokens — a ~46% cut on the larger half, with no change to the model or the experience. That is the whole optimisation game in one example.

The pricing model: per-1K tokens, input vs output

The pay-as-you-go meter is simple: (input tokens ÷ 1,000 × input rate) + (output tokens ÷ 1,000 × output rate), summed over every call. The two rates differ per model and output is the pricier one. (Some materials quote per-1M tokens; that’s the same number ÷ 1,000 — be consistent.)

Exact rates change by time and region, so treat numbers here as illustrative for the arithmetic and confirm live figures on the pricing page before committing a budget. The durable lesson is the shape — output costs multiples of input, and the mini/3.5 models are an order of magnitude cheaper than the flagship:

Charge type Metered on Typical relative cost Notes
Chat input (prompt) prompt_tokens Baseline (1×) Everything you send
Chat output (completion) completion_tokens Several× input Generation is pricier
Cached input Repeated prompt prefix Discounted vs normal input Prompt caching; auto for long static prefixes
Batch input/output Async, 24-h window jobs Discounted (~50%) vs Standard Non-urgent bulk workloads
Embeddings prompt_tokens only (no output) Tiny fraction of chat Indexing cost for RAG/search
Fine-tuning training Training tokens Per-token training rate Plus a deployment/hosting charge
Fine-tuned hosting Hosting time Hourly while deployed You pay even when idle

The key relationship is input vs output asymmetry. Because output is the expensive half, answer verbosity is often a bigger lever than prompt size. A RAG call with a 6,000-token prompt and a 100-token answer may cost less than a 500-token-prompt call that writes a 2,000-token essay. Reason about both halves; never optimise the prompt while ignoring max_tokens.

The same call costed three ways at illustrative rates (input ₹0.40/1K, output ₹1.60/1K — placeholders to show the method):

Scenario Input tokens Output tokens Input cost Output cost Total
Short Q, short A 200 150 ₹0.08 ₹0.24 ₹0.32
Big RAG prompt, short A 6,490 220 ₹2.60 ₹0.35 ₹2.95
Small prompt, long essay 500 2,000 ₹0.20 ₹3.20 ₹3.40

The third call costs more than the second despite a twelve-times-smaller prompt — output is 4× the rate and there’s ten times more of it. That’s why “just set max_tokens” is one of the highest-ROI changes available.

Choosing a model: GPT-4o vs GPT-4o mini vs GPT-3.5 Turbo

The biggest lever after “send fewer tokens” is which model you send them to. Flagship and small models differ by more than 10× for the same token count, and much production traffic — classification, extraction, short Q&A, routing — doesn’t need the flagship. The skill is routing: hard reasoning to the big model, everything else to a small one.

A capability-and-cost comparison (relative cost shows the gap, not a quote; capabilities and context sizes are the durable facts to confirm on the model page):

Model Context window Multimodal? Relative cost (rough) Best for Avoid for
GPT-4o ~128K Text + image (+ audio variants) Highest (1×) Hard reasoning, nuanced writing, vision Cheap high-volume classification
GPT-4o mini ~128K Text + image ~1/15–1/20 of 4o (much cheaper) High-volume Q&A, extraction, routing, summaries Deep multi-step reasoning at the edge
GPT-3.5 Turbo ~16K Text only Low (legacy budget option) Simple chat/transform on a budget Long context, vision, hardest tasks
text-embedding-3-small/large n/a (embeddings) n/a Tiny per token Building RAG/search vectors Generating text (it can’t)

The decision in practice is a routing table — match the task, not the team’s habit, to the model:

If the task is… Default to… Why Escalate to GPT-4o when…
Classify / tag / route a message GPT-4o mini Cheap, fast, accurate enough Categories are subtle/overlapping
Extract fields from a document GPT-4o mini Structured extraction is easy for mini Layout is messy or needs reasoning
Summarise a support thread GPT-4o mini Summarisation is mini’s sweet spot Output must be executive-grade nuance
Short factual Q&A over RAG GPT-4o mini RAG does the heavy lifting Synthesis across many chunks is required
Multi-step reasoning / planning GPT-4o Mini degrades on long reasoning chains (already the flagship)
Customer-facing nuanced writing GPT-4o Tone and subtlety matter (already the flagship)
Image understanding (vision) GPT-4o / mini (both vision) Need a multimodal model Fine visual detail / reasoning
Build a search index (embeddings) text-embedding-3-small Cheapest adequate vectors Recall needs the larger embedding model

A pragmatic pattern is a two-tier cascade: try GPT-4o mini first; if a cheap confidence check or validator says the answer is weak, escalate the same prompt to GPT-4o. Most traffic resolves on the cheap tier; you pay flagship rates only for the hard minority. Routing this centrally is what An LLM Gateway for Cost, Safety and Observability describes.

Why multi-turn chat gets expensive: the quadratic trap

This surprises nearly everyone and is the most common “the bill grew and nothing changed” cause. Because the model is stateless, “memory” is re-sending the transcript. If each turn adds roughly the same tokens, the input on turn n includes all prior turns, so cumulative input across N turns grows with N², not N.

Concretely — each exchange adds ~400 tokens, and you re-send the full history every turn:

Turn History sent (input) This turn’s input cost (rel.) Cumulative input cost (rel.)
1 400 1 1
5 2,000 5 15
10 4,000 10 55
20 8,000 20 210
40 16,000 40 820

By turn 40 a single conversation has cost 820 units of input where a stateless one-shot would cost 40 — a 20× multiplier hidden inside “let the user keep chatting.” A few long power-user sessions can dominate the bill. Mitigations, by how much the model must remember:

Strategy What it does Cost behaviour Trade-off
Full history (naive) Re-send every prior turn Quadratic — unbounded Perfect recall; worst cost
Sliding window Keep only the last k turns Linear, capped per turn Forgets anything older than k turns
Running summary Replace old turns with a short summary Near-constant per turn Summary loses detail; one extra call to make it
Summary + window Summary of old turns plus last k verbatim Constant-ish Best balance; slightly more code
Truncate to fit window Drop oldest until under the context limit Capped by window, not by cost intent Silent information loss if not designed

The rule: never re-send unbounded history in production. A sliding window of the last 6–10 turns plus a periodic running summary keeps cost flat with good-enough recall. “Append to the messages array forever” is the most expensive default in the SDK.

Prompt caching, Batch, and embeddings: the rate-changers

Three features change the rate, not the token count, each fitting a workload shape.

Prompt caching discounts the input tokens of a repeated prompt prefix. If many calls share a large identical opening — a big system prompt, fixed few-shot examples, a long instruction block — the platform serves that prefix from cache at a reduced input rate after the first call. The constraint: the cached part must be the prefix and byte-identical, so put static content at the very top and variable bits (question, chunks) at the bottom. A 2,000-token preamble hit thousands of times an hour is a material saving for zero behavioural change.

Batch runs requests asynchronously within a completion window (up to ~24 h) at a discounted rate (~half of Standard) against a separate, larger quota. It fits anything not latency-sensitive — overnight ticket summarisation, backlog classification, bulk embeddings. You trade immediacy for roughly half the price: superb for back-office work, useless for interactive chat.

Embeddings turn text into vectors for RAG and semantic search; they bill input-only (no completion) at a tiny fraction of chat. The cost that matters is volume: embedding a million documents is a real one-time bill, and re-embedding on every change adds up. Embedding queries at request time is negligible; embedding the corpus is the line to size.

How the rate-changers compare and when to reach for each:

Feature What it changes Best for Latency Quota
Prompt caching Cheaper input on a repeated prefix Big fixed system prompt hit often Same (real-time) Same deployment
Batch ~50% off, async Non-urgent bulk jobs Up to ~24 h Separate, larger
Embeddings Input-only, very cheap per token Building/refreshing RAG vectors Real-time Embeddings deployment
Provisioned (PTU) Flat capacity price Steady high volume, latency SLAs Lowest, predictable Reserved capacity
Standard PAYG Per-token, no commit Variable / low / unknown volume Good TPM/RPM limits

Pay-as-you-go vs Provisioned Throughput Units (PTU)

The deployment type is a strategic decision, not a setting. Standard (pay-as-you-go) charges per token with no commitment — you pay for exactly what you consume and nothing when idle. Provisioned Throughput Units (PTU) reserve dedicated capacity for a flat price; you get predictable, lower latency and a fixed bill regardless of tokens pushed through — wonderful at high steady load, wasteful at low or spiky load. Reservations (1-month/1-year) discount PTU capacity further for committed users.

The trade-off, side by side:

Dimension Standard (pay-as-you-go) Provisioned (PTU)
Billing basis Per token consumed Flat per reserved capacity (hour/month)
Cost at low/zero traffic ~Zero Full price (you pay for idle)
Cost at high steady traffic Scales linearly, can exceed PTU Fixed; often cheaper per token at scale
Latency Good, best-effort Lower, more predictable
Throughput ceiling TPM/RPM quota The reserved PTU capacity
Commitment None Capacity (and reservations 1-mo/1-yr)
Best for POCs, variable, low/unknown load Production at steady high volume with SLAs

A simple decision rule, expressed as a table you can apply without a spreadsheet:

Your situation Choose Why
Proof of concept / early product Standard PAYG Pay only for what you use; no idle cost
Spiky traffic with quiet nights Standard PAYG (+ caching) PTU idle cost would dwarf the savings
Unknown/forecast-uncertain volume Standard PAYG Don’t commit before you have the data
High, steady, predictable token volume PTU (+ reservation) Lower per-token cost and latency SLA
Strict, consistent latency requirement PTU Dedicated capacity removes noisy-neighbour jitter
Mixed: steady base + spiky peaks PTU for base, spillover to PAYG Reserve the floor, burst on demand

The break-even is a crossover: PTU has high fixed cost and ~zero marginal cost up to its capacity; PAYG has zero fixed cost and constant marginal cost. Below the crossover, PAYG wins; above it, PTU. The trap is buying PTUs on optimistic forecasts and running them at 20% utilisation. Earn your way into PTUs with real PAYG data, then size the reservation to your steady-state floor, not your peak.

Architecture at a glance

There is no system diagram here because the “architecture” that matters is a cost flow you carry in your head. Picture a request moving through five stages, watching the token meter at each.

Stage one — assembly: your code concatenates the system prompt, few-shot examples, the conversation history it chose to include, the RAG context retrieved from a search index, and the user’s question. Everything here is input tokens, and this is where 80–90% of the bill is decided — history and RAG are the components that grow. Stage two — the call: the prompt goes to the model deployment (pay-as-you-go or PTU), which enforces your TPM/RPM quota — the valve that throttles a runaway loop. Stage three — generation: the model produces a completion, bounded by max_tokens — the pricier output tokens. Stage four — accounting: the response returns a usage object with the exact prompt_tokens / completion_tokens / total_tokens split, the ground truth you log. Stage five — control: logged usage feeds dashboards, per-feature attribution, and budget alerts, closing the loop.

Money is spent at assembly and generation but understood at accounting. Every optimisation here targets one of three points: shrink what stage one assembles (trim the system prompt, window history, retrieve fewer chunks, cache the prefix), cap what stage three generates (max_tokens, brevity, cheaper model), and instrument stage four so the other two are decisions, not accidents. Narrate a request through those five stages and you understand Azure OpenAI billing better than most teams running it.

Real-world scenario

Saanvi Logistics built an internal “shipment copilot” — a RAG chat assistant over their policy and tariff documents on Azure OpenAI in Central India. The POC cost about ₹3,000 across a month of light use and got an enthusiastic rollout to all 600 operations staff. The team is three engineers; nobody built a token budget because the POC bill was a rounding error.

Two months later the Azure OpenAI line hit ₹2,40,000 and was still climbing. Finance asked why? and the team had no answer — they had never logged the usage object. The first fix was diagnostic, not optimisation: log prompt_tokens and completion_tokens per call, tagged with user and feature, into Application Insights (the pattern from Azure Monitor and Application Insights: Full-Stack Observability). Within a day the breakdown was damning.

Three things ate the budget, all token-mechanics, none a code bug. RAG with no discipline: the retriever returned the top 15 chunks of ~900 tokens, so every question carried ~13,500 input tokens — to answer questions a human could answer from one paragraph. Unbounded history: the chat kept the entire session transcript, so power users running 30–40-turn sessions each paid the quadratic tax, and a dozen of them dominated daily spend. The flagship for everything: every call, including “what’s the cutoff for same-day dispatch?”, went to GPT-4o where mini would have answered indistinguishably at a fraction of the rate.

The fixes were fast. RAG dropped from 15 chunks to 4 well-ranked chunks (with semantic re-ranking), cutting average input ~70%. Full-history became a sliding window of the last 6 turns plus a running summary, flattening the quadratic curve. They routed by task: a cheap mini classifier sent factual lookups to GPT-4o mini and reserved GPT-4o for multi-document synthesis — ~15% of traffic. They enabled prompt caching on the 1,800-token static prefix and set max_tokens to 400.

The bill fell from ₹2,40,000 to about ₹41,000 — an 83% cut — with no measurable quality drop, and every rupee now attributable on a dashboard. They set a budget alert at ₹60,000 so a regression pages them instead of surprising finance. The wiki lesson: “The model is stateless and the meter is per token. If you don’t decide what goes into the prompt, the defaults decide for you — expensively.”

The cost story as a before/after, because the order of fixes is the lesson:

Lever Before After Effect
Log usage per call Not logged Per user + feature Made the bill explainable (prerequisite)
RAG chunks per call 15 × ~900 tok 4 × ~900 tok, re-ranked ~70% less input per call
Conversation history Full transcript Window(6) + summary Killed the quadratic tax
Model routing GPT-4o for all mini default, 4o for ~15% Order-of-magnitude rate cut on most calls
Prompt caching Off On (1,800-tok prefix) Cheaper input on the fixed preamble
Output cap Unset max_tokens=400 Stopped long-answer drift
Budget alert None Alert at ₹60,000 Future regressions page, not surprise

Advantages and disadvantages

The per-token model makes Azure OpenAI both wonderfully accessible and quietly dangerous. Weigh it honestly:

Advantages (why per-token billing helps you) Disadvantages (why it bites)
Pay for exactly what you use — a POC genuinely costs pennies, no upfront commitment Cost scales with usage you may not be watching; it grows silently with traffic, prompt size, and history
Granular: the usage object gives exact, per-call accounting you can attribute to user/feature If you never log usage, the bill is an opaque lump you can’t break down or defend
Many cheap levers (smaller model, fewer chunks, max_tokens, caching) with no infra change The defaults are expensive — unbounded history, no output cap, flagship model — so doing nothing is the costly path
Mix-and-match: cheap model for easy traffic, flagship for hard, in the same app Requires deliberate routing logic; teams that “just use GPT-4o” overpay by 10×+
PTU/reservations give predictable cost and latency at scale when you’re ready Buying PTUs too early (low/spiky load) means paying full price for idle capacity
Quotas (TPM/RPM) and budgets cap blast radius — a runaway loop can be throttled Out of the box those caps may be generous; an un-capped bug can rack up cost fast before alerts fire
Batch and prompt caching cut the rate for the right workloads They only help specific shapes (bulk/async; repeated prefix) — not a universal discount

The model is right for almost everyone if you instrument it: log usage, set max_tokens, route models, bound history, budget-alert. It bites hardest on teams that treat the SDK call as the whole story and never look at the meter — most teams, for the first few months, until the invoice teaches the lesson.

Hands-on lab

Two parts: count tokens locally (free, no Azure spend), then deploy a model and read the real usage from one call. Run in Cloud Shell (Bash) or any machine with Python and the Azure CLI.

Part A — count tokens with tiktoken (free, offline).

Step 1 — install the tokenizer.

pip install --quiet tiktoken

Step 2 — count tokens for some sample text and verify the rules of thumb.

# save as count.py, then: python count.py
import tiktoken
enc = tiktoken.get_encoding("o200k_base")  # GPT-4o-family tokenizer

samples = [
    "Hello, world!",
    "Azure OpenAI bills you per token, not per word.",
    "tokenization",
    '{"id":1,"name":"widget","price":9.99}',
]
for s in samples:
    n = len(enc.encode(s))
    print(f"{n:>4} tokens | {len(s):>4} chars | ratio {len(s)/n:0.1f} chars/token | {s}")

Expected: short strings land near ~4 chars/token; tokenization splits into 2–3 tokens; the JSON shows a lower chars-per-token ratio (structure costs tokens). You’ve now felt why code and JSON tokenize less efficiently than prose.

Step 3 — estimate a document’s tokens before paying to send it.

import tiktoken
enc = tiktoken.get_encoding("o200k_base")
doc = open("some_policy.txt").read()   # any text file you have
tokens = len(enc.encode(doc))
print(f"{tokens} tokens  (~{tokens/1000:0.1f}K, ~{tokens*4} chars, ~{int(tokens*0.75)} words)")
print(f"At 4 chunks of 800 tokens, this doc is ~{-(-tokens//800)} chunks of RAG context")

This is the calculation you do before deciding how many chunks of a document to retrieve.

Part B — deploy a model and read the real usage (small spend).

Step 4 — create the resource and a deployment.

RG=rg-aoai-lab
LOC=eastus            # pick a region where your model/quota is available
ACCT=aoai-lab-$RANDOM
az group create -n $RG -l $LOC -o table

az cognitiveservices account create -n $ACCT -g $RG -l $LOC \
  --kind OpenAI --sku S0 --yes -o table

# Deploy a cheap model (GPT-4o mini) at a small capacity (TPM in thousands)
az cognitiveservices account deployment create -n $ACCT -g $RG \
  --deployment-name gpt4o-mini \
  --model-name gpt-4o-mini --model-version "2024-07-18" --model-format OpenAI \
  --sku-name Standard --sku-capacity 10 -o table

Expected: an account row, then a deployment named gpt4o-mini. The --sku-capacity 10 caps throughput to ~10K TPM — your blast-radius limit for the lab.

Step 5 — make one call and print the usage block.

ENDPOINT=$(az cognitiveservices account show -n $ACCT -g $RG --query properties.endpoint -o tsv)
KEY=$(az cognitiveservices account keys list -n $ACCT -g $RG --query key1 -o tsv)

curl -s "$ENDPOINT/openai/deployments/gpt4o-mini/chat/completions?api-version=2024-08-01-preview" \
  -H "Content-Type: application/json" -H "api-key: $KEY" \
  -d '{
        "messages": [
          {"role":"system","content":"You are a terse assistant. Answer in one sentence."},
          {"role":"user","content":"In one sentence, what is a token in an LLM?"}
        ],
        "max_tokens": 60
      }' | python -c "import sys,json; d=json.load(sys.stdin); print(json.dumps(d['usage'], indent=2))"

Expected output is the ground-truth meter, e.g.:

{
  "prompt_tokens": 33,
  "completion_tokens": 24,
  "total_tokens": 57
}

Step 6 — prove max_tokens caps output (and cost). Re-run Step 5 with "max_tokens": 5 and watch completion_tokens drop to ~5 and the answer cut off. Then ask for “three detailed paragraphs” without a cap and watch it balloon. You’ve now observed the most important output-cost lever directly.

Validation checklist. You counted tokens offline (proving a token ≠ a word), sized a document for RAG, deployed a capacity-capped model (quota as a blast-radius cap), read the exact billable prompt_tokens/completion_tokens split per call, and watched max_tokens control the expensive half — the whole token-economics loop in one sitting.

Cleanup (avoid lingering charges).

az group delete -n $RG --yes --no-wait

Cost note. A few chat calls on GPT-4o mini cost a tiny fraction of a rupee; Part A is entirely free (offline tokenizer). Deleting the resource group removes the account and deployment so nothing meters afterwards.

Common mistakes & troubleshooting

The “incidents” in a cost article are budget surprises and limit errors. First the scannable table, then the detail on the ones that bite hardest. Each is symptom → root cause → how to confirm → fix.

# Symptom Root cause Confirm with Fix
1 Bill grew but “nothing changed” Conversation history re-sent unbounded (quadratic) Log prompt_tokens; it climbs with turn count per session Window history to last k + running summary
2 Every call has huge prompt_tokens RAG retrieves too many/too-big chunks Log per-call prompt_tokens; inspect chunk count/size Retrieve fewer, smaller, re-ranked chunks
3 Output cost dominates; answers ramble No max_tokens; model verbose by default completion_tokens is large vs prompt Set max_tokens; instruct brevity
4 Cost 10× higher than peers’ similar app Flagship model used for trivial tasks Check which deployment each route hits Route easy tasks to GPT-4o mini
5 429 Too Many Requests under load TPM/RPM quota exceeded on the deployment Response headers / quota blade; az ...deployment show capacity Raise capacity, add retry/backoff, spread regions
6 Request fails: context length exceeded Prompt + max_tokens > context window Error message names token counts vs window limit Trim prompt; lower max_tokens; bigger-context model
7 PTU bill high but app barely used PTUs bought for low/spiky traffic PTU utilisation metric near-idle Move to PAYG; size PTU to steady floor only
8 Prompt caching “not working” Variable content placed before the static prefix Cache only applies to identical prefix Put static system/examples first, variable last
9 Embedding cost spiked Re-embedding the whole corpus on every change Token volume on the embeddings deployment Embed only changed docs; batch the rest
10 Forecast wildly off vs actual Estimated on words/requests, not tokens Compare estimate to logged total_tokens Re-forecast on avg tokens/call × calls
11 One feature/user blows the budget No per-feature attribution; a loop or power user Group logged usage by feature/user Attribute cost; cap or fix the offender
12 Surprise invoice, no early warning No cost budget / anomaly alert configured Cost Management shows no budget set Create a budget + alert (see Cost & sizing)

1. The bill grew but “nothing changed.” Almost always unbounded history: as sessions lengthen, each turn re-sends more transcript, so average prompt_tokens drifts up under identical traffic. Confirm: plot logged prompt_tokens against turn-number-in-session — a rising line is the smoking gun. Fix: a sliding window (last 6–10 turns) plus a running summary flattens the quadratic curve.

2. Every call carries a huge prompt. RAG without discipline — too many or too-large chunks, or no re-ranking so you pad with irrelevant context. Confirm: log prompt_tokens and the retrieved chunk count/size; thousands of tokens for a one-paragraph answer is the tell. Fix: fewer chunks (3–5 re-ranked beat 15 raw), smaller chunk size, semantic re-ranking.

5. 429 Too Many Requests. You exceeded the deployment’s TPM/RPM quota — a rate limit, not billing. Confirm: the 429 and retry-after header; az cognitiveservices account deployment show ... --query sku.capacity. Fix: exponential backoff/retry, raise capacity if quota allows, spread across regions/deployments, consider PTU for a justified steady rate.

6. Context length exceeded. Your prompt tokens plus reserved max_tokens exceed the context window — output space counts against the same ceiling. Confirm: the API error names the requested count and the limit. Fix: trim the prompt, lower max_tokens, or move to a larger-context model — but treat a too-long prompt as a cost smell, not just a limit to raise.

8. Prompt caching isn’t biting. Caching discounts only an identical prefix. Per-request content (question, chunks, a timestamp) placed before the static block makes the prefix differ every call. Confirm: no cached-token discount despite a large repeated system prompt. Fix: order static-first — system prompt and examples at the top, variable content at the bottom.

Best practices

Crisp, production-grade rules — one decision each:

Security notes

Cost and security overlap — a leaked key or open endpoint is also a spend incident, because someone else can run up your token bill.

Cost & sizing

Turn a volume estimate into a budget number with one formula:

Monthly cost ≈ (calls/day × 30) × ( (avg input tokens ÷ 1,000 × input rate) + (avg output tokens ÷ 1,000 × output rate) )

Fill in four quantities: calls/day, avg input tokens, avg output tokens, and the per-1K rates for your model (confirm live on the pricing page). A worked estimate at illustrative rates (input ₹0.40/1K, output ₹1.60/1K — placeholders) for three shapes:

Workload Calls/day Avg input tok Avg output tok Cost/call (illus.) Monthly (illus.)
Lean FAQ bot (mini, tight prompt) 50,000 800 150 ₹0.56 ~₹8.4 lakh (1×)
Same bot, RAG-heavy, uncapped output 50,000 6,500 1,200 ₹4.52 ~₹67.8 lakh (≈8×)
Batch summarisation (async, ~50% off) 200,000 1,500 300 ~₹0.54 ~₹32.4 lakh (discounted)

Treat the rupee totals as method demonstrations — plug in real rates and your token averages from logged usage for a defensible figure. The relationships are the lesson: the RAG-heavy uncapped variant costs ~8× the lean one for the same traffic, entirely from bigger input and uncapped output; Batch roughly halves the rate for deferrable work.

What drives the bill, ranked, with the lever and rough magnitude of each:

Bill driver Typical impact Primary lever Rough magnitude of the lever
Model choice Largest single multiplier Route to GPT-4o mini Up to ~10–20× cheaper per token
RAG context size Often the biggest input line Fewer/smaller/re-ranked chunks 50–80% input reduction common
Conversation history Silent quadratic growth Window + summarise Flattens N² to ~constant
Output verbosity The pricier half max_tokens + brevity Caps an unbounded cost
Deployment type Fixed vs per-token PAYG vs PTU to load shape Big swing either way if mismatched
Prompt caching Discount on fixed prefix Static-first ordering Cuts cost of a large preamble
Batch vs real-time ~50% rate difference Defer non-urgent work ~Half price for async jobs

To stop a surprise, set a budget with alerts on the resource group on day one:

# Anomaly/threshold alerts: create a budget on the resource group that holds the AOAI account
az consumption budget create \
  --budget-name aoai-monthly \
  --amount 60000 --time-grain Monthly \
  --category Cost \
  --resource-group rg-aoai-lab \
  --start-date 2026-06-01 --end-date 2027-06-01
// A monthly cost budget with email alerts at 80% and 100% of ₹60,000
resource budget 'Microsoft.Consumption/budgets@2023-11-01' = {
  name: 'aoai-monthly'
  properties: {
    category: 'Cost'
    amount: 60000
    timeGrain: 'Monthly'
    timePeriod: { startDate: '2026-06-01', endDate: '2027-06-01' }
    notifications: {
      warning80: {
        enabled: true
        operator: 'GreaterThanOrEqualTo'
        threshold: 80
        contactEmails: [ 'finops@example.com' ]
      }
      actual100: {
        enabled: true
        operator: 'GreaterThanOrEqualTo'
        threshold: 100
        contactEmails: [ 'finops@example.com' ]
      }
    }
  }
}

And cap the rate (worst-case spend velocity) by sizing deployment capacity — --sku-capacity is in thousands of TPM:

# Lower capacity = a smaller blast radius if something loops
az cognitiveservices account deployment create -n $ACCT -g $RG \
  --deployment-name gpt4o-mini --model-name gpt-4o-mini \
  --model-version "2024-07-18" --model-format OpenAI \
  --sku-name Standard --sku-capacity 20

The broader practice — tagging, showback, anomaly detection across the estate — lives in Azure FinOps and Cost Management: Controlling Cloud Spend at Scale.

Interview & exam questions

Useful for AI-900 / AI-102 prep and for any architect screening on GenAI economics. Question, then a model answer.

1. What is a token, and why does Azure OpenAI bill on tokens rather than words or API calls? A token is a sub-word chunk produced by the model’s tokenizer (~4 characters of English prose on average). Models process text as tokens, so compute — and therefore cost — scales with token count, not word count or request count. A single call can range from tens to over a hundred thousand tokens, so billing per call would be meaningless; per-token is the natural unit.

2. Explain the difference between input and output tokens and which is more expensive. Input (prompt) tokens are everything you send — system prompt, history, RAG context, the question. Output (completion) tokens are what the model generates. Output is typically several times more expensive per token because generation is more compute-intensive than reading, which is why capping max_tokens is a high-leverage cost control.

3. What is a context window, and what happens if you exceed it? It’s the maximum number of tokens a single request may contain, counting input and output together (e.g. ~128K for the GPT-4o family). Exceeding it doesn’t silently work: you get an error for an over-long request, or your own code must truncate history/context to fit, losing information. Reserved output space also counts against the window.

4. Why does multi-turn chat cost grow faster than linearly with the number of turns? Because the model is stateless, “memory” means re-sending prior turns as input each call. Turn n re-sends turns 1…n−1, so cumulative input across a conversation grows roughly with the square of the turn count. Windowing history or summarising older turns bounds it back to near-linear or constant per turn.

5. When would you choose GPT-4o mini over GPT-4o? For high-volume, lower-complexity tasks — classification, field extraction, routing, summarisation, short factual Q&A over RAG — where mini is accurate enough at a small fraction of the cost. Reserve GPT-4o for genuine multi-step reasoning, nuanced writing, or hard synthesis. A two-tier cascade (mini first, escalate on low confidence) captures most of the savings.

6. Compare pay-as-you-go (Standard) and Provisioned Throughput Units (PTU). Standard bills per token with no commitment — ideal for variable, low, or unknown volume, and ~free when idle. PTU reserves dedicated capacity for a flat price with lower, predictable latency — cheaper per token and better for SLAs at high steady volume, but you pay for idle capacity. Choose PAYG until real usage data justifies sizing a PTU reservation to your steady-state floor.

7. What does prompt caching do, and what’s the constraint? It discounts the input cost of a repeated prompt prefix served from cache. The constraint is that the cached portion must be an identical prefix across calls, so static content (system prompt, few-shot examples) must come first and variable content (question, retrieved chunks) last, or nothing caches.

8. How does RAG affect your token bill, and how do you control it? RAG pastes retrieved document chunks into the prompt as input tokens, frequently making it the largest input contributor. Control it with retrieval discipline: fewer chunks, smaller chunks, and semantic re-ranking so the few you send are the most relevant — often 3–5 good chunks beat 15 raw ones at a fraction of the input cost.

9. You get a 429 Too Many Requests. What does it mean and how do you respond? You exceeded the deployment’s tokens-per-minute or requests-per-minute quota — a rate limit, not a billing error. Respond with exponential backoff/retry, raise the deployment capacity if quota allows, distribute load across deployments/regions, and consider PTU if the sustained rate justifies reserved capacity.

10. How would you estimate the monthly cost of a new Azure OpenAI feature? Multiply calls/day × 30 by the per-call cost, where per-call cost = (avg input tokens ÷ 1,000 × input rate) + (avg output tokens ÷ 1,000 × output rate) for the chosen model. Get the token averages from logged usage on a pilot, never from word or request counts, and confirm current per-1K rates on the pricing page.

11. What’s the cheapest single change to reduce output cost, and why? Setting max_tokens. Output is the pricier half of every call, and many models default to verbose answers; capping output length directly bounds the expensive component with no model or infrastructure change.

12. Why are embeddings cheap, and where does embedding cost actually accumulate? Embeddings bill input-only (no generated output) and at a tiny per-token rate, so any single embedding is negligible. Cost accumulates with volume — embedding a large corpus, and especially re-embedding the whole corpus on every content change. Embed only changed documents and batch the rest.

Quick check

  1. A user types a 12-token question, but the call bills 6,000 input tokens. Name the two prompt components most likely responsible.
  2. The context window is 128K tokens and your prompt is already 127K tokens. Roughly how much room does the model have to answer, and what happens if your answer needs more?
  3. Why does turn 30 of a chat cost far more than turn 3, even if each message is the same length?
  4. You have a trivial “classify this email” task running on GPT-4o. What’s the first change you’d make and roughly what magnitude of saving might it yield?
  5. Your team is about to buy PTUs for a brand-new feature with unknown, spiky traffic. What’s the risk, and what should you do first?

Answers

  1. Conversation history (re-sent prior turns) and RAG context (retrieved document chunks) — the two input components that grow; the user’s question is almost never the cost.
  2. Only about 1K tokens of output room, since input + output share the 128K ceiling. If the answer needs more, you get a context-length error or must trim the prompt — reserved output space counts against the window.
  3. The model is stateless, so “memory” is re-sent transcript; turn 30 re-sends ~29 prior turns as input, while turn 3 re-sends ~2. Cumulative input grows roughly with the square of the turn count — the quadratic trap.
  4. Route it to GPT-4o mini (or 3.5) instead of the flagship; for trivial classification this can be on the order of 10–20× cheaper per token with no quality loss.
  5. The risk is paying full price for mostly-idle reserved capacity. Run on pay-as-you-go first to gather real steady-state usage, then size a PTU reservation to your floor — don’t commit on an optimistic forecast.

Glossary

Next steps

Azure OpenAITokensContext WindowLLM PricingCost ManagementGPT-4oFinOpsAI/ML
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading