How APIs work
Requests, responses, methods, status codes and headers.
Why it matters
Every call to an LLM is an HTTP request. So is every integration you've used in marketing: pulling numbers from an ad platform, adding a contact to a CRM, sending an email through a delivery service. When an AI agent "uses a tool", it's usually making one of these requests. Learn to read requests and responses, and most API errors explain themselves.
What you'll be able to do
- Name the parts of a URL and what each one does
- Choose the right HTTP method for reading, creating, updating and deleting
- Read a status code and know whose problem it is
- Turn a JSON response body into Python data
What it covers
- Two messages: a request and a response
- Anatomy of a URL
- Status codes
- Headers and bodies
- Worked example: building a request body
Two messages: a request and a response
HTTP is the set of rules browsers and servers use to talk. It works like ordering at a counter. You (the client) place an order (the request), and the kitchen (the server) hands something back (the response). When you open a web page, your browser is the client, and the response is the page for it to draw.
An API (application programming interface) uses exactly the same rules. But it's built for programs rather than people. Instead of a web page, it replies with data, almost always as JSON. Your code is the client.
Every request has four parts:
- a method: what you want to do (read, create, change, delete)
- a URL: what you want to do it to
- headers: extra information about the request, such as who you are and what format you're sending
- an optional body: data you're sending, such as a new campaign's details
Every response has three:
- a status code: a number saying how it went
- headers: information about the response
- a body: the data itself, often JSON
Anatomy of a URL
https://api.gradient.test/v1/campaigns?status=active&channel=search
| Part | Here | What it does |
|---|---|---|
| Scheme | https:// | how to connect; the s means encrypted |
| Host | api.gradient.test | which server to talk to |
| Path | /v1/campaigns | which resource on that server; v1 is the API version |
| Query string | ?status=active&channel=search | options for this request, as key=value pairs joined by & |
You know query strings already: UTM tags are exactly this. In ?utm_source=newsletter&utm_campaign=spring, the analytics tool reads the key-value pairs after the ?. An API reads them the same way. Here they filter which campaigns to return.
api.gradient.test is the academy's practice API. It pretends to be an ad platform with six campaigns and a small product catalog. You can experiment without breaking anything.
Methods
| Method | Meaning | Example |
|---|---|---|
GET | read something | list campaigns |
POST | create something, or submit data to be processed | create a campaign; send a prompt to an LLM |
PUT / PATCH | replace / partly update something | change a campaign's budget |
DELETE | remove something | delete a draft |
A GET should never change anything on the server. That's why it's safe to repeat. Calls to an LLM are POST requests, because you send a body containing your messages.
Show the answer
It only reads, so it shouldn't change anything on the server
Reading twice gives you the same data and leaves the server as it was. Repeating a POST might create something twice.
Status codes
The status code is the first thing to read in any response. The first digit tells you the family:
| Family | Meaning | Common codes |
|---|---|---|
| 1xx | informational, rarely seen | 100 Continue |
| 2xx | success | 200 OK, 201 Created, 204 No Content |
| 3xx | redirect: look elsewhere | 301 Moved Permanently, 302 Found |
| 4xx | client error: something about your request is wrong | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests |
| 5xx | server error: the server failed | 500 Internal Server Error, 503 Service Unavailable |
The 4xx/5xx split tells you where to look:
- A 4xx means fix your request: a typo in the URL, a missing field, a bad API key.
- A 5xx means the problem is on their side. Waiting and trying again may work.
Two codes you'll see a lot with LLM APIs: 401, meaning your API key is missing or wrong, and 429, meaning you're sending requests faster than you're allowed to.
401 and 403 sound alike. 401 means "I don't know who you are" (no key, or an invalid one). 403 means "I know who you are, and you're not allowed to do this".
Send some requests and watch the codes:
status = 404 print(status // 100)
Show the answer
4
The first digit gives the family. 4 means the request was the problem, like asking for something that isn't there.
Pick a preset, send it, then edit the URL, method or body and send again. The echo endpoint shows exactly what the server received.
GET https://api.gradient.test/v1/campaigns/cmp_999 and get status 404. Whose problem is it, and what should you check?Show the answer
Yours: the request points at something that doesn't exist, so check the ID or path
404 is a client error: no resource at that path. Retrying won't help; fixing the ID will. A bad key would be 401 or 403.
Headers and bodies
Headers are Name: value lines that travel with a request or response. A few you'll see constantly:
Content-Type: application/jsonsays the body is JSON.X-Api-Key: ...orAuthorization: Bearer ...carries your credentials (covered in the API keys lesson).Accept: application/jsonasks for JSON back.
The body of a request or response is just text. When it's JSON, you already know how to work with it. json.loads turns JSON text into Python dictionaries and lists. json.dumps turns Python data into JSON text to send.
Here is a response body exactly as the practice API sends it, as one string:
import json body = '{"data": [{"id": "cmp_101", "name": "Spring Sale - Search", "clicks": 5470}, {"id": "cmp_104", "name": "Newsletter Promo - Email", "clicks": 1920}], "count": 2}' result = json.loads(body) print(type(result)) print(result["count"]) for campaign in result["data"]: print(campaign["name"], campaign["clicks"])
Error responses are JSON too, and a well-designed API puts a readable message inside:
import json status = 404 body = '{"error": {"type": "not_found", "message": "No campaign with id cmp_999."}}' if status >= 400: details = json.loads(body) print("Request failed with", status) print(details["error"]["message"])
When a request fails, print the status code and the body. The answer to "why didn't this work?" is usually sitting in that message.
Show the answer
Wait a little and try again: 5xx means the server had a problem
5xx errors are on the server's side, and they're often short-lived. A 4xx is the one that means fix your request.
Worked example: building a request body
You want to create a campaign. The API expects a JSON body with name and budget. Build the data as a Python dictionary, then turn it into the text that would be sent.
import json new_campaign = { "name": "Summer Launch - Social", "budget": 1500, "channel": "social", } body = json.dumps(new_campaign) print(body) print(type(body))
It prints {"name": "Summer Launch - Social", "budget": 1500, "channel": "social"}, then <class 'str'>: the body is now text, ready to send.
You always build request data as normal Python, a dictionary here. Turn it into JSON text only at the moment of sending. Notice the output uses double quotes. That's JSON's rule, and json.dumps handles it for you. In the next lesson, the requests library does this step for you when you pass json=new_campaign.
https://api.gradient.test/v1/products?category=apparel is the query string?Show the answer
?category=apparel
Everything after the ? is the query string: key=value options for this request.
Practice what you just read
- 3 code examples you can edit and run
- a practice exercise that checks your code
- a 5-question quiz
- 5 review cards for spaced repetition
Try a quiz question
Which HTTP method should a request use to create a new campaign?