Here's a reality check that most developers never get: your API is a product. It doesn't matter if it's consumed by your own frontend team, a mobile app, or thousands of third-party developers — the quality of your API design directly determines how much pain or pleasure people experience when integrating with your system.
Bad APIs create endless support tickets, confusing documentation, and frustrated developers who resort to workarounds that haunt your codebase for years. Great APIs feel invisible. They do exactly what you expect, communicate clearly when something goes wrong, and scale gracefully as your application grows.
The difference between the two comes down to following established REST conventions and design patterns. Not because rules are fun, but because predictability is the ultimate feature of any API. When developers can guess how your API works based on their experience with other well-designed APIs, you've won.
This guide covers everything you need to design REST APIs that developers genuinely enjoy using — from URL structure and HTTP methods to error handling, versioning, pagination, authentication, and rate limiting. Whether you're building your first API or refactoring a legacy one that's held together with duct tape and prayers, these best practices will transform your backend into something you're proud to ship.
1. What Is a REST API and Why Should You Care About Design?
REST (Representational State Transfer) is an architectural style for designing networked applications, introduced by Roy Fielding in his 2000 doctoral dissertation. A REST API uses standard HTTP methods to perform operations on resources identified by URLs.
But here's the thing most tutorials miss: REST is a set of constraints, not a specification. There's no RFC that says "this is exactly how a REST API must look." That flexibility is both a blessing and a curse — it gives you freedom, but it also means the internet is littered with APIs that claim to be RESTful while violating every principle REST stands for.
Why does design matter so much? Consider the numbers:
- Over 80% of internet traffic now flows through APIs, according to Akamai's latest web security research.
- API-first companies like Stripe, Twilio, and Plaid have built multi-billion dollar businesses primarily on the quality of their API design.
- Developer experience (DX) has become a competitive advantage. A 2025 Postman State of APIs survey found that 52% of developers cite poor documentation and design as the primary reason they abandon an API.
- Maintenance costs compound. A poorly designed API that ships to production creates backward-compatibility obligations that can haunt you for a decade.
The core REST constraints you need to internalize are:
- Client-Server Separation: The client and server are independent. The client doesn't need to know how data is stored. The server doesn't care how data is displayed.
- Statelessness: Each request contains all the information needed to process it. The server doesn't store session state between requests.
- Uniform Interface: Resources are identified by URLs. Operations use standard HTTP methods. Responses include enough information for the client to manipulate the resource.
- Cacheability: Responses should indicate whether they can be cached, reducing unnecessary network calls.
With those principles in mind, let's build an API that actually respects them.
2. URL Structure and Resource Naming: Your API's First Impression
Your URL structure is the first thing developers see. Get it right, and your API feels intuitive before anyone reads the docs. Get it wrong, and developers spend more time guessing URLs than building features.
Rule 1: Use nouns, not verbs. Resources are things, not actions. The HTTP method provides the verb.
❌ Bad:
GET /getUsers
POST /createUser
POST /deleteUser/123
GET /fetchOrdersByUser?userId=456✅ Good:
GET /users
POST /users
DELETE /users/123
GET /users/456/ordersRule 2: Use plural nouns consistently. Don't mix /user and /users. Pick plural and stick with it. This eliminates the cognitive load of remembering which resources are singular and which are plural.
Rule 3: Use kebab-case for multi-word resources.
✅ /user-profiles
✅ /order-items
✅ /payment-methods
❌ /userProfiles
❌ /user_profiles
❌ /UserProfilesRule 4: Express relationships through nesting, but don't go deeper than two levels.
# Good: one level of nesting
GET /users/123/orders
GET /users/123/addresses
# Acceptable: two levels when necessary
GET /users/123/orders/456/items
# Bad: too deep
GET /users/123/orders/456/items/789/reviewsWhen nesting gets too deep, flatten it. Instead of /users/123/orders/456/items/789/reviews, use /reviews?item_id=789 or /order-items/789/reviews.
Rule 5: Use query parameters for filtering, sorting, and pagination — not for identifying resources.
# Identifying a resource: use the URL path
GET /users/123
# Filtering resources: use query parameters
GET /users?role=admin&status=active
GET /orders?sort=-created_at&limit=20
GET /products?category=electronics&min_price=100Rule 6: Be consistent with trailing slashes. Pick one convention (with or without) and enforce it everywhere. Most modern APIs omit trailing slashes. If someone hits your API with a trailing slash, either redirect or accept it — don't return a 404.
3. HTTP Methods and Status Codes: Speak the Language Correctly
HTTP gives you a rich vocabulary of methods and status codes. Using them correctly makes your API predictable and self-documenting. Misusing them creates confusion that no amount of documentation can fix.
The five methods you'll use 99% of the time:
- GET — Retrieve a resource or collection. Must be safe (no side effects) and idempotent (calling it multiple times produces the same result).
- POST — Create a new resource. Not idempotent — calling it twice creates two resources.
- PUT — Replace a resource entirely. Idempotent — sending the same PUT request twice produces the same state.
- PATCH — Partially update a resource. Only send the fields you want to change.
- DELETE — Remove a resource. Should be idempotent — deleting an already-deleted resource should return success, not an error.
The PUT vs. PATCH debate: This trips up a lot of developers. Use PUT when the client sends the complete resource representation. Use PATCH when the client sends only the fields that changed. In practice, PATCH is more common because clients rarely have or want to send the entire object.
# PUT: replace the entire user object
PUT /users/123
{
"name": "Jane Smith",
"email": "jane@example.com",
"role": "admin",
"department": "Engineering"
}
# PATCH: update only the role
PATCH /users/123
{
"role": "admin"
}Status codes that matter (and when to use each):
Success codes (2xx):
- 200 OK — Standard success for GET, PUT, PATCH requests that return data.
- 201 Created — A new resource was successfully created (POST). Include a
Locationheader pointing to the new resource. - 204 No Content — Success with no response body. Ideal for DELETE operations.
Client error codes (4xx):
- 400 Bad Request — The request was malformed. Invalid JSON, missing required fields, wrong data types.
- 401 Unauthorized — Authentication failed or wasn't provided. (Yes, the name is misleading — it's about authentication, not authorization.)
- 403 Forbidden — Authenticated but not authorized. The server knows who you are and you're not allowed.
- 404 Not Found — The resource doesn't exist.
- 409 Conflict — The request conflicts with the current state (e.g., trying to create a user with a duplicate email).
- 422 Unprocessable Entity — The syntax is correct but the semantics are wrong. Great for validation errors.
- 429 Too Many Requests — Rate limit exceeded. Include a
Retry-Afterheader.
Server error codes (5xx):
- 500 Internal Server Error — Something broke on the server. Log it, alert on it, and never expose internal details to the client.
- 502 Bad Gateway — An upstream service failed.
- 503 Service Unavailable — The server is temporarily down (maintenance, overload). Include a
Retry-Afterheader.
Stop returning 200 for everything. One of the most common API anti-patterns is wrapping every response in a 200 status with a success: false field in the body. This breaks HTTP caching, confuses middleware, and makes error handling unnecessarily complex for every client.
4. Error Handling: Tell Developers What Went Wrong and How to Fix It
Error responses are where most APIs fall apart. A generic {"error": "Something went wrong"} is the API equivalent of a shrug. Developers need actionable information to debug issues quickly — especially at 2 AM when production is on fire.
A consistent error response format:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The request body contains invalid fields.",
"details": [
{
"field": "email",
"message": "Must be a valid email address.",
"rejected_value": "not-an-email"
},
{
"field": "age",
"message": "Must be a positive integer.",
"rejected_value": -5
}
],
"request_id": "req_abc123def456",
"documentation_url": "https://api.example.com/docs/errors#VALIDATION_ERROR"
}
}The anatomy of a great error response:
- Machine-readable error code. A constant string like
VALIDATION_ERROR,RESOURCE_NOT_FOUND, orRATE_LIMIT_EXCEEDED. Clients can switch on this reliably, unlike human-readable messages that might change. - Human-readable message. A clear explanation of what went wrong, written for the developer reading logs — not the end user.
- Field-level details. For validation errors, specify which fields failed and why. Bonus: include the rejected value so the developer can see what was sent.
- Request ID. A unique identifier for the request that can be used to correlate with server-side logs. This is invaluable for debugging.
- Documentation link. Point developers to relevant docs. Stripe does this brilliantly — every error includes a link to a page explaining the error and how to resolve it.
Common error handling mistakes to avoid:
- Leaking stack traces in production. Internal server errors should log the full stack trace server-side but return a generic message to the client. Never expose database queries, file paths, or internal service names.
- Using inconsistent error formats. If some endpoints return
{"error": "..."}and others return{"message": "...", "status": "fail"}, your clients need special handling for every endpoint. - Returning HTML error pages from an API. When your reverse proxy or framework returns a default HTML 404 or 500 page to an API client, it's immediately obvious your error handling has gaps.
- Swallowing errors silently. If a partial operation fails (e.g., creating a user succeeded but sending the welcome email failed), communicate what succeeded and what didn't.
5. Pagination: Don't Make Clients Download Your Entire Database
Any endpoint that returns a collection of resources needs pagination. Even if you only have 50 records today, you'll have 50,000 tomorrow, and an unbounded GET /users that returns everything will bring your server to its knees.
Three common pagination strategies:
1. Offset-based pagination (simplest, most common):
GET /users?offset=0&limit=20
GET /users?offset=20&limit=20
GET /users?offset=40&limit=20Alternatively, use page and per_page:
GET /users?page=1&per_page=20
GET /users?page=2&per_page=20Pros: Simple to implement and understand. Allows jumping to any page.
Cons: Performance degrades with large offsets (the database still scans skipped rows). Results can shift if new items are inserted between requests.
2. Cursor-based pagination (best for large datasets):
GET /users?limit=20
GET /users?limit=20&after=cursor_abc123
GET /users?limit=20&after=cursor_def456The cursor is typically an encoded reference to the last item in the previous page (often a base64-encoded ID or timestamp).
Pros: Consistent performance regardless of dataset size. No missed or duplicated results when data changes.
Cons: Can't jump to arbitrary pages. More complex to implement.
3. Keyset pagination (cursor without the encoding):
GET /users?limit=20&sort=created_at
GET /users?limit=20&sort=created_at&created_after=2026-03-15T10:30:00ZPros: Transparent — the client can see and manipulate the cursor value. Great performance.
Cons: Requires a unique, sequential column. Same "no page jumping" limitation as cursor-based.
Pagination response metadata:
Always include enough information for the client to navigate:
{
"data": [...],
"pagination": {
"total": 1250,
"limit": 20,
"offset": 40,
"has_more": true,
"next": "/users?offset=60&limit=20",
"previous": "/users?offset=20&limit=20"
}
}For cursor-based pagination:
{
"data": [...],
"pagination": {
"has_more": true,
"next_cursor": "cursor_abc123"
}
}Set sensible defaults and maximums. Default to 20-50 items per page. Cap the maximum at 100-200. If a client requests limit=10000, either reject it or silently cap it and indicate the actual limit in the response.
6. API Versioning: Plan for Change From Day One
Your API will change. Features will be added, fields renamed, entire workflows restructured. The question isn't whether you'll need versioning — it's how you'll implement it without breaking every client that depends on your current API.
Four versioning strategies:
1. URL path versioning (most common, most explicit):
GET /v1/users
GET /v2/usersPros: Dead simple. Immediately visible. Easy to route at the load balancer level. This is what Stripe, GitHub, and most major APIs use.
Cons: Technically, the URL should identify a resource, and the version isn't part of the resource. Purists object (but pragmatists ship).
2. Query parameter versioning:
GET /users?version=1
GET /users?version=2Pros: Keeps the URL clean. Easy to default to the latest version.
Cons: Easy to forget. Harder to cache. Less visible in logs.
3. Header versioning:
GET /users
Accept: application/vnd.myapi.v2+jsonPros: RESTfully correct — the URL identifies the resource, the header specifies the representation. GitHub's API supports this alongside URL versioning.
Cons: Invisible in browser URLs. Requires clients to set custom headers. Harder to test casually with curl.
4. No explicit versioning (evolve in place):
Add new fields without removing old ones. Mark deprecated fields with warnings. Only make backward-compatible changes. This is what GraphQL encourages.
Pros: No version management overhead. Clients only use what they need.
Cons: You can never remove anything. The API accumulates cruft over time. Not always feasible for breaking changes.
Our recommendation: URL path versioning for most teams. It's the most practical, most visible, and most widely understood. Save the academic debates for conference talks — in production, clarity beats purity every time.
Versioning rules to live by:
- Version from day one. Adding
/v1/costs nothing and saves enormous pain later. - Support at least two versions simultaneously to give clients time to migrate.
- Set clear deprecation timelines. "v1 will be supported until December 2027" is infinitely better than "v1 will be deprecated eventually."
- Send deprecation warnings in response headers so automated tools can detect and alert on upcoming changes.
7. Authentication and Security: Lock Your API Down
An insecure API is a liability, regardless of how beautifully designed the rest of it is. API security breaches make headlines regularly — from exposed customer data to unauthorized access that costs companies millions. Here's how to get authentication and security right.
Authentication methods ranked by use case:
1. API Keys (simplest, least secure for sensitive operations):
GET /users
Authorization: Bearer sk_live_abc123def456Best for: Server-to-server communication, rate limiting, identifying the calling application.
Not for: User-level authentication. API keys don't represent a user — they represent an application.
2. OAuth 2.0 + JWT (industry standard for user authentication):
POST /oauth/token
{
"grant_type": "authorization_code",
"code": "auth_code_from_redirect",
"client_id": "your_app_id",
"client_secret": "your_app_secret"
}
# Response
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_abc123"
}Best for: Any API where users grant third-party applications access to their data. Social logins, marketplace integrations, SaaS platforms.
3. Session-based auth (for traditional web apps):
Best for: APIs consumed exclusively by your own frontend. Simple and well-understood, but doesn't scale well for third-party consumption.
Security best practices that aren't optional:
- Always use HTTPS. In 2026, there is zero excuse for serving an API over HTTP. TLS is free (Let's Encrypt), fast, and required for any authentication scheme to be secure.
- Rate limit everything. Protect against brute force attacks, credential stuffing, and abuse. Return 429 with a
Retry-Afterheader when limits are exceeded. - Validate all input server-side. Never trust client input. Validate data types, lengths, ranges, and formats. SQL injection and XSS attacks still work against APIs that skip validation.
- Use short-lived access tokens with refresh tokens. Access tokens should expire in 15-60 minutes. Refresh tokens handle renewal without requiring re-authentication.
- Implement CORS correctly. Don't set
Access-Control-Allow-Origin: *on authenticated endpoints. Whitelist specific domains. - Log and monitor everything. Track authentication failures, unusual access patterns, and requests from suspicious IPs. You can't respond to attacks you don't see.
- Never expose sensitive data in URLs. Tokens, passwords, and API keys should travel in headers or request bodies — never in query parameters, which get logged everywhere.
8. Rate Limiting: Protect Your API Without Ruining the Experience
Rate limiting isn't just about preventing abuse — it's about ensuring fair access for all clients and protecting your infrastructure from both malicious attacks and accidental traffic spikes (like when a client has a retry loop with no backoff).
Common rate limiting strategies:
- Fixed window: Allow X requests per minute/hour. Simple but can cause thundering herd problems at window boundaries.
- Sliding window: Track requests over a rolling time period. Smoother but more complex to implement.
- Token bucket: Clients get a bucket of tokens that refills at a steady rate. Each request costs a token. Allows bursts while maintaining an average rate.
- Leaky bucket: Requests queue up and are processed at a fixed rate. Smooths out traffic but adds latency during bursts.
Communicate limits through headers:
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1711353600
X-RateLimit-Policy: 1000;w=3600When a client exceeds the limit:
HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711353600
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "You have exceeded the rate limit of 1000 requests per hour.",
"retry_after": 45
}
}Rate limiting best practices:
- Document your limits clearly. Developers shouldn't have to discover limits by hitting them. Put rate limits in your documentation, onboarding materials, and dashboard.
- Differentiate limits by tier. Free tier might get 100 requests/hour. Paid plans get 10,000. Enterprise gets custom limits.
- Rate limit by multiple dimensions. Per API key, per IP address, per user, and per endpoint. A single endpoint (like login) might have stricter limits than read-only endpoints.
- Provide a way to check limits without consuming them. A dedicated
GET /rate-limitendpoint or consistent headers on every response. - Be generous with legitimate use cases. If your rate limits are so tight that normal usage triggers them, you'll generate more support tickets than you prevent attacks.
9. Request and Response Design: Consistency Is Everything
Once you've nailed your URLs, methods, and error handling, the next battle is making your request and response payloads consistent, predictable, and pleasant to work with.
Use a consistent envelope (or don't — just be consistent):
Some APIs wrap every response in an envelope:
{
"status": "success",
"data": { ... },
"meta": { ... }
}Others return the resource directly:
{
"id": 123,
"name": "Jane Smith",
"email": "jane@example.com"
}Both approaches work. The envelope pattern is useful when you need to include metadata (pagination, rate limits, deprecation warnings) alongside the data. Direct responses are simpler and more idiomatic REST. Pick one and use it everywhere.
Field naming conventions:
- Use snake_case for JSON fields — it's the most common convention and works well across languages. Stripe, GitHub, and most major APIs use it.
- Be consistent with date formats. Use ISO 8601 everywhere:
2026-03-25T14:30:00Z. Never use Unix timestamps in external APIs — humans can't read them, and timezone handling becomes a nightmare. - Use null, not missing keys, for absent values. If a field can be empty, include it with
nullrather than omitting it. This makes it clear the field exists in the schema but has no value. - Return created/updated resources in mutation responses. After a POST or PATCH, return the full resource so the client doesn't need a follow-up GET.
Filtering and searching patterns:
# Simple equality filters
GET /users?status=active&role=admin
# Range filters
GET /orders?min_total=100&max_total=500
GET /events?start_after=2026-03-01&start_before=2026-04-01
# Full-text search
GET /products?q=wireless+headphones
# Sorting
GET /users?sort=created_at # ascending (default)
GET /users?sort=-created_at # descending (prefix with -)
GET /users?sort=last_name,-created_at # multiple fieldsPartial responses (field selection):
# Return only specific fields
GET /users/123?fields=id,name,email
# Or use GraphQL-style approach
GET /users/123?include=profile,ordersThis reduces bandwidth and improves performance, especially for mobile clients or endpoints with large resource objects.
10. Documentation and Developer Experience: Your API Is Only as Good as Its Docs
You can design the most elegant API in the world, but if developers can't figure out how to use it, it doesn't exist. Documentation isn't an afterthought — it's a core feature of your API product.
What your API documentation must include:
- Getting started guide. A new developer should be able to make their first successful API call within 5 minutes. Include authentication setup, a sample request/response, and common use cases.
- Authentication walkthrough. Don't just list the auth methods — walk through the entire flow with code examples. Show how to obtain tokens, refresh them, and handle expiration.
- Endpoint reference. Every endpoint needs: URL, method, description, required/optional parameters, request body schema, response schema, example request, example response, and possible error codes.
- Error code glossary. List every error code your API can return, what it means, and how to resolve it. Stripe's error documentation is the gold standard here.
- Rate limit documentation. Explain your limits, how to check them, and what happens when they're exceeded.
- Changelog. Track every change, deprecation, and new feature. Developers need to know what's changed since they last integrated.
Tools that make documentation easier:
- OpenAPI (Swagger): Define your API as a spec, then generate interactive docs, client SDKs, and server stubs automatically. The OpenAPI 3.1 spec is now fully compatible with JSON Schema, making it more powerful than ever.
- Postman Collections: Share a ready-to-use collection that developers can import and start testing immediately.
- Redoc or Stoplight: Beautiful, responsive API docs generated from OpenAPI specs.
Developer experience tips that set you apart:
- Provide SDKs in popular languages. A Python SDK, a Node.js SDK, and a cURL example cover 90% of developers.
- Include a sandbox/test environment. Let developers experiment without affecting production data. Stripe's test mode (with
sk_test_keys) is the model to follow. - Offer webhooks for async events. Instead of forcing clients to poll for changes, push events to them. Document the webhook payload, retry policy, and signature verification.
- Provide idempotency keys. Allow clients to safely retry POST requests by including an
Idempotency-Keyheader. If a request with the same key is received twice, return the original response without processing the operation again.
11. 7 Common REST API Design Mistakes (and How to Avoid Them)
Even experienced developers make these mistakes. Here's what to watch for in your own APIs:
Mistake 1: Ignoring HATEOAS entirely. HATEOAS (Hypermedia as the Engine of Application State) means including links in responses that tell the client what actions are available. You don't need to implement full HATEOAS, but including a self link and relevant action URLs makes your API more navigable:
{
"id": 123,
"name": "Jane Smith",
"_links": {
"self": "/users/123",
"orders": "/users/123/orders",
"update": "/users/123",
"delete": "/users/123"
}
}Mistake 2: Chatty APIs that require multiple round trips. If fetching a user's profile page requires calls to /users/123, /users/123/orders, /users/123/notifications, and /users/123/preferences — you've designed a chatty API. Consider a composite endpoint or allow field expansion: GET /users/123?expand=orders,preferences.
Mistake 3: Using the wrong HTTP method. POST for everything is not REST. GET requests that modify data break caching and browser behavior. DELETE requests that require a body are controversial and poorly supported by some HTTP clients.
Mistake 4: Inconsistent naming across endpoints. If /users returns created_at but /orders returns createdAt, you've just made every client developer's life harder. Establish a style guide and enforce it with linters.
Mistake 5: No support for bulk operations. If a client needs to create 100 items, making 100 separate POST requests is brutal. Provide batch endpoints:
POST /users/batch
{
"users": [
{ "name": "User 1", "email": "user1@example.com" },
{ "name": "User 2", "email": "user2@example.com" }
]
}Mistake 6: Breaking backward compatibility. Removing fields, changing types, renaming properties, or altering response structures without versioning breaks existing clients. Add new fields freely, but never remove or rename existing ones without a new version.
Mistake 7: Not treating your API as a product. The best APIs have product managers, user research (developer feedback), roadmaps, and changelogs. If you're just tacking endpoints onto your backend as an afterthought, it shows.
12. Learning From the Best: APIs Worth Studying
The best way to internalize good API design is to study APIs that are universally praised by developers. Here are the ones worth spending time with:
Stripe: Widely considered the gold standard in API design. Key lessons: consistent resource naming, excellent error messages with documentation links, idempotency keys, test/live mode separation, and versioning that preserves backward compatibility for years. Their API documentation is so good that developers have cited it as a reason for choosing Stripe over competitors.
GitHub: One of the most well-documented and developer-friendly APIs. Key lessons: HATEOAS-style links in responses, comprehensive webhook system, excellent rate limit headers, and support for both REST and GraphQL. Their API versioning through date-based headers (X-GitHub-Api-Version: 2022-11-28) is a creative approach worth studying.
Twilio: Built an entire business on API developer experience. Key lessons: clear resource hierarchy (Accounts → Messages, Calls), pagination with next/previous page URLs, webhook-first architecture for async events, and truly exceptional quickstart guides that get developers from zero to a working integration in minutes.
Slack: Their Web API demonstrates how to handle a complex domain with a clean API. Key lessons: clear scoping and permissions model, event-based webhooks (Events API), interactive components, and real-time messaging (WebSocket-based RTM API alongside the REST API).
What these APIs have in common:
- Obsessive consistency — every endpoint follows the same patterns
- Excellent error messages that help developers fix problems
- Interactive documentation with real examples
- SDKs in every major language
- Sandbox environments for safe testing
- Transparent rate limiting with clear headers
- Comprehensive changelogs and migration guides
Spend an afternoon building a small integration with each of these APIs. You'll learn more about API design from the experience than from any article — including this one.
13. The REST API Design Checklist: Ship With Confidence
Before you launch your API (or your next version), run through this checklist. Print it, bookmark it, tape it to your monitor — whatever keeps it in front of you during development.
URL Design:
- ☐ Resource URLs use plural nouns (not verbs)
- ☐ URLs use kebab-case for multi-word resources
- ☐ Nesting is limited to two levels maximum
- ☐ Query parameters handle filtering, sorting, and pagination
- ☐ Trailing slashes are handled consistently
HTTP Methods & Status Codes:
- ☐ GET, POST, PUT, PATCH, DELETE are used correctly
- ☐ GET requests have no side effects
- ☐ POST returns 201 with a Location header
- ☐ DELETE returns 204
- ☐ Appropriate 4xx codes for client errors
- ☐ 429 with Retry-After for rate limits
Error Handling:
- ☐ Consistent error response format across all endpoints
- ☐ Machine-readable error codes
- ☐ Human-readable error messages
- ☐ Field-level validation details
- ☐ Request IDs for debugging
- ☐ No leaked stack traces or internal details
Pagination:
- ☐ All collection endpoints are paginated
- ☐ Default and maximum limits are enforced
- ☐ Response includes total count and navigation links
Security:
- ☐ HTTPS only
- ☐ Authentication on all non-public endpoints
- ☐ Rate limiting on all endpoints
- ☐ Input validation on all inputs
- ☐ CORS configured correctly
- ☐ Sensitive data excluded from URLs and logs
Documentation:
- ☐ Getting started guide with 5-minute quickstart
- ☐ Complete endpoint reference with examples
- ☐ Error code glossary
- ☐ Authentication walkthrough
- ☐ Changelog maintained
- ☐ OpenAPI spec published
Developer Experience:
- ☐ SDKs in popular languages
- ☐ Sandbox/test environment available
- ☐ Webhooks for async events
- ☐ Idempotency support for POST requests
- ☐ API versioning from day one
You don't need to check every box before launching, but the more you check, the more developers will love working with your API. Start with the fundamentals — clean URLs, correct status codes, consistent errors, and basic docs — and iterate from there. The best APIs aren't designed in a single sprint. They're refined over years based on real developer feedback.
Now stop reading about API design and go build one. The best way to learn these patterns is to ship an API, get feedback, and iterate. Your future self (and every developer who integrates with your API) will thank you.