REST API Guidelines
What is REST
REST is an acronym for REpresentational State Transfer — an architectural style for distributed hypermedia systems, first presented by Roy Fielding in his 2000 dissertation.
Like any architectural style, REST has its own 6 guiding constraints, which must all be satisfied for an interface to be called RESTful.
Guiding principles of REST
- Client-server — separating the user interface concerns from the data storage concerns improves portability of the UI across platforms and improves scalability by simplifying server components.
- Stateless — each request from the client to server must contain all the information needed to understand the request; session state is kept entirely on the client.
- Cacheable — responses must be labelled, implicitly or explicitly, as cacheable or non-cacheable, so a client cache can reuse cacheable responses for later equivalent requests.
- Uniform interface — a set of constraints (resource identification, manipulation through representations, self-descriptive messages, and hypermedia as the engine of application state) that simplify overall architecture and improve visibility.
- Layered system — components can only “see” the immediate layer they interact with, not beyond it.
- Code on demand (optional) — client functionality can be extended by downloading and executing code (applets, scripts), reducing what needs to be pre-implemented.
Best practices for implementing REST APIs
These practices apply regardless of stack or framework: key requirements, URL and action design, SSL, versioning, authentication, query parameters for filtering, field selection, resource representation on POST/PUT/PATCH, JSON vs XML, pagination, HTTP status codes, and documentation.
Key requirements
An API is a developer’s UI. It should:
- use web standards where they make sense.
- be friendly to the developer and be explorable.
- be simple, intuitive, and consistent, so adoption is easy and pleasant.
- provide enough flexibility to power the majority of the client UI.
- be efficient and scalable, while balancing the other requirements.
URLs and actions
Separate your API into logical resources, manipulated via HTTP methods (GET, POST, PUT, PATCH, DELETE) with specific meanings. A resource should be a noun, not a verb:
GET /users— retrieves a list of usersGET /users/12— retrieves user #12POST /users— creates a new userPUT /users/12— updates user #12PATCH /users/12— partially updates user #12DELETE /users/12— deletes user #12
Naming format
There’s no strict naming convention, but keep the URL structure clean and consistent —
always use a plural, even for a single instance, to avoid odd pluralization
(person/people, goose/geese) and to match how most frameworks natively handle a
/users and /users/12 pair under one controller.
Relations
If a relationship only exists within another resource, nest it:
GET /users/12/messages— messages for user #12GET /users/12/messages/5— message #5 for user #12POST /users/12/messages— create a message for user #12PUT /users/12/messages/5— update message #5 for user #12PATCH /users/12/messages/5— partially update message #5 for user #12DELETE /users/12/messages/5— delete message #5 for user #12
If a relationship can exist independently, just include an identifier for it in the resource’s representation, and let the consumer hit the relation’s own endpoint — unless it’s commonly requested alongside the resource, in which case consider embedding it to avoid a second round trip.
Actions that aren’t CRUD
- Restructure the action as a resource field, if it takes no parameters — e.g. map an
activate action to a boolean
activatedfield, updated viaPATCH /users/12/activate. - Treat it as a RESTful sub-resource — e.g. GitHub’s API lets you star a gist with
PUT /gists/:id/starand unstar withDELETE /gists/:id/star.
SSL
Always use SSL — one of the basic security measures in API design. Expose every endpoint via SSL.
Versioning
Always version your API. It helps you iterate faster, prevents invalid requests from
hitting updated endpoints, and smooths over major version transitions by letting you
keep old versions running for a period. Put the version number in the URL, e.g.
/v1/users/.
Authentication
A RESTful API should be stateless — authentication shouldn’t depend on cookies or sessions; each request should carry its own credentials.
With SSL everywhere, credentials can be simplified to a randomly generated access token
delivered in the username field of HTTP Basic Auth — fully browser-explorable, since the
browser prompts for credentials on a 401 Unauthorized.
Where it’s not practical to have users copy a token manually, use OAuth 2 instead, which provides secure token transfer to a third party via Bearer tokens, also over SSL.
Query parameters for advanced filtering
Keep base resource URLs lean; implement filtering, sorting, and searching as query parameters:
- Filtering — a unique query parameter per filterable field, e.g.
GET /users?status=active. - Sorting — a generic
sortparameter taking comma-separated fields, each with an optional leading-for descending order, e.g.GET /users?sort=-priorityorGET /users?sort=-priority,created_at. - Searching — a
qparameter for full-text search, passed straight to the search engine, with output in the same format as a normal list result.
Combined:
GET /users?sort=-updated_at— recently updated usersGET /users?status=closed&sort=-updated_at— recently closed usersGET /users?q=return&status=active&sort=-priority,created_at— highest-priority active users mentioning “return”
You can also package common filter combinations into aliased paths, e.g.
GET /users/recently_closed.
Return fields
Let consumers select which fields come back, via a fields query parameter taking a
comma-separated list — e.g. GET /users?fields=id,subject,customer_name,updated_at&status=active&sort=-updated_at.
Resource representation for POST, PUT, and PATCH
A PUT, POST, or PATCH call may modify fields beyond the ones provided (e.g.
created_at/updated_at timestamps). Return the updated (or created) representation as
part of the response, so the consumer doesn’t need a follow-up call. For a POST that
creates a resource, use 201 Created with a Location header pointing to the new
resource.
JSON and XML
Return JSON or XML — both easily parsable, with JSON recommended. Follow the target language’s naming conventions for field names: camelCase for C#/Java, snake_case for Python/Ruby.
Pagination
Use query parameters like page (page number) and pageSize (items per page). Return
the total record count and total page count, either in the body or via custom headers
like X-Total-Count / X-Total-Page, plus previous/next page URLs if useful.
HTTP status codes
- 200 OK — successful GET, PUT, PATCH, or DELETE (or a POST without creation).
- 201 Created — successful POST that creates a resource; pair with a
Locationheader. - 204 No Content — successful request with no response body (e.g. DELETE).
- 304 Not Modified — used with HTTP caching headers.
- 400 Bad Request — the request is malformed.
- 401 Unauthorized — missing or invalid authentication.
- 403 Forbidden — authenticated but not authorized for the resource.
- 404 Not Found — resource doesn’t exist.
- 405 Method Not Allowed — HTTP method not allowed for this user.
- 410 Gone — resource permanently unavailable (useful for retired API versions).
- 415 Unsupported Media Type — wrong content type on the request.
- 422 Unprocessable Entity — validation errors.
- 429 Too Many Requests — rate limited.
Documentation
An API is only as good as its documentation. Tools like Swagger help document REST APIs. Docs should show complete request/response examples. Once a public API ships, you’ve committed to not breaking it without notice — include deprecation schedules, and announce externally visible updates via a change-log or mailing list, ideally both.