DevelopmentNovember 15, 2025

Oh Man, Look at Your API!

This is my small contribution to making the world a better place. It might be useful if you're building your own amazing API or if you have the patience to improve one that already exists. These tips come from my own painful experience with the APIs I've had to work with. Some of them made me hate them, others trained my intuition or even helped me develop a kind of paranormal power. And others made me burst into hysterical laughter. As you can see, there's a lot to tell, so let's get into it!

A good API is boring. No exceptions

Here you shouldn't try to invent anything. System behavior should be the same everywhere and at every stage. This applies both to the principles you use to build endpoints and to the input and output data formats. You need boring, standard error messages and request/response headers. Fields of each data type should always have the same input and output format. All empty fields should look the same. Programming an API is a lot like lying: the more you tangle things up around it, the more you (and your clients) will have to untangle later. In other words, the more boring you design your API, the happier your clients will be with it.

By the way, this is advice worth keeping in mind when designing your entire information system.

Documentation

We all know the most repeated advice is to document your API, and yet a lot of people avoid it. I get it, there never seems to be time for this kind of thing, but documentation is the face of your API. It might be worth setting aside the more interesting programming challenges to invest time in writing it. Here are some basic tips to do it well:

  • Always provide working examples of HTTP requests and responses, or real code.
  • The documentation should be as consistent in format as the API itself.
  • The core principles of your API should be described clearly and in detail.
  • Use API frameworks like Swagger, Apiary or Postman.

Remember that, without good documentation, using your amazing API turns into putting together a complicated puzzle.

Example of API documentation in Postman
Example of API documentation in Postman

Error handling

Even if an API has poor documentation, you can get pretty far with good error messages alone. As a rule, precise and clear error messages are key to the user experience. Even for the people developing the API, good error messages build more confidence in the work and help them react quickly to failures.

Poorly designed error:


HTTP/1.1 422 Unprocessable Entity{
- “error”: Invalid Attribute
}

Well designed error:


HTTP/1.1 422 Unprocessable Entity
Content-Type: application/vnd.api+json{
- "errors": [
- - {
- - - "status": "422",
- - - "source": { "pointer": "/data/attributes/first-name" },
- - - "title": "Invalid Attribute",
- - - "details": "First name must contain min three chars."
- - }
- ]
}

Authorization

Any kind of authorization works, as long as it's STANDARDIZED. Please don't play around with this! Working with authorization processes becomes a real problem if they have something quirky about them or are too weak. An example with OAuth: an API that generated a new refresh token every time the access token was refreshed, and also required the access token's expiration time. In my opinion, that's excessive. First, let your users decide what level of security they need and whether you can allow tokens that never expire. Second, keep the processes simple. Many good APIs use static refresh tokens. Secure isn't the same as confusing.

Data formats

Don't even think about inventing new data formats! I've seen APIs with their own proprietary formats, and they're always a disaster. Parsing your unique format slows work down enormously and turns it into a nightmare. Sure, I know it's hard to hold back your creativity when it overflows with odd ideas, and it's hard to discard them out of pure parental attachment to your creation, but you have to resist. We already have JSON, XML and the rest, right? That's enough.

Make use of specifications like JSON API. Stripe, Braintree and many other companies already use it successfully. JSON reads well, it's an easy-to-use structured data format, and the JSON API specification is, overall, well thought out.

Also, please don't support multiple data formats. Clients usually don't care which standard format is used, because every language and technology already knows how to generate and parse any standard type. Supporting multiple formats only slows down the maintenance of your API and its documentation.

Use HTTP status codes

It's a lot of fun! It's very confusing for a developer to receive internal API errors with an HTTP 200 status. This slows development down a lot. And in many cases HTTP status codes can double as an error message. Some common examples:


200 (OK) — success, handle responded data.
500 (Internal Server Error) — something went wrong, ask for API system technical support.
400 (Bad Request) — wrong API request, check documentation.
401 (Unauthorized) — wrong client credentials, recheck them or if API key token has expired, then refresh.
403 (Forbidden) — unpermitted action, current user does not have enough access rights.
404 (Not Found) — record not found, maybe it doesn’t exist anymore or wrong record ID was used.
405 (Method Not Allowed) — this API method is not allowed anymore.
406 (Not Acceptable) — not acceptable action, probably from attempt to save invalid data.

You can check the rest of the codes on Wikipedia, and I promise you'll always find a suitable one.

Content-type

This tip is very simple. Unless you have a specific reason to change it, the content type should always be the same. It helps keep the client architecture consistent and speeds up and simplifies development. Also avoid supporting multiple content types on the same endpoint, because it can confuse whoever works with it. For example, your endpoint "A" works fine, but endpoint "B" returns a Bad Request error even though you're using it in exactly the same way. Changing the content type doesn't seem like the obvious fix.

Object structure

From the client side, it always helps a lot to know which fields an entity actually has. Ideally, the API should have a specific endpoint that returns the complete field map of the entity. If not, at least make sure the object or list getter returns ALL fields, even if they're empty. There's no need for dynamism or brevity here.


{
- "id": "9c7fd1e5-97f0-4b6a-979c-1d7adf5bda16",
- "email": "example@gmail.com",
- "firstname": "John",
- "lastname": "Smith",
- "timezone": null,
- "phone_code": "RU",
- "phone": null,
- "created_at": "2018-03-27T05:40:31.929Z",
- "updated_at": "2018-04-08T09:24:39.683Z",
- "citizenship": null
}

Pagination

While working on an API connector for a well-known CRM, I discovered it had NO record pagination. When I asked technical support, they told me "unfortunately we haven't included it, but it's a good idea to implement." I found that unbelievable. I know we all know full well why we can't load all the data at once, so let me tell you what, in my view, perfect pagination would look like:

  • It's useful to be able to control the page size. That way the user decides for themselves the balance between the quantity and quality of requests.
  • Include meta information so you know the total number of records. If you're loading all the records, it helps a lot to see progress. Also, counting records shouldn't require loading every page — let's not waste time or money.
  • Clear and consistent names for pagination variables, for example: page_number, the current page number; page_size, the size of each page; pages_total_count, the total number of pages.

Images and files

It might seem obvious, but you shouldn't include file or image content inside the response data. It's much better to return a temporary download link.

Date and time

I want to stress how important it is to explicitly state which time zone you're using. It can be part of the time format or simply a note in the documentation. How to handle time zones deserves an article of its own, but, to keep it simple for now: you can return the date as an integer number of seconds since the Unix epoch, with the time converted to UTC. Each client can then convert it to their own format and time zone.


{
...
"created_at" => article.created_at.utc.to_i
}.to_json #=> { ..., created_at: 1523556863 }

Payment plans

Use HTTP status codes to let the client know when they try to use paid endpoints with a free or "insufficient" plan. Sometimes it's quite hard to understand what went wrong, and time gets wasted fighting with nothing while testing request formats. I suggest using HTTP code 402 Payment Required here. You can choose another one, but it needs to be clear what's happening. The API documentation should also explain these terms of use.

API limits

If you programmatically limit the number of requests to your API, I want to see a clear error message when that limit is reached. Ideally, there would be a specific endpoint with that information. Your clients shouldn't have to count their own requests — you already have that data. It's a good idea to show the available calls in the HTTP response headers. For example:


X-RateLimit-Limit - Request limit per hour
X-RateLimit-Remaining - The number of requests left for the time window

Example of API limits on Zoho CRM.

Logic

While developing your API, it's a very good idea to put yourself in the client's shoes. That way you'll spot a lot of useful things from the start. For example, I've seen APIs where, if you delete a record twice, you get the same response both times. In my opinion, that's wrong, because the client might mistakenly believe they deleted it again, or that they deleted a different record. If the record had already been deleted, it should return an HTTP 404 code to help your clients realize their mistake.

Another note on APIs

In my view, having odd endpoints can be a symptom of something badly designed in your project's architecture. So instead of jumping straight into building facades and adapters, take a breather and look around. There's a good chance you'll spot an opportunity for a refactoring pass that kills two birds with one stone: simplifying your API's interface and improving your project's structure.

Epilogue

I want to sincerely thank all the developers who build high-quality, easy-to-use APIs. I know you often do it driven by your own conscience and sense of aesthetics. At first glance, this effort doesn't bring obvious benefits, but I believe it pays off in the long run because it increases customer loyalty. All the best to everyone putting in the hard work on this difficult task!

Tell us about your case

Get in touch