AI Computer Institute currently serves students across Grades 8 to 12. Imagine the platform adds a doubt-solving assistant: a student types a question, and within two seconds gets a clear, step-by-step answer grounded in the right chapter. Behind that two-second reply sits a decision every AI product team has to make in 2026: which model API answers the question, what exactly gets sent to it, and what it costs to run at the scale of 50,000 students. This chapter builds that system from the wire protocol up — the actual bytes that cross the network between your backend and Claude, GPT, or Gemini — because the difference between a demo that works once in a notebook and a product that survives 100,000 requests a day lives entirely in details most tutorials skip: how conversation state is carried, how tokens are counted and billed, and how a model asks your code to do something on its behalf.
What an API call to a language model actually is
Strip away the SDK and a call to Claude, GPT, or Gemini is one HTTP POST request carrying a JSON body, answered by one HTTP response carrying another JSON body. There is no persistent connection, no login session, no server-side "conversation" object sitting on Anthropic's or OpenAI's or Google's infrastructure waiting for your next message. Every single request is a complete, self-contained transaction: you send everything the model needs to know, and it sends back a completion. This is the single most consequential architectural fact in this chapter, and the rest of the chapter is largely about its downstream effects — on cost, on how "memory" is faked, and on how tool use is implemented as two independent requests rather than one long-lived call.
The JSON body has three load-bearing parts across all three providers, even though the field names differ: a model identifier (which specific model version answers this request), a message history (the conversation so far, as a list of role-tagged turns), and generation controls (how many tokens the model may produce, how creative or deterministic sampling should be, what tools it may invoke). Everything else — system instructions, tool definitions, structured-output schemas, safety settings — is scaffolding around those three.
Anatomy of a request: system, messages, roles
Here is the same question — "Explain why 2 is the only even prime number" — as a raw request body to each provider. Claude's Messages API:
POST https://api.anthropic.com/v1/messages
Headers: x-api-key: <key>, anthropic-version: 2023-06-01
{
"model": "claude-opus-5",
"max_tokens": 1024,
"system": "You are the AICI doubt-solving assistant. Explain step by step.",
"messages": [
{"role": "user", "content": "Explain why 2 is the only even prime number"}
]
}
OpenAI's Chat Completions API folds the instruction into the message list itself, as a message with role "system", rather than a separate top-level field:
POST https://api.openai.com/v1/chat/completions
{
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "You are the AICI doubt-solving assistant. Explain step by step."},
{"role": "user", "content": "Explain why 2 is the only even prime number"}
]
}
Gemini's generateContent endpoint uses yet a third shape: the array is called contents rather than messages, each turn is an object with a role and a list of parts (built for interleaving text, images, and function results in one turn), and the system instruction sits in its own top-level field:
POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent
{
"systemInstruction": {"parts": [{"text": "You are the AICI doubt-solving assistant. Explain step by step."}]},
"contents": [
{"role": "user", "parts": [{"text": "Explain why 2 is the only even prime number"}]}
]
}
Three real differences worth naming, because they trip up anyone porting code between providers. First, role vocabulary: Claude and OpenAI both use "assistant" for the model's own turns; Gemini uses "model" instead — copy an OpenAI conversation history into a Gemini request unchanged and every assistant turn gets rejected or silently misread. Second, where the system prompt lives: OpenAI treats it as just another message in the array (which means it competes for position with user turns and can, in principle, be duplicated or reordered by careless code); Claude and Gemini keep it as a separate top-level field, which is architecturally cleaner and also what makes prompt caching straightforward on Claude — a stable, separately-addressed block that never moves. Third, content shape: Claude and OpenAI accept a bare string as content for plain text turns; Gemini always requires the more verbose parts array, because that array is Gemini's uniform slot for text, inline images, and function-call results alike. Endpoint URLs, exact model identifiers, and minor field names shift as providers ship new versions — treat the shapes above as the stable architecture and always check current provider documentation for the model string itself.
Tokens, pricing, and the cost ledger
Every provider bills by tokens, not characters or words, and input and output tokens are priced separately — output is always markedly more expensive per token than input, because generating text costs more compute than reading it. Claude Opus 5, for instance, prices input at $5 and output at $25 per million tokens (illustrative figures for this worked example — verify current Opus pricing against Anthropic's live documentation before using in a real cost model). This asymmetry is exactly why "keep answers concise" is a cost lever, not just a UX preference: output tokens are typically 5x the price of input tokens.
Work a concrete scenario. Scenario A: a student asks a plain explanatory question with no tool involved — "Explain why 2 is the only even prime number." Suppose a logged production run shows the request used 125 input tokens (a compact system prompt plus the question) and 400 output tokens (a worked explanation with an example). The cost of that one call on Claude Opus 5:
input_cost = 125 / 1,000,000 * $5 = $0.000625
output_cost = 400 / 1,000,000 * $25 = $0.010000
total = $0.010625 per query
Now scale it. At 50,000 students asking an average of 2 questions a day, that is 100,000 queries/day:
100,000 * $0.010625 = $1,062.50 / day ≈ $31,875 / month (30-day month)
Route the same traffic to Claude Haiku 4.5 instead — $1 input / $5 output per million tokens, roughly a fifth of Opus's rate:
input_cost = 125 / 1,000,000 * $1 = $0.000125
output_cost = 400 / 1,000,000 * $5 = $0.002000
total = $0.002125 per query
100,000 * $0.002125 = $212.50 / day ≈ $6,375 / month
That five-times cost gap is why real products route by task difficulty rather than sending every request to the strongest model: a factual definition or a routine "what's my attendance" lookup goes to a cheap, fast model; a question that requires multi-step mathematical reasoning or evaluating a student's proof goes to the frontier model. Model tiering is not a nice-to-have optimization — at Indian ed-tech scale, it is the difference between a sustainable unit economics model and one that cannot survive its own growth.
Statelessness: the conversation lives in your code, not the model
Here is a misconception nearly every student carries in from using a chat app: that Claude, GPT, or Gemini "remember" what was said earlier in a conversation, the way a person recalls yesterday's discussion. They do not, and the API makes this unavoidably visible in a way that a polished chat UI hides. The model has no memory across requests. What creates the illusion of memory is entirely client-side discipline: your code keeps a growing list of every prior turn and resends the entire list, verbatim, on every single request. Drop a message from that list before resending it, and the model has no way to know it ever existed — not because it "forgot," but because it never received it on that call.
This has a cost consequence that compounds silently. Suppose each message — whether the user's question or the assistant's reply — averages 150 tokens, and a student has a 10-turn conversation with no truncation or caching. Turn k's input resends the (k-1) prior exchanges (2 messages each) plus the new question:
input_tokens(k) = 150 * (2*(k-1) + 1) = 150 * (2k - 1)
k=1: 150 * 1 = 150 tokens
k=10: 150 * 19 = 2,850 tokens
Sum the input tokens actually transferred across all 10 turns of the session: sum_{k=1}^{10} 150*(2k-1) = 150 * sum_{k=1}^{10}(2k-1). The sum of the first n odd numbers is the clean identity n², so for n=10 that sum is 100, giving 150 * 100 = 15,000 total input tokens moved over the session — a quadratic growth in cumulative tokens transferred even though every individual message stayed a constant 150 tokens. This is precisely the mechanism prompt caching exists to blunt: since the growing history is a stable prefix that repeats byte-for-byte on every request, Claude's prompt caching lets the provider skip re-processing the already-seen prefix and bill the repeated portion at roughly a tenth of standard input price.
Tool use: closing the loop
Statelessness also explains why "function calling" is really the client running a small loop around two independent API calls, not the model reaching out and doing something mid-response. Say the assistant needs to answer "What is my attendance percentage? My student ID is AICI104" — a question the model cannot answer from its training data, because it requires a live lookup against the school's database. You give the model a tool it may request:
import anthropic
client = anthropic.Anthropic()
def get_attendance(student_id: str) -> dict:
# Real lookup against the school database, defined here for the demo
records = {"AICI104": {"present_days": 172, "total_days": 180}}
return records.get(student_id, {"present_days": 0, "total_days": 0})
attendance_tool = {
"name": "get_attendance",
"description": "Look up a student's attendance record by student ID.",
"input_schema": {
"type": "object",
"properties": {
"student_id": {"type": "string", "description": "e.g. AICI104"}
},
"required": ["student_id"]
}
}
messages = [
{"role": "user", "content": "What is my attendance percentage? My student ID is AICI104."}
]
response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=[attendance_tool],
messages=messages
)
Because answering correctly requires data the model doesn't have, Claude's API contract guarantees response.stop_reason == "tool_use" here rather than a guessed answer, and response.content holds a tool-use block naming get_attendance with {"student_id": "AICI104"} as its input. Nothing has executed yet — the model has only asked. Your code runs the function locally, then starts a second, entirely independent HTTP request that appends both the model's tool-use turn and your function's result to the message list:
tool_use_block = next(b for b in response.content if b.type == "tool_use")
result = get_attendance(**tool_use_block.input)
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": str(result)
}]
})
final_response = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
tools=[attendance_tool],
messages=messages
)
The second call carries the entire conversation again — original question, the model's own tool request, and the tool's result — because that second call knows nothing about the first beyond what is in this new messages list. final_response.stop_reason will be "end_turn", and its text content will describe the computed figure (172/180 ≈ 95.6%); the exact wording is not guaranteed since model output is not deterministic, but the state machine around it — tool_use, then end_turn, with the message list strictly growing — is. The diagram below traces both round trips and shows why the second call's input-token count is larger than the first: it is paying, again, for everything the first call already contained.
Notice call 2's input bar (210 tokens) is nearly double call 1's (125 tokens) — it is paying to resend the original question, the model's own tool-use turn, and the tool result, none of which existed when call 1 was made. This is the tool loop expressed entirely as statelessness plus bookkeeping: no bytes cross a "session," only a message list that your code owns and grows.
Streaming, latency, and rate limits
A non-streaming request waits for the full response before returning anything, which is fine for a background classification job but unacceptable for a chat interface — a 400-token answer at typical generation speed can take several seconds, and a UI that shows nothing for that long reads as broken. Streaming solves this by having the provider send the response as a sequence of small chunks over one open HTTP connection (Server-Sent Events under the hood) as tokens are generated, so the UI can render text as it arrives. Streaming does not reduce the token count or the bill — total input and output tokens are identical either way — it only changes delivery timing. All three providers support it, and the Anthropic SDK exposes a streaming context manager with a get_final_message() helper for when you want the complete object once the stream ends rather than handling every individual event.
Production code also has to handle failure without falling over. A 429 means the account is being rate-limited; a 5xx means a transient server-side fault; both are worth retrying with exponential backoff. A 400 (malformed request) or 401 (bad credentials) is not retryable — retrying it just wastes another round trip on the same bug. The Anthropic Python SDK retries connection errors, 408, 409, 429, and 5xx automatically (two retries by default), but production systems still typically wrap calls in an explicit, most-specific-first exception chain so different failure classes get different handling — alert on repeated authentication failures, back off and retry on rate limits, surface a friendly error to the student on a genuine bad request:
import anthropic
try:
response = client.messages.create(model="claude-opus-5", max_tokens=1024, messages=messages)
except anthropic.RateLimitError as e:
retry_after = int(e.response.headers.get("retry-after", "30"))
# back off and retry after retry_after seconds
except anthropic.APIStatusError as e:
if e.status_code >= 500:
pass # transient, safe to retry
else:
raise # 4xx other than 429: fix the request, don't retry blindly
except anthropic.APIConnectionError:
pass # network fault, retry with backoff
Choosing between Claude, GPT, and Gemini
The three APIs converge on the same underlying idea — send a role-tagged message history, get back generated text or a tool request — but differ enough in wire shape and platform behavior that porting code between them is a real engineering task, not a find-and-replace.
| Aspect | Claude (Anthropic) | GPT (OpenAI) | Gemini (Google) |
|---|---|---|---|
| Endpoint | POST /v1/messages | POST /v1/chat/completions | POST /v1beta/models/{model}:generateContent |
| Assistant role name | assistant | assistant | model |
| System prompt | separate top-level system field | a message with role: "system" inside the array | separate top-level systemInstruction field |
| Conversation state | stateless; resend full messages | stateless; resend full messages | stateless; resend full contents |
| Tool/function field | tools, model returns a tool_use content block | tools (type function), model returns tool_calls | tools (functionDeclarations), model returns a functionCall part |
| Multimodal content unit | typed content blocks (text, image, document) | typed content parts within a message | parts array — the uniform slot for text, image, and function results |
Exact context-window sizes, per-model pricing, and specific model identifiers change frequently on all three platforms; verify the current numbers against each provider's live documentation before shipping a cost estimate or capacity plan rather than trusting a cached figure.
Active recall
Attempt each question before reading its answer.
1. What three components appear in essentially every request to Claude, GPT, and Gemini, and what does each control?
2. A 10-turn conversation averages 150 tokens per message (whether the user's question or the assistant's reply), with no truncation or caching. How many input tokens does turn 10 alone send, and what is the total input tokens transferred across all 10 turns?
3. After Claude returns stop_reason: "tool_use", why can't it simply continue generating once your code has the tool's result — why does the API require a second, separate request?
4. Using the attendance-lookup Scenario B numbers (call 1: 125 input / 60 output tokens; call 2: 210 input / 400 output tokens), suppose the assistant is now instructed to always show full step-by-step derivations, doubling final output to 800 tokens, and the school also adds session memory so this exchange is actually a student's third turn, meaning 600 tokens of prior history get resent as input on call 2 in addition to the 210 already accounted for. Recompute call 2's cost on Claude Haiku 4.5 ($1 input / $5 output per million tokens) before and after these two changes, and state which one contributes more to the increase.
5. True or false: turning off streaming reduces token usage and therefore cost. Justify your answer.
6. Gemini's generateContent request has no top-level system field like Claude's. Where does the system instruction go instead, and what is the name of the array holding the conversation turns?
Answers
1. A model identifier (which model version handles the request), a message history (the role-tagged conversation, resent in full every call since the API is stateless), and generation controls such as a token cap and, where tools are involved, a tool definitions list.
2. Turn k's input resends (k-1) prior exchanges (2 messages each) plus the new question: input(k) = 150*(2k-1). At k=10: 150*19 = 2,850 tokens. The cumulative total across all 10 turns is 150 * sum_{k=1}^{10}(2k-1) = 150 * 10² = 150 * 100 = 15,000 tokens — using the identity that the sum of the first n odd numbers is n².
3. Because inference is stateless per call: once the API returns a response, that request is over and the model instance handling it retains nothing. There is no channel for your code to hand the tool's result to an already-finished request. The only way to give the model new information — including a tool result — is to start a brand-new request that includes that information in its message list, which is exactly what the second POST does.
4. Baseline call 2 on Haiku: 210/1e6*1 + 400/1e6*5 = 0.00021 + 0.002 = $0.00221. Isolating only the doubled output (800 tokens, input still 210): 210/1e6*1 + 800/1e6*5 = 0.00021 + 0.004 = $0.00421 — output cost alone roughly doubles the total. Now add the 600 tokens of resent session history on top of the original 210, with output still at 800: input becomes 210+600=810 tokens, giving 810/1e6*1 + 800/1e6*5 = 0.00081 + 0.004 = $0.00481. Going from $0.00221 to $0.00481 is roughly a 2.18x increase. The output-length doubling contributes the larger single jump ($0.00221 → $0.00421, +90%); the resent history adds a further $0.0006 (+14% on top of that) — smaller in isolation here because Haiku's input price is a fifth of its output price, so a token of output costs five times what a token of input costs. This is also exactly the situation prompt caching is built for: the 600 tokens of repeated history are a stable prefix that caching would bill at roughly a tenth of standard input price instead of full price.
5. False. Streaming changes only how the response is delivered — as incremental chunks over one connection instead of one blocking response — not how many tokens are generated or billed. A streamed and a non-streamed call to the same model with the same input producing the same output are billed identically; streaming is a latency and UX choice, not a cost lever.
6. The system instruction goes in Gemini's top-level systemInstruction field (structured the same way as any turn: a parts array containing the text). The conversation turns live in the contents array, where Claude and OpenAI both call the equivalent array messages.
Practice Exercises
Now it is time to practice! Complete these challenges to solidify your understanding:
- Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
- Exercise 2: Find a real-world example where building with apis: claude, gpt, and gemini is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
- Exercise 3: Create a mind-map connecting building with apis: claude, gpt, and gemini to at least 3 other topics you have studied.
Key Takeaways — Summary and Recap
Let us recap what we covered: the core ideas behind building with apis: claude, gpt, and gemini, how they connect to real-world applications, and why they matter for your journey in computer science. Remember these key points as you move forward. For competitive exam preparation (CBSE, JEE, BITSAT), focus on understanding the WHY behind each concept, not just the WHAT.