REST APIs & Web Mechanics
Prerequisite:
Overview
REST (Representational State Transfer) is an architectural style for web APIs where resources are identified by URIs, manipulated via standard HTTP verbs, and represented in self-describing formats - almost always JSON. Stateless request-response semantics allow horizontal scaling; HTTP caching reduces load; authentication is handled via API keys, JWTs, or OAuth 2.0. Understanding REST mechanics makes you a better API designer and a faster debugger.
- Problem it solves: Before REST, RPC-style APIs (SOAP, XML-RPC) were complex and tightly coupled to implementation details; REST’s uniform interface enables decoupled, scalable, and widely understood API design.
- Alternatives: GraphQL for flexible client-specified queries; gRPC for high-performance binary RPC; tRPC for type-safe full-stack TypeScript APIs; WebSockets for bidirectional streaming.
- Pros: Simple, supported by every HTTP client; human-readable with
curl; easy to cache GET responses; no special client library needed. - Cons: Over-fetching and under-fetching are endemic; versioning REST APIs gracefully is hard; no standard for real-time updates.
HTTP Methods and Their Semantics
| Method | Meaning | Idempotent? | Safe? |
|---|---|---|---|
| GET | Read a resource | Yes | Yes |
| POST | Create a new resource | No | No |
| PUT | Replace a resource | Yes | No |
| PATCH | Partially update a resource | No | No |
| DELETE | Remove a resource | Yes | No |
Idempotent means calling the operation N times produces the same result as calling it once. GET, PUT, and DELETE are idempotent; POST is not - sending the same POST twice may create two records.
HTTP Status Codes
Know these cold:
| Code | Meaning |
|---|---|
| 200 OK | Success with a body |
| 201 Created | Resource was created (include Location header pointing to new resource) |
| 204 No Content | Success with no body (common for DELETE) |
| 400 Bad Request | Malformed request - the client sent something wrong |
| 401 Unauthorized | Not authenticated - provide valid credentials |
| 403 Forbidden | Authenticated but not authorized |
| 404 Not Found | Resource does not exist |
| 422 Unprocessable Entity | Structurally valid but semantically wrong (validation error) |
| 429 Too Many Requests | Rate limit exceeded |
| 500 Internal Server Error | The server failed - not the client’s fault |
| 502 Bad Gateway | Upstream service failed (reverse proxy got a bad response) |
| 503 Service Unavailable | Server is overloaded or in maintenance |
Resource-Oriented URL Design
REST organises APIs around resources (nouns), not actions (verbs):
# Good: resource-oriented
GET /users # list users
POST /users # create a user
GET /users/42 # get user 42
PUT /users/42 # replace user 42
PATCH /users/42 # update fields on user 42
DELETE /users/42 # delete user 42
GET /users/42/posts # list posts belonging to user 42
# Bad: action-oriented (RPC style)
GET /getUser?id=42
POST /createUser
POST /deleteUser?id=42
Authentication
API keys are the simplest mechanism - a long random token passed in the Authorization header:
Authorization: Bearer sk-abcdef123456
JWT (JSON Web Token) is a compact, self-contained token with three Base64URL-encoded parts separated by dots: header.payload.signature. The header declares the algorithm (HS256, RS256); the payload contains claims (sub, exp, iat, custom fields); the signature is computed over header + payload.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiI0MiIsImV4cCI6MTcyNTM5MzYwMH0
.xHsE8KfW2kKqbNn_abc123
The server verifies the signature without a database lookup - the token is stateless. Expiry (exp) is validated client-side by the receiving server.
OAuth 2.0 delegates authorization to an identity provider. The most common flow - Authorization Code - redirects the user to the IdP, which returns a short-lived code, which the app exchanges for an access token. Use this when your app acts on behalf of a user on a third-party service.
Rate Limiting and Backoff
Well-behaved APIs return 429 Too Many Requests with a Retry-After header. Well-behaved clients implement exponential backoff with jitter:
import time, random
def request_with_backoff(func, max_retries=5):
for attempt in range(max_retries):
response = func()
if response.status_code == 429:
wait = (2 ** attempt) + random.uniform(0, 1)
time.sleep(wait)
continue
return response
raise Exception("Max retries exceeded")
Pagination
Offset-based: GET /users?offset=100&limit=50. Simple but inconsistent if rows are inserted during pagination.
Cursor-based: GET /users?after=eyJpZCI6MTAwfQ&limit=50. The cursor encodes the position in the result set (often a Base64-encoded ID or timestamp). Consistent, efficient, and the right choice for high-volume feeds.
The response includes the next cursor:
{
"data": [...],
"next_cursor": "eyJpZCI6MTUwfQ",
"has_more": true
}
Python requests and httpx
import requests
# GET with query params and auth header
resp = requests.get(
"https://api.example.com/users",
params={"limit": 50, "after": cursor},
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
resp.raise_for_status() # raises HTTPError on 4xx/5xx
data = resp.json()
# POST with JSON body
resp = requests.post(
"https://api.example.com/users",
json={"name": "Megha", "email": "m@example.com"},
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
httpx is a modern alternative with async support:
import httpx, asyncio
async def fetch():
async with httpx.AsyncClient() as client:
resp = await client.get("https://api.example.com/users", timeout=10)
return resp.json()
Examples
Call a REST API with pagination:
import requests
def fetch_all_users(base_url, token):
users = []
cursor = None
while True:
params = {"limit": 100}
if cursor:
params["after"] = cursor
resp = requests.get(
f"{base_url}/users",
params=params,
headers={"Authorization": f"Bearer {token}"},
timeout=10,
)
resp.raise_for_status()
body = resp.json()
users.extend(body["data"])
if not body.get("has_more"):
break
cursor = body["next_cursor"]
return users
Design URL scheme for a blog API:
GET /posts # list posts (with ?tag=, ?author=, ?limit=)
POST /posts # create a post
GET /posts/{slug} # get a specific post by slug
PATCH /posts/{slug} # update title/body/tags
DELETE /posts/{slug} # delete a post
GET /posts/{slug}/comments # list comments on a post
POST /posts/{slug}/comments # add a comment
DELETE /posts/{slug}/comments/{id} # delete a specific comment
Inspect a raw HTTP exchange with curl:
curl -v -X POST https://api.example.com/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Megha", "email": "m@example.com"}'
# > shows request headers
# < shows response headers
# response body follows
REST is a set of constraints, not a specification. The discipline of resource-oriented design, correct status codes, and idempotent operations is what makes an API predictable and easy for clients to build on top of.
Read Next: