# REST APIs, Explained

> The mental model behind the web's dominant API style - things live at URLs, HTTP methods are the verbs you apply to them, and every request stands on its own.


---

# REST APIs, Explained

You've called REST APIs. You've sent a `GET` to fetch some users, `POST`ed a form, maybe seen a `404`
and sighed. But "REST" itself has probably stayed a fuzzy word - something everyone says, nobody
defines, and that somehow describes most of the APIs you'll ever touch.

Here's the relief: REST is not a framework, a library, or a magic protocol. It's a small set of ideas
about how to lay an API out - *things live at addresses, and you act on them with a fixed handful of
verbs.* Once those ideas click, you can read almost any web API on sight, and design one that other
people can read too.

## How to read this

- **Need a quick reference?** Phase 1 has the resource-and-method grid, and Phase 2 has the status-code
  and convention cheat sheet - both are scannable on their own.
- **Want REST to finally make sense?** Read in order. Phase 1 installs the mental model, Phase 2 turns
  it into real endpoints, and Phase 3 tells you the plain truth about where REST strains.

## The phases

1. **[Resources & Verbs](01-resources-and-verbs.md)** - the core mental model: resources live at URLs,
   HTTP methods are the verbs, and every request is self-contained (stateless).
2. **[Designing Endpoints](02-designing-endpoints.md)** - the practical conventions: nouns not verbs,
   collections vs. items, meaningful status codes, and query params for filtering, sorting, and paging.
3. **[REST in the Real World](03-rest-in-the-real-world.md)** - the clear-eyed part: REST is a *style*, not
   a law, and the pain points (over-fetching, round trips, versioning) that lead people to other tools.

> This guide stays on the dominant request/response style. GraphQL - a different answer to REST's
> pain points - gets its own home in [GraphQL, Explained](/guides/graphql-explained), and the deeper
> craft of evolving an API safely lives in [Designing APIs That Last](/guides/designing-apis-that-last).


---

# Resources & Verbs - The REST Mental Model

The word "REST" gets thrown around like it's a piece of technology you install. It isn't. REST is a way
of *thinking* about an API, and it rests on a surprisingly small foundation. Learn these three ideas and
you'll be able to look at an unfamiliar API and predict how it works before reading a line of its docs.

The three ideas:

1. **Resources** - the "things" your API is about - each live at a URL.
2. **HTTP methods** are the **verbs** - the small fixed set of actions you apply to those things.
3. **Statelessness** - every request carries everything the server needs to handle it, on its own.

Let's install them one at a time.

## 1. A resource is a "thing" that lives at an address

A *resource* is any noun your API cares about: a user, an order, a blog post, a photo. REST's first move
is to give every one of those things its own address - a URL. The URL is the thing's name and its
location, both at once.

📝 **Terminology - resource.** A resource is the conceptual "thing" (a particular user). The URL
(`/users/42`) is how you refer to it. The actual bytes you get back (the JSON describing that user) are a
*representation* of the resource - one snapshot of it, in one format.

There are two flavors of address, and the difference matters:

```text
   /users          ← a COLLECTION: "all the users" (the whole shelf)
   /users/42       ← an ITEM:       "the one user with id 42" (one book on the shelf)
```

A collection URL points at the group; an item URL points at one member of it, usually identified by an
ID. Almost every REST URL you'll ever see is one of these two shapes, sometimes nested
(`/users/42/orders` - "the orders belonging to user 42").

Coming from older code, people are tempted to put the *action* in the URL: `/getUser?id=42`,
`/createUser`, `/deleteUser`. That feels natural - it reads like a function call - but it throws away the
whole idea. In REST the URL names the *thing*, never the action. The action comes from the HTTP method,
which is the next idea.

## 2. HTTP methods are the verbs

HTTP already ships with a small set of verbs - `GET`, `POST`, `PUT`, `PATCH`, `DELETE`. REST's second
move is to use those as *the* actions on a resource. The URL says *which thing*; the method says *what to
do to it*. You don't invent new verbs; you reuse these five.

Here's the grid that, once it's in your head, lets you read most of REST. Pair a method with a URL and
the meaning is unambiguous:

```text
                 /users  (the collection)        /users/42  (one item)
              ┌─────────────────────────────┬─────────────────────────────┐
   GET        │ list all users              │ read user 42                │
   POST       │ create a new user           │ (rarely used on an item)    │
   PUT        │ (rarely used on collection) │ replace user 42 entirely    │
   PATCH      │ (rarely used on collection) │ update part of user 42      │
   DELETE     │ (rarely used on collection) │ remove user 42              │
              └─────────────────────────────┴─────────────────────────────┘

   read = GET   create = POST   replace = PUT   modify = PATCH   remove = DELETE
```

The natural pairings are the ones that map to the four things you do with data - create, read, update,
delete (often abbreviated **CRUD**): `POST` to a collection creates, `GET` reads, `PUT`/`PATCH` update,
`DELETE` removes.

Watch the same noun, `/articles`, do four different jobs purely by changing the verb:

```http
GET /articles/108 HTTP/1.1
Host: api.example.com
```
```http
HTTP/1.1 200 OK
Content-Type: application/json

{ "id": 108, "title": "Reading a Stack Trace", "published": true }
```
You asked to *read* the article at `/articles/108`. The server answered `200 OK` and handed back a
representation of it as JSON. `GET` only reads - it changed nothing on the server.

```http
POST /articles HTTP/1.1
Host: api.example.com
Content-Type: application/json

{ "title": "Untitled draft", "published": false }
```
```http
HTTP/1.1 201 Created
Location: /articles/109

{ "id": 109, "title": "Untitled draft", "published": false }
```
You `POST`ed to the *collection* `/articles` to create a new one. The server made the article, assigned
it the id `109`, and told you two things: the status `201 Created` (a new resource exists now) and a
`Location` header pointing at its fresh URL. You didn't choose the id - the server did.

### `PUT` vs. `PATCH` - the one that trips everyone up

This pair confuses almost everybody the first time, so here it is plainly. Both update an existing item;
the difference is *how much* you send.

- **`PUT` replaces the whole thing.** You send the complete resource, and the server overwrites it with
  exactly that. Any field you leave out is treated as "make it empty/gone," because you sent the *whole*
  new version.
- **`PATCH` changes only the parts you send.** You send just the fields you want to alter; everything
  else stays as it was.

```http
PATCH /articles/108 HTTP/1.1
Content-Type: application/json

{ "published": true }
```
```http
HTTP/1.1 200 OK

{ "id": 108, "title": "Reading a Stack Trace", "published": true }
```
You sent only `published`, and only that field changed - the `title` was untouched because `PATCH` means
"merge these changes in." Had you used `PUT` with that same one-field body, a strict server would read it
as "the article is now *only* `published: true`," wiping the title.

⚠️ **Gotcha - `PUT` with a partial body silently deletes fields.** This is the classic data-loss bug:
you mean to flip one flag, you reach for `PUT`, you send a small body, and fields you never mentioned get
blanked out because `PUT` means "replace everything." When you want a partial update, reach for `PATCH`.
Use `PUT` only when you genuinely intend to send the complete resource.

💡 **Key point - safe and idempotent.** Two properties explain a lot of REST's behavior. `GET` is
*safe*: it never changes server state, so it's fine to retry, cache, or prefetch. `PUT` and `DELETE` are
*idempotent*: doing them twice lands you in the same place as doing them once (deleting an already-deleted
thing still leaves it deleted). `POST` is neither - `POST` twice and you'll often create two records.
That's why a refreshed checkout page sometimes warns you about double-submitting.

## 3. Statelessness - every request stands on its own

*Stateless* means the server keeps no memory of your previous requests between calls. Each request must
carry everything the server needs to understand and authorize it - who you are, what you want, any data
involved. The server handles it and forgets you the moment it responds.

It's tempting to imagine the server "remembers you're logged in" the way a desktop program remembers you
opened a file. It doesn't. That's why nearly every request to a protected API re-sends proof of identity
 - typically a token in a header - *every single time:*

```http
GET /account/settings HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiInR5cCI6...
```
You re-presented your credentials on this request, because the server didn't retain them from your last
one. The token *is* the request's memory - it travels with the call instead of living on the server.

Statelessness sounds like extra work, but it's the reason REST APIs scale and stay debuggable. Because no
single server is holding "your session," any server behind a load balancer can answer any request - 
they're interchangeable. And because each request is self-contained, you can copy one into a tool like
`curl` or Postman and replay it in isolation to reproduce a bug.

## Recap

1. **Resources are the nouns** - the things your API is about - and each lives at a **URL**, either a
   *collection* (`/users`) or an *item* (`/users/42`).
2. **HTTP methods are the verbs** - `GET` read, `POST` create, `PUT` replace, `PATCH` partial-update,
   `DELETE` remove - and method + URL together name an unambiguous action.
3. **`PUT` replaces the whole resource; `PATCH` changes only what you send** - mixing them up silently
   deletes fields.
4. **Statelessness** means each request carries everything the server needs, so any server can answer it
   and any request can be replayed on its own.

With the mental model in place, the next phase turns it into endpoints you'd actually be proud to ship.


---

# Designing Endpoints - Conventions That Read Well

Knowing the mental model is one thing; laying out an API somebody else can use without reading a manual is
another. The good news is that REST has a well-worn set of conventions, and they're mostly common sense
once you see why each exists. Follow them and a stranger can guess your endpoints; ignore them and even
your own teammates will be grep-ing the source to find out what `/doUserThing` does.

This phase is the practical layer: how to name things, what to return, and how to handle the everyday
needs - filtering, sorting, and paging - that every real API runs into.

## The endpoint cheat sheet

> **Designing something now? Scan this, then read the section for the part you're unsure about.**

| You want to… | Do this |
|---|---|
| Name an endpoint | Use a **plural noun**: `/orders`, not `/getOrders` or `/order` (§1) |
| Act on one record vs. many | **Item** `/orders/42` vs. **collection** `/orders` (§1) |
| Say "it worked" | `200 OK` (read/update), `201 Created` (new), `204 No Content` (delete) (§2) |
| Say "you messed up" | `400` bad request, `401` not logged in, `403` not allowed, `404` not found (§2) |
| Say "we messed up" | `500` server error (§2) |
| Filter / sort / paginate a list | **Query params**: `?status=open&sort=-created&page=2` (§3) |

---

## 1. Name with nouns, and be consistent

A REST URL names a *thing*, and the HTTP method supplies the action - so the URL should be a noun, not a
verb. The verb is already in the method; repeating it in the path (`GET /getOrders`) is redundant and
breaks the pattern that makes APIs predictable.

```text
   ❌ verb-in-URL (don't)         ✅ noun + method (do)
   GET  /getAllOrders            GET    /orders
   POST /createOrder             POST   /orders
   GET  /getOrderById?id=42      GET    /orders/42
   POST /updateOrder             PATCH  /orders/42
   POST /deleteOrder?id=42       DELETE /orders/42
```

Three conventions make the noun style click:

- **Plural for collections.** Prefer `/orders` over `/order`. Then `/orders` reads as "the orders" and
  `/orders/42` as "order 42" - one consistent rule instead of guessing singular vs. plural per endpoint.
- **Nest to show ownership.** `/users/42/orders` means "the orders belonging to user 42." Nest one level
  for a clear parent-child relationship; resist nesting three or four deep - it gets unwieldy fast, and
  usually `/orders?user=42` reads better past one level.
- **Lowercase, hyphenated, no file extensions.** `/blog-posts`, not `/BlogPosts` or `/blog_posts.json`.

💡 **Key point.** Consistency beats cleverness. An API where *every* collection is a plural noun and
*every* item is `/collection/{id}` is one a developer can navigate by guessing. Each special-case
exception is a thing they now have to look up.

## 2. Return status codes that actually mean something

Every HTTP response carries a three-digit *status code* that tells the caller, at a glance, how it went.
The number isn't decoration - clients branch on it. Returning the *right* one is part of your API's
contract, not an afterthought.

📝 **Terminology - the families.** The first digit tells the whole story: **2xx** = it worked, **3xx** =
go somewhere else (redirects), **4xx** = the *caller* did something wrong, **5xx** = the *server* did.
That single digit is enough to know whose problem it is.

Here are the ones you'll reach for constantly:

```text
   2xx  success
     200 OK            standard success (a GET that found data, a PATCH that worked)
     201 Created       a POST made a new resource (return its Location)
     204 No Content    success, nothing to send back (a DELETE that worked)

   4xx  the caller's fault
     400 Bad Request   the request body/params are malformed or invalid
     401 Unauthorized  you didn't prove who you are (missing/bad credentials)
     403 Forbidden     we know who you are; you're not allowed to do this
     404 Not Found     no resource at this URL
     409 Conflict      the request clashes with current state (e.g. duplicate)

   5xx  the server's fault
     500 Internal Server Error   something blew up on our side
```

⚠️ **Gotcha - `401` vs. `403`.** They feel interchangeable; they're not. `401 Unauthorized` actually
means *unauthenticated* - "I don't know who you are, log in." `403 Forbidden` means *authenticated but not
permitted* - "I know exactly who you are, and you still can't touch this." Sending `403` for a missing
login tells the client to fix the wrong thing.

A delete, done right:

```http
DELETE /orders/42 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiInR5cCI6...
```
```http
HTTP/1.1 204 No Content
```
The order was removed, and the server returned `204 No Content` - success, with an empty body because
there's nothing meaningful to send back about a thing that no longer exists. The caller reads `204` and
knows the delete worked without having to parse anything.

And an error, done right - note that a good `4xx` *explains itself* in the body:

```http
POST /orders HTTP/1.1
Content-Type: application/json

{ "items": [] }
```
```http
HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "validation_failed",
  "message": "An order must contain at least one item.",
  "field": "items"
}
```
The server rejected the empty order with `400` *and* a JSON body naming what was wrong and where. The
status code tells the client's code how to branch; the message tells the human reading the logs what to
fix. Returning `400` with a blank body is technically correct and practically useless.

## 3. Query params for filtering, sorting, and pagination

When you `GET` a collection, you rarely want *all* of it in *any* order. The convention is to shape the
result with **query parameters** - the `?key=value` pairs after the URL. The path still names the
collection; the query refines which slice you get and how it's arranged.

📝 **Terminology - query string.** Everything after the `?` in a URL is the *query string*:
`?status=open&sort=-created&page=2` is three parameters (`status`, `sort`, `page`) joined by `&`. They're
for *narrowing or shaping* a read, not for identifying the resource - that's the path's job.

Three jobs, three families of params:

- **Filtering** - narrow the set: `?status=open`, `?author=42`, `?created_after=2026-01-01`.
- **Sorting** - order the set: `?sort=created` (ascending) or `?sort=-created` (a leading `-` for
  descending is a common convention).
- **Pagination** - return one page at a time so you don't dump a million rows: `?page=2&per_page=25`.

"Give me the second page of open orders, newest first, 25 per page":

```http
GET /orders?status=open&sort=-created&page=2&per_page=25 HTTP/1.1
Host: api.example.com
```
```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": [
    { "id": 1180, "status": "open", "created": "2026-06-18T09:12:00Z" },
    { "id": 1179, "status": "open", "created": "2026-06-18T08:55:00Z" }
  ],
  "page": 2,
  "per_page": 25,
  "total": 312
}
```
The path `/orders` named the collection; the query string did the rest - filtered to `open`, sorted
newest-first, and returned page 2. The server wrapped the list in an envelope with paging info (`page`,
`per_page`, `total`) so the client knows there are 312 matches and can build "page 13 of 13." Returning a
bare array instead leaves the client blind to how much more there is.

⚠️ **Gotcha - always paginate list endpoints from day one.** It's tempting to return the whole collection
while it's small. Then the table grows, one `GET /orders` tries to serialize a hundred thousand rows, and
the endpoint times out - for *every* caller at once. Bolting pagination on later is a breaking change to
everyone using it. Build it in before you need it; a default like `per_page=25` costs nothing early and
saves an outage later.

💡 **Key point - path identifies, query refines.** If a value picks out *which resource* you mean, it
belongs in the path (`/orders/42`). If it *shapes a read* of a collection - filter, sort, page - it
belongs in the query string. Keeping that line clean is most of what makes an API feel coherent.

## Recap

1. **Name with plural nouns** (`/orders`, `/orders/42`); the HTTP method is the verb, so never put the
   action in the URL.
2. **Return meaningful status codes** - `200`/`201`/`204` for success, `400`/`401`/`403`/`404` for caller
   errors, `500` for yours - and explain `4xx` errors in the body.
3. **`401` is "log in"; `403` is "you're logged in but not allowed."**
4. **Use query params** for filtering (`?status=open`), sorting (`?sort=-created`), and pagination
   (`?page=2`) - and paginate list endpoints from the start.
5. **Path identifies the resource; query string refines a read of it.**

You can now design endpoints that read cleanly. The last phase steps back and tells you the plain truth:
where this style holds up, and where it starts to hurt.


---

# REST in the Real World - Where It Bends and Where It Breaks

If you've read the first two phases, you might now be worried that some API you use daily isn't "really
REST" - it has a `/search` endpoint, or a `POST /orders/42/cancel` that's clearly a verb. Relax. That
instinct is healthy, and the answer is freeing: almost no production API is textbook-pure REST, and that's
fine. This phase is the clear-eyed conversation about what REST is in practice, and the genuine pain points
that make teams reach for other tools.

## REST is a style, not a law

REST was described in 2000 by Roy Fielding as an *architectural style* - a set of constraints, not a
specification you can fail a compliance test against. (source:
https://ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm) There's no committee that certifies
an API "RESTful." So in everyday speech, "REST API" has come to mean something looser and more practical:
*an HTTP API organized around resources and methods, returning JSON, using status codes sensibly.* That's
the API you'll build and consume 95% of the time.

Newcomers sometimes treat the conventions from Phase 2 as sacred rules and feel guilty breaking them. But
real apps have needs that don't map cleanly onto five verbs and a noun. Two common, *accepted* pragmatic
deviations:

- **Actions that aren't CRUD.** "Cancel this order," "publish this draft," "send this email" are verbs,
  not nouns. The pragmatic move is a sub-resource that reads as an action:
  `POST /orders/42/cancellation` or, very commonly, `POST /orders/42/cancel`. Purists wince; teams ship
  it; the world keeps turning.
- **Search.** Complex search rarely fits filter params, so a dedicated `GET /search?q=...` (or even a
  `POST /search` with a query body) is standard and nobody objects.

💡 **Key point.** The conventions exist to make APIs *predictable*, not to win an argument. Follow them by
default because predictability is valuable; break them deliberately when a real need doesn't fit, and your
API will still be perfectly good. "Pragmatic REST" is the norm, not a compromise to apologize for.

⚠️ **Gotcha - but don't tunnel actions through `GET`.** There's one line you should *not* cross casually.
A `GET` is supposed to be *safe* - read-only, retryable, cacheable (see Phase 1). The moment you make a
`GET` change data - `GET /orders/42/delete`, or a `GET` that charges a card - you've broken a promise the
entire web relies on. Browsers prefetch links, proxies cache `GET`s, crawlers follow them, and a
"retry on timeout" will happily fire it twice. People have wiped their own data because an admin tool put
a destructive action behind a `GET` link that something prefetched. Anything that changes state goes
behind `POST`/`PUT`/`PATCH`/`DELETE`. Bend the noun rules freely; never bend this one.

## Pain point 1: over-fetching and under-fetching

This is the most-felt limitation of resource-shaped APIs, and it has two faces.

**Over-fetching** - you ask for a resource and get *more* than you need. Your mobile screen only shows a
user's name and avatar, but the resource hands you the whole record:

```http
GET /users/42 HTTP/1.1
```
```http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 42,
  "name": "Dana Okoro",
  "avatar_url": "https://cdn.example.com/a/42.png",
  "email": "dana@example.com",
  "bio": "...a few paragraphs...",
  "preferences": { "...": "..." },
  "billing_address": { "...": "..." },
  "created": "2023-02-11T00:00:00Z"
}
```
You needed two fields and the server sent ten. Each endpoint returns a *fixed* representation, so it
ships the full record to everyone - wasting bandwidth, which stings most on slow mobile connections.

**Under-fetching** - the opposite: one resource isn't enough, so you make several calls. To render
"a user and the titles of their last 5 orders," the user resource doesn't include orders, so:

```mermaid
sequenceDiagram
  participant Client
  participant API
  Client->>API: GET /users/42
  API-->>Client: the user
  Client->>API: GET /users/42/orders
  API-->>Client: their 5 orders
  loop one round trip per order - the "N+1 requests" problem
    Client->>API: GET /orders/{id}
    API-->>Client: that order's details
  end
```

One screen became seven sequential requests, each paying its own network round trip. On a fast connection
it's invisible; on a phone in a tunnel, those round trips stack up into a visibly slow screen. This is the
**"chatty API" / N+1 requests** problem.

There's tension here, too: you can fight over-fetching by making resources skinnier, but that makes
under-fetching worse (more calls to assemble a page), and you can fight under-fetching by fattening
resources, which worsens over-fetching. Resource-shaped APIs make you pick a point on that spectrum for
*everyone*.

> This exact pain - the client wanting to ask for precisely the fields and relationships it needs, in one
> request - is the problem [GraphQL, Explained](/guides/graphql-explained) was built to solve. It's worth
> understanding REST's limitation first, because it's *why* GraphQL exists.

## Pain point 2: versioning

APIs are contracts, and contracts change. The day you rename a field, split an endpoint, or make an
optional param required, every existing client that relied on the old shape can break - and you often
can't update those clients (they're mobile apps in the wild, third-party integrations, scripts you've
never heard of). You need to evolve without pulling the rug out.

The common, blunt tool is to put a version in the URL and run the old and new shapes side by side:

```http
GET /v1/users/42 HTTP/1.1
```
```http
GET /v2/users/42 HTTP/1.1
```
`/v1` keeps serving the old shape to old clients while `/v2` ships the new one. Nobody breaks; you
migrate callers over time, then retire `/v1` once it's quiet. (Some teams version via a header instead of
the URL - same idea, different placement.) It works, but every live version is code you maintain, test,
and can't delete, so versions are expensive and you want as few as possible.

The deeper craft here - how to change an API *without* needing a new version most of the time (additive
changes, deprecation policies, tolerant readers) - is a whole discipline of its own.

> That discipline is exactly the subject of
> [Designing APIs That Last](/guides/designing-apis-that-last) - how to evolve a contract for years
> without breaking the people who depend on it.

## So when is REST the right call?

In short? Most of the time. REST's pain points are real, but so are its strengths, and for the common case
it's hard to beat:

```text
   REST shines when…                        REST strains when…
   ─────────────────────────────────        ─────────────────────────────────
   resources map cleanly to nouns           clients need wildly different
   (users, orders, posts)                   slices of the data per screen

   you want HTTP caching, proxies,          one screen needs data from many
   and tooling to "just work"               resources (chatty / N+1)

   many independent clients, each            mobile bandwidth is precious and
   doing simple CRUD                         over-fetching is costly

   you value predictability and a           the data is a deeply connected
   low learning curve                       graph you query many ways
```

REST is the dependable default - well-understood, tooled to death, and predictable. Reach for something
else when your pain points line up with the right-hand column, not because REST is "old." A boring,
consistent REST API that your whole team can read is worth more than a clever one nobody can predict.

## Recap

1. **REST is a style, not a law** - real APIs are *pragmatic*, with `/search` endpoints and action
   sub-resources, and that's normal.
2. **The one hard rule: never change state through `GET`** - safe methods get prefetched, cached, and
   retried.
3. **Over-fetching** (too much data per call) and **under-fetching** (too many calls per screen) are
   REST's core tension - and the reason [GraphQL](/guides/graphql-explained) exists.
4. **Versioning** (`/v1`, `/v2`) lets you evolve without breaking old clients, but every version is code
   you must maintain - [Designing APIs That Last](/guides/designing-apis-that-last) covers doing it well.
5. **REST is the right default for most APIs** - choose something else when your needs match the
   strain column, not out of fashion.

That's REST, plain and simple. You can now read an unfamiliar API on sight, design one others can navigate, and
recognize the moment its style stops serving you - which is exactly when the related guides pick up.

Watch it animated: [REST vs. GraphQL](/explainers/RESTvsGraphQL.dc.html)
