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 than 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 mockup what snippet of what our documentation might look like for a call that allows us to create a coffee bean. Create Coffee Bean Description :This will create a coffee bean resource Endpoint : /coffee-beans Method: Post Request Body Schema: application/json Fields : 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 purchase Request example { : , : , : : , : , : } Response:HTTP code: Content/Type: application/json Response example: { : { : , : , brazil roast dark limited quantity "type" "arabica" "region" "brazil" "roast" "dark" "limited" true "quantity" 10 201 "coffee_bean" "id" "8ed6b848-150f-490e-903c-e6f6042dbe6b" "type" "arabica" "region: " ", " ": " ", " ": true, " ": 10 } } Let's dissect each part of the documentation. , 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. First This is a great starting off point if this is all of our create calls did. If there are available query parameters or specific authentication headers that need to be set these should be documented. , 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. Second Below fields, we show a …while maybe not necessary it may be useful to provide an example to clear up any confusion one might have. example request the 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 resource that confirms our request input was valid. The response returns all of the information we provided in addition to a unique ID for this coffee bean. Third, Response 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, , is which resource your API path is modeling. Uniform Resource Identifiers Let's take our example. This API exposes a resource of a as we know it has a type, a region, a roast, etc so we know that can we manipulate a . That being said our URI should model the resource, not an action — we do not want calls such as . Our documentation will then address in what ways you can manipulate said resource. coffee-bean coffee-bean coffee-bean /create-coffee-bean Below is an example of how 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 the 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 instead of underscores . coffee-bean - _ Consistent Types No matter what calls we are making on a resource, the types from our requests and responses should be consistent. Let's revisit the call we know that the field quantity is of . This int type should be consistent in the request/response across all calls on the coffee bean resource. While it may seem innocent and minor to have the be returned as in another call, this can lead to possible issues and bugs for developers interacting with your API. create coffee bean int quantity string Another consistency that should be achieved is in the returns top-level node. Let's take two calls Get Coffee Bean and List Coffee Beans. These will return two different resources get will return a single coffee bean while the list will return many. So the responses should look like this: // Get Coffee Bean { : {} } // List Coffee Beans { : [{},{}] } "coffee_bean" "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, let's 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 our errors will always have a top-level node of and will always return the HTTP status code, a message explaining what has occurred, a unique error code, and which resource this happens in. error The benefit to having all errors be returned 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 plays 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. https://jsonapi.org/ 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 https://www.openapis.org/ https://github.com/OAI/OpenAPI-Specification https://swagger.io https://stoplight.io Also published at https://medium.com/swlh/what-makes-a-good-api-7126d1630f96