Employees API

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.

Base URLhttps://employees-api.taila30d72.ts.net
TokenAsk your teacher for the class token
You needPython 3 with the requests package (and pandas for the data examples)
It's a playground. You can create, change and delete anything. Every night at 03:00 (Korea time) all data goes back to the original, so you can't break anything for long. Your classmates use the same data, so practise on records you created.

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.

Client your Python program (or a browser) Server this API checks token & rules Database PostgreSQL employees tables ① request ④ response ② SQL query ③ rows You never touch the database directly: the server decides what you may do.

A request has four parts

  1. Method: what you want to do: GET (read), POST (create), PUT/PATCH (change), DELETE.
  2. URL: which thing: https://…/api/employees/10001. The part after the address is the path; anything after ? is the query string.
  3. Headers: extra information, like your token: Authorization: Bearer …
  4. Body (optional): the data you send, written as JSON. Only for POST, PUT and PATCH.

A response has three parts

  1. Status code: a number saying how it went: 200 OK, 201 created, 404 not found…
  2. Headers: information about the answer, like Content-Type: application/json.
  3. 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.

Request (client → server)
Response (server → client)

JSON in one minute

JSON is how the API writes data. It maps directly onto Python:

JSONPythonExample
object { }dict{"id": 10001, "first_name": "Georgi"}
array [ ]list["Staff", "Senior Staff"]
stringstr"1953-09-02" (dates are strings)
numberint / float88958
true / falseTrue / False"is_current": true
nullNone"next": null (no next page)

Quick start

1

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.

2

Install requests

In a terminal (or a notebook cell starting with !):

pip install requests pandas
3

Make your first call from Python

Replace YOUR_TOKEN with the class token and run:

4

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)
Keep it out of public places. Don't post the token online or push it to GitHub. Anyone with it can change the data. (If it leaks, your teacher can replace it.)

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:

CRUDHTTP methodSQLExample hereSuccess code
ReadGETSELECTGET /api/employees/10001200 OK
CreatePOSTINSERTPOST /api/employees201 Created
Update (all fields)PUTUPDATE … SET every columnPUT /api/employees/500001200 OK
Update (some fields)PATCHUPDATE … SET one columnPATCH /api/employees/500001200 OK
DeleteDELETEDELETEDELETE /api/employees/500001204 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.

CodeNameWhat it means hereWhat to do
200OKWorked; the answer is in the body.Read response.json().
201CreatedYour POST made a new record.The body is the new record; the Location header is its address.
202AcceptedWork started and will finish later (the teacher's reset).Check /api/health later.
204No ContentWorked, nothing to send back (after DELETE).Don't call .json(): the body is empty.
400Bad RequestThe body isn't valid JSON.Use json= in requests, not data=.
401UnauthorizedNo token, or a wrong one.Check the Authorization header.
403ForbiddenYour token is fine but not allowed to do this.Only the teacher can reset.
404Not FoundNo such endpoint, or no record with that id.Check the path and the id.
405Method Not AllowedThat path doesn't accept that method.Check the method (GET/POST/…).
409ConflictIt already exists (same id or same start date).Use a different id/date, or PATCH the existing one.
422UnprocessableA value is missing or invalid.Read error.details: it names the field.
429Too Many RequestsYou sent too many requests in one minute.Wait the seconds in the Retry-After header.
500Server ErrorA bug on the server. Not your fault.Tell your teacher.
503UnavailableThe nightly reset is running, or the database is down.Wait a minute and retry.
504TimeoutYour 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 parameterMeaningDefault
limitHow many items per page (1–100)20
offsetHow many items to skip from the start0

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.

employeeid · first_name · last_namegender · birth_date · hire_date salaryemployee_id · amountfrom_date · to_date titleemployee_id · titlefrom_date · to_date department_employeeemployee_id · department_idfrom_date · to_date department_manageremployee_id · department_idfrom_date · to_date departmentid · name
“9999-01-01” means “still current”. A salary, title or department membership that hasn't ended has 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.

OpenAI API gpt-4o-mini api.openai.com Your Python program decides what runs Employees API this server real data POST /v1/responses + OpenAI key answer + tokens GET /api/… + class token JSON data The two APIs never talk to each other: your program carries information between them.

Same concepts you already know

ConceptEmployees APIOpenAI API
Base URLhttps://employees-api.taila30d72.ts.nethttps://api.openai.com/v1
Who you areClass tokenYour OpenAI API key (sk-…)
HeaderAuthorization: Bearer … for both
Main requestGET /api/employees/10001POST /v1/responses with a JSON body
Wrong key401 for both
Too fast429 for both: wait and retry
CostFreePay per token (about ¾ of a word): $0.15 per million in, $0.60 per million out

Set up once

1

Get an API key

Your teacher gives it to you, or create one at platform.openai.com/api-keys. It starts with sk-.

2

Install the library

pip install openai requests pandas
3

Put 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.

Mac / Linux terminal
export OPENAI_API_KEY="sk-..."
Windows PowerShell
$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-..."
Your key is money. Anyone who has it can spend on your account. Never paste it into code you share, a GitHub repo, a screenshot or a chat.
Model retirement: OpenAI will switch off 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

  1. In step 3, change the instructions so the model answers like a pirate, or in Uzbek. What changes, what doesn't?
  2. In step 6, add a field "department" (which department should handle the comment) to the schema, and count comments per department with pandas.
  3. 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?”
  4. Use step 8's cost formula: how much would labelling 10,000 comments with step 6 cost?
  5. Make the tool loop print how many tokens the whole conversation used in total.

Rules and limits

RequestsAt 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 sizeAt most 100 items per page.
Slow queriesRequests that take over 15 seconds are stopped (504).
Nightly resetEvery 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 dataThe 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 10001 in /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 limit and offset.
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 .