A well-designed REST API is a joy to consume. A poorly designed one generates a constant stream of support tickets, client bugs, and integration headaches. The difference lies in applying a set of consistent, time-tested best practices that make your API intuitive, predictable, and maintainable. This guide covers the principles used by world-class APIs like Stripe, GitHub, and Twilio.
1. Use Nouns for Resources, Not Verbs
REST is resource-oriented. URLs should identify things (nouns), not actions (verbs). The HTTP method (GET, POST, PUT, DELETE) communicates the action:
# ❌ Verb-based URLs (RPC style) GET /getUser?id=42 POST /createUser POST /deleteUser?id=42 POST /updateUserEmail # ✅ Noun-based URLs (REST style) GET /users/42 # Retrieve user 42 POST /users # Create a new user DELETE /users/42 # Delete user 42 PATCH /users/42 # Update specific fields of user 42
2. Use Plural Nouns for Collection Endpoints
Always use plural nouns for resource collections. This is consistent and more natural when reading the URL — /users returns a collection of users, and /users/42 returns a single user:
GET /articles # List all articles POST /articles # Create a new article GET /articles/slug # Get a specific article PUT /articles/slug # Replace an article entirely PATCH /articles/slug # Update specific fields of an article DELETE /articles/slug # Delete an article
3. Use HTTP Methods Correctly
Use the semantically correct HTTP method for each operation:
- GET — Retrieve data. Must be safe (no side effects) and idempotent (calling it multiple times returns the same result).
- POST — Create a new resource. Not idempotent — calling it twice creates two resources.
- PUT — Replace an entire resource with the provided data. Idempotent.
- PATCH — Partially update a resource (send only the fields that changed). Not required to be idempotent.
- DELETE — Remove a resource. Idempotent — deleting an already-deleted resource should return 404, not an error.
4. Return Correct HTTP Status Codes
HTTP status codes communicate the result of an operation without the client needing to parse the response body. Using the wrong status code forces clients to implement extra logic and undermines the semantics of HTTP:
200 OK # Successful GET, PATCH, DELETE 201 Created # Successful POST (return the created resource) 204 No Content # Successful DELETE (no body to return) 400 Bad Request # Invalid request syntax or validation error 401 Unauthorized # Missing or invalid authentication credentials 403 Forbidden # Authenticated but not authorised for this resource 404 Not Found # Resource does not exist 409 Conflict # Conflict with current state (e.g., duplicate email) 422 Unprocessable # Valid syntax but semantic validation failed 429 Too Many Requests # Rate limit exceeded 500 Internal Server Error # Unexpected server-side error
Never return 200 OK with an error message in the body. If something went wrong, return an appropriate 4xx or 5xx status code.
5. Design Consistent Error Responses
Every error response should follow the same structure so clients can handle errors generically:
{
"error": {
"status": 422,
"code": "VALIDATION_ERROR",
"message": "The request data is invalid.",
"details": [
{
"field": "email",
"message": "Must be a valid email address."
},
{
"field": "password",
"message": "Must be at least 8 characters long."
}
]
}
}
6. Version Your API from Day One
APIs evolve. You will need to make breaking changes. If you don't version your API from the start, any breaking change will immediately break all existing integrations. The most common versioning strategies:
- URL versioning (most common):
/api/v1/users,/api/v2/users— Simple, explicit, and easy to test in a browser. - Header versioning:
Accept: application/vnd.myapi.v2+json— Keeps URLs clean but harder to test and cache. - Query parameter:
/api/users?version=2— Simple but non-standard.
URL versioning is recommended for most public APIs due to its simplicity and visibility. Increment the version number (v1 → v2) only for breaking changes. Additive changes (new fields, new endpoints) are not breaking changes.
7. Implement Pagination for Collections
Never return an unbounded list of resources. Always paginate collection endpoints and return pagination metadata in the response:
{
"data": [
{ "id": "usr_abc123", "name": "Alice" },
{ "id": "usr_def456", "name": "Bob" }
],
"pagination": {
"total": 847,
"page": 1,
"per_page": 20,
"next_cursor": "usr_def456",
"has_more": true
}
}
Cursor-based pagination (using the ID of the last item as a cursor) is preferred over offset pagination for large datasets because it remains consistent even when records are added or deleted between page requests.
8. Use HTTPS — Always
Every API should be served exclusively over HTTPS. This is non-negotiable. HTTP transmits all data — including authentication tokens, passwords, and sensitive user data — in plaintext that anyone on the network can read and modify.
9. Implement Rate Limiting and Return Rate Limit Headers
Rate limiting protects your API from abuse and ensures fair usage. Communicate the current rate limit state to clients via standard headers:
X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 734 X-RateLimit-Reset: 1718481600
When a client exceeds the rate limit, return 429 Too Many Requests with a Retry-After header indicating when they can retry.
10. Document Your API Thoroughly
An undocumented API is essentially unusable for external developers. Use the OpenAPI Specification (OAS, formerly Swagger) to document your API endpoints — it's an industry standard that enables automatic generation of interactive documentation, client SDKs, and API testing tools.
Work with JSON API Data Instantly
Format API responses for debugging or compress JSON payloads for production with our free tools.