Abhilash Meesala

Designing RESTful API’s that don't suck

REST is an architectural style and not a strict standard. This has led to subjective interpretations on REST API design ranging from strict academic dogmatic approaches to developer friendly pragmatic approaches.

My goal with this post is to describe best practices to design RESTful API’s that don’t suck. I tried to approach the design through API consumer point of view; of course this means that the design may not always satisfy a ‘standard’ if it doesn’t feel right from a consumer’s perspective, but this approach will definitely make API consumers life easy.

Resources

nouns (not verbs !)

Use nouns for a resource. Sometimes your internal models may not map to resources exactly, in those cases try to use nouns that make sense from an API consumer perspective. The key here is that your API consumer shouldn’t know irrelevant internal implementation details through your API.

In cases where you perform some calculation or conversions it does make sense to use verbs, for example a calculator api might expose /calculate as a resource which is perfectly fine too. The base line is that choose nouns where they make sense.

How many resources ?

Don’t abstract at the highest level, at the same time don’t try to be too concrete.

For example, modelling everything under the bookstore as /items would be abstracting at the highest level while modelling /articles , /stories , /papers , /comics might be an overkill. Aim for keeping the number of resources between 12-24.

Base URL’s per resource

There should only be 2 base URL’s per resource; one to deal with collection and the second one is for dealing with a specific element in the collection

/books : deals with collection

/books/9876 : deals with a specific element with ID 9876 in the collection

Keeping verbs out of your base URL’s

Always try to delegate verbs using HTTP verbs. Consider a bookstore example, the resource /getBooks mixes verb(get) and noun to get a collection of books. Things get more ugly when we are trying to update or delete the collection

DELETE /getBooks

Though original url appears good and intuitive, using it with a different HTTP verb is counter intuitive and might confuse the user. Delegating the verb to use HTTP verbs is a much cleaner solution

GET /books            get's collection of books
DELETE /books         delete's the collection
POST /books           creates the collection

What about actions that doesn’t fit in to HTTP verbs ?

You can always restructure the action to appear like a field of a resource or treat it like a sub-resource. For example, ‘star’ action could be mapped to a boolean field and can be updated via a PUT or PATCH or can be updated via

PUT /stories/9876/star
DELETE /stories/9876/star

If there’s no way that you can map an action to fit into a sensible restful structure thats fine too. Do whats right from API consumer perspective.

Singular or Plural nouns ?

Use either plural or singular nouns, but never both. Always be consistent on the convention that you follow throughout your API.

Associations

API’s should be very intuitive when developing for associations.

GET /authors/9876/books intuitively suggests that its requesting books by author with an ID 9876. There is two levels of traversal here, one for the author and one for books. Make sure that you don’t cross more than three levels.

In cases where you need to model complex associations beyond 2-3 levels sweep the complexity behind the query parameters ‘?’

GET /authors/9876/collections/1234/lang/en/genre/fiction can always be modelled as GET /authors/1987/collections/1234?lang=en&genre=fiction

What goes into query string ?

There’s no hard and fast rule. Any value objects or optional states which might not have an ID associated with them can go in query string.

Versioning

Always version your API, never release an API with out a version flag. Versioning allows major API changes from breaking older applications. Two options exist for versioning an API, through a header or through url.

Versioning through URL /v1/accounts/1234/balancesheets/30

Versioning through Headers X-Api-Version: 2010-10-10

Academically speaking version should be in the header but Personally, I’m in favour of putting the version in the URL - specifying the version with a simple v’x’ notation in URL is simple and forces the user to always hit a versioned URL. URL versioning has a major benefit that it makes easier to explore the API through browser based agents.

Partial Responses

Partial responses help in cutting down bandwidth usage, especially for mobile devices.

You can support partial response by providing an optional ‘fields’ parameter which when present returns only the requested fields.

GET /books/1234 might return entire information about the book which may be not be needed always.

GET /books/1234?fields=name,description,author

Providing fields parameter enables you to request only required information. It is essential that the ‘fields’ parameter is made optional, as we shouldn’t expect user to request explicit fields on every request.

Pagination

Pagination is a necessity. Here are three simple approaches to handle pagination.

/books?offset=3&limit=5
/books?page=4&pagesize=10
/books?start=10&count=10

All the above three are semantically same, they only differ by naming convention. I personally recommend using offset and limit because it is sort of a standard. If pagination parameters are not given, default to a sensible limit.

Add metadata with each paginated response indicating the total number of records available, number of results returned and current page index etc.


{
    ....
    metadata : {
        "totalRecords" : 1200,
        "currentPageSize" : 60,
        "currentPageIndex" : 2
    }
}

Link header introduced by RFC 5988 is quickly gaining adoption and is another way to provide pagination the right way today. However Link header might not be a complete solution in situations where a developer would like to know about number of results available and have manual control over offsets,limits and number of results.

Error format

From the perspective of the API consumer, everything at the other side of that interface is a black box. Providing meaningful error responses in a known consumable format will make developers life easy.

The API should always return sensible HTTP status codes. There are only 3 types of errors that can happen between the app and the api

success : 2xx series

client error : 4xx series

server error : 5xxx series

Don’t’ try to use all the possible http status codes, developer should be familiar with the status codes that you return. The following are a viable subset of error codes that most of the developers are acquainted with.

200 OK
201 Created
304 Not Modified
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
405 Method Not Allowed
410 Gone
412 Precondition failed
415 Unsupported Media type
500 Internal Server Error
503 Service Unavailable

There may be few cases where a client intercepts http error codes before they are handed over to application. provide an optional suppress_response_code parameter which when provided always sends a 200 OK response with appropriate error status code embedded in the response.

DELETE /books/1234 401 UNAUTHORIZED
DELETE /books/1234?supress_response_code=true 200 OK

Ensure that your message payload is as descriptive as possible, It should atleast contain

http_code: optional http code which can be populated when supress_response_code is present
code: error_code
message: short detail
description: more info /url where more information on this error can be found

Response formats

The API should support multiple formats. There are two prominent approaches to specify format

Specifying format in the URL

GET /books?type=json
GET /books.json

Through ‘Accept’ header

GET /books
Accept: application/json

Supporting both the approaches is usually not a good idea since it causes confusion over which one takes priority. What ever be the approach you choose, stick to it uniformly. Always default to JSON when a format is not specified.

Response

Assuming that the API defaults to JSON response and is consumed by Javascript, the right thing to do is follow JavaScript naming conventions for fields in the response.

Any updates/ creation should always return a complete resource representation, it avoids second hit to a GET api.

Provide an option to pretty print the response through an optional boolean ‘pretty’ request parameter.

It’s not uncommon to rate limit api’s so it makes sense to always return the rate limit metadata in the headers so that the developers can be aware of it. Ensure that you atleast send 3 fields indicating

X-Rate-Limit: API Limit
X-Rate-Limit-Remaining: API limit remaining
X-Rate-Limit-Reset: Number of seconds to reset

It’s a good practice to keep your API consumer informed.

SSL and Authentication

Always use SSL, No exceptions. It will make both API developers and consumers lives easier. It would also simplify Authentication efforts, you can get away with simple access tokens in most cases.

Use Standard compliant OAuth2 for authentication. Don’t try to create your own authentication protocols, stick to the standards on this one.

Never redirect a non-SSL API access to their SSL counterparts. Throw back a error message saying non-SSL API access is not allowed.

HTTP method overrides

Some HTTP clients can only work with GET and POST requests. Increase accessibility to these clients by providing X-HTTP-Method-Override header. Whenever the header is present treat the request type as the verb present in the header.

X-HTTP-Method-Override: DELETE

Never accept Method override header over a GET request, allow it only through a POST request if you must.

Documentation

An API is only as good as its documentation. The docs should show an example request/response cycle for each API. The documentation should cover every parameter of an API including any edge/conflicting cases clearly stating what would happen in each scenario. Provide user engagement on documentation through a comment system.

Bonus Points

Provide an alias to most frequently accessed API’s. It would help cut down number of API calls made and will also ease the developers life.

Provide a caching header if the response generated by an API is static over a period of time.

Consolidate all API requests under one easy to remember & intuitive subdomain. In most cases it would be api.site.com

To conclude, Think through API design from an API consumer perspective. It should be simple, consistent, intuitive and most importantly friendly to the developer