21

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

Paul D'Ambra
  • 329
  • 2
  • 7
  • 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
  • Yep, we're actually just talking about that internally... Trying to decide if it's better to represent an explicit domain absence as a 200 response rather than a 404. Can't make our minds up – Paul D'Ambra Sep 25 '17 at 16:13
  • 15
    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
    Possible duplicate of [Should HTTP status codes be used to represent business logic errors on a server?](https://softwareengineering.stackexchange.com/questions/341732/should-http-status-codes-be-used-to-represent-business-logic-errors-on-a-server) – gnat Sep 25 '17 at 21:41
  • 1
    In this case there's not much difference. Do whatever feels more convenient for you, **document** it, and **be consistent** throughout your application. – el.pescado - нет войне Sep 26 '17 at 05:38
  • @gnat that feels like a really different question to me... This is very specifically a question about where a resource is present as an indication of the absence of a choice – Paul D'Ambra Sep 26 '17 at 07:47

5 Answers5

25

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.

Brandon Arnold
  • 1,253
  • 9
  • 15
  • 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
  • @shortstuffsushi I actually agree that the spec is not especially clear on some of this stuff. But I have made a point to say that the op himself states what the purpose of his API is, and that not having a spirit animal is a valid state. Since the client is making a request that is valid by the ops own statement, HTTP 404 doesn't seem to be a good fit. – Brandon Arnold Sep 25 '17 at 23:11
  • ^ refers to the case in which person `id` does exist, but the spirit animal does not. ^_^ In the case where person `id` does not exist, I lean towards the 404 but am certainly persuadable to something on the 2xx side. – Brandon Arnold Sep 25 '17 at 23:15
  • 5
    When my browser makes a request to https://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"). – user253751 Sep 26 '17 at 00:12
  • @immibis Haha. That is a nice point. Two things. (1) This is a concrete case of you requesting a resource that does not exist, and not an API request. That's an important nuance. (2) Think about what you're saying. In JS you typically pass a `success()` and `error()` callback during your http service call. Are you suggesting we look at the HTTP code in the error callback, to discern between "Server failed to respond" and "Spirit animal does not exist"? In my experience, that is never how this is done. – Brandon Arnold Sep 26 '17 at 00:28
  • yep, the distinction here is that the absence of questions at /unicorn is not meaningful in and of itself. Nobody gave you the unicorns link and told you to expect it to work - you invented it... compare the disambiguation of /person/{id}/selectedSpiritAnimal and /person/{id}/potato I've invented the potato link, there's no expectation that it means anything, so 404 seems appropriate. So interesting how much scope there is in the spec for disagreement though! – Paul D'Ambra Sep 26 '17 at 12:35
  • @PaulD'Ambra, what about `/proc/{id}/stack` vs `/proc/{id}/potato` if process `{id}` doesn't exist? How does `read()` distiguish them? Neither files systems nor HTTP contain templating as concepts. Both should give Not Found/ENOENT – Paul Draper Sep 26 '17 at 17:25
  • Absolutely /process/{id} exists or it doesn't. I'm not really thinking about the file system though so don't understand your example. Is that a case where, as is common!, Technology choice affects API shape? – Paul D'Ambra Sep 26 '17 at 19:11
  • Gotta agree with @Andy and others here. If `http://example.com/person/123/spiritanimal` doesn't exist, the correct response code is 404 - Not Found. The client mistake was in requesting a non-existent resource. That said, I also agree with the answer by Qwerky that the better API would make use of HATEOS if SpiritAnimal is an Entity in this domain. – Paul Sep 29 '17 at 19:31
  • @Paul I agree that Qwerky provided a fine, comprehensive alternative, and I also agree that the API design smells in this case. However, op provided an API design constraint, and under that constraint the client is being directed to hit the example API even in the null case. So you suggest we tell the client to implement an error callback and interpret the error code, so as to decide whether or not to throw an error notification or not (in the null case)? If so, I have to amicably disagree. If your only point is to redesign the API, we have no disagreement whatsoever. – Brandon Arnold Oct 02 '17 at 17:28
  • @BrandonArnold the OP did not constrain the API as I read the question, and even asked if the 404 was the best approach. Furthermore, directing the client to handle an error callback is no more onerous than telling them to test for a null case. – Paul Oct 02 '17 at 22:27
  • @Paul well I definitely appreciate the feedback. – Brandon Arnold Oct 02 '17 at 22:42
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.

Qwerky
  • 1,582
  • 8
  • 17
  • 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.

TheCatWhisperer
  • 5,231
  • 1
  • 22
  • 41
  • 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
  • @VincentSavard I do not agree with 204... there *is* content. The content is that the spirit animal is not currently on file. There is not reason to send back a http code other than 200 (assuming the request was processed without errors) if a call to this method is expected even when there is no spirit animal. It is reasonable to believe this method will be called for users without spirit animals. 204 would be more appropriate if the method was called for a *user* whom did not exist as this is not expected use of the API. – TheCatWhisperer Sep 25 '17 at 16:49
  • 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
  • That's interesting. When you said "the spirit animal is not currently on file", it seemed quite similar to me to "there is no content". I also can't say I agree that returning a 204 would be "communicating domain information", since HTTP status codes have nothing to do with the domain and would be abstracted. And I disagree that returning a 204 would make sense if the user does not exist, I definitely consider that a 404. It seems we have quite different views on the matter! – Vincent Savard Sep 25 '17 at 17:10
  • 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
  • 1
    @formixian and vicent. Let's say you are consuming this web api via C# and the WebRequestClass, returning many status codes besides 200 will result in an exception being thrown. You can argue that this a problem with the implementation, but the fact is, the consumer of this API is expecting a JSON response, and returning one for what is a *reasonable* request makes the API easier to consume. While I would not criticize returning 204/empty, I am not sure why you are so intent on co-opting http status codes for something that really has nothing to do with http. Continued... – TheCatWhisperer Sep 25 '17 at 20:18
  • 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
  • @TheCatWhisperer I would return an empty body, just as the HTTP 204 response description states _AND_ I would document the API according to the implemented behavior. Documentation is the key here ;). – formixian Sep 25 '17 at 21:08
  • 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
    `404 is for representing a technical problem` - this isn't accurate, the 5xx class of codes represents a "problem," 4xx indicates the request can't be fulfilled due to a client error (like requesting something that doesn't exist). – shortstuffsushi Sep 25 '17 at 23:01
  • @Shane You're forgetting this is in the context of a REST API. "For a certain user, when accessing the `spiritAnimal` resource, there was no content to be found despite the client making a valid request.", that feels like the spirit of a 204 to me. I think a lot of us have quite different views which are irreconcilable through a StackExchange comment thread, so I guess I'll leave it at that. – Vincent Savard Sep 25 '17 at 23:35
  • @shortstuffsushi why is the client requesting the incorrect url not a technical problem? 5xx is for *serverside* technical problems – TheCatWhisperer Sep 26 '17 at 14:04
  • @VincentSavard The context of this is also a JSON API, why would you not communicating in JSON? Why force the consumer to be checking status codes for routine requests? – TheCatWhisperer Sep 26 '17 at 14:25
  • I guess we can argue the meaning of technical problem vs user doing or requesting the wrong thing or a thing that doesn't exist, but it's wrong to call 400s "technical problems" in my book. – shortstuffsushi Sep 26 '17 at 16:40
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.

9000
  • 24,162
  • 4
  • 51
  • 79
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.

Paul Draper
  • 5,972
  • 3
  • 22
  • 37
  • 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. https://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 04: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 05:26
  • @BrandonArnold, yes. The request cannot be successfully filled. It is due to the client requesting something that doesn't exist. What if the API returned the actual image (Content-Type: image/png), not just the name? What would you do then? – Paul Draper Sep 26 '17 at 17:17
  • @Krumia, from HTTP protocol perspective, it'd be a request for a missing resource either way. If you wanted to differentiate, include the detailed explanation in the body. Now my question: how would a Linux user distinguish between reading `/proc/123/limits` and `/proc/123/limts` (notice the misspelling of limits)? – Paul Draper Sep 26 '17 at 17:22
  • 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
  • @BrandonArnold "The point is HTTP 4xx codes are meant for when the caller is using API incorrectly". Don't equate 4xx with bug in client. If the requests the spirit animal and get 403, does the client have a bug? Maybe. Or maybe the owner of the resource hasn't shared the spirit animal (analogous to 404, where the owner hasn't chosen one at all). "Using the API incorrectly" isn't revealed by the status code. 4xx simply means the request cannot be fulfilled, due to the request. – Paul Draper Sep 26 '17 at 17:34
  • @PaulDraper Well, that is how I feel about it based on how I've seen it used in the past. But your experience is equally valid and there are certainly gray areas in all this stuff. – Brandon Arnold Sep 26 '17 at 17:38
  • @PaulDraper The *spec* equates 4xx with either a bug in the client or user input that isn't valid (which is sometimes beyond the control of the client code). The server can't distinguish between "user entered garbage" and "client code has a bug," though. So it just barfs with a message indicating, "For whatever reason, you gave me garbage." That's the point of 4xx. Asking for additional, valid details on the resource doesn't constitute a garbage request. – jpmc26 Sep 29 '17 at 08:22