The Authoritative Guide on REST API Conventions
...or something like that
Designing a REST API can be difficult. Some frameworks, like Ruby on Rails, come with built-in conventions, which help, but sometimes your framework is quite willing to let you shoot yourself in the foot. For developers using those frameworks, I'd like to offer the following, which is a collection of conventions/rules that will help you write an intuitive and consistent API. (For developers not using a framework - what's wrong with you?)
URLs should reference entities (nouns) and not actions (verbs).
- The action or verb should be defined by HTTP (GET, PUT, POST, DELETE)
Entity names in URLs should be plural.
- There are exceptions to this rule (e.g. when the entity name is not an actual entity that can be listed), but the exceptions should be rare.
Use GET for listing entities or reading a single entity.
- e.g.
GET /entities
returns a list of entites (list may be wrapped) - e.g.
GET /entities/123
returns a single entity with ID 123
- e.g.
Use PUT for idempotent entity changes.
- An operation is considered idempotent if it has the same result regardless of how many times it is executed. Think of idempotent as assignment or overwriting, e.g.
x = 1
orMap.put
- Generally applies to update operations
- e.g.
PUT /entities/123
updates entity with ID 123
- An operation is considered idempotent if it has the same result regardless of how many times it is executed. Think of idempotent as assignment or overwriting, e.g.
Use POST for non-idempotent entity creations/changes.
- An operation that is not idempotent is one that causes changes each time it is called, e.g.
x++
orList.add
- Generally applies to create operations
- e.g.
POST /entities
creates a new entity
- An operation that is not idempotent is one that causes changes each time it is called, e.g.
Use DELETE for deleting entities.
- e.g.
DELETE /entities/123
deletes entity with ID 123
- e.g.
Entity names should correspond to a particular object type.
- This means that return types should be the same when the last entity name in a URL is the same.
- e.g.
/burgers
and/burgers?meat=beef
and/restaurants/123/burgers
should all return the same object type.
The response body for a GET (singular entity) should be a valid request body for a PUT at the same URL.
- e.g. If Fleet object type is returned from
GET /fleets/123
, then Fleet object type should be accepted byPUT /fleets/123
(assuming that an update is supported)
- e.g. If Fleet object type is returned from
PUT and POST should accept data as request body in proper content type.
- i.e. Creations should be accomplished by POSTing JSON or XML (instead of name-value pairs or URL-encoded forms) in the request body.
Nested paths should be reserved for entity relationships or context, not for categorization.
- This means (1) a nested path should always contain an ID of the parent entity and (2) any level of a nested path should be a valid URL.
- e.g.
/admin/messages
should be/adminmessages
or just/messages
Nested paths should probably be limited to 1 or 2 levels.
- It’s generally easier to switch context than it is to maintain a full hierarchy with parent IDs.
- e.g.
/distributors/123/accounts/456/fleets/789/mobiles/321/pins/6
is certainly ridiculous when/mobiles/321/pins/6
is specific enough to get the same thing.
Filtering, sorting, and paging an entity list should be accomplished via URL parameters.
- e.g.
/mobiles?fleetId={fleetId}&page={num}&pageSize={num}&sort={propertyName}
- e.g.
Use consistent entity/parameter names when possible.
- Check existing API or other designs when you have a question about a name that may be used elsewhere.
- It’s ok to support multiple names of the same thing for the same call, but be consistent.
- e.g. If
?lat={lat}&lon={lon}
is already used somewhere, then don’t use?latitude={lat}&longitude={lon}
or?lat={lat}&lng={lon}
unless you already support the first option.
Err on the side of flexibility.
- e.g. Support both
/fleets/123/mobiles
and/mobiles?fleetId=123
- e.g. Support both
Version your API via the URL.
- This makes it easy for clients to ask for a specific version and helps you to segregate differences cleanly.
- e.g. Version 1 of your API should be available under
/v1
(or similar) with URLs like/v1/contacts
and/v1/teams/5/members
. Version 2 would then be available under/v2
.
Use appropriate HTTP status codes for error responses.
- e.g. An invalid request should return a status code in the 400s, and an unexpected server error should return a status code in the 500s.
All errors returned should use the same data structure in the body of the response.
- When errors occur, you need to give your client enough info about the error to know what went wrong and how to fix it - as long as the info does not present a security vulnerability.
- This means an invalid request like
GET /events?occurringBefore=never
does not return a response with a list of Event objects, it returns a response with an Error object (stating that "never" is not a valid value for the "occurringBefore" param). - Your Error response should probably include fields like an application-defined
code
(to help distinguish between specific problems that might return the same HTTP status), amessage
for the client program (does not need to support i18n), and auserMessage
for the end-user (does need to support i18n).
I also highly recommend dogfooding your own API (i.e write and use your own client for your API). Doing so will certainly help you weed out the confusing and difficult parts of your API.
Obviously, these guidelines don’t cover everything (e.g. consistent data structures across all lists, conventional formats for certain datatypes, how to do authentication e.g. OAuth, etc), but these are battle-tested and should serve as basic rules your API should conform to. As you're building and testing your API, you'll undoubtedly encounter questions that are not addressed here. When that happens, discuss with your team and add to your adopted conventions as necessary.
(Originally written Monday, February 25th, 2013)
Lost Turing to Eugene Goostman, I now just pretend to be human.