REST API Design Principles
REST isn't a specification — it's a set of architectural constraints. Understanding those constraints helps you build APIs that are intuitive, consistent, and easy to evolve over time.
Published April 14, 2026REST (Representational State Transfer) was defined by Roy Fielding in his 2000 dissertation as a set of constraints for distributed hypermedia systems. In practice, "REST API" now means an HTTP API that follows certain conventions around URLs, HTTP methods, and response formats. The conventions exist for good reasons — understanding them makes you a better API designer.
Think in resources, not actions
The fundamental REST idea is that your API exposes resources (nouns), not operations (verbs). The HTTP method carries the verb. Compare:
# Not REST
POST /getUser
POST /createUser
POST /deleteUser
# REST
GET /users/42
POST /users
DELETE /users/42
Resources are plural nouns. Collections (/users) and items (/users/42) are the two patterns. Nested resources express relationships: /users/42/orders is the order collection belonging to user 42.
HTTP methods and their semantics
Use the right method for the right operation. This isn't arbitrary style — clients and infrastructure (caches, proxies) behave differently based on method.
- GET — retrieve a resource. Must be safe (no side effects) and idempotent.
- POST — create a new resource, or trigger a process. Not idempotent.
- PUT — replace a resource entirely. Idempotent: sending it twice has the same result as once.
- PATCH — partially update a resource. Sends only the fields to change.
- DELETE — remove a resource. Idempotent.
The biggest mistake is overloading POST for everything. A second POST for a resource creation could create a duplicate; a second PUT won't, because it's replacing what's already there.
Status codes are part of the interface
Return meaningful HTTP status codes. Clients shouldn't have to parse a body to know whether a request succeeded.
200 OK - successful GET, PUT, PATCH
201 Created - successful POST that created something
204 No Content - successful DELETE (no body)
400 Bad Request - invalid input from the client
401 Unauthorized - not authenticated
403 Forbidden - authenticated but not allowed
404 Not Found - resource doesn't exist
409 Conflict - duplicate, or business rule violation
422 Unprocessable Entity - validation failed (common in APIs)
500 Internal Server Error - something broke on the server
Never return 200 with an error message in the body. That breaks every HTTP client library, every log aggregator, and every monitoring tool that parses status codes.
Response shape consistency
Pick one response envelope and stick to it everywhere. A common pattern:
{
"data": { "id": 42, "name": "Jane Smith", "email": "[email protected]" },
"meta": { "requestId": "abc-123" }
}
// Error response
{
"error": {
"code": "VALIDATION_FAILED",
"message": "Email address is invalid.",
"fields": { "email": "Must be a valid email address." }
}
}
Consistency matters more than any particular shape. An API that returns arrays sometimes and objects other times, or uses error vs errors vs message inconsistently, is painful to integrate.
Versioning your API
APIs change. If you have external consumers, you need a versioning strategy before your first public release.
URL versioning is the most common and most visible approach: /v1/users, /v2/users. Easy to route, easy to test in a browser, easy to deprecate by redirecting. The downside is that the version leaks into every URL.
Header versioning keeps URLs clean: Accept: application/vnd.myapi.v2+json. Purists prefer this because the URL represents the resource, not the API version. Harder to test without curl or Postman.
Whichever you choose: don't break existing clients without deprecating the old version first, document what changed, and give consumers a migration window measured in months, not days.
Pagination, filtering, and sorting
Collections should never return unbounded lists. Use cursor-based or offset-based pagination consistently:
GET /users?page=2&per_page=50
GET /users?cursor=eyJpZCI6MTAwfQ&limit=50
Expose filtering and sorting via query parameters: GET /users?role=admin&sort=created_at&order=desc. Document which fields are filterable and sortable — don't silently ignore unrecognised parameters.
What makes an API good to work with
Good API design is mostly about consistency and predictability. If you can read one endpoint and correctly guess how five others behave, the API is well-designed. Surprising behaviour — a DELETE that returns 200 with a body, a POST that creates but returns 200 instead of 201, error formats that vary by endpoint — creates integration bugs and wastes consumer time.
Write your own client for your own API before releasing it. If anything feels awkward, it's a design problem, not a documentation problem.