Programming for Data Analysis · Practice API
Talk to a real database from Python
This API lets you read and change an employees database: 300,024 people, their departments, job titles and 2.8 million salary records. You send requests from Python; the server answers with data. Along the way you'll learn how every web API works.
https://employees-api.taila30d72.ts.netrequests package (and pandas for the data examples)How an API works
An API (Application Programming Interface) is a way for one program to ask another program for something. A web API does it over the internet using HTTP, the same language your browser speaks.
A request has four parts
- Method: what you want to do:
GET(read),POST(create),PUT/PATCH(change),DELETE. - URL: which thing:
https://…/api/employees/10001. The part after the address is the path; anything after?is the query string. - Headers: extra information, like your token:
Authorization: Bearer … - Body (optional): the data you send, written as JSON. Only for POST, PUT and PATCH.
A response has three parts
- Status code: a number saying how it went:
200OK,201created,404not found… - Headers: information about the answer, like
Content-Type: application/json. - Body: the data, as JSON. In Python,
response.json()turns it into dicts and lists.
What it actually looks like
This is the exact text that travels over the network when you ask for employee 10001, and the answer that comes back.
Python's requests library writes and reads this text for you.
JSON in one minute
JSON is how the API writes data. It maps directly onto Python:
| JSON | Python | Example |
|---|---|---|
object { } | dict | {"id": 10001, "first_name": "Georgi"} |
array [ ] | list | ["Staff", "Senior Staff"] |
| string | str | "1953-09-02" (dates are strings) |
| number | int / float | 88958 |
true / false | True / False | "is_current": true |
null | None | "next": null (no next page) |
Quick start
Open it in your browser
Your browser can make GET requests too. Click: /api/health.
You'll see JSON with "status": "ok". That page needs no token.
Install requests
In a terminal (or a notebook cell starting with !):
pip install requests pandasMake your first call from Python
Replace YOUR_TOKEN with the class token and run:
Explore
Every endpoint below has a copy-ready sample, the real response, and a Try it button that sends the request from this page. Paste your token in the box at the top first.
Your token
Except for /api/health and /api, every request must prove you're in the class. You do that with a
token: a long secret string your teacher gives you. Send it in the Authorization header with the word
Bearer and a space in front:
Authorization: Bearer YOUR_TOKEN
In Python, make the header once and pass it to every call:
headers = {"Authorization": f"Bearer {TOKEN}"}
response = requests.get(f"{BASE_URL}/api/departments", headers=headers)
Forget the header and you get 401 Unauthorized:
Methods = CRUD
Almost everything you do with data is one of four things: Create, Read, Update, Delete. Each has an HTTP method. If you know SQL, here's the match:
| CRUD | HTTP method | SQL | Example here | Success code |
|---|---|---|---|---|
| Read | GET | SELECT | GET /api/employees/10001 | 200 OK |
| Create | POST | INSERT | POST /api/employees | 201 Created |
| Update (all fields) | PUT | UPDATE … SET every column | PUT /api/employees/500001 | 200 OK |
| Update (some fields) | PATCH | UPDATE … SET one column | PATCH /api/employees/500001 | 200 OK |
| Delete | DELETE | DELETE | DELETE /api/employees/500001 | 204 No Content |
GET is safe: it never changes anything, so you can repeat it freely. POST, PUT, PATCH and DELETE change data.
Status codes
The first digit tells you the story: 2xx it worked, 4xx something is wrong with your request, 5xx something is wrong on the server.
| Code | Name | What it means here | What to do |
|---|---|---|---|
| 200 | OK | Worked; the answer is in the body. | Read response.json(). |
| 201 | Created | Your POST made a new record. | The body is the new record; the Location header is its address. |
| 202 | Accepted | Work started and will finish later (the teacher's reset). | Check /api/health later. |
| 204 | No Content | Worked, nothing to send back (after DELETE). | Don't call .json(): the body is empty. |
| 400 | Bad Request | The body isn't valid JSON. | Use json= in requests, not data=. |
| 401 | Unauthorized | No token, or a wrong one. | Check the Authorization header. |
| 403 | Forbidden | Your token is fine but not allowed to do this. | Only the teacher can reset. |
| 404 | Not Found | No such endpoint, or no record with that id. | Check the path and the id. |
| 405 | Method Not Allowed | That path doesn't accept that method. | Check the method (GET/POST/…). |
| 409 | Conflict | It already exists (same id or same start date). | Use a different id/date, or PATCH the existing one. |
| 422 | Unprocessable | A value is missing or invalid. | Read error.details: it names the field. |
| 429 | Too Many Requests | You sent too many requests in one minute. | Wait the seconds in the Retry-After header. |
| 500 | Server Error | A bug on the server. Not your fault. | Tell your teacher. |
| 503 | Unavailable | The nightly reset is running, or the database is down. | Wait a minute and retry. |
| 504 | Timeout | Your query took too long. | Use smaller pages or more filters. |
Errors
Every error has the same shape, so your code can handle all of them the same way:
{
"error": {
"status": 404,
"code": "not_found",
"message": "No employee with id 1."
}
}
code is for your program (it never changes); message is for you to read. For 422 errors there is
also details, a list saying exactly which field is wrong and why. Here's a real one: the request below
has a bad gender, an impossible date, and missing fields:
In Python, check the status before using the data:
response = requests.get(f"{BASE_URL}/api/employees/1", headers=headers)
if response.ok: # True for any 2xx code
print(response.json())
else:
error = response.json()["error"]
print("Problem:", response.status_code, error["message"])
Pages and filters
There are 300,024 employees: far too many for one response. Lists come back one page at a time:
| Query parameter | Meaning | Default |
|---|---|---|
limit | How many items per page (1–100) | 20 |
offset | How many items to skip from the start | 0 |
So page 1 is ?limit=20&offset=0, page 2 is ?limit=20&offset=20, and so on. Every list answer
tells you where you are:
{
"total": 300024, // matches in all pages
"limit": 20,
"offset": 0,
"count": 20, // items on this page
"next": "https://employees-api.taila30d72.ts.net/api/employees?limit=20&offset=20",
"previous": null, // null = this is the first page
"data": [ ... ]
}
Follow next until it is null to collect everything (see the tutorial).
Filters are more query parameters, like ?gender=F&department_id=d005. In requests, pass a dict
as params= and it builds the query string for you.
The data
Six tables, linked by ids. An employee can have many salaries and titles over the years, and can move between departments.
to_date 9999-01-01. The API adds "is_current": true to make that easier to spot.
Dates are always written YYYY-MM-DD.The data covers 1985 to 2002 and is a well-known sample database: the people are made up. Department ids look like
d001–d009; employee ids run from 10001 to 499999, and new ones you create start at 500000.
Endpoints
Each endpoint below shows: what it does, its inputs, a Python sample you can copy, the real request and response it produced when we ran it, and what the sample printed. Samples that change data first create their own record, so they never touch your classmates' work.
Practice tutorials
Longer programs that combine several calls. Each one was run against the live API; the output shown is real.
Case study
Talk to an AI model with the OpenAI API
You now know how APIs work. OpenAI's API is the same idea: you send an HTTP request with a key and JSON, and get JSON back. The only difference is what answers: a large language model, gpt-4o-mini. By the end of this case study your Python program will let the model look things up in the Employees API by itself.
Same concepts you already know
| Concept | Employees API | OpenAI API |
|---|---|---|
| Base URL | https://employees-api.taila30d72.ts.net | https://api.openai.com/v1 |
| Who you are | Class token | Your OpenAI API key (sk-…) |
| Header | Authorization: Bearer … for both | |
| Main request | GET /api/employees/10001 | POST /v1/responses with a JSON body |
| Wrong key | 401 for both | |
| Too fast | 429 for both: wait and retry | |
| Cost | Free | Pay per token (about ¾ of a word): $0.15 per million in, $0.60 per million out |
Set up once
Get an API key
Your teacher gives it to you, or create one at platform.openai.com/api-keys. It starts with sk-.
Install the library
pip install openai requests pandasPut the key in an environment variable, not in your code
The library reads OPENAI_API_KEY automatically, so the key never appears in files you share.
export OPENAI_API_KEY="sk-..."$env:OPENAI_API_KEY = "sk-..."In a Jupyter or Colab notebook, run this in a cell (and delete the cell before sharing the notebook):
import os
os.environ["OPENAI_API_KEY"] = "sk-..."gpt-4o-mini on January 14, 2027 and
recommends gpt-6-luna instead. Every example keeps the model name in one line, MODEL = "gpt-4o-mini":
change that word and the rest keeps working.The steps
Each step builds on the one before. Every sample was run for real; the output shown is what it printed. AI answers vary, so yours will be worded differently.
Try it yourself
- In step 3, change the instructions so the model answers like a pirate, or in Uzbek. What changes, what doesn't?
- In step 6, add a field
"department"(which department should handle the comment) to the schema, and count comments per department with pandas. - In step 7, add a third tool
list_department_employees(department_id)that calls/api/departments/{department_id}/employees, then ask: “Who are three people in Finance?” - Use step 8's cost formula: how much would labelling 10,000 comments with step 6 cost?
- Make the tool loop print how many tokens the whole conversation used in total.
Rules and limits
| Requests | At most 600 per minute per network address. Everyone on the same Wi-Fi shares this, so add a short
time.sleep() in loops. Over the limit you get 429 with a Retry-After header. |
|---|---|
| Page size | At most 100 items per page. |
| Slow queries | Requests that take over 15 seconds are stopped (504). |
| Nightly reset | Every night at 03:00 Korea time all data returns to the original; it takes about a minute, during which calls get 503. Anything you created is gone after that. |
| Shared data | The whole class uses the same database. Practise on records you created; please don't delete other people's. |
Advanced: the machine-readable OpenAPI description is at /api/openapi.json, and an interactive Swagger page at /api/swagger.
Glossary
- API
- A set of rules for how programs ask each other for things.
- Endpoint
- One method + path the API answers, like
GET /api/employees. - Client / Server
- The client asks (your Python program); the server answers (this API).
- HTTP
- The language of the web: requests with methods, headers and bodies; responses with status codes.
- Base URL
- The start of every address:
https://employees-api.taila30d72.ts.net. - Path parameter
- A value inside the path, like the
10001in/api/employees/10001. - Query parameter
- A value after
?in the URL, like?limit=5. Used for filters and pages. - Header
- A name: value line sent with a request or response, like
Content-Type: application/json. - Body / Payload
- The data sent with a request (POST/PUT/PATCH) or response, written as JSON.
- JSON
- A text format for data with objects, arrays, strings, numbers, true/false and null.
- Token
- A secret string that proves who you are. Sent as
Authorization: Bearer …. - Status code
- A three-digit number in every response saying how the request went.
- CRUD
- Create, Read, Update, Delete: the four basic things you do with data.
- Pagination
- Splitting a long list into pages with
limitandoffset. - Rate limit
- A cap on how many requests you may send per minute.
- REST
- A common style of web API: things are “resources” at URLs, and methods say what to do with them.
Examples on this page were recorded from the live API on .