On a recent flight home across the country, in the wee hours of the night, I threw out a question to the Kin + Carta engineering staff in our Slack channel: “What are the most common poor practices that you’ve seen when consuming or extending enterprise APIs?” The responses started to roll in quickly. We were outside normal working hours, but people’s opinions, passion, and in some cases rage started to pour out. It was clear that I had struck a nerve.
What resulted was a great conversation about our mutual goals to educate the community about common pitfalls in enterprise API design and how to avoid them, and in turn, make all parties’ lives easier. The staff rattled off more than 30 poor practices, but I decided to pick the top 10 based on their broader relevance as they relate to avoiding poor API design.
#10: Implement pagination into large datasets from the beginning, even if it isn’t required on day one
If a GET call returns a list of data that may eventually grow so large that you might need to paginate it, even if you don’t need the pagination now, implement it from the get-go. And subsequently, when you’re implementing pagination, return the total count in every response — don’t make the consumer keep requesting additional pages until they no longer get the proper data back.
#9: Use proper HTTP methods and don’t let invocations have unforeseen side effects
For example, ensure that a GET request isn’t actually mutating the data. Use proper REST/HTTP method best practices and ensure that your API aligns with what all developers (besides yourself) would expect when hitting that endpoint.
#8: Use standard ISO dates
This might seem super simple, but unfortunately, it is far too common that dates are implemented in multiple ways across different sets of APIs. Use standard ISO date formats and remove the work for downstream consumers. Use a “liberal input, strict output” approach and accept input dates in multiple standard formats.
#7: Use static contracts
Developers love known entities and despise unforeseen unknowns. Thus, don’t make it so that API response formats differ based on statuses or results. For example, fields that don’t apply should be sent back as null. Be conscious to not let your input API break on absence of optional fields. Adopt a similar “liberal input, strict output” approach.
Make sure that no matter what happens, the user gets a response that matches his or her contract. If you catch an exception, don’t return a 500 with a stack-trace dump. Instead, return a 500 with a proper JSON object that indicates the error. This often requires putting a proxy such as Apache or NGINX in front of your API.
#6: Avoid inconsistent data models across different endpoints
As an example, an account entity in one endpoint should look like an account entity in adjacent endpoints. Don’t allow your models to be different because this will surely confuse the developers who consume your API. Be pragmatic and sensitive in cases where service-read clients require tabular formats and use a separate “R” service when CQRS (Command Query Responsibility Segregation) is not feasible.
#5: Don't try to save data transfer cost by using abbreviated field names. Just enable gzip software like a normal person
File this one away in the “don’t get too cute” category. HTTP provides a method and scheme for compression, so there is no need to invent your own along the way.
#4: Don’t do things like return HTML markup in an endpoint response
SMH. Bad. Just bad. Keep presentation logic out of your API at all times. Make no exceptions. Ever.
#3: Use HTTP status codes correctly
Don’t bury the status of an API call in the body of an API response. Use proper HTTP status codes to report successes and failures. Return only 200-range status codes when an API call is truly successful. Return 500-range status codes when things have failed. Use a 202 status code for unknown or partial successes and a 206 for partially completed successes in more complex cases.
#2: Leverage HTTP to maximize efficiency
Lean on HTTP mechanisms to identify ways to keep the communication chatter between client and server to a minimum. Use PATCH for partial updates and don’t return an entire object if only a single field has been updated.
Also, implement conditional GET availability to help consumers determine if a resource has been updated since the last time they fetched it. Look to leverage the HEAD method as an economical alternative to GET for scenarios wherein the user just needs to know if resource exists. Also, provide optional filters so that GET reduces the size of the output and server processing time, if possible.
#1: Have your API’s properly documented and provide sample responses for all endpoints
We’ll end with one that seems like a no-brainer, and yet far too often API consumers are left to guess or trial and error how to best understand an API due to an absence of proper documentation. Lean on the OpenAPI spec and leverage tools that provide OpenAPI support for your API.