Learning Goals
3 min- Send JSON to a server with
requests.post(url, json=...). - Read created resources (status 201) and form responses.
- Authenticate with the four common patterns: query, header, Bearer token, Basic auth.
- Keep secrets out of source — environment variables and
.envfiles.
Warm-Up · GET vs POST
5 minGET read ?q=python safe to repeat
POST create body: {"title": ...} creates a new thing
PUT replace body: {"title": ...} overwrite by id
PATCH update body: {"title": ...} partial change
DELETE remove ?id=42 remove by idThe five HTTP verbs. Today is POST. The others come up in Level 4's SQLite + Flask lessons.
POST = "please create this for me". The data travels in the request body, usually as JSON. The server responds with the freshly-created resource and HTTP 201.
New Concept · post(), Headers, Auth
14 minYour first POST
The test API https://httpbin.org/post echoes back what you sent — perfect for learning.
import requests r = requests.post( "https://httpbin.org/post", json={"name": "Aisyah", "score": 92}, timeout=5, ) print(r.status_code) # → 200 (httpbin always echoes 200) print(r.json()["json"]) # → {'name': 'Aisyah', 'score': 92}
The json= kwarg serialises your dict to JSON and sets the right Content-Type header automatically.
Form data vs JSON
# Form data (looks like HTML form submission) r = requests.post("https://httpbin.org/post", data={"k": "v"}) # JSON (most modern APIs) r = requests.post("https://httpbin.org/post", json={"k": "v"})
Four common auth patterns
1. Query parameter
r = requests.get( "https://api.openweathermap.org/data/2.5/weather", params={"q": "Kuala Lumpur", "appid": API_KEY}, timeout=5, )
2. Header (custom)
r = requests.get( "https://api.example.com/data", headers={"X-API-Key": API_KEY}, timeout=5, )
3. Bearer token (most modern APIs, including OpenAI & Anthropic)
r = requests.get( "https://api.example.com/data", headers={"Authorization": f"Bearer {TOKEN}"}, timeout=5, )
4. Basic auth (username + password)
r = requests.get( "https://api.example.com/data", auth=("username", "password"), timeout=5, )
Where to keep your key
NEVER commit a key to git. Use an environment variable or a local .env file that's git-ignored:
import os API_KEY = os.environ["OPENWEATHER_KEY"] # Or, in a .env file: # OPENWEATHER_KEY=abcd1234 # Then in code: from dotenv import load_dotenv load_dotenv() API_KEY = os.environ["OPENWEATHER_KEY"]
python-dotenv (pip install python-dotenv) reads .env into os.environ. Add .env to .gitignore — and never paste a real key into a screenshot, a chat or a blog post.
Worked Example · POST to a Public Todo API
12 minJSONPlaceholder accepts POSTs without auth — it pretends to save your data and returns a 201 with a fake id. Perfect playground.
# new_todo.py — create a new todo via POST import requests URL = "https://jsonplaceholder.typicode.com/todos" new = { "userId": 1, "title": "Finish PY-L4-11 homework", "completed": False, } r = requests.post(URL, json=new, timeout=5) r.raise_for_status() print("status:", r.status_code) print("body :", r.json()) print(f"\n🎉 server assigned id = {r.json()['id']}")
Sample output
status: 201
body : {'userId': 1, 'title': 'Finish PY-L4-11 homework', 'completed': False, 'id': 201}
🎉 server assigned id = 201The status is 201 Created, not 200. The response body is your todo plus a server-assigned id. (JSONPlaceholder doesn't really save it — but a real API would.)
Read the diff
The full POST recipe: build a Python dict, pass it via json=, check status, read the returned object — especially any server-generated fields like id. From here on every API write operation looks the same.
Try It Yourself
13 minPOST any dict to https://httpbin.org/post and verify the server echoes the JSON exactly. Print just the body it received.
Hint
import requests r = requests.post("https://httpbin.org/post", json={"any": "thing", "you": "want"}) print(r.json()["json"])
Hit https://httpbin.org/headers with a custom X-API-Key header. Print the headers the server received and confirm your key is in there.
Hint
import requests r = requests.get("https://httpbin.org/headers", headers={"X-API-Key": "test-1234"}) print(r.json()["headers"])
Create a .env file with DEMO_KEY=hello. In Python use python-dotenv to load it and print the value. Add .env to your .gitignore.
Hint
# pip install python-dotenv import os from dotenv import load_dotenv load_dotenv() print(os.environ.get("DEMO_KEY", "(missing)"))
If the key is missing, you get the friendly fallback "(missing)" instead of a KeyError.
Mini-Challenge · Send a Tiny Message
8 minPick a free webhook service like https://ntfy.sh (no signup needed!) and POST a text message to a topic you make up. The notification will appear on the ntfy.sh website at that topic URL.
Show one possible solution
# ping.py — post a message to a public ntfy.sh topic import requests TOPIC = "advaslearning-py-l4-test-abc123" # any unguessable string r = requests.post( f"https://ntfy.sh/{TOPIC}", data="Hello from Python — PY-L4-11!".encode("utf-8"), timeout=5, ) r.raise_for_status() print(f"sent ✅ open https://ntfy.sh/{TOPIC} in a browser")
Non-negotiables: pick an unguessable topic name (anyone with the URL can see), encode the message to bytes, check the status.
Recap
3 minPOST sends data; json= makes it easy. Auth comes in four flavours — query, header, Bearer, Basic — and you pick whichever the API requires. Never paste a key into source: use os.environ + a .env file. Tomorrow we cash in: trivia questions live from a real API.
Vocabulary Card
- POST
- HTTP method for "create this resource". Body usually carries JSON.
- Bearer token
- An auth pattern where you send
Authorization: Bearer <token>. - Basic auth
- Username + password, sent encoded in the Authorization header.
requestshandles it viaauth=. - .env
- A text file of
KEY=valuelines, loaded intoos.environ. Always git-ignored.
Homework
4 minSign up for a free API that requires a key — OpenWeather, IPGeolocation, NewsAPI, Alpha Vantage. Build auth_demo.py:
- Load the key from
.env. - Make one call with the key.
- Print the first 3 most useful fields.
- Add a
# .env.examplefile with the key name (no real value) so a teammate knows what to set.
Sample · auth_demo.py (OpenWeather)
# auth_demo.py — OpenWeather current conditions import os, requests from dotenv import load_dotenv load_dotenv() KEY = os.environ["OPENWEATHER_KEY"] r = requests.get( "https://api.openweathermap.org/data/2.5/weather", params={"q": "Kuala Lumpur,my", "appid": KEY, "units": "metric"}, timeout=10, ) r.raise_for_status() d = r.json() print(f"📍 {d['name']}") print(f" {d['weather'][0]['description']}") print(f" {d['main']['temp']}°C feels {d['main']['feels_like']}°C")
.env.example
OPENWEATHER_KEY=
Non-negotiables: key never in code, .env.example committed, real key in .env only, .env in .gitignore.