Command Palette

Search for a command to run...

Hectal
PHASE 2Beginner ~20 min· topic 1 of 9

Topic 2.1

The HTTP API Under Every SDK

In one line

Calling a model means sending an HTTPS POST with a JSON body (model name, messages, limits) and reading a JSON reply. SDKs wrap this request; knowing the raw shape lets you debug, switch providers and call any model from any language.

Think of it like this

Ordering at a restaurant counter with a printed form. You fill in the form (what you want, how much), hand it over with your membership card, and get back a receipt with the dish and the price. The form is the request, the card is the API key, the receipt is the reply. A model call works the same way, and any waiter (any SDK, any language) that fills in the form correctly gets the same food.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

HTTP
The standard way programs ask each other for things over the internet: a request goes out, a response comes back.
Request / response
What you send (URL, headers, body) and what the server sends back (status, headers, body).
JSON
A text format for structured data: objects in curly braces, lists in square brackets. Every model API uses it.
Endpoint
One URL of an API that does one job, such as /v1/chat/completions for chat.
Header
A named extra line sent with a request or response, such as Authorization or Content-Type.
API key
A secret string that proves who is calling and who should be billed.
Status code
The number at the top of a response: 200 means OK, 401 bad key, 429 too many requests, 500 server trouble.
Fake server
A function in your own program that pretends to be the remote API so examples run offline.

Step by step

01Step 1: it is just a web request

When you type a web address, your browser sends a GET request and receives HTML. A model call is the same idea with two changes: the method is POST (you send a body) and both bodies are JSON. Nothing in it needs a special library.

Step 1: it is just a web requestdiagram
Rendering diagram…

02Step 2: build the body as plain data

Make a dictionary (an object in JavaScript) with model, messages and max_tokens, then turn it into JSON text. The first example prints that text so you can see exactly what goes over the wire.

main.pywhole filepython
import json

request = {"model": "small-model", "messages": [{"role": "user", "content": "Hi"}], "max_tokens": 50}
print(json.dumps(request))

03Step 3: add the headers

The Authorization: Bearer <key> header carries your key. In the example the fake server compares it with the key it expects and answers 401 if it does not match, as a real server does.

04Step 4: read the reply

Check the status first. Only on 200 parse the JSON and walk down: choices[0].message.content is the answer, choices[0].finish_reason says why it stopped, usage has the token counts. Print each so you can match them with the raw JSON line above.

05Step 5: see the failure shape

The last lines of the example send a wrong key. The status is 401 and the body is a different JSON shape with an error object. A client that reads choices without checking the status would crash with a confusing "key not found" instead of telling you the key is wrong.

06Step 6: the same call for real

The blocks after the example show the same call with curl, Python (httpx), JavaScript (fetch) and Java (HttpClient). They differ only in syntax. SDKs (Topic 2.5) wrap exactly this request so you do not write it each time.

Try it yourself

  1. 1

    Predict the token counts

    Before running, count the characters of the question (30). The fake counts about 4 characters per token, rounding up. Predict prompt_tokens, then run and compare.

  2. 2

    Change the question

    Change the user message so it no longer mentions France. What does the answer become? Where in the fake server is that decided? (A real server uses a model instead of an if.)

  3. 3

    Compare the languages

    Run the example in all three languages and compare the request body line. Why is the key order the same everywhere?

Code & diagrams

A chat-completions call built and read by hand New tab

The fake server checks the key and builds a reply of the real shape. Java uses the course's Json helper; real Java projects use Jackson.

Sign in to run this example in your browser.

Expected output

POST /v1/chat/completions
request body: {"model":"small-model","messages":[{"role":"user","content":"What is the capital of France?"}],"max_tokens":50}
status: 200
response body: {"id":"chatcmpl-001","object":"chat.completion","model":"small-model","choices":[{"index":0,"message":{"role":"assistant","content":"Paris."},"finish_reason":"stop"}],"usage":{"prompt_tokens":8,"completion_tokens":2,"total_tokens":10}}
answer: Paris.
finish_reason: stop
tokens: 8 in, 2 out
wrong key -> 401: Invalid API key
The same call with real HTTP codepython

Not run here: it needs your own GROQ_API_KEY and a network. Other OpenAI-compatible providers change only the URL, key and model name.

# pip install httpx      (any HTTP client works: requests, urllib, aiohttp)
import os

import httpx

response = httpx.post(
    "https://api.groq.com/openai/v1/chat/completions",
    headers={"Authorization": "Bearer " + os.environ["GROQ_API_KEY"]},  # the key comes from the environment
    json={
        "model": "openai/gpt-oss-20b",
        "messages": [{"role": "user", "content": "What is the capital of France?"}],
        "max_tokens": 300,
    },
    timeout=30,  # seconds: never call a remote service without a timeout (Topic 2.7)
)
response.raise_for_status()  # turns 4xx and 5xx into an exception (Topic 2.6)
reply = response.json()
print(reply["choices"][0]["message"]["content"])
The same call with curlpython

curl is a shell command, so all three tabs show the same text. Run it in a terminal with GROQ_API_KEY set.

# The whole API is one HTTPS request. Replace the URL and model with your provider's.
curl https://api.groq.com/openai/v1/chat/completions \
  -H "Authorization: Bearer $GROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-oss-20b",
    "messages": [{"role": "user", "content": "What is the capital of France?"}],
    "max_tokens": 300
  }'

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Skip the status check

In the Python example, replace the final print line (after the wrong-key call) with code that reads the 401 body as if it were a success: try: then print("wrong key -> " + json.loads(text)["choices"][0]["message"]["content"]), then except KeyError as e: and print("wrong key -> missing field: " + e.args[0]).

terminal
$ Run
── what you'll see ──
wrong key -> missing field: choices

Myth vs fact

Myth

You need a special library to call a model.

Fact

A model call is a plain HTTPS request with JSON. Libraries add convenience (types, retries, streaming helpers), not capability.

Myth

Every provider has a completely different API.

Fact

Many providers accept the OpenAI-style chat-completions shape, so switching is often a base URL, key and model name. A few (Anthropic, Google) have their own shapes.

Myth

A successful call (HTTP 200) means the answer is good.

Fact

200 means the request worked. The answer may still be cut off, empty or wrong; check finish_reason and validate the content.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Log the request id (providers return one in a response header such as x-request-id) with every failed call. Support teams ask for it first.

  • ▸

    Cost is usage times price per token, and input tokens are re-sent on every turn (Topic 2.2). Read usage from the reply instead of estimating (Topic 6.1).

  • ▸

    Reuse HTTP connections: creating a new secure (TLS) connection for every call adds tens of milliseconds. Keep one client object per process (httpx.Client, one HttpClient, the global fetch agent do this for you).

Remember this

  1. 1

    Why this exists. A model runs on someone else's powerful computers, not yours. To use it your program must send text over the internet and get text back. The web already has a standard way to do that: HTTP, where a client sends a request (a URL, headers, a body) and a server sends a response (a status number, headers, a body). Model providers chose JSON for the bodies. There is no magic channel: if you can send an HTTP request, you can call a model.

  2. 2

    The request. Four parts matter. The URL (for chat models usually ending in /chat/completions). The method, always POST. Headers: Authorization: Bearer <key> proves who you are, and Content-Type: application/json says the body is JSON. The body: at least model (which model) and messages (the conversation, Topic 2.2); usually max_tokens (the most tokens, small pieces of text, it may write, Topic 1.1; some newer OpenAI models call it max_completion_tokens) and sometimes temperature (how random, Topic 1.6).

  3. 3

    The response. A status code first: 200 means success, anything else means trouble (Topic 2.6). On success the JSON body holds choices (a list; the first item has the model's message and a finish_reason) and usage (how many tokens went in and came out, which is what you are billed for). Topic 2.3 reads each field.

  4. 4

    One shape, many providers. The request shape that OpenAI introduced for "chat completions" became the common dialect: Groq, Together, Mistral, DeepSeek, local servers like Ollama and vLLM and many others accept it, so often only the base URL, the key and the model name change. Anthropic and Google use their own shapes (Topic 2.5). Learning the common one covers most of the market.

  5. 5

    Why build it by hand first. The example below builds the request as a plain dictionary, turns it into compact JSON text, hands it to a fake server function that checks the key and answers, and parses the reply. No network is used, so it is free and always gives the same result, but every byte is the real shape. When an SDK call fails later, you can reason about the bytes it sent.

  6. 6

    Two things never to forget. The key is a secret that costs money if stolen, so it lives in an environment variable on a server (Topic 2.4). And a request can fail or hang, so real code always sets a timeout and checks the status before trusting the body (Topics 2.6, 2.7).

Explain it without notes

01

What is inside a chat-completions request, and what comes back?

02

Why can the same client code often talk to several providers?

03

Why should code check the status code before reading choices?

Practice

01

Write build_request(question) that returns the request dictionary for a one-message chat to model small-model with max_tokens 50, and print it as compact JSON.

02

Write describe(status, body_text) that returns "ok: <content>" for status 200 (read choices[0].message.content) and "error <status>: <message>" otherwise (read error.message).

Trade-offs

  • ↔

    Raw HTTP has no dependencies and never goes out of date, but you write retries, types and streaming yourself; an SDK saves code and adds a dependency.

  • ↔

    An OpenAI-compatible endpoint maximises portability but may hide provider-specific features (such as prompt caching controls).

Done when you can

  • Done when you can write the headers and JSON body of a chat-completions request from memory.

  • Done when you can name the fields to read in the reply: choices[0].message.content, finish_reason and usage.

  • Done when you check the status code before parsing a body.

  • Done when you can run the same call with curl and in one programming language.