REST API Design & Authentication¶
REST isn't a spec, it's a vibe. Predictable conventions are the product.
The hook¶
REST won the API wars of the 2010s because the conventions were good enough and the tooling was everywhere. Curl, Postman, every language's HTTP library — they all spoke the same dialect.
Building a clean REST API is mostly about being predictable. Resources as nouns. Verbs as actions. Status codes as outcomes. Get the conventions right and integrations get easy. Get them wrong and every consumer pays the price — guessing endpoints, parsing weird errors, retrying the wrong way.
The good APIs feel obvious. That obviousness is the whole craft.
The concept¶
REST is a set of conventions for building HTTP APIs around resources. Five things matter:
- Resource-based URLs — paths are nouns.
/users/42/orders, not/getOrdersForUser?id=42. - HTTP verbs as actions — GET reads, POST creates, PUT replaces, PATCH updates, DELETE removes.
- Status codes as outcomes — 2xx success, 4xx the client did something wrong, 5xx the server did.
- Stateless — every request carries its own context. No server-side session juggling between calls.
- Hypermedia (HATEOAS) — responses link to related actions. Beautiful in theory, mostly skipped in practice.
If you nail the first four, you have a REST API people can use without reading much documentation. That's the bar.
Diagram¶
flowchart LR
A[/users/] -->|GET list, POST create| B[/users/{id}/]
B -->|GET, PUT, PATCH, DELETE| C[/users/{id}/orders/]
C -->|GET list, POST create| D[/users/{id}/orders/{order_id}/]
D -->|GET, PATCH, DELETE| D
The hierarchy reads top-down. Each level is a resource. The verbs at each arrow are the legal actions. A consumer who learns one branch can guess the rest.
Example — Stripe's API as the gold standard¶
If you're going to copy one API's conventions, copy Stripe's. It's the design template that has aged best — versioned, predictable, well-documented, and forgiving of network failures.
URL design
Versioned in the path (/v1/). Resources are plural nouns. Nesting reflects ownership — a charge belongs to a customer.
Verb usage
GET /v1/charges/{id} retrieves a charge. POST /v1/charges creates one. There's no DELETE on charges — money already moved. Instead, you POST /v1/refunds. That's a semantic choice: model the real-world action, don't pretend a charge can be erased.
Status codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad parameters (you sent garbage) |
| 401 | Bad API key |
| 402 | Payment required (card declined, etc.) |
| 404 | Resource doesn't exist |
| 429 | Rate limited — back off |
| 500 | Stripe broke — retry safely |
Idempotency keys
POST is not idempotent by default. Stripe lets clients send a header:
Same key, same request body, same response — even if you fire it ten times. The retry safely returns the original charge instead of creating ten. This is the move every payment-style API should copy.
Pagination
Cursor-based, not page-number-based:
Cursors don't break when records are inserted mid-iteration. Page numbers do.
Errors
Structured JSON, every time:
{
"error": {
"type": "card_error",
"code": "card_declined",
"message": "Your card was declined.",
"param": "source"
}
}
Consumers can branch on code programmatically and show message to humans. No guessing.
Mechanics¶
REST conventions
| Convention | Rule | Example |
|---|---|---|
| URL design | Plural nouns, nested by ownership | /users/42/orders/100 |
| GET | Safe + idempotent — read only, no side effects | GET /orders/100 |
| POST | Create; not idempotent (use idempotency keys for safety) | POST /orders |
| PUT | Full replace; idempotent | PUT /orders/100 |
| PATCH | Partial update; should be idempotent | PATCH /orders/100 |
| DELETE | Remove; idempotent (second delete returns 404 or 204) | DELETE /orders/100 |
| Status codes | 2xx success, 4xx client fault, 5xx server fault | 200, 201, 204, 400, 401, 404, 429, 500 |
| Versioning | URL path (Stripe, GitHub) or Accept header (more REST-pure, less common) |
/v1/... or Accept: application/vnd.api.v1+json |
Auth methods
| Method | How it works | When to use |
|---|---|---|
| API Key | Static key in a header (Authorization: Bearer your-api-key-here) |
Server-to-server, internal tools, simple SaaS |
| Bearer Token (OAuth/JWT) | Short-lived token issued by an auth flow; sent in Authorization header |
Public APIs, third-party access, user-delegated permissions |
| HTTP Basic | Authorization: Basic base64(user:password) |
Legacy systems, internal admin tools — only over TLS |
| HMAC signature | Client signs the request with a secret; server recomputes and compares | High-security APIs (AWS SigV4 style) — proves integrity, not just identity |
| mTLS | Mutual TLS — client presents a certificate, server verifies it | Service-to-service inside a trusted network, regulated environments |
Default: API key for B2B, OAuth/JWT for anything user-facing. Reach for HMAC or mTLS only when the threat model demands it.
Related concepts¶
| Concept | What it is | Relevance |
|---|---|---|
| HTTP fundamentals | Verbs, status codes, headers, statelessness | REST is the convention layer on top of HTTP. Can't do REST without HTTP fluency. |
| OAuth / JWT | Token-based delegated authentication | The default auth mechanism for any user-facing or third-party REST API. |
| API Gateway | Front door for APIs — auth, rate limiting, routing, transformation | Where you enforce cross-cutting REST concerns instead of duplicating in every service. |
| Rate limiting | Capping requests per client to protect the service | Pairs with REST's 429 status code. Every public API needs it. |
| Idempotency | Safe-to-retry semantics for non-idempotent operations | The Stripe-style Idempotency-Key is how you make POST safe across network failures. |
| API versioning | Path (/v1/) or header-based versioning |
Lets you evolve the API without breaking existing clients. |
| GraphQL | Single endpoint, client-specified queries | The main alternative to REST when nested fetches dominate the workload. |
| gRPC | Binary RPC over HTTP/2 with schema-first contracts | Service-to-service alternative when throughput and tight contracts matter more than browser tooling. |
When (and when not) to use REST¶
Use REST when:
- You're building a public-facing API — REST has the broadest tooling, docs, and developer familiarity
- You have a mix of clients (web, mobile, third-party integrators) — every language has a clean HTTP client
- Your domain is CRUD-heavy and maps cleanly to resources (users, orders, posts, files)
- You want maximum tooling support — Postman, OpenAPI, curl, every API gateway on the planet
Skip REST when:
- The frontend does heavy nested fetches (a screen needs user + posts + comments + author of each comment) — GraphQL collapses that into one round trip
- You need service-to-service at high throughput — gRPC's binary framing and HTTP/2 streams beat JSON over HTTP/1.1
- You need real-time bidirectional updates — WebSockets or Server-Sent Events, not REST polling
- Your domain is fundamentally action-oriented, not resource-oriented —
cancelSubscription,runPayroll,triggerExportmay be more honest as RPC than as forced-into-REST nouns
REST isn't always the right tool. But it's almost always the right default for an external-facing HTTP API in 2026.
Key takeaway¶
- REST is conventions, not a strict spec — the value is in being predictable to consumers
- Resources as nouns, verbs as actions, status codes as outcomes — get those three right and 80% of the design lands
- Idempotency keys on POST are the Stripe quality bar — copy them for any operation where retries cost money
- Cursor pagination beats page numbers once data starts moving
- HATEOAS sounds great, almost nobody ships it — don't feel bad about skipping it
- Auth: API key for internal/B2B, OAuth/JWT for user-facing — anything else needs a specific reason
Quiz available in the SLAM OG app — three questions on REST conventions, idempotency, and status code semantics.