mnot’s blog

Design depends largely on constraints.” — Charles Eames

Wednesday, 15 May 2013

Indicating Problems in HTTP APIs

A common part of HTTP-based APIs is telling the client that something has gone wrong. Most APIs do this in some fashion, whether they call it a “Fault” (very SOAP-y), “Error” or whatever.

Most of them define a new format for just this purpose; for examples, see Amazon’s, OpenStack’s, Twitter’s, Facebook’s, and SalesForce’s. Twitter's is fairly representative: 

{"errors":[{"message":"Sorry, that page does not exist","code":34}]}

Here, they associate a human-readable message and an error code with the error. That’s a good start, but how is it related to the HTTP status code? And, how am I supposed to know how to find that code?

Good HTTP APIs don’t make developers hunt through documentation to find things like this; self-documenting formats give is a  way to communicate these kinds of details in a way that’s easy to find.

Good HTTP APIs also use media types to indicate the format of the content, for similar reasons; however, most of these don’t, and as a result developers and tools again need to understand that they’re working with a particular API, rather than just examining the message.

Of course, there’s a good reason these formats are so casually defined; doing it the “right” way can be onerous, with a trip through IANA and the media type review list. Who wants to bother - especially when you have enough market power to force your users to suck it up?

I think we can do better. Clients shouldn’t have to pick through 30 slightly different formats and implement parsers specific to each one; it’s a waste of energy. People creating APIs shouldn’t have to guess what a good format looks like, only running into problems down the road. And they certainly shouldn’t have to register new media types for “Fault” formats of every API they create.

So, a while back I decided to come up with a generic format for indicating the details of a problem encountered in using an HTTP-based API. The current draft has an example:

   HTTP/1.1 403 Forbidden
   Content-Type: application/api-problem+json
   Content-Language: en

   {
    "problemType": "http://example.com/probs/out-of-credit",
    "title": "You do not have enough credit.",
    "detail": "Your current balance is 30, but that costs 50.",
    "problemInstance": "http://example.net/account/12345/msgs/abc",
    "balance": 30,
    "accounts": ["http://example.net/account/12345",
                 "http://example.net/account/67890"]
   }

Notice that there’s a firm association between the problem type and the HTTP status code it’s conveyed within; that the problem type is identified with a URL, allowing folks to look it up, reference it unambiguously, and even reuse it in other APIs. Also, see how the format is extensible, so that you can convey machine-readable details of a problem. The optional “problemInstance” property even gives a unique identifier for this occurrence of the problem, to allow your support folks to figure out what’s going on.

Current APIs can introduce this format in a backwards-compatible fashion using content negotiation; if the client includes “Accept: application/api-problem+json” in the request, you know that they understand the format. Likewise, client tools can now abstract out problems into a common interface, rather than forcing developers to dig through the format.

There’s also a healthy dose of advice about how to use HTTP well (“RESTfully”, if you must), and Erik Wilde has also included an XML-based format whose canonical data model is the JSON, for those APIs that still choose to use XML.

To be clear - this isn’t going to solve world hunger, but I do think that APIs that choose to adopt it will avoid a few headaches, and their users will appreciate not having quite so many API-specific special cases to deal with. After all, the whole point of using HTTP for APIs is to get as much leverage as we can out of shared code and concepts.

Please have a read through the draft; lots of folks have reviewed it, and I think it’s nearly ready, but I’d still love any feedback you have. I’m going to be pushing for its use in IETF HTTP-based APIs, as well as others I come into contact with.


Filed under: HTTP Protocol Design Standards Web Services

15 Comments

Matthew Kerwin ಠ_ಠ said:

Wonderful! I'd always wondered about statements like "If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity" [RFC 2616, §10.4.4] and just how to describe reasons in the entity. I will definitely be implementing this draft/spec in my APIs as the opportunities arise.

Wednesday, May 15 2013 at 10:56 AM +10:00

Ruben Verborgh said:

Great to see URLs as values. This makes me wonder: when will we see URLs as property keys, so we can look them up, too? This could eliminate the out-of-band information, such as a media type. Do you see a future in that?

(Yes, I deliberately didn't mention RDF or Turtle ;-)

Thursday, May 16 2013 at 3:19 AM +10:00

Larry Garfield said:

If you use PHP, there's a shiny new PHP library available via Composer for working with ApiProblem, too:

https://packagist.org/packages/crell/api-problem

(Disclaimer: Yes, I wrote it. :-) )

Thursday, May 16 2013 at 5:47 AM +10:00

Colin Taylor said:

I'm confused how is this consistent with the definition of 406 in HTTPbis ?

"If authentication credentials were provided in the request, the server considers them insufficient to grant access. The client should not repeat the request with the same credentials. The client may repeat the request with new or different credentials. However, a request might be forbidden for reasons unrelated to the credentials."

Does this paragraph need clarifying because it does seem (as in this case) it's appropriate to resubmit with the same credentials?

Monday, May 20 2013 at 1:53 PM +10:00

Leandro Lpez said:

This is very interesting, I'm really looking forward to see it implemented. Just out of curiosity: what's the status of this RFC?

Thursday, May 23 2013 at 5:36 AM +10:00

aviflax.com said:

Fantastic!

One thought: I think media type could be a bit simpler: the subtype problem+json would be just as clear and a little more broadly applicable than api-problem+json. (It should suffice to indicate “a JSON representation of a problem” — there’s no need to further qualify the representation as being about APIs.)

Friday, May 31 2013 at 12:32 AM +10:00

andreineculau.myopenid.com said:

I thought I already add a bit of "this is how we do it" mambo to the party, but I don't see my comment (if there was indeed one), so here goes:

* there can be more than 1 problem, and if the server manages to output more than 1, it is useful for the client to try to fix them all in one go. Switching to a list or predefining a problemType like http://example.com/multipart would be great to see in the standard.

* targeted user, or targeted localization.. albeit I'm not coming from an ideal/simple context, I have to support 2 types of "users" - the API consumer and the API end-user (e.g. the app [dev] and the app user). This may sound utterly revolting, but at times (think laws, terms, conditions, marketing, etc) the API consumer is just a bridge between the other two, since the end-user has agreements with both. In this context localization via Accept-Language is peculiar, if not impossible. So, to simplify - API responses are always in English, and always directed to the API consumer. Parts of these responses may be keys in a localized dictionary available to the API consumer, but they target the end-user

* httpStatus - it only adds noise and confusion, and also one out-of-sync possibility. If there is an advantage to duplicating the status code from status-line, it is not clarified at all afaik in the standard

Thank you for your work, Mark. Indeed, we can&should do better.

---

PS: I subscribe to the "application/problem+json" suggestion. Maybe you can clarify what is specific to an API about this media-type. I see the media-type itself very generic -- it's a problem response message, delivering information about it's pair request. If anything, if the correlation to an HTTP status code is strong (it defines the problem) rather than categorizational or a mark of clarity (ie. if you can use the same problemType under 2 status codes, then the problemType is not granular enough), then it could be "application/http-problem+json". Otherwise, I am not sure if I see a reason in not allowing this media-type to be used outside HTTP.

PPS: about doing it the "right" way - imo it's about discovery, brevity, transparency and interface/usability. The latter being the biggest obstacle. You need to have a full-time job or a big drive in order to cancel out the obstacles/annoyances. Also imho it's good enough if people invent vnd media-types. At a later stage, it will be easier to gather and design a generic backbone that should suffice for all of category x (e.g. error/problem) media-types.

Sunday, June 16 2013 at 9:23 AM +10:00

aniket-patil.myopenid.com said:

Hey, this is Aniket from the Box API team. We've thought long and hard about how to come up with actionable error responses for app developers. We have something very similar to your proposed draft in the new version (v2) of the Box API. See http://tech.blog.box.com/2013/04/get-developer-hugs-with-rich-error-handling-in-your-api/

A few observations about the proposed document format:

1. The previous commenter brought up a valid point about the server reporting multiple error in the body. We've supported this by having multiple JSON objects, each object describes one instance of a problem with the HTTP request. An example is available in the link above.

2. One thing we found very useful was to include a "location" key in the error response. HTTP APIs can get quite sophisticated, in terms of multiple query parameters, request headers and complex request bodies. The "location" key quickly tells the API consumer where the error is located. It is set to one of 'entity-body', 'url' or 'header'. This would be considered an 'extension member' in the draft.

3. Every HTTP API provider has their own style when formatting the JSON/XML keys. For e.g. some prefer to use underscores to separate multiple words, while other camelCase it (as proposed in the draft). It would be awkward for the error responses to look different from the rest of the API. Do you plan to include support for this in the draft?

Monday, June 24 2013 at 5:15 AM +10:00

boxaniket.wordpress.com said:

Hi Mark,

Sorry for the delay.

In order to solve the "style" problem, I can think of 2 solutions:
1. Avoid multi-word keys. For e.g. you could pick an alternative key for 'problemType' such as 'problem'. This only alleviates the problem and does not solve it completely. There could still be API providers that use a different case for their keys, e.g. captialize the first letter of every key.
2. Define a new link relation type for the canonical 'problem' URL. For e.g. 'api-problem' and then use JSON linking in the representation to represent the link in the response. For e.g.
"link":{
"rel" : "api-problem",
"href" : "http://example.com/probs/out-of-credit"
}

I agree that the single error case does get more complex, since it it now embedded inside a JSON array, instead of keeping the JSON keys at the root level. The flipside, however, is the consistency in structure of error response (irrespective of number of errors) and ease-of-consumption for API clients. From a CS point of view, I would imagine that if my API request can have one or multiple errors, they would logically be represented in a similar way.
The most intuitive way to do that would be to have a list of errors, and insulate clients from writing code that checks if my response contains an array (for multiple errors) or a single object (for one error). Extending this to HTTP, if we went the way of having separate media types based on single or multiple errors, I would think that clients would need to write extra parsing logic to handle the different media types and the unique ways keys were nested in the media types. Ultimately there's a trade-off (make the common case simpler and increase client-side parsing logic OR define a generic media type and reduce client-side logic and make the common case slightly more complex). The question is, how much more complex is it to parse a single object from a JSON array? I would say, negligible.

You also pointed out the media type proliferation problem here http://www.mnot.net/blog/2012/04/17/profiles and having separate media types would exacerbate that. Which brings to another question - why was the api problem json type defined as its own media type (application/api-problem+json)?
Could we have used the profile Link header param instead?

Monday, July 22 2013 at 5:15 AM +10:00

boxaniket.wordpress.com said:

I see your point with returning multiple errors. In the Box API, if there are multiple errors that happen with a request, they all belong to the same status code e.g. 4xx, or 5xx which makes it possible to return all of those in the HTTP response. The API error handling mechanism is built so that it weeds out client errors (4xx) first, before forwarding the request down the stack. Hence there's never a case where the server could return multiple errors where each maps to a different HTTP status

Monday, August 5 2013 at 10:29 AM +10:00

Leave a comment


Creative Commons