These notes are not intended as a comprehensive guide to the topic. Their purpose is to guide you through the main areas you should learn about, with resources provided for further exploration. The goal is for you to learn enough to complete the associated challenge.
These notes explain what a web API is and why you might build one before moving to how to design requests, responses, URLs, and errors.
Examples use JSON and a simple space theme with planets, moons, and missions.
Helpful references
An API is a contract that lets one piece of software ask another to do something or to provide information
For web APIs the contract travels over the internet using HTTP which is the same protocol your browser uses to load web pages
Plain language
/planets{"name": "Jupiter"}Why teams build an API
A good API is predictable, well documented, and hard to misuse
Read more
A useful mental model is a polite conversation
Read more
REST is a set of ideas for building web APIs that fit neatly with HTTP
It treats the things in your system as resources such as planets and moons and gives them stable URLs
You manipulate those resources using standard HTTP methods such as GET and POST
Why REST is popular
Read more
Every request has the same basic shape
METHOD PATH HTTP/VERSION
Requests can also have headers, which provide information about the request:
METHOD PATH HTTP/VERSION
Header-Name: value
Another-Header: value
Requests can also have an optional body:
METHOD PATH HTTP/VERSION
Header-Name: value
Another-Header: value
optional request body
Every response has the same basic shape
HTTP/VERSION STATUS_CODE Reason-Phrase
Header-Name: value
Another-Header: value
optional response body
Key terms
/planets/42Read more
| Method | Plain English meaning | Typical use |
|---|---|---|
| GET | Tell me about this thing or list of things | Read a resource or collection |
| POST | Create a new thing under this URL or run a command | Create in a collection |
| PUT | Replace the thing at this exact URL | Full update or create at a known URL |
| PATCH | Change part of the thing at this URL | Partial update |
| DELETE | Remove the thing at this URL | Delete |
Two useful properties
Read more
A status code tells the client what happened
Success
Location header that points to the new resourceClient problems
If-Match ETag does not match which protects against lost updatesContent-Type is wrongServer problems
Read more
A resource can be represented in different formats
We will use JSON which is simple to read and write
Tell the server what you are sending and what you can accept
Content-Type: application/json for request bodiesAccept: application/json to say what response formats you will acceptKeep JSON simple and stable. Use predictable field names and types.
Use ISO 8601 date strings such as 1610-01-07
Read more
We will design an API that stores information about planets, their moons, and space missions that visit those planets
This scenario gives clear examples of common API patterns
Think in nouns, not verbs. Nouns become resources, and resources get URLs
Top level resources have their own URL paths
/planets/moons/missionsUse predictable, hierarchical paths
GET /planetsGET /planets/42GET /planets/42/moonsPOST /planets/42/moonsSimple path rules
/space-missions/v1 - this allows you to upgrade the API in future with breaking changesRead more
Request
POST /planets
Content-Type: application/json
Accept: application/json
{
"name": "Jupiter",
"discovered_at": "1610-01-07"
}
Response
HTTP/1.1 201 Created
Location: /planets/1
Content-Type: application/json
{
"id": 1,
"name": "Jupiter",
"discovered_at": "1610-01-07"
}
Request
GET /planets/1/moons?sort=name&order=asc&page=2&per_page=3
Accept: application/json
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"data": [
{ "id": 7, "name": "Callisto" },
{ "id": 8, "name": "Europa" },
{ "id": 9, "name": "Ganymede" }
],
"meta": { "page": 2, "per_page": 3, "total": 12 }
}
Read more
Use query parameters for common collection concerns
GET /missions?visited_planet=1GET /planets?discovered_at_gte=1600-01-01GET /planets?sort=discovered_at&order=descpage and per_page is easy to learncursor token scales better for very large or changing data setsRead more
Use a consistent format for errors, for example:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
{
"title": "Validation failed",
"status": 422,
"detail": "One or more fields are invalid",
"errors": [
{ "field": "name", "message": "Name is required" },
{ "field": "discovered_at", "message": "Use ISO date format YYYY-MM-DD" }
]
}
Use status codes:
Read more
Start simple and keep secrets out of URLs
Common approaches
Authorization: Api-Key <key>Authorization: Bearer <token>Return 401 for missing or invalid credentials and 403 when authenticated but not allowed
Read more
Write the contract down in OpenAPI so tools can read it
Benefits
A tiny OpenAPI fragment
openapi: 3.1.0
info:
title: Space API
version: 1.0.0
servers:
- url: https://api.example.com
paths:
/planets:
get:
summary: List planets
parameters:
- in: query
name: page
schema: { type: integer, minimum: 1, default: 1 }
- in: query
name: per_page
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Planet'
/planets/{planet_id}:
get:
summary: Get a planet
parameters:
- in: path
name: planet_id
required: true
schema: { type: integer }
responses:
"200":
description: OK
components:
schemas:
Planet:
type: object
properties:
id: { type: integer }
name: { type: string }
discovered_at: { type: string, format: date }
Read more
This is a sketch to show where HTTP concerns live. It reads like Express pseudo-code
Responsibilities
// routes
router.get('/planets', listPlanets)
router.post('/planets', authenticate, createPlanet)
router.get('/planets/:id', getPlanet)
router.patch('/planets/:id', authenticate, updatePlanet)
router.delete('/planets/:id', authenticate, deletePlanet)
// handler shapes
async function listPlanets(req, res) {
const { page = 1, per_page = 20, sort = 'name', order = 'asc', fields } = req.query
const result = await db.planets.findMany({ page, per_page, sort, order, fields })
res.status(200).json({
data: result.rows,
meta: { page, per_page, total: result.total }
})
}
async function createPlanet(req, res) {
const planet = await db.planets.create(req.body)
res.status(201).json(planet)
}
Read more
HttpOnly, Secure, and an appropriate SameSite value.Read more
2025-10-13T09:00:00ZRead more
GET /planets/{planet_id}/moons with response schema and an example