18

Imagine an API to identify whether a person has selected their spirit animal. They can only have zero or one spirit animals.

Currently:

/person/{id}/selectedSpiritAnimal

when they have selected an animal returns http 200 and {selectedAnimal:mole}

but when they have no selection it returns http 404.

This makes my spirit animal unhappy as we're representing a valid domain concern - having not yet selected a spirit animal - as an HTTP error.

Plus, as a business - erm Sprit-Animal-Hampers-R-us - we want to know when someone has no selection so we can prompt them.

What's a better response here:

HTTP 200 and {selectedAnimal:null}

or even more explicit

HTTP 200 and {selectedAnimal:null, spiritAnimalSelected: false}

Or is it better to return a 404? Since much like this image has not yet been uploaded when viewing an image online would be a 404. this person has not selected a spirit animal might be a 404


This question has been proposed as a duplicate but that question addresses an otherwise valid URL being requested when the application has been configured to not allow the change that URL represents.

Whereas here I'm looking at how one represents a resource where the absence of the resource is meaningful. I.e. it is valid for the client to request the URL and the response is you have successfully requested the resource which represents an absence of a thing.

So this isn't 'business logic' but rather a circumstance where the absence of a thing has meaning (it may be as many of my colleagues are arguing that 404 is still correct) but I'm not sure how to map that to the spec.


Very difficult to pick an answer. I've changed my mind multiple times over the conversation here and the one ongoing at work.

The thing that settles it for me here is that the spec says that a 4xx is when the client has erred. In this instance the client has been told to expect a response from the selectedSpiritAnimal url so has not erred.

The consensus amongst my colleagues is that this is a symptom of a bad API design

It would probably be better that we simply request /person/{id} and that returns a set of link relations for the person... then if you aren't given the /selectedSpiritAnimal link (when a person has no selection) but you call it anyway then a 404 makes sense. Or that you implement partial responses and let /person/{id} return a more full document unless the client requests a subset of the data

  • 5
    You haven't explained why you believe it is an issue to return a 404. From a domain point of view, you don't know whether you received a 404 or a 200, that's abstracted by the client layer. – Vincent Savard Sep 25 '17 at 16:04
  • 13
    or maybe it's better to return 204 no-content? – Paul D'Ambra Sep 25 '17 at 16:18
  • 6
    I suppose my worry with 404 is disambiguating a config change that introduces a bad URL template from a person with no spirit animal – Paul D'Ambra Sep 25 '17 at 16:24
  • 2
    @PaulD'Ambra For what it's worth, I would not use a no-content. I haven't seen HTTP codes handled on the front-end that way in Javascript since the XmlHttpRequest days. But it is certainly valid. – Brandon Arnold Sep 25 '17 at 19:50
  • 1
24

HTTP 4xx codes are probably not the right choice for this scenario. You state that having zero spirit animals is a valid state, and the API route person/{id}/selectedSpiritAnimal will account for whether person id does or does not have one.

HTTP 4xx responses are reserved for the situation when a client has done something incorrect in the request (see w3's archive of the original spec). But the client is making a valid request, whether or not person id has a spirit animal.

So I lean toward the second solutions using a properly formatted JSON body in the response and an HTTP 2xx code.

Now if you get such a request and it turns out person id does not exist, a 4xx code makes more sense.

  • 17
    "HTTP 4xx responses are reserved for the situation when a client has done something incorrect in the request ". When making authoritative statements about the spec, please reference the actual HTTP spec, and not (a) a framework built on top of it, or (b) a random blog post. – Eric Stein Sep 25 '17 at 17:01
  • 5
    @EricStein I did feel a bit lazy about that; thanks for the critique. Updated. – Brandon Arnold Sep 25 '17 at 17:04
  • 1
    I feel like the RFC link here is kind of conflicting. On the one hand the 4xx portion says cases in which the client seems to have erred, but on the other the 404 directly says The server has not found anything matching the Request-URI. You could consider requesting something that doesn't exist a client error. I understand the person not existing vs a sub prop not existing, but that could be indicated as the response message. 204 also seems relevant, since the entity exists, but has no content for a sub property. – shortstuffsushi Sep 25 '17 at 23:04
  • 1
    The client did do something wrong; it requested the selected spirit animal for a user when it doesn't exist. That's exactly what 404 means... you've asked for something which isn't there. – Andy Sep 25 '17 at 23:10
  • 5
    When my browser makes a request to softwareengineering.stackexchange.com/questions/unicorn it's a valid request and yet I still get a 404 (there just happens to be no question with the ID "unicorn"). – immibis Sep 26 '17 at 0:12
24

Let me introduce you to the Richardson Maturity Model.

Your problem is that you are representing two resources as one, where you should really have two resources which have a relationship indicated by hypermedia. Using hypermedia to describe relationships is the glorious level 3 of Rest.

Your person should live under the URI /person/{id} and the animal should live under /spiritanimal/{id}. The person should indicate that it has spirit animal by using a link to the animal.

Lets imagine a person called Bob, who has id 123 and a Unicorn spirit animal.

GET /person/123

would return;

{
  "name": "Bob",
  "links": [
    {
      "rel": "spiritanimal",
      "uri": "/spiritanimals/789"
    }
  ]
}

Now anybody who reads person 123 will know that they have a spirit animal, and has the URI where they can get more info on it.

GET /spitiranimal/789

might return

{
  "type": "Unicorn"
}

Now lets imagine a person called Fred, who has id 456 and no spirit animal.

GET /person/456

would return;

{
  "name": "Fred",
  "links": [
  ]
}

Now anybody who reads person 456 will know that they have no spirit animal, as there is no link. There is no need to use any HTTP status code to represent the lack of a relationship.

  • 1
    Was just adding to the question that, given the ability to change the api, some level of HATEOAS would probably be the right solution. That's a great example of that – Paul D'Ambra Sep 26 '17 at 13:10
1

This is the appropriate url for getting spirit animals; therefore, a 404 error is inappropriate. 404 is for representing a technical problem, not a logic problem.

The appropriate solution is to return http 200 and {"selectedAnimal": null}

You should have a seperate webmethod /person/{id}/hasSelectedSpiritAnimal which returns {"isSpiritAnimalSelected": false}. Behind the scenes, it may or may not make the same method calls, just returning false if null, but that is up for it to decide, not the consuming code.

It is better to avoid combining to separate queries into one web method without a compelling reason to do so, even if the queries are closely related.

  • 1
    Could you explain why you believe this is the appropriate solution? I can't make any sense of returning data when there's no data to be returned. I agree with you that a 404 does not make sense since the URL is fine, so a 204 seems to be making the most sense to me. – Vincent Savard Sep 25 '17 at 16:36
  • 2
    @VincentSavard Status codes are more difficult to work with than json responses, and are more appropriate for technical issues rather then communicating domain information. – TheCatWhisperer Sep 25 '17 at 16:49
  • 2
    204: No Content with an empty body. It is all clear and self explanatory. No need to argue further. All in all, when there is no clear design pattern an SE should pick one, bend it to its need and provide documentation. Returning a body with a 204 response is not consequent. – formixian Sep 25 '17 at 18:02
  • 2
    @formixian ... If you were creating a bjson/tcp api instead of a json/http, what would you return for this case? Relying on http codes seems to me to unnecessarily bind the results of the api to its http implementation. – TheCatWhisperer Sep 25 '17 at 20:21
  • 2
    @VincentSavard When you said "the spirit animal is not currently on file", it seemed quite similar to me to "there is no content" No content means no content. i.e: it returns "". These two aren't similar at all. "the spirit animal is not currently on file" is very different from "". – Shane Sep 25 '17 at 22:02
1

What your endpoint represents is not just an animal; it's an animal or lack of it. It's a value best represented by an Optional / Maybe / Nullable / etc.

So legitimate values (as in 200 OK) may be:

  • {'animal': <some animal>, 'selected': true}
  • {'animal': null, 'selected': false}

I could imagine that DELETE method, when applied to the endpoint, can set 'selected' to false again, that is, unset the selected animal.

You can, of course, drop the 'selected' key here, it is only shown for clarity; string vs null is enough for the distinction.

0

You should use 404.

The 404 (Not Found) status code indicates that the origin server did not find a current representation for the target resource

RFC7231

HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 25 Sep 2017 00:00:00 GMT

"mole"

HTTP/1.1 404 Not Found
Content-Type: text/plain
Date: Mon, 25 Sep 2017 00:00:00 GMT

this person has not selected a spirit animal

Since you are making an programming interface, not a human interface, the 404 text is optional.

I prefer this because I prefer standard protocols as much as possible. HTTP has a way to represent non-existance, and that's what I would use.

EDIT: Suppose you added a feature where users could choose whether to share their spirit animals, and someone didn't share it. Would you return 200 OK null, or 200 OK "Unauthorized? Or would you use the the standard 401 Unauthorized/403 Forbidden? This seems directly analogous to not choosing one in the first place.


Alternatively, if you want to used 200 OK + JSON, you should return null.

HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 25 Sep 2017 00:00:00 GMT

"mole"

HTTP/1.1 200 OK
Content-Type: application/json
Date: Mon, 25 Sep 2017 00:00:00 GMT

null

Keep things clean. Create more wrapping only if necessary.

  • 2
    The data is found, though. It just happens that the data is null (has no value). This is different than the client requesting something for which no slot to hold the value exists. You wouldn't suggest throwing an AttributeError in Python when the value happens to be None; that would make the interface impractical and unnecessarily difficult to work with. More pragmatically, many HTTP client APIs might convert the 404 into the language's standard error handling mechanism (error code, exception, error type), meaning you'd have to suppress an extraneous error. – jpmc26 Sep 25 '17 at 22:21
  • @Paul Draper Scroll up a little bit in the spec, you will see that it makes a blanket statement about HTTP 4xx: "The 4xx class of status code is intended for cases in which the client seems to have erred." The op has stated clearly that not having a spirit animal is a valid state for which the API should account. tools.ietf.org/html/rfc7231#section-6.5 – Brandon Arnold Sep 25 '17 at 22:53
  • How do you, then, distinguish between indicating the non-existence of the requested resource (i.e. no animal selected), and client requesting an invalid path (i.e. /person/{id}/selectedSpritAnimal, observe the typo in Sprit). – sampathsris Sep 26 '17 at 4:10
  • 1
    Regardless of intent, a 404 strictly means the resource was not found. If a selectedSpiritAnimal is a resource, and none exists, then there is nothing to GET and a 404 is appropriate. However if the client wants to know whether a spirit animal was selected then the server should expose another resource /person/{id}/isSpiritAnimalSelected which would tell the client whether one was selected. Seems to me the same classic example of testing whether a file exists prior to reading it. Just read the file and handle the ENOENT error should it be thrown. – aaaaaa Sep 26 '17 at 5:26
  • 1
    @PaulDraper The point is not whether or not the resource exists. The point is HTTP 4xx codes are meant for when the caller is using API incorrectly. Op states that /person/{id}/selectedSpiritAnimal is meant to be used even if spirit animal does not exist. This means that HTTP 4xx is incorrect, when used in such a case. Op also correctly states that this may be a smell of bad API design. – Brandon Arnold Sep 26 '17 at 17:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.