Skip Navigation

What is your preferred API error response and why?

I prefer simplicity and using the first example but I'd be happy to hear other options. Here's a few examples:

 http
    
HTTP/1.1 403 POST /endpoint
{ "message": "Unauthorized access" }

  
 http
    
HTTP/1.1 403 POST /endpoint
Unauthorized access (no json)

  
 http
    
HTTP/1.1 403 POST /endpoint
{ "error": "Unauthorized access" }

  
 http
    
HTTP/1.1 403 POST /endpoint
{
  "code": "UNAUTHORIZED",
  "message": "Unauthorized access",
}

  
 http
    
HTTP/1.1 200 (🤡) POST /endpoint
{
  "error": true,
  "message": "Unauthorized access",
}

  
 http
    
HTTP/1.1 403 POST /endpoint
{
  "status": 403,
  "code": "UNAUTHORIZED",
  "message": "Unauthorized access",
}

  

Or your own example.

89 comments
  • I'm a data engineer, and have seen an ungodly ammount of 200-but-actually-no-stuff-is-broken errors and it's the bane of my life!

    We have generic code to handle pulling in api data, and transforming it. It's obviously check the status code, but any time an API implements this we have to choose between:

    • having code fail wierdly further down the line because can't parse the status
    • adding in some kind of insane if not response.ok or "actually no there's an error really" in response.content logic

    Every time you ignore protocols and invent your own, you are making everyone sad.

    Will take recommendations of support groups I can join for victims of terrible apis.

  • Anything but the last one. Don't duplicate the http code in the body, else you're now maintaining something you don't need to maintain.

    I'm not a fan of codes that repeat information in the body either, but I think if you had used a different example like "INVALID_BLAH" or something then the message covered what was invalid, then it would be fine. Like someone else said, the error data should be in an object as well, so that you don't have to use polymorphism to figure out whether it's an error or not. That also allows partially complete responses, e.g. data returns, along with an error.

  •  http
        
    HTTP/1.1 403 UNAUTHORIZED
    {
      "error": {
        "status": "UNAUTHORIZED",
        "message": "Unauthorized access",
      },
    }
    
      

    I would separate the status from the HTTP status.

    1. The HTTP status is great for reasonable default behaviours from clients.
    2. The application status can be used for adding more specific errors. (Is the access token expired, is your account blocked, is your organization blocked)

    Even if you don't need the status now, it is nice to have it if you want to add it in the future.

    You can use a string or an integer as the status code, string is probably a bit more convenient for easy readability.

    The message should be something that could be sent directly to the user, but mostly helpful to developers.

  • Is the last one real? Has any sane dev made something like that monstrosity? It's not like the others are any better, but who would ever think of doing the last one and why?

    • I have worked for financial institutions that have variations of the last one. If I saw it I wouldn't even blink. Semi realistic reasons might be:

      Status attribute - because the project is using the base library of [project whatever] which was the brain child of eNtErPrIsE aRcHiTeCt whose hands on skills are useless and the off-shore dev team who assigned [random newbie] because that's who was available at the time. They used a status attribute because they didn't know how to get the status of the http response. No-one with budget control is interested in hearing about technical debt at the moment. Everyone has to use it now else the poorly written test classes fail.

      Message code: because "we need codes that won't ever change even if the message does". Bonus points if this is, in fact, never used as intended and changes more frequently than...

      Message: "because we still need to put something human readable in the log". Bonus points x2 if this is localised to the location of the server rather than the locale of the request. Bonus x3 if this is what subsequent business logic is built on leading to obscure errors when the service is moved from AWS East Virginia to AWS London (requests to London returning "colour" instead of "color" break [pick any service you never thought would get broken by this]).

      I have seen it all etc

  • I really like the fifth one. So you might always get a surprise message in your response

  • A simple error code is sufficient in all of these cases. The error provided gives no additional information. There is no need for a body for these responses.

    • There may be a need for additional information, there just isn't any in these responses. Using a basic JSON schema like the Problem Details RFC provides a standard way to add that information if necessary. Error codes are also often too general to have an application specific meaning. For example, is a "400 bad request" response caused by a malformed payload, a syntactically valid but semantically invalid payload, or what? Hence you put some data in the response body.

      • A plain 400 without explanation is definitely not great UX. But for something like 403, not specifying the error may be intentional for security reasons.

  • Anything except the 2nd to last one, which is, unfortunately, mandated by my employer's internal code style guidelines. 🫠

89 comments