HTTP & REST APIs Deep Dive
What You'll Build Today
Welcome to Day 15. Today marks a massive shift in your journey. Up until now, your Python code has lived in isolation on your computer. It calculates numbers, manipulates text, and maybe reads a file or two. But it has been lonely.
Today, we break those walls down. You are going to build a NASA Image Fetcher.
Your script will talk to NASA's servers, authenticate itself as a valid application, request the "Astronomy Picture of the Day," and process the data returned.
Here is why this matters:
* Client-Server Architecture: You will learn that your code is the "Client" asking for resources, and the "Server" is the machine delivering them.
* HTTP Requests: You will learn the language of the web. This is how your browser loads websites, but you'll do it with code.
* APIs (Application Programming Interfaces): You will learn how programs talk to other programs.
* JSON: You will master the specific data format that 99% of modern AI tools (including OpenAI) use to send and receive information.
* Authentication: You will learn how to use API Keys so servers know who you are.
If you want to build an AI app, you are essentially building a system that sends text to an API and waits for a response. Today is the foundation of that skill.
The Problem
Imagine you want to send a prompt to an AI model hosted on a server. The server expects a very specific format. If you miss a comma, use the wrong bracket, or misspell a header, the server rejects you.
Without the right tools, communicating with a server is nightmare-inducingly difficult. Under the hood, the internet works by opening "sockets" (connections) and sending raw text back and forth.
Here is what it looks like if you try to make a simple request to Google using Python's built-in, low-level tools.
The "Hard Way" (Do not run this, just look at the pain):import socket
import ssl
# 1. We have to manually handle the connection
hostname = 'www.google.com'
context = ssl.create_default_context()
# 2. We have to create a raw socket
with socket.create_connection((hostname, 443)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
# 3. We have to manually type out the HTTP protocol text
# If you forget the \r\n (newlines), it fails.
# If you forget the 'Host' header, it fails.
request = f"GET / HTTP/1.1\r\nHost: {hostname}\r\nConnection: close\r\n\r\n"
# 4. We have to encode it to bytes
ssock.send(request.encode())
# 5. We have to manually receive data in chunks
response = b""
while True:
data = ssock.recv(4096)
if not data:
break
response += data
# 6. The output is a messy mix of headers and HTML
print(response.decode()[:500])
Why this is painful:
request string, the server ignores you.response is a giant block of text. You have to write complex code just to separate the metadata (headers) from the actual content (body).There has to be a better way. We need a tool that handles the handshake, the formatting, and the parsing for us, so we can focus on the data.
Let's Build It
We are going to use a Python library called requests. It is the most popular Python library in the world because it makes HTTP simple.
If you haven't installed it yet, you would typically run pip install requests in your terminal. For this lesson, we assume you have your environment set up.
Step 1: The Basic GET Request
Let's start by asking a server for data. We will use a testing service called httpbin.org. It's a server designed to test your code.
We will perform a GET request. This is the digital equivalent of politely asking, "Can I see this page?"
import requests
# The URL we want to access
url = "https://httpbin.org/get"
# Make the request
print("Sending request...")
response = requests.get(url)
# Check the status code
# 200 means "OK" (Success)
# 404 means "Not Found"
# 500 means "Server Error"
print(f"Status Code: {response.status_code}")
# Print the text content of the response
print("\nResponse Body:")
print(response.text)
Why this matters: Notice we didn't open sockets or manage bytes. We just said requests.get(). The response object holds everything we need.
Step 2: Understanding JSON
APIs rarely return plain text or HTML. They return JSON (JavaScript Object Notation). To Python, JSON looks exactly like a dictionary (key-value pairs) or a list.
Let's modify our code to parse the JSON automatically.
import requests
url = "https://httpbin.org/get"
response = requests.get(url)
if response.status_code == 200:
# .json() converts the text response directly into a Python dictionary
data = response.json()
print("Full Data Dictionary:")
print(data)
# Now we can access specific keys, just like a normal dictionary
print("\nAccessing specific data:")
print(f"The URL requested was: {data['url']}")
print(f"Your IP address is: {data['origin']}")
else:
print("Something went wrong.")
Why this matters: AI models return their answers in JSON. Being able to convert that response into a dictionary (data['choices'][0]['message']) is how you extract the AI's answer.
Step 3: Sending Data (POST)
When you use ChatGPT, you aren't just getting a page; you are sending your prompt. In HTTP, sending data to create something new is usually a POST request.
We have to send two things:
import requests
url = "https://httpbin.org/post"
# This is the data we want to send
payload = {
"user_name": "Student_15",
"mission": "Learn APIs",
"day": 15
}
# We don't need to manually set headers for JSON if we use the 'json' parameter
# requests handles the 'Content-Type: application/json' header for us automatically
response = requests.post(url, json=payload)
print(f"Status: {response.status_code}")
# httpbin echoes back the data we sent so we can verify it
response_data = response.json()
print("\nServer received this data from us:")
print(response_data['json'])
Step 4: Real World - The NASA API
Now we are ready for the real thing. We will use NASA's API.
NASA requires an API Key. This is like a password that tells NASA you are allowed to access their data.
We will use the special key DEMO_KEY which NASA provides for testing. It has strict "Rate Limits" (you can't call it too often), but it works for this lesson.
import requests
# Base URL for the Astronomy Picture of the Day (APOD)
url = "https://api.nasa.gov/planetary/apod"
# We send the API key as a "query parameter".
# It gets attached to the URL like this: ?api_key=DEMO_KEY
params = {
"api_key": "DEMO_KEY",
"date": "2023-10-27" # Let's pick a specific date
}
print(f"Requesting NASA data for {params['date']}...")
response = requests.get(url, params=params)
if response.status_code == 200:
data = response.json()
print("\nSUCCESS!")
print(f"Title: {data['title']}")
print(f"Date: {data['date']}")
print(f"Explanation: {data['explanation']}")
print(f"Image URL: {data['url']}")
else:
print(f"Failed with code {response.status_code}")
print(response.text)
Step 5: Handling Errors and Rate Limits
What happens if you make a mistake? Or if you call the API 100 times in a second? The server will block you. This is called Rate Limiting.
If you hit a rate limit, you usually get a 429 status code ("Too Many Requests"). If you have a bad key, you get 403 ("Forbidden") or 401 ("Unauthorized").
Let's write robust code that anticipates failure.
import requests
import time
url = "https://api.nasa.gov/planetary/apod"
# Let's intentionally use a bad key to see the error
params = {
"api_key": "BAD_KEY_123",
"date": "2023-10-27"
}
print("Attempting request with bad key...")
response = requests.get(url, params=params)
# Check for specific issues
if response.status_code == 200:
print("Success!")
data = response.json()
print(data['title'])
elif response.status_code == 403:
print("ERROR: Authentication failed. Check your API Key.")
elif response.status_code == 429:
print("ERROR: You are going too fast! (Rate Limit Exceeded)")
# In a real app, you might wait and try again here
else:
# Catch-all for other errors (like 404 or 500)
print(f"Unknown error occurred: {response.status_code}")
Why this matters: When you build AI apps, the API might be down, or you might run out of credits. If your code assumes status_code is always 200, your app will crash immediately when something goes wrong. Always handle the "else".
Now You Try
You have a working script that fetches text about an image. Now extend it.
The API returns an url pointing to the actual JPG image. Use requests.get(image_url) to fetch the image data. Then, use Python file handling (remember open(filename, 'wb')?) to write the binary content to a file on your computer named space.jpg.
Wrap your request in a loop. Create a list of 3 different dates (e.g., ['2023-01-01', '2020-07-20', '2015-10-31']). Loop through them, fetching the title and explanation for each date.
Inside your loop from step 2, add import time and time.sleep(2). This tells your script to pause for 2 seconds between requests. This is good citizenship when using public APIs to avoid hitting rate limits.
Challenge Project: The Mock OpenAI Client
Your challenge is to build a script that pretends to call OpenAI's GPT-4, but actually sends the data to httpbin.org to verify you formatted it correctly. This is the exact structure you will use in Phase 3 of the bootcamp.
https://httpbin.org/postAuthorization header with the value "Bearer sk-fake-key-123". * model: "gpt-4"
* messages: A list containing one dictionary: {"role": "user", "content": "Tell me a joke."}
* temperature: 0.7
headers and json from the response to prove your Authorization token and payload arrived correctly.Sending Mock GPT Request...
Success! (200)
Server received these Headers:
...
Authorization: Bearer sk-fake-key-123
...
Server received this Payload:
{'messages': [{'content': 'Tell me a joke.', 'role': 'user'}], 'model': 'gpt-4', 'temperature': 0.7}
Hints:
* Remember that headers are a dictionary passed to the headers= argument in requests.post.
* The payload goes into the json= argument.
What You Learned
Today you stepped outside your local environment and connected to the global network.
* HTTP Methods: You know that GET retrieves data and POST sends data.
* Status Codes: You know that 200 is good, 403 is a permission error, and 429 means slow down.
* Requests Library: You can use requests.get() and requests.post() to interact with servers.
* JSON: You can access API data using response.json().
* Authentication: You learned how to pass API keys in parameters or headers.
Why This Matters:Every time you use an LLM in code, you are doing exactly what you did today. When you write:
# Future code you will write
openai.ChatCompletion.create(model="gpt-4", messages=...)
The library is just a wrapper doing a requests.post with your API key in the header and your prompt in the JSON body. You now understand the machinery under the hood.