01 What is an API?
API stands for Application Programming Interface. It is a contract that lets two pieces of software talk to each other without either one needing to know how the other is built internally.
Think of a restaurant. You (the client) don't walk into the kitchen and cook. You tell the waiter what you want from a menu, the kitchen (the server) prepares it, and the waiter brings back exactly what you ordered. The menu is the API: a defined list of things you can ask for, and the format you must ask in.
Why APIs matter
๐ Decoupling
Frontend and backend evolve independently as long as the contract holds.
โป๏ธ Reusability
One API can serve a web app, an iOS app, a partner, and internal scripts.
๐ Security
You expose only specific operations โ never your database directly.
๐ Scalability
Services can be scaled, cached, and rate-limited independently.
An API is an abstraction boundary. It hides complexity and exposes capability.
02 Anatomy of an API Request
Every HTTP API call has the same five ingredients. Learn these and every API becomes readable.
| Part | What it is | Example |
|---|---|---|
| Method | The verb โ what action you want | GET POST |
| URL / Endpoint | The address of the resource | https://api.shop.com/v1/products/42 |
| Headers | Metadata: auth, content type, caching | Authorization: Bearer โฆ |
| Query params | Filters, pagination, sorting (in the URL) | ?page=2&limit=20&sort=price |
| Body | The payload you send (POST/PUT/PATCH) | {"name":"Laptop","price":999} |
A real request, decoded
POST /v1/products?notify=true HTTP/1.1
Host: api.shop.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
Accept: application/json
{
"name": "Mechanical Keyboard",
"price": 129.99,
"tags": ["hardware", "input"]
}
And the response
HTTP/1.1 201 Created
Content-Type: application/json
Location: /v1/products/1087
{
"id": 1087,
"name": "Mechanical Keyboard",
"price": 129.99,
"createdAt": "2025-01-14T09:22:31Z"
}
Idempotency matters: calling GET, PUT, or DELETE
twice should leave the system in the same state as calling it once. POST is not idempotent โ two calls usually create two records.
03 Types of APIs
APIs are classified in two useful ways: by architecture/protocol and by audience.
A. By architecture & protocol
REST
Resource-oriented, uses standard HTTP verbs and JSON. The default choice for most web APIs.
GraphQL
Single endpoint, client specifies exactly which fields it needs. Great for complex, nested data.
SOAP
XML-based, strict contract via WSDL. Still common in banking, healthcare, and legacy enterprise.
gRPC
Binary Protocol Buffers over HTTP/2. Very fast, strongly typed, ideal for internal microservices.
WebSocket
Persistent, full-duplex connection. Perfect for chat, live dashboards, collaborative editing.
Webhooks
Reverse APIs โ the server calls you when an event happens. No polling needed.
RPC / JSON-RPC
Call a remote function by name. Simple and direct, less resource-oriented than REST.
SSE (Server-Sent Events)
One-way streaming from server to client over plain HTTP. Simpler than WebSockets for feeds.
B. By audience
| Type | Who uses it | Example |
|---|---|---|
| Public / Open | Anyone, often with a key or free tier | OpenWeather, Stripe, GitHub |
| Partner | Specific vetted businesses | Airline booking partners |
| Private / Internal | Only your own teams | Microservice-to-microservice |
| Composite | Aggregates several APIs into one | A dashboard mashing weather + maps + news |
Start with REST + JSON unless you have a specific reason not to. Choose GraphQL when clients need flexible queries, gRPC for high-performance internal calls, and WebSockets when you need real-time push.
04 HTTP Methods (Verbs)
REST maps actions onto standard HTTP methods. Using them correctly makes your API predictable.
| Method | Purpose | Idempotent? | Has body? | Example |
|---|---|---|---|---|
| GET | Read a resource or collection | Yes | No | GET /books |
| POST | Create a new resource | No | Yes | POST /books |
| PUT | Replace a resource entirely | Yes | Yes | PUT /books/7 |
| PATCH | Update part of a resource | No* | Yes | PATCH /books/7 |
| DELETE | Remove a resource | Yes | Rarely | DELETE /books/7 |
*PATCH can be made idempotent depending on the operations used.
Less-common but useful methods
HEADโ same as GET but returns only headers (check existence, size, or last-modified).OPTIONSโ asks what methods a resource supports. Browsers use it for CORS preflight.TRACEโ echoes the request back; almost never used, often disabled for security.CONNECTโ establishes a tunnel (e.g. HTTPS through a proxy).
05 HTTP Status Codes
The three-digit number in every response tells the client what happened โ without reading the body.
| Range | Meaning | Common codes |
|---|---|---|
| 1xx | Informational | 101 Switching Protocols |
| 2xx | Success | 200 OK, 201 Created, 202 Accepted, 204 No Content |
| 3xx | Redirection | 301 Moved Permanently, 304 Not Modified |
| 4xx | Client error โ you messed up | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity, 429 Too Many Requests |
| 5xx | Server error โ they messed up | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout |
401 Unauthorized actually means "unauthenticated" (we don't know who you are). 403 Forbidden means "authenticated but not allowed" (we know who you are, and no).
Returning errors consistently
{
"error": {
"code": "VALIDATION_FAILED",
"message": "The 'price' field must be a positive number.",
"field": "price",
"requestId": "req_8f2a91c"
}
}
A great error tells the client what went wrong, where, and how to fix it โ without leaking stack traces or database internals.
06 Authentication & Authorization
Authentication = who are you? Authorization = what are you allowed to do?
๐ API Keys
Simple shared secret sent in a header. Easy to start, hard to rotate, no user context.
๐งพ Basic Auth
Base64 of user:password. Only acceptable over HTTPS. Legacy but simple.
๐ซ JWT (Bearer)
Signed, self-contained token with claims. Stateless โ great for distributed systems.
๐ OAuth 2.0
Delegated access without sharing passwords. "Sign in with Google" is built on this.
๐ชช OIDC
OAuth 2.0 plus an identity layer. Adds a standard id_token with user info.
๐ mTLS
Both client and server present certificates. Common in zero-trust internal networks.
Sending credentials
# API key (header)
X-API-Key: sk_live_9f8a7b6c5d
# Bearer token (JWT or OAuth access token)
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# HTTP Basic
Authorization: Basic dXNlcjpwYXNzd29yZA==
The OAuth 2.0 Authorization Code flow
JWT structure โ three Base64 parts
header.payload.signature
# Decoded payload example
{
"sub": "user_42",
"scope": "read:books write:books",
"iat": 1705312800,
"exp": 1705316400
}
Never put secrets in client-side JavaScript, query strings, or a public Git repo. Query strings get logged by proxies and browsers. Ship secrets only in server-side environment variables.
07 How to Build an API
Here is the end-to-end process, followed by two complete working examples.
The 7-step process
- Define the use cases. What will clients actually need to do? List them as user stories.
- Model the resources. Nouns become endpoints:
/users,/orders,/orders/42/items. - Design the contract. Choose URLs, methods, request/response shapes, and error formats. Write it down (OpenAPI/Swagger).
- Pick a stack. Node/Express, Python/FastAPI, Go/Fiber, Java/Spring, Ruby/Rails โ any is fine.
- Implement + validate. Parse and validate every input. Never trust the client.
- Add cross-cutting concerns. Auth, rate limiting, logging, error handling, CORS, versioning.
- Document, test, deploy, monitor. Automate tests, ship behind HTTPS, watch latency and error rates.
Example A โ Node.js + Express
server.jsconst express = require('express');
const app = express();
app.use(express.json()); // parse JSON bodies
// In-memory "database" for the demo
let books = [
{ id: 1, title: 'The Pragmatic Programmer', author: 'Hunt & Thomas' }
];
let nextId = 2;
// ---------- READ ALL ----------
app.get('/api/books', (req, res) => {
res.json({ data: books, count: books.length });
});
// ---------- READ ONE ----------
app.get('/api/books/:id', (req, res) => {
const book = books.find(b => b.id === Number(req.params.id));
if (!book) {
return res.status(404).json({ error: 'Book not found' });
}
res.json(book);
});
// ---------- CREATE ----------
app.post('/api/books', (req, res) => {
const { title, author } = req.body;
// Validation: never trust the client
if (!title || typeof title !== 'string') {
return res.status(400).json({ error: 'title is required' });
}
const book = { id: nextId++, title, author: author || 'Unknown' };
books.push(book);
res.status(201).json(book);
});
// ---------- UPDATE (partial) ----------
app.patch('/api/books/:id', (req, res) => {
const book = books.find(b => b.id === Number(req.params.id));
if (!book) return res.status(404).json({ error: 'Book not found' });
Object.assign(book, req.body);
res.json(book);
});
// ---------- DELETE ----------
app.delete('/api/books/:id', (req, res) => {
const before = books.length;
books = books.filter(b => b.id !== Number(req.params.id));
if (books.length === before) {
return res.status(404).json({ error: 'Book not found' });
}
res.status(204).send();
});
app.listen(3000, () => console.log('API running on http://localhost:3000'));
Example B โ Python + FastAPI
main.pyfrom fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(title="Books API", version="1.0")
class BookIn(BaseModel):
title: str
author: str = "Unknown"
class Book(BookIn):
id: int
books: list[Book] = [Book(id=1, title="Dune", author="Herbert")]
@app.get("/api/books")
def list_books():
return {"data": books, "count": len(books)}
@app.get("/api/books/{book_id}")
def get_book(book_id: int):
for b in books:
if b.id == book_id:
return b
raise HTTPException(status_code=404, detail="Book not found")
@app.post("/api/books", status_code=201)
def create_book(payload: BookIn):
new_id = max((b.id for b in books), default=0) + 1
book = Book(id=new_id, **payload.model_dump())
books.append(book)
return book
# Run with: uvicorn main:app --reload
# Free interactive docs at http://localhost:8000/docs
FastAPI generates OpenAPI documentation automatically from your type hints โ you get a live, clickable API console for free.
08 Designing Clean Endpoints
โ Do
- Use plural nouns for collections:
/users, not/getUser. - Use nesting for real ownership:
/users/42/orders. - Use query parameters for filtering, sorting, and pagination.
- Version your API from day one:
/v1/...or a custom header. - Return consistent envelopes for lists:
{ data: [...], meta: {...} }. - Use kebab-case for multi-word paths:
/order-items, not/orderItems.
โ Don't
- Put verbs in the URL:
/deleteUser/42โ useDELETE /users/42. - Return
200 OKwith an error message inside the body. - Expose raw database IDs or internal table names.
- Break existing clients โ add fields, don't rename or remove them without a new version.
- Nest more than two levels deep:
/users/1/orders/2/items/3/reviewsis a code smell.
Pagination, filtering, sorting
# Offset pagination (simple, good for small datasets)
GET /v1/products?page=3&limit=20
# Cursor pagination (stable & fast for large datasets)
GET /v1/products?limit=20&cursor=eyJpZCI6MTA4N30
# Filtering + sorting (minus = descending)
GET /v1/products?category=laptops&minPrice=500&sort=-createdAt
# Sparse fieldsets (return only what you need)
GET /v1/products?fields=id,name,price
{
"data": [ /* 20 items */ ],
"meta": {
"page": 3,
"limit": 20,
"total": 412,
"nextCursor": "eyJpZCI6MTEwN30"
}
}
API Design Maturity โ the Richardson Maturity Model
| Level | Name | What it adds |
|---|---|---|
| 0 | The Swamp of POX | One endpoint, one method (like XML-RPC over POST). |
| 1 | Resources | Distinct URLs per resource. |
| 2 | HTTP Verbs | Uses GET/POST/PUT/DELETE correctly + status codes. Most APIs live here. |
| 3 | Hypermedia (HATEOAS) | Responses include links to related actions. Rare in practice. |
09 How to Connect to an API
Consuming an API follows the same loop in every language: build a request โ send it โ check the status โ parse the body โ handle errors.
JavaScript โ fetch (built into browsers & Node 18+)
async function createBook() {
const response = await fetch('https://api.example.com/v1/books', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_TOKEN}`
},
body: JSON.stringify({ title: 'Clean Code', author: 'Robert Martin' })
});
// fetch does NOT throw on 4xx/5xx โ check manually
if (!response.ok) {
const detail = await response.text();
throw new Error(`HTTP ${response.status}: ${detail}`);
}
return response.json();
}
JavaScript โ axios (popular library)
import axios from 'axios';
const api = axios.create({
baseURL: 'https://api.example.com/v1',
timeout: 10000,
headers: { Authorization: `Bearer ${token}` }
});
try {
const { data } = await api.get('/books', { params: { page: 1, limit: 20 } });
console.log(data);
} catch (err) {
if (err.response) console.error('Server said:', err.response.status);
else if (err.request) console.error('No response received');
else console.error('Setup error:', err.message);
}
Python โ requests
import requests
response = requests.post(
"https://api.example.com/v1/books",
headers={"Authorization": f"Bearer {TOKEN}"},
json={"title": "Clean Code", "author": "Robert Martin"},
timeout=10,
)
response.raise_for_status() # throws on 4xx/5xx
book = response.json()
print(book["id"])
cURL โ the universal debugging tool
# GET with auth header
curl https://api.example.com/v1/books \
-H "Authorization: Bearer $TOKEN"
# POST with a JSON body (-i shows response headers)
curl -i -X POST https://api.example.com/v1/books \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"title":"Clean Code","author":"Robert Martin"}'
GraphQL โ one endpoint, exact fields
query {
user(id: "42") {
name
email
orders(last: 3) {
id
total
}
}
}
const res = await fetch('https://api.example.com/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify({ query, variables: { id: '42' } })
});
Handling rate limits gracefully
// Respect 429 and the Retry-After header
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(url, options);
if (res.status !== 429 && res.status < 500) return res;
const wait = Number(res.headers.get('Retry-After')) * 1000
|| 2 ** attempt * 500; // exponential backoff
await new Promise(r => setTimeout(r, wait));
}
throw new Error('Max retries exceeded');
}
Browsers block cross-origin requests unless the server sends
Access-Control-Allow-Origin. CORS is a browser rule โ server-to-server
calls (cURL, Python, Node) are unaffected. You must fix CORS on the server, not the client.
10 REST vs GraphQL vs SOAP vs gRPC
| Feature | REST | GraphQL | SOAP | gRPC |
|---|---|---|---|---|
| Format | JSON (usually) | JSON | XML | Protobuf (binary) |
| Transport | HTTP/1.1, HTTP/2 | HTTP | HTTP, SMTP, TCP | HTTP/2 |
| Endpoints | Many | One | Many | Many |
| Typing | Loose | Strong (schema) | Very strong (WSDL) | Very strong (.proto) |
| Over/under-fetching | Common problem | Solved | Verbose | Efficient |
| Caching | Excellent (HTTP) | Manual | Poor | Limited |
| Browser support | Native | Native | Poor | Needs grpc-web |
| Streaming | Limited | Subscriptions | No | Excellent (bidirectional) |
| Best for | Public APIs, CRUD | Complex UIs, mobile | Enterprise, legacy | Internal microservices |
Real systems mix these. It's completely normal to have a public REST API, gRPC between internal services, and GraphQL for the mobile client.
11 API Versioning
Every API changes. Versioning is how you evolve without breaking existing clients.
Four common strategies
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URI Path | GET /v1/books |
Obvious, easy to route, cache-friendly | Pollutes URLs; "v1" isn't a real resource |
| Query param | GET /books?version=1 |
Simple to add | Easy to forget; hurts caching |
| Custom header | X-API-Version: 1 |
Clean URLs | Invisible in a browser address bar |
| Content negotiation | Accept: application/vnd.shop.v1+json |
RESTful "correct" approach | Verbose; hard to test manually |
Breaking vs non-breaking changes
โ Safe (non-breaking)
Adding a new endpoint, adding an optional request field, adding a field to a response, adding a new enum value that clients can ignore.
โ Breaking
Renaming or removing fields, changing a type (string โ number), making an optional field required, changing URL structure, changing error codes.
Deprecation done right
Deprecation: Sat, 01 Mar 2025 00:00:00 GMT
Sunset: Tue, 01 Jul 2025 00:00:00 GMT
Link: <https://api.example.com/v2/books>; rel="successor-version"
- Announce the change in a changelog and email list months ahead.
- Log which clients still hit the old version so you can reach out.
- Run both versions in parallel for a deprecation window.
- Return the
Sunsetheader so clients know the deadline programmatically.
Use MAJOR.MINOR.PATCH โ bump MAJOR for breaking changes, MINOR for backward-compatible additions, PATCH for bug fixes. Most public APIs expose only the MAJOR number in the URL.
12 API Security Deep Dive
APIs are a top attack target because they often expose business logic and data directly. Here is what to defend against.
OWASP API Security Top 10 (2023)
| # | Risk | In plain English |
|---|---|---|
| API1 | Broken Object Level Authorization (BOLA) | User A can read User B's data by changing an ID in the URL. The #1 API risk. |
| API2 | Broken Authentication | Weak login, no token expiry, predictable reset codes. |
| API3 | Broken Object Property Level Authorization | Too much data returned, or fields the client shouldn't be able to set (e.g. isAdmin). |
| API4 | Unrestricted Resource Consumption | No rate limits or pagination caps โ DoS or huge bills. |
| API5 | Broken Function Level Authorization | A normal user can call admin endpoints. |
| API6 | Unrestricted Access to Sensitive Business Flows | Scalping, spam, or abuse via automated legitimate calls. |
| API7 | Server-Side Request Forgery (SSRF) | Attacker makes your server fetch internal URLs (e.g. cloud metadata). |
| API8 | Security Misconfiguration | Debug mode in prod, permissive CORS, verbose errors. |
| API9 | Improper Inventory Management | Forgotten old API versions still exposed. |
| API10 | Unsafe Consumption of APIs | Trusting third-party API data without validation. |
Rate-limiting algorithms
Fixed Window
Count requests per fixed time bucket (e.g. 100/min). Simple but allows bursts at edges.
Sliding Window
Smoother than fixed window โ counts requests over the last N seconds continuously.
Token Bucket
Bucket refills tokens at a steady rate; each request takes one. Allows controlled bursts.
Leaky Bucket
Requests "leak" out at a constant rate. Smooths traffic, no bursts allowed.
Standard rate-limit headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1705316400
Retry-After: 30
Essential security headers
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Cache-Control: no-store # for sensitive responses
- Authorize every request โ never rely on the client to hide data.
- Validate and sanitize every input, including headers and query params.
- Use parameterized queries to stop injection.
- Rotate secrets regularly and store them in a secrets manager, not in code.
- Log security events โ but redact tokens, passwords, and PII.
- Test with a security scanner (OWASP ZAP, Burp Suite) before shipping.
13 Architecture Patterns
How APIs are arranged in real systems โ the shapes you'll see in job interviews and production diagrams.
๐ข Monolith
One deployable app. Simple to start; all code talks in-process. Works fine for most teams.
๐งฉ Microservices
Many small services, each owning its data. Communicate via APIs (REST, gRPC) or events.
๐ช API Gateway
Single front door: routing, auth, rate limits, logging, versioning. Often Kong, NGINX, AWS API Gateway.
๐จ BFF (Backend for Frontend)
One tailored API per client (web, iOS, Android) sitting in front of shared services.
๐ธ๏ธ Service Mesh
Sidecar proxies (Istio, Linkerd) handle mTLS, retries, and telemetry between services.
๐จ Event-Driven
Services publish events to a broker (Kafka, RabbitMQ, SNS). Great for async workflows.
โ๏ธ Serverless APIs
Each endpoint is a function (AWS Lambda, Cloudflare Workers). Pay-per-invocation, auto-scaling.
๐งฑ API Composition
A layer that fans out to several APIs and merges results โ common in mobile backends.
Typical layered request path
Microservice communication: sync vs async
| Style | How | When to use |
|---|---|---|
| Synchronous | REST or gRPC call, client waits for reply | Need an immediate answer (e.g. fetch user profile). |
| Asynchronous | Publish event to a queue or topic | Long tasks, fan-out, decoupling (e.g. send email after order). |
In distributed systems, add timeouts, retries with backoff, circuit breakers (stop calling a failing service), bulkheads (isolate failures), and idempotency keys so retries don't duplicate side effects.
14 Caching & Performance
Caching is the single highest-leverage way to make an API fast and cheap. HTTP has caching built in โ use it.
Key caching headers
| Header | What it does | Example |
|---|---|---|
Cache-Control | Who may cache and for how long | public, max-age=3600 |
ETag | Fingerprint of the response body | "33a64df5" |
Last-Modified | When the resource last changed | Wed, 15 Jan 2025 10:00:00 GMT |
Vary | Which request headers affect the response | Accept, Accept-Encoding |
Expires | Legacy absolute expiry time | Wed, 15 Jan 2025 11:00:00 GMT |
Conditional requests โ saving bandwidth
# First request: server responds with an ETag
GET /v1/products/42
ETag: "33a64df5"
# Next request: client sends the ETag back
GET /v1/products/42
If-None-Match: "33a64df5"
# If unchanged, server replies with a tiny 304 and no body
HTTP/1.1 304 Not Modified
Cache-Control directives worth knowing
publicโ any cache (including CDNs) may store it.privateโ only the end user's browser may cache it.no-storeโ never cache (use for sensitive data).no-cacheโ may store, but must revalidate before each use.max-age=600โ fresh for 600 seconds.stale-while-revalidate=60โ serve stale for 60s while refreshing in the background.
Other performance wins
๐๏ธ Compression
Enable gzip or brotli. JSON compresses 70โ90%. Always send Accept-Encoding.
๐ Connection reuse
HTTP keep-alive and connection pools avoid TCP + TLS handshakes per request.
๐ Pagination
Never return unbounded lists. Cap limit server-side (e.g. max 100).
๐งฎ Avoid N+1 queries
One query per item in a loop is the classic API performance killer. Batch or join.
๐ CDN
Put a CDN in front of cacheable GETs to serve users from an edge near them.
๐ฆ Partial responses
Let clients request only the fields they need (?fields= or GraphQL).
15 Real-time APIs
Sometimes the server needs to tell the client something now, instead of waiting to be asked. Four main patterns exist.
| Pattern | Direction | Latency | Best for |
|---|---|---|---|
| Short polling | Client โ server, repeatedly | High (interval-bound) | Simple dashboards, low-frequency updates |
| Long polling | Client โ server, held open | Medium | Legacy real-time when WebSockets aren't available |
| Server-Sent Events (SSE) | Server โ client, one-way | Low | Live feeds, notifications, progress bars |
| WebSockets | Bidirectional | Lowest | Chat, multiplayer, collaborative editing |
| Webhooks | Server โ server, event-driven | Low | Payments, CI/CD, third-party integrations |
WebSocket โ minimal example
// Browser side
const ws = new WebSocket('wss://api.example.com/ws');
ws.addEventListener('open', () => ws.send(JSON.stringify({ type: 'subscribe', room: 'prices' })));
ws.addEventListener('message', e => console.log('Update:', JSON.parse(e.data)));
ws.addEventListener('close', () => console.log('Disconnected'));
Server-Sent Events โ simpler, one-way
// Browser side
const es = new EventSource('/api/notifications/stream');
es.onmessage = e => console.log('Notification:', e.data);
es.onerror = () => console.log('Stream lost, retryingโฆ');
Webhook โ the server calls you
// Express receiver for a Stripe-style webhook
app.post('/webhooks/payment', express.raw({ type: '*/*' }), (req, res) => {
const signature = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body, signature, process.env.WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
if (event.type === 'payment_intent.succeeded') {
// handle the eventโฆ
}
res.json({ received: true }); // respond fast
});
- Verify the signature โ never trust an unauthenticated webhook.
- Respond 2xx quickly โ do heavy work asynchronously.
- Be idempotent โ the same event may be delivered more than once.
- Use HTTPS and an allowlist of source IPs if possible.
16 Documentation & Developer Experience
A great API with bad docs is a bad API. DX (developer experience) is a feature.
๐ OpenAPI / Swagger
Machine-readable spec. Generates docs, SDKs, mocks, and tests. The industry standard for REST.
๐ GraphQL SDL
The schema is the documentation. Tools like GraphiQL give an interactive explorer.
๐ Postman Collections
Runnable examples your users can import and try in one click.
๐ Quickstart
A 5-minute "hello world" โ copy, paste, run. If it takes longer, users bounce.
๐งช Sandbox environment
Fake data, no real charges. Lets developers experiment safely.
๐ฆ Client SDKs
Official libraries in JS, Python, Go, etc. Reduce time-to-first-call dramatically.
๐ Changelog
Dated list of changes, marked as breaking / added / fixed. Subscribe-able.
๐ Developer portal
One place for docs, keys, status page, support, and terms.
What a great doc page contains
- Authentication โ how to get and use a key or token.
- Base URL and environments (sandbox vs production).
- Every endpoint with method, path, params, sample request, sample response.
- Error reference โ every code and what it means.
- Rate limits โ the numbers and the headers.
- Pagination โ how it works and where to find the next page.
- Webhooks โ events, payloads, signature verification.
- Changelog & deprecation policy.
OpenAPI snippet
openapi: 3.0.3
info:
title: Books API
version: 1.0.0
paths:
/books:
get:
summary: List all books
responses:
'200':
description: A list of books
content:
application/json:
schema:
type: array
items: { $ref: '#/components/schemas/Book' }
If a new developer can't make a successful API call in under 10 minutes with only your docs, your onboarding is broken.
17 Testing & Monitoring
Untested APIs break silently. Unmonitored APIs break loudly at 3 a.m.
Testing layers
| Layer | What it checks | Tools |
|---|---|---|
| Unit | One handler or function in isolation | Jest, Pytest, Go test |
| Integration | Endpoint + database + auth working together | Supertest, Testcontainers |
| Contract | The API still matches the agreed schema | Pact, Dredd, Schemathesis |
| End-to-end | The full user journey across services | Cypress, Playwright, Postman |
| Load | Behaviour under high traffic | k6, JMeter, Artillery, Locust |
| Security | Common vulnerabilities | OWASP ZAP, Burp Suite |
Monitoring: the four golden signals
โฑ๏ธ Latency
How long requests take. Track p50, p95, p99 โ averages hide pain.
๐ฆ Traffic
Requests per second, by endpoint and by client.
โ Errors
Rate of 5xx and 4xx responses. Alert on spikes, not just totals.
๐ง Saturation
How full are your resources? CPU, memory, DB connections, queue depth.
Observability: the three pillars
- Logs โ structured JSON events with a request ID so you can trace a single call.
- Metrics โ numeric time series for dashboards and alerting.
- Traces โ a single request's journey across services (OpenTelemetry, Jaeger, Tempo).
SLI, SLO, SLA โ what's the difference?
| Term | Meaning | Example |
|---|---|---|
| SLI | Indicator โ a number you measure | p99 latency is 240 ms |
| SLO | Objective โ an internal target | 99.9% of requests under 500 ms |
| SLA | Agreement โ a contract with customers, often with penalties | 99.95% uptime or credits |
Distributed tracing โ propag
# Trace context propagates via headers
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
X-Request-ID: req_8f2a91c
Give every response a unique X-Request-ID and include it in logs. When a customer reports a bug, they can paste the ID and you can find the exact trace in seconds.
18 Best Practices Checklist
๐ Always HTTPS
Never transmit tokens or data over plain HTTP. Enforce TLS and use HSTS.
๐ Document everything
Use OpenAPI/Swagger. Include examples for every endpoint and every error.
๐งฑ Validate all input
Type, length, range, format. Reject early with a clear 400 and field-level detail.
๐ฆ Rate limit
Protect your backend. Return 429 with Retry-After and X-RateLimit-* headers.
๐ข Version from day one
/v1/ in the path or a custom media type. Never break existing clients.
๐ Log & monitor
Track latency (p50/p95/p99), error rate, and throughput. Add a request ID to every response.
โก Cache smartly
Send ETag, Cache-Control, and support conditional requests with 304.
๐งช Test automatically
Unit tests for handlers, integration tests for endpoints, contract tests against the schema.
โป๏ธ Idempotency keys
For payment-like POSTs, let clients send an Idempotency-Key to prevent duplicates.
๐ CORS & security headers
Set Content-Security-Policy, X-Content-Type-Options, and a strict origin allowlist.
๐ Paginate everything
Cap limit server-side. Never let a client pull an unbounded table.
๐ฃ Publish a changelog
Announce changes early. Mark breaking ones clearly. Give clients time to migrate.
Security quick wins
- Least privilege: scopes and roles should grant the minimum needed.
- Short-lived tokens: access tokens expire in minutes; refresh tokens rotate.
- Never log secrets: redact
Authorizationheaders from logs. - Prevent injection: always use parameterized queries for the database.
- Limit payload size: cap request bodies to avoid memory exhaustion.
- Use a gateway: centralize auth, throttling, and TLS termination.
19 Tools & Ecosystem
| Tool | Purpose |
|---|---|
| Postman | GUI client for building, testing, and sharing API requests |
| Insomnia | Lightweight REST/GraphQL client |
| Bruno | Offline, Git-friendly API client |
| Swagger / OpenAPI | Specification format + interactive documentation UI |
| Stoplight | Visual API design, mocking, and documentation platform |
| httpie | Human-friendly command-line HTTP client |
| curl | Universal CLI tool โ available everywhere |
| Jest / Pytest / Supertest | Automated endpoint testing |
| Pact | Consumer-driven contract testing |
| k6 / Artillery / JMeter | Load and performance testing |
| OWASP ZAP / Burp Suite | Security scanning |
| ngrok / localtunnel | Expose your local API to the internet for webhook testing |
| Kong / NGINX / AWS API Gateway | API gateways: routing, auth, rate limiting |
| OpenTelemetry / Jaeger | Distributed tracing and observability |
| Redis | Cache, rate-limit counters, session store |
| Kafka / RabbitMQ / SNS+SQS | Event-driven messaging between services |
Popular frameworks for building APIs
| Language | Framework | Notes |
|---|---|---|
| JavaScript / TypeScript | Express, Fastify, NestJS, Hono | Huge ecosystem; Fastify and Hono are very fast |
| Python | FastAPI, Flask, Django REST Framework | FastAPI gives OpenAPI docs for free |
| Go | Gin, Fiber, Echo, Chi | Excellent performance and concurrency |
| Java / Kotlin | Spring Boot, Ktor, Micronaut | Enterprise favourite; Spring has everything |
| Ruby | Rails API, Sinatra | Rapid development, convention over configuration |
| C# / .NET | ASP.NET Core | High performance, strong tooling |
| Rust | Axum, Actix Web, Rocket | Maximum performance and safety |
| PHP | Laravel, Symfony | Still powers a huge share of the web |
20 Technical Glossary
Core API vocabulary in plain language.
| Term | Meaning |
|---|---|
| Endpoint | A specific URL path that accepts requests, e.g. /v1/users |
| Resource | The "thing" an endpoint represents โ a user, an order, a book |
| Payload | The data carried in a request or response body |
| Header | Keyโvalue metadata sent with a request or response |
| Query string | The ?key=value&โฆ portion of a URL used for filters and options |
| Path parameter | A variable inside the URL path, e.g. /users/:id |
| Idempotent | Repeating the call produces the same result as calling once |
| Idempotency key | A unique client-supplied value that lets the server deduplicate retried writes |
| Stateless | The server keeps no session state between requests; every call is self-contained |
| Stateful | The server remembers things between calls (e.g. a WebSocket session) |
| Serialization | Turning an object into a transmittable format like JSON or XML |
| Deserialization | Turning received data back into an object your code can use |
| Marshalling | Another word for serialization, common in Java/Go ecosystems |
| Rate limiting | Restricting how many requests a client may make per time window |
| Throttling | Slowing down requests rather than rejecting them outright |
| Quota | A long-term usage allowance (e.g. 10 000 calls per month) |
| Webhook | A user-defined HTTP callback triggered by an event on the provider side |
| Polling | Repeatedly asking the server for updates; the alternative to webhooks |
| Long polling | The server holds the request open until there's something to return |
| API Gateway | A layer in front of services handling auth, routing, and rate limits |
| Reverse proxy | A server that sits in front of your app, forwarding and possibly caching requests |
| SDK | A language-specific library that wraps an API for convenience |
| Contract | The agreed request/response schema between client and server |
| Schema | A formal definition of what data is allowed and its types |
| OpenAPI | A standard, machine-readable description format for REST APIs |
| Swagger UI | The interactive web page that renders an OpenAPI spec |
| Mock server | A fake API that returns sample responses, used before the real one exists |
| Sandbox | A safe test environment with fake data and no real side effects |
| Bearer token | A credential sent in the Authorization header; whoever holds it is granted access |
| Access token | Short-lived credential that proves you're authorized to call an API |
| Refresh token | Long-lived credential used to get new access tokens without re-login |
| Scope | A named permission granted to a token, e.g. read:orders |
| Claim | A piece of information inside a JWT, like sub (subject) or exp (expiry) |
| CORS | Browser security rule governing cross-origin requests |
| Preflight | An automatic OPTIONS request the browser sends before a cross-origin call |
| ETag | A fingerprint of a response used for cache validation |
| Conditional request | A request that includes validators (If-None-Match) so the server can return 304 |
| Cache invalidation | Removing or refreshing stale cached data โ famously hard |
| TTL | Time To Live โ how long a cached item or token stays valid |
| CDN | Content Delivery Network โ edge servers that cache and serve content near users |
| Circuit breaker | A pattern that stops calling a failing service to prevent cascading failures |
| Backpressure | Signalling upstream to slow down because you're overwhelmed |
| Retry with backoff | Retrying a failed call, waiting longer each time to avoid hammering the server |
| Fan-out | Sending one request/event to many downstream consumers |
| BFF | Backend For Frontend โ a tailored API layer per client type |
| Sidecar | A helper process running alongside a service (common in service meshes) |
| Service mesh | Infrastructure layer that manages service-to-service traffic (Istio, Linkerd) |
| HATEOAS | Hypermedia As The Engine Of Application State โ responses include links to next actions |
| Over-fetching | Receiving more data than you need (common in REST) |
| Under-fetching | Needing multiple calls to assemble one screen (also common in REST) |
| Resolver | In GraphQL, the function that fetches data for one field |
| Mutation | In GraphQL, an operation that changes data (like POST/PUT/DELETE) |
| Subscription | In GraphQL, a long-lived operation that pushes updates over a socket |
| Batching | Combining many small calls into one to reduce overhead |
| N+1 problem | Making one query per item in a list, tanking performance |
| Pagination | Splitting large result sets into pages |
| Cursor | An opaque pointer to "where you are" in a paginated list |
| Envelope | A wrapper object around data, e.g. { data, meta, error } |
| Payload size limit | A cap on how large a request body may be |
| Request ID | A unique ID per request, used to correlate logs and traces |
| Correlation ID | An ID shared across a chain of calls across services |
| Health check | An endpoint (often /health) that reports if the service is alive |
| Readiness probe | Signals a service is ready to receive traffic |
| Liveness probe | Signals a service is still running and should not be restarted |
| Blue/green deploy | Running two identical environments, switching traffic between them |
| Canary release | Rolling out a change to a small percentage of users first |
| Feature flag | A runtime switch to turn behaviour on/off without redeploying |
| Backward compatible | Old clients keep working after the change |
| Deprecation | Marking something as obsolete but still supported for a transition period |
| Sunset | The date after which a deprecated thing stops working |
21 Acronyms & Idioms for Beginners
Every field has its own language. Here is the API world's โ explained without jargon.
A. Acronyms you'll see everywhere
A contract that lets two programs talk to each other.
An architectural style for web APIs built on resources and HTTP verbs.
The language browsers and servers speak. Almost every API rides on it.
HTTP encrypted with TLS. Always use this in production.
The encryption layer behind HTTPS. SSL is the older name; TLS is what's used today.
Lightweight text format for data. The default for modern APIs.
Older, verbose markup format. Still used by SOAP and some enterprise systems.
Human-friendly data format, common for config files and OpenAPI specs.
The full web address, e.g. https://api.example.com/v1/books.
Umbrella terms: a URI identifies a resource; a URN names it without saying how to find it.
The four basic operations on data. Maps to POST, GET, PUT/PATCH, DELETE.
A language-specific library that wraps an API so you don't hand-craft HTTP calls.
A text-based tool you run in a terminal, like curl.
A visual app with buttons and windows โ like Postman.
A signed, self-contained token used for stateless authentication.
A standard for granting apps access to your data without sharing your password.
OAuth 2.0 plus an identity layer โ the "Sign in withโฆ" standard.
Log in once, access many systems.
Requiring more than a password โ an SMS code, an authenticator app, a hardware key.
Both client and server present certificates. Common inside zero-trust networks.
A browser rule that decides whether a page can call an API on a different domain.
Tricking a logged-in browser into making an unwanted request. Prevented with tokens and SameSite cookies.
Injecting malicious scripts into a page. Prevented by escaping output and CSP headers.
Sneaking SQL through unvalidated input. Prevented with parameterized queries.
Making a server fetch an internal URL on the attacker's behalf.
The #1 API security risk: users can access objects they don't own by changing an ID.
A nonprofit that publishes the famous Top 10 security risk lists.
A filter that blocks common attacks before they reach your app.
Flooding a service with traffic from many sources to knock it offline.
A strict, XML-based protocol for enterprise APIs. Uses WSDL to describe itself.
The XML contract that describes a SOAP service.
A high-performance binary RPC framework using HTTP/2 and Protocol Buffers.
Calling a function on another machine as if it were local.
A one-way stream from server to browser over plain HTTP.
A persistent, two-way connection between client and server.
A tailored API layer for one specific client type.
Edge servers around the world that cache and serve content near your users.
How long a cached item or token remains valid.
A value that uniquely identifies something. UUIDs look like f81d4fae-7dec-11d0-a765-00a0c91e6bf6.
Data that can identify a person โ names, emails, SSNs. Handle with care and never log it raw.
EU privacy law. Affects how APIs store, expose, and delete personal data.
The customer contract / the internal target / the number you measure.
Automatically testing and deploying every code change.
How pleasant your API is to use โ docs, SDKs, errors, sandbox.
How pleasant your product is for end users.
A library that lets you talk to a database with objects instead of raw SQL (e.g. Prisma, SQLAlchemy).
Where data lives. Relational uses tables and joins; NoSQL uses documents, key-value, or graphs.
Queue order. Most message queues are FIFO; stacks are LIFO.
A test that exercises the full flow from user to database and back.
"95% of requests are faster than X." More useful than averages for spotting pain.
B. Idioms & phrases you'll hear on day one
| Phrase | What it really means |
|---|---|
| "Hit the endpoint" | Send an HTTP request to that URL. |
| "Stand up an API" | Build and deploy an API so it's running. |
| "Spin up a server" | Start a new server instance. |
| "Round trip" | One full request-and-response cycle. Fewer round trips = faster app. |
| "Chatty API" | An API that forces many small calls to do one job. Usually a design smell. |
| "Over-fetching" | Getting more data back than you'll use. Wastes bandwidth. |
| "Under-fetching" | Needing several calls to assemble one screen. GraphQL was invented to fix this. |
| "N+1 problem" | One query per item in a list. The classic performance killer. |
| "Thundering herd" | Thousands of clients retrying at once and crushing the server. |
| "Retry storm" | Same idea โ uncontrolled retries making an outage worse. |
| "Back-of-the-envelope" | A rough estimate, not a precise measurement. |
| "Happy path" | The scenario where everything works. Always test the unhappy paths too. |
| "Edge case" | An unusual input that breaks naรฏve code โ empty lists, huge numbers, weird Unicode. |
| "Bikeshedding" | Arguing about trivia (like naming) while ignoring the hard problems. |
| "Boil the ocean" | Try to do everything at once instead of shipping something small. |
| "Dogfooding" | Using your own API internally before releasing it. Best way to find pain points. |
| "Eating your own dog food" | The full version of the above. |
| "Greenfield" | A brand-new project with no legacy constraints. |
| "Brownfield" | Adding to an existing, messy system. Most real work is brownfield. |
| "Legacy code" | Code that works but is scary to touch. Often the reason SOAP still exists. |
| "Technical debt" | Shortcuts taken now that will cost more to fix later. |
| "Yak shaving" | A chain of small tasks that distracts from the original goal. |
| "Rubber duck debugging" | Explaining your problem out loud until you spot the bug yourself. |
| "Works on my machine" | The classic excuse when something only fails in production. |
| "Ship it" | Deploy it. Often said with a mix of hope and fatalism. |
| "Bake in the oven" | Run a new change in a test environment for a while before full rollout. |
| "Break glass" | An emergency override to bypass normal controls. |
| "Blast radius" | How much breaks if a change goes wrong. Keep it small. |
| "Graceful degradation" | When one part fails, the rest still works โ just with less functionality. |
| "Fail fast" | Return an error immediately rather than waiting or guessing. |
| "Fail closed" | On error, deny access. The safe default for security. |
| "Fail open" | On error, allow access. Almost always the wrong choice for auth. |
| "Source of truth" | The authoritative place where a piece of data lives. |
| "Single source of truth" | One place โ never two โ that owns a piece of data. |
| "Idempotent operation" | Doing it twice is the same as doing it once. |
| "Contract-first" | Write the API spec before writing code. |
| "Code-first" | Write the code, then generate the spec from it. |
| "Wrapping an API" | Building a layer around someone else's API to simplify or normalize it. |
| "Shimming" | A thin adapter that makes two incompatible things work together. |
| "Glue code" | Code whose only job is connecting other pieces together. |
| "Vendor lock-in" | Being so dependent on one provider that switching is painful. |
| "Feature creep" | Slowly adding more scope than anyone agreed to. |
| "Scope creep" | Same idea, but for the project as a whole. |
| "MVP" | Minimum Viable Product โ the smallest thing that delivers value. |
| "POC" | Proof of Concept โ a quick experiment to see if an idea works. |
| "Spike" | A short, time-boxed investigation to reduce uncertainty. |
| "Smoke test" | A quick check that the basics still work after a change. |
| "Synthetic monitoring" | Bots that call your API on a schedule to detect outages before users do. |
| "Golden path" | The recommended, well-supported way to do something. Also called the "paved road". |
| "Paved road" | The supported tooling and patterns teams should default to. |
| "Off the paved road" | Doing something custom and unsupported โ allowed, but you own the consequences. |
| "Postman-driven development" | Half-joking: building an API by clicking around Postman instead of writing proper tests. |
| "Fire and forget" | Send a request or event without waiting for the result. |
| "At-least-once delivery" | A message may arrive more than once โ so your handler must be idempotent. |
| "At-most-once delivery" | A message may be lost but never duplicated. |
| "Exactly-once delivery" | The holy grail โ very hard to truly achieve; usually simulated with idempotency. |
| "Eventually consistent" | Data will match across systemsโฆ eventually. Not instantly. |
| "Strongly consistent" | Every read sees the latest write. Slower but simpler to reason about. |
If you understand the sections above and the two glossaries, you can read almost any API documentation, hold your own in a design discussion, and build or consume a real API. The rest is practice.