Index Ai Academy
Phase 0 · Tools · Talking to APIs

How APIs work

Requests, responses, methods, status codes and headers.

About 25 minutes · Free lesson

Start this lesson

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

  1. Two messages: a request and a response
  2. Anatomy of a URL
  3. Status codes
  4. Headers and bodies
  5. 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:

  1. a method: what you want to do (read, create, change, delete)
  2. a URL: what you want to do it to
  3. headers: extra information about the request, such as who you are and what format you're sending
  4. an optional body: data you're sending, such as a new campaign's details

Every response has three:

  1. a status code: a number saying how it went
  2. headers: information about the response
  3. a body: the data itself, often JSON

Anatomy of a URL

https://api.gradient.test/v1/campaigns?status=active&channel=search
PartHereWhat it does
Schemehttps://how to connect; the s means encrypted
Hostapi.gradient.testwhich server to talk to
Path/v1/campaignswhich resource on that server; v1 is the API version
Query string?status=active&channel=searchoptions 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

MethodMeaningExample
GETread somethinglist campaigns
POSTcreate something, or submit data to be processedcreate a campaign; send a prompt to an LLM
PUT / PATCHreplace / partly update somethingchange a campaign's budget
DELETEremove somethingdelete 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.

Quick check
Why is it safe to repeat a GET request?
  1. Browsers cache every request
  2. It costs nothing to run
  3. It only reads, so it shouldn't change anything on the server
  4. The server ignores repeats
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:

FamilyMeaningCommon codes
1xxinformational, rarely seen100 Continue
2xxsuccess200 OK, 201 Created, 204 No Content
3xxredirect: look elsewhere301 Moved Permanently, 302 Found
4xxclient error: something about your request is wrong400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests
5xxserver error: the server failed500 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:

Quick check
What does this print?
status = 404
print(status // 100)
  1. 0
  2. 4
  3. 404
  4. 40
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.

Try it now

Pick a preset, send it, then edit the URL, method or body and send again. The echo endpoint shows exactly what the server received.

Quick check
You request GET https://api.gradient.test/v1/campaigns/cmp_999 and get status 404. Whose problem is it, and what should you check?
  1. Nobody's: 404 means success with an empty result
  2. The server's: wait a minute and try again
  3. Yours: the request points at something that doesn't exist, so check the ID or path
  4. Your API key is wrong
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/json says the body is JSON.
  • X-Api-Key: ... or Authorization: Bearer ... carries your credentials (covered in the API keys lesson).
  • Accept: application/json asks 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"])
It runs right here in your browser, with nothing to install.

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.

Quick check
Your script gets status 503 from an API. What's the sensible next step?
  1. Give up: 503 means the API no longer exists
  2. Get a new API key
  3. Rewrite your request: 5xx means your request was wrong
  4. Wait a little and try again: 5xx means the server had a problem
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

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))
In the lesson you can change this and run it.

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.

Quick check
Which part of https://api.gradient.test/v1/products?category=apparel is the query string?
  1. ?category=apparel
  2. api.gradient.test
  3. https://
  4. /v1/products
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

Start this lesson

Try a quiz question

Which HTTP method should a request use to create a new campaign?

  1. POST
  2. GET
  3. DELETE
  4. HEAD

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson