How to Write API Errors That Keep Your Users Moving Forward

The secret to good DX is doing the right thing when things go wrong. Most error messages are a dead end, telling what went wrong but offering no help. The best error messages feel more like guardrails, gently guiding the developer back onto the path and keeping things moving forward.

This guide will focus mostly on RESTful APIs, however they should be applicable to any sort of error thrown by a dev tool.

(If you're more of a show-don't-tell kind of person, you can just browse all our error codes!)

What a good API error response looks like

Error messages from APIs tend to look a bit like this:

{"error": "Not Found", "description": "Version not found", "errors": null }

It's not wrong, but... it's not very helpful. We're going to aim to make them look a bit more like this:

{
  "error": "VERSION_FORK_NOT_FOUND",
  "message": "We couldn't find the version (1.0.2) you're trying to fork from",
  "suggestion": "You need to pass an existing version (1.0.0, 1.0.1) in via the `for` parameter",
  "docs": "https://developer.example.com/logs/1a70daae-c1a7-11ea-b3de-0242ac130004",
  "help": "If you need help, email support@readme.io and mention log '1a70daae-c1a7-11ea-b3de-0242ac130004'.",
  "poem": [
    "This request unfortunately failed",
    "But hey, I guess that's life",
    "We couldn't find your version fork",
    "Or your version spoon or version knife"
  ]
}

Let's break it down.

error: A unique ID for every error message

"error": "VERSION_FORK_NOT_FOUND"

The nice thing about RESTful APIs is they come with built-in status codes. But the codes mean so many different things in different situations, and some are general enough that they don't really help. Sure, 401 means the user is unauthorized, but why? Is their API key invalid, or is it missing? Or maybe you had a security breach and rotated everyone's API keys? We know 404 is Not Found, but it doesn't specify what couldn't be found.

Normally, APIs rely on error messages like Not Found or Bad Request as the identifier. That makes it hard to handle specific errors in the code, so it's good to include something more unique and specific.

You want people to know they can check for these in the code. Status codes are helpful but too vague sometimes, and error messages meant for humans tend to be a bit long to check for in code. These are meant to be referenced by the code, so they should be short and immutable.

message: Descriptive message of what went wrong

"message": "We couldn't find the version (1.0.2) you're trying to fork from"

Don't just explain what went wrong... tailor it to the user. Be as verbose as possible in diagnosing the error, so that the person reading the message knew exactly what's going on. The error above is for computers; this message is for all the humans out there!

In our API errors, we have the ability for us to include variables. So rather than just saying something like “the version you specified...”, we do our best to do things like show them what we’re seeing (such as "the version you specified (v1.0)..."). Here's some examples:

  • When we say your API key isn’t right, we show you a few characters of what we're seeing you sent us to help with debugging (Such as "Your API key that starts with X341 is invalid")
  • If you don’t have permission to do something, we do our best to tell you who does
  • When you pick a value that doesn’t exist, we list the available things that do exist ("...you need to pick an existing version (1.0, 1.1, 1.2)...")

suggestion: How to fix it

"suggestion": "You need to pass an existing version (1.0.0, 1.0.1) in via the `for` parameter"

Most of the time, error messages focus on the problem and leaves it at that. How negative!

They dutifully report what went wrong, which makes sense… but they usually stop just short of telling us what we actually want to know, which is how to fix the problem. That’s a job for Stack Overflow or something, I suppose.

Always have a suggestion field for every error message. While the message field answers “what’s wrong”, the suggestion answers “how to fix it”. We don’t want our users to have to do much detective work when they hit an error. We also do our best to be specific, like linking directly to the correct docs page or telling them what to change.

If there's an issue with their API key, tell them where to find their proper API key. If they formatted something wrong, tell them how to format it correctly. If they're missing a parameter, show them the docs for that parameter.

Bonus: As you're writing these out, you'll start to notice something: "wait, I can just fix this on the backend!" Rather than telling the user to send the API key as the password rather than the user via Basic Auth, just accept it in both places. Rather than telling the user to pass in a Content Type, assume it's JSON if your API only supports JSON. Good error messages are awesome, but skipping error messages completely is even better.

docs: A link to more information

"docs": "https://developer.example.com/logs/1a70daae-c1a7-11ea-b3de-0242ac130004"

In every one of our errors, we include a link to the documentation. And not just any documentation! It's a specific link to docs for the endpoint, and has the user's full request embedded right in it.

So, not only can the user see everything about the endpoint they're dealing with, they can also see the exact request they made. They can replay it in the UI, see what incorrect data they sent, and share the link with our support team to get more help.

Even if you don't have the ability to embed data into the docs, it's still a good habit to include a link for every single error. It saves people from Googling, gives a clear next step for people who are stuck, and ensures there actually is a doc associated with each error.

(If this seems cool, check out API Metrics!)

help: How to contact you

"help": "If you need help, email support@readme.io and mention log '1a70daae-c1a7-11ea-b3de-0242ac130004'."

Have some sort of way to contact you. We include our email address and a way to reference each log (for example, "If you need help, email support@readme.io and mention log '1a70daae-c1a7-11ea-b3de-0242ac130004'").

You want to give users as many ways as possible to fix their problem. Developers tend to prefer to figure things out themselves, however giving them explicit permission to email you will go a long way toward resolving issues quickly!

poem: Err on the side of whimsy

"poem": [
  "This request unfortunately failed",
  "But hey, I guess that's life",
  "We couldn't find your version fork",
  "Or your version spoon or version knife"
]

Okay, you don't need this one... but one of our core values at ReadMe is to err on the side of whimsy, and what better place to live up to it than in an error? So we wrote a unique poem for each error message:

If you're interested, here's a list of all our error poems.

That's a wrap!

Here's a recap of what your error messages should contain (poem optional!)

  • error: Unique code for every error message
  • message: Descriptive messages of what went wrong
  • suggestion: How to fix the issue
  • docs: Link to more information
  • help: How to contact you
  • poem: Our company-mandated dose of whimsy!

Hopefully your users never see errors. But when they inevitably do, they'll appreciate you put in the time to make the experience as pleasant and possible.

Stay In-Touch

Want to hear from us about APIs, documentation, DX and what's new at ReadMe?