While a cup of coffee may seem like its two parts of hot water and coffee grinds there is much more to it. The same can be said for a well designed API. A good API does more then just taking input and returning output. It offers a few key items that make it enjoyable and easy to work with.

These few key items can sometimes be overlooked or not get the extra polish they deserve which detract from the API. Below we’ll touch upon these and how they can enhance the API experience.

Documentation

The first interaction any developer will have with an API is its documentation. Good documentation is informative and clear which allows developers to quickly start integrating with the API.

Let’s mock up what snippet of what our documentation might look like for a call that allows us to create a coffee bean.

Create Coffee BeanDescription :This will create a coffee bean resource
Endpoint : /coffee-beans
Method: Post

Request Body Schema: application/jsonFields : 
type     string  (required) : Type of coffee bean
region   string  (required) : Region from where coffee is from
roast    string  (required) : Type of roast for this coffee bean
limited  boolean (optional) : If this bean is a limited batch
quantity int     (optional) : Available bag quantity to purchaseRequest example
{
  "type"    : "arabica",
  "region"  : "brazil",
  "roast":  : "dark",
  "limited" : true,
  "quantity": 10
}

Response:HTTP code: 201
Content/Type: application/json
Response example:
{
  "coffee_bean": {
    "id": "8ed6b848-150f-490e-903c-e6f6042dbe6b",
    "type": "arabica",
    "region: "brazil",
    "roast": "dark",
    "limited": true,
    "quantity": 10
  }
}

Lets dissect each part of the documentation.

First, it starts with a clear name for this API call Create Coffee Bean . Then it is followed up with a description, endpoint, what format the request should be in, and the HTTP method. This gives us enough information to understand the purpose of this call and how we may want to use it.

This is a great starting off point if this is all of our create call did. If there are available query parameters or specific authentication headers that need to be set these should be documented.

Second, the fields section outlines what should and shouldn’t have to be set in the request call along with their respective types. We also do know this request has to be sent as a JSON thanks to the first section of the documentation.

Below fields we show a example request…while maybe not necessary it may be useful to provide an example to clear up any confusion one might have.

Third, the Response section informs us what kind of response(s) we could expect from the call. In the example above a create call will return a 201 with a coffee_bean resource that confirms out request input was valid. The response returns all of the information we provided in addition to a unique ID for this coffee bean.

Now while this response section is clear as to what you can expect for calls that work what if a call fails? If we had authentication requirements for the create call and those credentials were not supplied then a 401 response could be returned.

Returning all possible responses good or bad makes it clear what kind of behavior one would expect to encounter with the API…it offers consistency. It also always for developers to building in proper error handling within their integration with the API.

Good URI Design

A URI, Uniform Resource Identifiers, is which resource your API path is modeling.

Lets take our coffee-bean example. This API exposes a resource of a coffee-bean as we know it has a type, a region, a roast, etc so we know that can we manipulate a coffee-bean. That being said our URI should model the resource not an action — we do not want calls such as /create-coffee-bean. Our documentation will then address in what ways you can manipulate said resource.

Below is an example of how the you could manipulate the coffee-bean resource.

POST   /coffee-beans      Create Coffee Bean
GET    /coffee-beans      List all coffee beans
GET    /coffee-beans/{id} Get specific coffee bean based on ID
Patch  /coffee-beans/{id} Update specific coffee bean based on ID
Delete /coffee-beans/{id} Delete specific coffee bean based on ID

You will notice that our coffee-bean resource is plural. The rule of thumb here is resources should be pluralized unless they are a singleton resource. Another note URIs should always be lowercase and dashes - should be used in stead of underscores _.

Consistent Types

No matter what calls we are making on a resource the types from our requests and responses should be consistent.

Lets revisit the create coffee bean call we know that the field quantity is of int. This int type should be consistent from the in the request/response across all calls on the coffee bean resource. While it may seem innocent and minor to have the quantity be returned as string in the another call this can lead to possible issues and bugs for developers interacting with your API.

Another consistent that should be achieved is in the returns top level node. Lets take two calls Get Coffee Bean and List Coffee Beans. These will return two different resources get will return a single coffee bean while list will return many. So the responses should look like:

// Get Coffee Bean
{
  "coffee_bean": {}
}

// List Coffee Beans
{
  "coffee_beans": [{},{}]
}

Looking at these responses you can easily and quickly determine what resource was called. It also extends itself to easier integrations a developer can model an object and easily unmarshal these JSON responses.

Pagination

As more and more users interact with your API the amount of data they are storing will also grow. While this may not seem like a problem at first lets imagine that a user has created over 500 different coffee beans using our API.

This can put a heavy load on our API and not to mention it is a very large payload to return. Having your APIs list calls be paginated will help alleviate these issues and still allow for users to use your API without having to worry if their List request will time out or take too long.

Informative Errors

While we never want an API to not work properly or throw errors it is bound to happen. This is why an API should return consistent and informative errors.

Here we have a sample error response.

{
  "error": {
    "status_code": 401,
    "message": "unable to authorize request",
    "error_code": "er401auth",
    "resource": "coffee-bean",
   }
}

Here ours errors will always have a top level node of error and will always return the HTTP status code, a message explaining to what has occurred, a unique error code, and which resource this happen in.

The benefit to having all errors be return as JSON like this is as a developer this gives me all the information I may want for logging and error handling on the integration side. With error responses like this the integration can easily build in error checking and not worry that the API might throw an unknown error.

Final Thoughts

While each of these key items each play a role in having a good API there is one similarity across all of them. That similarity is consistency. Without consistency in an API it will be difficult to work with and not offer a stable integration.

There are still a few more recommendations I would like to share.

JSON API Specification

If you are designing your resources JSON structure I would recommend following a JSON specification. This will have your APIs JSON structure stay consistent with other public APIs and not throw developers for a loop when you use a JSON schema you came up with.

OpenAPI Specification/Documentation

The OpenAPI Specification (OAS) is a Linux Foundation Collaborative Project aimed to define standards for language agnostic interface for HTTP APIs. If you model your API to a validate OAS yaml/json you can then use this to generate documentation. There are various tools that make this easy the two I recommend would be Swagger or Stoplight.

There are also