API
APIs From First Principles HTTP · requests · the logic underneath
v1 · 2026
A study wiki

APIs From First Principles

Start with two programs that need to talk and nothing else. Build up, one honest layer at a time, until "REST API" stops being a buzzword and becomes something you could reconstruct from scratch on a whiteboard. No memorization. Every rule earns its place.

Chapter 00·~4 min

How to read this #

This wiki teaches APIs the way you would derive a theorem: assume as little as possible, then show that each new idea is forced by a problem you already understand. We do not start with "an API is a contract between services." We start with the question that makes that sentence necessary.

The spine of the argument is short. Two programs want to exchange information. They are on different machines. To do that reliably they need a shared set of rules, a protocol. HTTP is one such protocol, and it happens to be the one the web was built on, so it is the one most APIs reuse. Once you understand HTTP as a request-and-response conversation, an "API" is just a deliberate, documented set of HTTP conversations a server agrees to have with you. That is the whole story. Everything else is detail in service of that story.

The through-line

Programs need to talk → talking across machines needs rules → HTTP is the rules → an API is a server publishing which HTTP conversations it will honor. Hold that chain in your head and the rest follows.

Read top to bottom the first time. The chapters are ordered so that nothing depends on something defined later. Code samples use curl, a command-line tool that speaks HTTP directly, because it hides nothing. If you can read a curl command you can read what any library is doing under the hood.

A note on rigor: where a term has a precise definition fixed by a standard, this wiki links to the standard itself rather than paraphrasing it loosely. The authority for HTTP is the IETF's RFC 9110, HTTP Semantics, and the most readable working reference is MDN's HTTP documentation. When you want to go deeper than a study note should go, those are where to go.

Chapter 01·First principles

The problem APIs solve #

This chapter is about why any of this exists. Skip it and the rules look arbitrary. Sit with it and the rules look inevitable.

Start with one program on one computer. When it needs data, it reads from memory or disk. A function calls another function; the caller knows the callee's name, hands it arguments, and gets a return value. This works because both halves live in the same process, share the same memory, and were compiled together. There is no ambiguity about what a "string" is or how a number is laid out in bytes, because one author controlled both sides.

Now move the callee to a different machine. Maybe it holds data too large for your computer, or it belongs to a different company, or a thousand other programs need the same answer and it is wasteful for each to compute it. The instant the two halves are separated, every assumption that made the function call trivial breaks:

  • They no longer share memory, so you cannot just pass a pointer. You must serialize: turn your data into a sequence of bytes that can travel, and turn it back on the other side.
  • They were not compiled together and may be written in different languages, so they need an agreed format for those bytes that neither side gets to assume privately.
  • The connection between them can be slow, can reorder data, can drop it, or can fail entirely, so they need rules for what to do when things go wrong.
  • The callee may serve many callers at once and remembers nothing about you between calls unless told to, so each request must carry its own context.

Every one of these is a problem of agreement between strangers. The two programs cannot read each other's source. They can only exchange bytes and rely on a shared convention for what those bytes mean. That shared convention is a protocol. An API, when we get to it, will be a specific protocol-level agreement: "send me bytes shaped like this, and I will send you bytes shaped like that."

Connects

The local function call never disappears as a mental model. A well-designed API is an attempt to make a call across the network feel as much like a local function call as the laws of physics and trust will allow. Knowing exactly which guarantees you lose when you cross the machine boundary is the most useful thing in this whole wiki.

So the design pressure is set before we have named a single technology. We need: a way to encode data as bytes, a shared format, a transport that moves bytes between machines, and rules for addressing, ordering, and failure. The next chapter takes those needs and shows what the internet already provides to meet them.

Chapter 02·The transport

The network underneath #

This chapter gives you exactly enough networking to understand HTTP and no more. The goal is not to teach networking; it is to remove the magic from the words "the request travels to the server."

Addressing: IP and ports

Every machine reachable on a network has an IP address, a number that identifies it the way a street address identifies a building. One machine runs many programs, so an address alone is not enough; you also need a port, a number that picks which program on that machine should receive the bytes. The pair (IP address, port) names a specific listening program. Web servers conventionally listen on port 80 for plain HTTP and 443 for encrypted HTTPS, which is why you rarely type them.

Humans do not memorize IP addresses, so DNS, the Domain Name System, maps a name like api.example.com to an IP address. When your program contacts that name, a DNS lookup happens first, quietly, to find the number. DNS is its own protocol; for our purposes it is a phone book. The authoritative description lives at RFC 1034.

Reliable delivery: TCP

Raw networks lose and reorder packets. TCP, the Transmission Control Protocol, is the layer that turns that unreliable packet delivery into something we can trust: a connection that behaves like an ordered, gap-free, two-way stream of bytes. When you write bytes into a TCP connection, TCP guarantees they arrive on the other end in the same order, with retransmission of anything lost, or it tells you the connection failed. It does this through a handshake to set up the connection and acknowledgements to confirm delivery. The canonical reference is RFC 9293.

This matters because HTTP is built directly on top of TCP. HTTP does not worry about lost packets or ordering; it assumes a reliable byte stream and gets to be a simple text conversation precisely because TCP already solved the hard delivery problem underneath it. This layering, each protocol standing on the guarantees of the one below, is the central design move of the internet.

Privacy and trust: TLS, the S in HTTPS

Plain TCP carries bytes in the clear; anyone between you and the server can read or alter them. TLS (Transport Layer Security) wraps the TCP stream in encryption and authentication, so the bytes are private and you can verify you are really talking to api.example.com and not an impostor. "HTTPS" is just "HTTP running inside a TLS-secured TCP connection." Same HTTP on top; one more protective layer underneath. See RFC 8446 for TLS 1.3.

The client / server shape

One program listens on a port, waiting for connections. That is the server. Another program initiates a connection and speaks first. That is the client. The relationship is asymmetric by design: the server is always-on and reactive, the client drives. Your browser is a client. curl is a client. The phrase "make a request to an API" means "be the client that opens a connection to a server and speaks first."

Where we are

We now have: a way to name a program on the network (IP + port, via DNS), a reliable ordered byte stream to it (TCP), optional privacy (TLS), and a clear client/server roles. We have delivery. What we still lack is a shared language for what to say over that stream. That language is HTTP.

Chapter 03·The protocol

What HTTP actually is #

HTTP is the agreed format for the bytes a client and server exchange over their connection. Strip away the mystique and it is astonishingly plain: HTTP is a text-based request-and-response conversation. The client sends one text message describing what it wants. The server sends one text message back. That is one HTTP transaction.

It is worth seeing the actual bytes once, because every framework and library is just generating and parsing these. Here is a complete, real HTTP request as it travels over the connection:

GET /users/42 HTTP/1.1
Host: api.example.com
Accept: application/json
User-Agent: curl/8.4.0

And the server's complete response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 49

{"id": 42, "name": "Ada", "role": "engineer"}

Look closely, because the structure is identical on both sides and never varies:

Anatomy of a request

  1. The start line: GET /users/42 HTTP/1.1. Three parts: the method (GET, the verb describing the action), the target (/users/42, the path identifying what you are acting on), and the HTTP version.
  2. Headers: one per line, Name: value. These are metadata about the request: which host you want (essential, since one server hosts many sites), what response formats you can accept, who you are. Headers are the side channel for everything that is not the action or the body.
  3. A blank line: this is structural, not decorative. It marks the end of the headers. Everything after it is the body.
  4. An optional body: the payload you are sending. A GET usually has none. A POST carries its data here.

Anatomy of a response

Mirror image. A status line (HTTP/1.1 200 OK: version, a numeric status code, a human label), then headers describing the response, then a blank line, then the body, which is the actual content you asked for.

The shape never changes

Start line, headers, blank line, body. Request and response both. Once you have internalized those four parts you can read any HTTP traffic, and you will notice that "calling an API" produces exactly these bytes whether you used curl, Python, or a browser.

Two properties that define HTTP's character

It is stateless. Each request is understood entirely on its own. The server does not, by default, remember your previous request. If request two depends on request one, request two must carry whatever context it needs (an ID, a token). This is a deliberate trade: the server stays simple and can be replicated freely, at the cost of clients re-sending context. Statelessness is the property that lets one logical service run as a thousand identical machines behind a load balancer, since no single machine holds a conversation in memory.

It is a pull model, request-then-response. The client always speaks first and the server only answers. The server cannot spontaneously push a message to a resting client over plain HTTP. That constraint is why technologies like WebSockets exist for genuinely bidirectional needs, but the overwhelming majority of APIs live happily inside the request-response shape.

The precise meaning of every part above is fixed by RFC 9110. You do not need to read it to use HTTP, but it is the ground truth when a question has no obvious answer.

Chapter 04·The verbs

Methods: the verbs of HTTP #

The method is the first word of every request, and it answers one question: what kind of action is this? The genius of HTTP is that it fixes a small vocabulary of verbs with agreed meanings, so a client and server who have never met still understand what a request intends. The path says what you are acting on; the method says what you want done to it.

MethodIntentHas body?Safe?Idempotent?
GETRead a resourceNoYesYes
POSTCreate, or trigger an actionYesNoNo
PUTReplace a resource wholesaleYesNoYes
PATCHModify part of a resourceYesNoNo
DELETERemove a resourceUsually noNoYes

Two words in that table carry real weight, and they are worth defining precisely because API design leans on them constantly.

Safe means the request is not supposed to change anything on the server. Reading is safe. Because GET is safe, the whole infrastructure of the web feels free to repeat it: browsers prefetch GET URLs, caches store their results, crawlers follow them. You must never hide a state change behind a GET, because the world assumes GET is harmless and will call it whenever it likes.

Idempotent means making the same request twice has the same effect as making it once. DELETE is idempotent: delete user 42, and deleting user 42 again leaves the same end state (gone), even if the second call reports "already gone." This is not a pedantic distinction. Networks fail mid-request all the time, and a client that cannot tell whether its request arrived needs to know whether retrying is safe. Idempotent methods can be retried blindly. POST cannot, because two POSTs to create an order may create two orders, which is why payment and ordering flows need extra care.

The cardinal sin

Putting a side effect behind GET. A link that deletes data when merely visited will be triggered by a crawler, a prefetch, or a cache, with no human intent behind it. The safe/idempotent contract is a promise the entire web relies on. Honor it.

These properties are not enforced by the protocol; HTTP cannot stop you from writing a GET that deletes things. They are semantic promises defined in RFC 9110 §9, and the value of an API comes precisely from keeping those promises so that clients can reason about it. The next two chapters take the two methods you will use most, GET and POST, and examine them closely, because the contrast between them teaches the whole verb system.

Chapter 05·Reading

GET in depth #

GET is how you read. It is the most common request on the internet by an enormous margin, and understanding it well teaches you most of what you need about how requests carry information.

A GET request names a resource in its path and asks for a copy of it. It carries no body. So how does a GET ask a question rather than just naming a thing? Through the query string: the part of the URL after a ?, a list of key=value pairs joined by &.

GET /search?q=coffee&limit=10&sort=rating HTTP/1.1
Host: api.example.com

Here the path /search names the action and the query string supplies the parameters: search term coffee, at most ten results, sorted by rating. Everything the request needs is in the URL itself, which is exactly why GET is so easy to cache, bookmark, log, and share. The full address is the question.

That visibility is also GET's constraint. Because the parameters live in the URL, they are not secret (URLs appear in logs, browser history, and analytics), and they are length-limited in practice. So you put filters and selectors in a query string, never passwords or large payloads. Special characters in values must be percent-encoded (a space becomes %20) so they do not collide with the URL's own syntax. The rules for what a URL may contain are fixed by RFC 3986.

Try it

This is a real, public API you can hit right now. It returns JSON describing a user.

curl https://jsonplaceholder.typicode.com/users/1

Add -v (verbose) and curl will print the actual request and response lines, the bytes from Chapter 03, so you can watch the anatomy in the wild:

curl -v https://jsonplaceholder.typicode.com/users/1
Connects

Recall safe and idempotent from Chapter 04. GET is both. That is what licenses caching: a cache can store the response to GET /users/1 and serve it again without asking the server, because GET promises it changed nothing and that repeating it is fine. The Cache-Control header (Chapter 07) is how a server tells caches how long that stored copy stays valid.

Mental model for GET: a pure function from URL to representation. Same URL, same answer, no side effects. When an API endpoint fits that shape, it should be a GET, and clients will trust it accordingly.

Chapter 06·Writing

POST in depth #

POST is how you send data that creates or changes something. Where GET puts its small parameters in the URL, POST carries a full payload in the request body, below the blank line. This is the other half of the conversation: GET pulls, POST pushes.

Because the body can be anything, the server needs to be told how to interpret those bytes. That is the job of the Content-Type header. The same POST body bytes mean different things depending on the declared type. Three you will meet constantly:

Content-TypeBody looks likeUsed for
application/json{"name":"Ada"}Almost all modern APIs
application/x-www-form-urlencodedname=Ada&role=engClassic HTML forms
multipart/form-dataencoded partsFile uploads

Here is a POST that creates a user, sending JSON. Note the Content-Type declaring the format and Content-Length telling the server how many body bytes to read:

POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 38

{"name": "Ada", "role": "engineer"}

With curl, the same request. The -d flag supplies the body and -H sets a header:

curl -X POST https://jsonplaceholder.typicode.com/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Ada", "role": "engineer"}'

The server reads the body, validates it, creates the resource, and conventionally responds 201 Created (Chapter 08) with the new resource, including the ID it assigned, in the response body.

Why POST is neither safe nor idempotent

POST changes server state, so it is not safe; a cache must never replay it. And two identical POSTs to /users create two users, so it is not idempotent. This is the practical reason a double-clicked "Submit Order" button can place two orders, and why serious APIs defend against it with an idempotency key: the client generates a unique token, sends it with the POST, and the server records that token so a retry with the same token returns the original result instead of acting twice. Stripe's idempotency design is the canonical real-world example worth reading.

GET vs POST, the clean line

If the request only reads and can be safely repeated, it is a GET and its inputs go in the URL. If it changes something or carries a payload too big or too private for a URL, it is a POST (or PUT/PATCH/DELETE) and its data goes in the body. That single decision drives most of API design.

Chapter 07·Metadata

Headers: the metadata channel #

Headers are how the client and server say everything that is not the action, the target, or the body. They are simple Name: value lines, but they carry the negotiation that makes HTTP flexible. You do not need to memorize them; you need to understand the few categories they fall into, so that any header you meet has an obvious slot.

Content description

Content-Type declares the format of the body being sent. Content-Length states its size in bytes. On a response, these describe what the server is returning. These answer "what are these body bytes?"

Content negotiation

Accept lets the client say what formats it can handle, for example Accept: application/json. The server tries to honor it. This is how the same endpoint can serve JSON to a program and HTML to a browser: the client asks for what it wants and the server picks the best match it can produce. It is a small, elegant negotiation rather than separate URLs per format.

Authentication and identity

Authorization carries credentials proving who is making the request, most often a token: Authorization: Bearer eyJhbGc.... Because HTTP is stateless (Chapter 03), this credential must ride on every request that needs it; the server remembers nothing between calls. This is the single most important header for APIs and the subject of Chapter 11.

Caching and conditional requests

Cache-Control lets a server tell caches how long a response may be reused, which is what makes GET fast at scale. ETag gives a response a version fingerprint; a client can later send If-None-Match with that tag, and the server can reply 304 Not Modified with no body if nothing changed, saving bandwidth. This is the polite, bandwidth-saving conversation that powers the web's speed.

Connects

Headers are where HTTP's extensibility lives. The start line and body are rigid; headers are open-ended, so new capabilities (compression, CORS, tracing, rate-limit signals) get added as headers without changing the protocol's shape. MDN keeps a complete, current header reference; treat it as a dictionary, not a reading assignment.

Chapter 08·The response language

Status codes #

Every response begins with a three-digit status code, and it is the server's one-glance verdict on what happened. The codes are not random; the first digit sorts them into five classes, and knowing the classes is worth far more than memorizing individual numbers.

ClassMeaningYou will see
1xxInformational, request received, continuingRare in practice
2xxSuccess200 OK, 201 Created, 204 No Content
3xxRedirection, look elsewhere301 Moved, 304 Not Modified
4xxClient error, you sent something wrong400, 401, 403, 404, 429
5xxServer error, the server failed500, 502, 503

The single most useful distinction is 4xx versus 5xx, because it assigns blame and tells you what to do. A 4xx means the problem is in your request and resending it unchanged will fail identically; fix the request. A 5xx means the server broke while handling a request that may have been perfectly valid; the right move is often to wait and retry, especially for idempotent methods. Confusing the two leads to the two classic bugs: retrying a 4xx forever (it will never succeed) and giving up on a 5xx that a retry would have fixed.

A handful you should recognize on sight:

  • 200 OK: success, body contains the result. 201 Created: a POST made a new resource. 204 No Content: success, deliberately no body (common after DELETE).
  • 400 Bad Request: malformed input. 401 Unauthorized: you are not authenticated (you have not proven who you are). 403 Forbidden: you are authenticated but not allowed. 404 Not Found: no such resource. 429 Too Many Requests: you are being rate-limited, slow down.
  • 500 Internal Server Error: the server threw and did not handle it. 503 Service Unavailable: the server is up but temporarily cannot serve, often overloaded or in maintenance.
401 vs 403, a classic trip-up

401 means "I do not know who you are," fix it by authenticating. 403 means "I know exactly who you are and you may not do this," authenticating again will not help. The names are historical and unhelpful; the meanings are precise.

The complete, authoritative registry of status codes is maintained by IANA, with semantics defined in RFC 9110 §15. A good API uses these codes faithfully so that clients can react to the class without parsing prose.

Chapter 09·The leap

What an API actually is #

Here is the chapter the whole wiki was building toward. We now have HTTP: a stateless request-response protocol with verbs, paths, headers, bodies, and status codes. An API takes that general machinery and makes it specific and promised.

API stands for Application Programming Interface, and the word interface is the entire idea. An interface is the boundary at which two parts of a system meet and the set of operations one offers the other, deliberately separated from how those operations are implemented inside. A power socket is an interface: you agree on the shape of the plug and the voltage, and you do not care how the power plant works. A function signature is an interface: you agree on the name, arguments, and return type, and the body is hidden.

A web API is that same notion expressed in HTTP. A server publishes a set of HTTP requests it agrees to honor, what each does, what it expects in the path, query, headers, and body, and what it will return. Calling the API means making exactly those agreed requests. The server's internal code, database, and language are hidden behind the boundary. You program against the interface, not the implementation.

The definition, earned

An API is a documented contract for a set of HTTP conversations a server promises to have. "GET /users/{id} returns that user as JSON, or 404 if absent." That single sentence names a method, a path shape, a success response, and a failure response. A full API is a collection of such sentences. Nothing more mystical than that.

This reframes everything in the earlier chapters as vocabulary for writing the contract. The methods are the verbs the contract can use. Status codes are how the contract reports outcomes. Headers carry the contract's metadata and credentials. The contract is built entirely from pieces you already understand.

Why bother with this boundary at all? Because it is what makes large software possible. The team behind the API can rewrite their database, switch languages, scale to a thousand machines, and as long as the contract holds, every client keeps working untouched. And one API can serve a web app, a phone app, a partner company, and a script you wrote this morning, all of them strangers to the implementation, all of them speaking the same published interface. The interface decouples the two sides so each can change independently. That decoupling is the entire economic reason APIs exist.

Connects to Chapter 01

Remember the local function call we lost when we crossed the machine boundary? The API is how we get a disciplined version of it back. A documented endpoint is a function signature you can call over the network. The contract restores the predictability that separation took away.

Chapter 10·The dominant style

REST and its rationale #

An API is a set of HTTP conversations, but HTTP alone does not tell you how to organize them. You could name your endpoints anything. REST is the most common set of conventions for organizing them well, and almost every API you meet calls itself "RESTful," so it pays to understand not just the rules but why they were chosen.

REST (Representational State Transfer) was described by Roy Fielding in his 2000 dissertation, Chapter 5. It is not a standard you implement; it is an architectural style, a set of constraints that, taken together, produce systems with the properties the web needs: scale, simplicity, and independent evolution of client and server. The core idea is small and elegant.

The central move: resources and representations

REST says: model your system as resources, the nouns your system is about (a user, an order, a photo), each named by a URL. Then use HTTP's existing verbs to act on those nouns. You do not invent an action for everything. You expose nouns and let the standard methods supply the verbs:

GET    /users        list users
POST   /users        create a user
GET    /users/42     read user 42
PUT    /users/42     replace user 42
PATCH  /users/42     update part of user 42
DELETE /users/42     delete user 42

Notice how much this gives you for free. The URL is a noun. The method is the verb. Anyone who knows HTTP can guess most of your API from one example, because the verbs already have fixed meanings (Chapter 04) and you reused them instead of inventing /createUser, /deleteUser, /getUserById. The contract becomes predictable, which is the whole point of a contract.

A representation is the concrete form of a resource as it travels, almost always JSON today. The resource "user 42" is an abstract thing in the server's database; the JSON object you receive is a representation of its current state. You read a representation with GET and send a new one with PUT or PATCH. The name "Representational State Transfer" is literally describing this: you transfer representations of resource state back and forth.

The constraints that earn the properties

  • Client/server: separate the two so each evolves independently. (Chapter 09's decoupling.)
  • Stateless: each request carries everything needed to understand it; the server keeps no per-client session. This is what lets the server scale horizontally to many identical machines. (Chapter 03.)
  • Uniform interface: the same small set of methods and the resource/URL convention everywhere, so clients can be generic. This is the constraint that makes REST feel consistent.
  • Cacheable: responses say whether they may be cached, so reads scale cheaply. (This is why GET being safe matters.)
  • Layered: a client cannot tell whether it talks to the real server or an intermediary like a load balancer or cache, which lets you insert those freely.
Why these and not others

Every REST constraint trades a little freedom for a system-wide property. Statelessness costs you re-sent context but buys effortless scaling. A uniform interface costs you bespoke convenience but buys generic, reusable clients and caches. REST is the web's answer to "what rules make a distributed system stay simple as it grows?" The constraints are the answer, and each one pays for itself.

REST is dominant but not the only style. GraphQL lets clients ask for exactly the fields they want from one endpoint, trading REST's uniformity for query flexibility. gRPC optimizes for fast service-to-service calls with binary payloads. Knowing REST's rationale lets you judge when a different trade is worth it, rather than cargo-culting whatever is fashionable.

Chapter 11·Putting it together

Designing an API #

This chapter turns the principles into the handful of decisions you actually make when designing or reading an API. None of it is new; it is the earlier chapters applied with judgment.

Name resources as nouns, plural and hierarchical

Use /users and /users/42, not /getUser. Nest to express relationships: /users/42/orders is "the orders belonging to user 42." Let the HTTP method, not the URL, carry the verb. If you feel the urge to put a verb in a path, first ask whether you are missing a resource; often POST /users/42/orders expresses what /placeOrder was trying to say, more cleanly.

Choose the right method, and mean it

Reads are GET. Creation is POST. Wholesale replacement is PUT. Partial update is PATCH. Removal is DELETE. Honor the safe and idempotent promises from Chapter 04, because clients, caches, and retries depend on them. An endpoint that violates its method's contract is a latent bug waiting for a retry or a crawler to trigger it.

Use status codes faithfully

Return 201 on creation, 200 on a successful read, 204 when there is deliberately no body, 400 for bad input, 401/403 for auth, 404 for missing, 429 when rate-limiting, 5xx only when the server itself failed. Do not return 200 with an error message in the body; that forces every client to parse prose to learn what a status code would have told them instantly. The status code is the verdict; let it do its job.

Speak JSON, consistently

Use application/json for bodies and set the Content-Type header to match. Keep field names and shapes consistent across endpoints. Consistency is a feature: a client that learns one endpoint should be able to predict the next.

Authenticate with a token in the Authorization header

Because HTTP is stateless, identity rides on each request, conventionally Authorization: Bearer <token>. The two patterns you will meet most:

  • API keys: a long secret string issued to a developer, sent on each request. Simple, good for server-to-server.
  • OAuth 2.0 tokens: a standard flow where a user grants an app limited access without handing over their password, yielding a short-lived bearer token. This is the "Log in with Google" machinery. The standard is RFC 6749, with a readable overview at oauth.net.
Always over TLS

A bearer token is a password-equivalent: whoever holds it can act as you. Sending one over plain HTTP exposes it to anyone on the path. Credentials travel only over HTTPS. No exceptions. (Chapter 02's TLS is not optional here.)

Design for change: versioning and pagination

Your contract will need to evolve, so plan for it. Version your API (commonly a prefix like /v1/users) so you can ship breaking changes as /v2 without breaking existing clients, which is the decoupling promise from Chapter 09 made concrete. And never return an unbounded list; paginate with parameters like ?limit=20&offset=40 or a cursor, so a request for "all users" cannot accidentally try to send a million records in one response.

Chapter 12·Seeing it whole

End-to-end walkthrough #

One concrete session ties every chapter together. Imagine a small API for a notes app at https://api.notes.example. We will read, create, update, and delete, watching the methods, headers, bodies, and status codes do exactly what the earlier chapters predicted. These are illustrative; the public jsonplaceholder.typicode.com mirrors the same shapes if you want to run real ones.

1. Authenticate, then read a list

Every request carries the token. This first one lists notes; it is a GET, so it is safe and its filters live in the query string.

curl https://api.notes.example/v1/notes?limit=2 \
  -H "Authorization: Bearer TOKEN123" \
  -H "Accept: application/json"
HTTP/1.1 200 OK
Content-Type: application/json

[{"id": 1, "title": "Buy coffee"},
 {"id": 2, "title": "Call Ada"}]

200, a JSON array, exactly as the contract promises for a successful read.

2. Create a note

Creation is a POST with a JSON body. The Content-Type tells the server how to read the body.

curl -X POST https://api.notes.example/v1/notes \
  -H "Authorization: Bearer TOKEN123" \
  -H "Content-Type: application/json" \
  -d '{"title": "Renew passport"}'
HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/notes/3

{"id": 3, "title": "Renew passport"}

201, not 200, because something new exists. The server assigned id: 3 and even told us its address in the Location header. The client did not choose the ID; the server owns that.

3. Update part of it

Changing one field is a PATCH (partial update), carrying only what changed.

curl -X PATCH https://api.notes.example/v1/notes/3 \
  -H "Authorization: Bearer TOKEN123" \
  -H "Content-Type: application/json" \
  -d '{"title": "Renew passport before June"}'
HTTP/1.1 200 OK

{"id": 3, "title": "Renew passport before June"}

4. Delete it

curl -X DELETE https://api.notes.example/v1/notes/3 \
  -H "Authorization: Bearer TOKEN123"
HTTP/1.1 204 No Content

204: success with no body, because there is nothing left to return. DELETE is idempotent, so if the network drops and you retry, deleting note 3 again is harmless; you will get 204 or 404, never a duplicate-deletion disaster.

5. Watch a failure

Read the note we just deleted and the contract reports the truth:

curl https://api.notes.example/v1/notes/3 \
  -H "Authorization: Bearer TOKEN123"
HTTP/1.1 404 Not Found

{"error": "note not found"}
Everything is here

That one session used: TLS (the https), a stateless token on every request, the resource/URL convention, four verbs honoring their contracts, content negotiation via Accept and Content-Type, JSON representations, and the full range of status codes assigning success and blame. There is no further secret. This is what working with an API looks like, end to end.

Chapter 13·Keep these

Mental models worth keeping #

If you forget the details, keep these. Each one compresses a whole chapter into something you can carry.

An API is a function call across a trust and machine boundary
Everything HTTP adds is the cost of crossing that boundary: serialization, a shared format, addressing, failure handling, and per-request identity. Know what you lose versus a local call.
HTTP is a text request-response conversation
Start line, headers, blank line, body, on both sides. Every library on earth just generates and parses these bytes.
The URL is the noun, the method is the verb
Resources get addresses; the small fixed set of methods supplies the actions. This is why a RESTful API is predictable from one example.
Safe and idempotent are promises clients build on
GET changes nothing and can be cached and repeated. Idempotent methods can be retried after a network failure. Honor these or retries and caches will hurt you.
Statelessness is the price of scale
The server forgets you between requests, so you re-send context and credentials each time. In exchange it can run as a thousand identical machines.
The status code is the verdict; trust the first digit
2xx worked, 4xx your fault (fix the request), 5xx their fault (consider a retry). Do not bury errors in a 200 body.
The one-sentence summary

An API is a server publishing a documented set of HTTP conversations it promises to honor; HTTP is the shared rules for those conversations; the rules exist because two programs on different machines can only exchange bytes and must agree in advance on what those bytes mean.

Chapter 14·Go to the source

Primary sources #

When a study note is no longer enough, these are the authorities, not blog posts about them. Read the source.

  • HTTP semantics, the ground truth: RFC 9110. Methods, status codes, headers, all defined precisely. RFC 9112 covers the on-the-wire HTTP/1.1 message format.
  • The most readable working reference: MDN Web Docs, HTTP. Methods, headers, status codes, and CORS, kept current and example-rich.
  • URLs: RFC 3986, the syntax of URIs, including the query string and percent-encoding.
  • TCP and TLS, the layers below: RFC 9293 (TCP) and RFC 8446 (TLS 1.3).
  • REST, from the source: Roy Fielding's dissertation, Chapter 5. The original argument, still the clearest.
  • Authentication: OAuth 2.0 at RFC 6749 and the friendlier oauth.net; idempotency in practice via Stripe's docs.
  • The status code registry: IANA, the complete authoritative list.
  • Alternatives to REST: GraphQL and gRPC, for when REST's trade-offs are not the ones you want.
A study suggestion

The fastest way to make all of this permanent is to install curl, point it at a free public API like https://jsonplaceholder.typicode.com, and run a GET, a POST, and a DELETE with -v so you watch the raw bytes from Chapter 03 scroll past. Reading about the request-response conversation is one thing; seeing your own request and its response on the wire makes it yours.