21 ms·
I've been abusing HTTP Status Codes in my APIs for years
- smokey_circles 4y agoInteresting take. Feels cleaner to me, and while I want to argue about the opinionated payload bit, I suppose that's something that should be clearly defined in the contract anyway. Maybe I've been abusing status codes too...
- zinekeller 4y agoHonestly, if you're abusing it that way I'll probably be using error 400 for all semantically-invalid requests. This is also compliant with the definition. > The 400 (Bad Request) status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).
- that_james 4y agoI don't think there's a correct answer, this is just an opinion I stumbled into this morning. The objective mostly was to try help form a way of reasoning about HTTP APIs from the consumer's perspective > error 400 for all semantically-invalid requests Counter point: Passing `a` to the example endpoint is not semantically-invalid from the perspective of HTTP, it's a perfectly valid URL. Nothing about that request is invalid, it's only invalid to the business layer, which has nothing to do with the HTTP layer. Put another way: The server _is_ processing the request, that's how it send back the "employee not found" record. I guess it depends where you want to draw the boundary of server. Which is not to say you are wrong if you return a 400, but I suspect you might get a couple queries about it from consumers. The objective was mostly about clarity. Maybe I'm being over-simplistic, but I like the idea of using protocol errors for protocol problems and domain errors for domain problems. Just keeps client-side logic cleaner in my experience
- zinekeller 4y agoI personally disagree (but I agree that there might not be a "correct" answer - unless IETF stepped and defined exactly what a bad request is). If the server knows that "a" is not valid, then on the server's perspective it's an invalid request - therefore it's semantically-invalid for that server (and a minor nitpick - "a" is invalid in almost all servers, it should be "/a"). Client errors need not to be the user agent - it encompasses even user errors.
- that_james 4y agoThat's a very good point. It's also why I raised the issue of where your "server boundary" is. I dunno how else to phrase it, but what I mean is the separation of domain logic and technical logic. In my understanding, 400 denotes an HTTP request that doesn't comply with the RFC (missing headers, bad format etc) but I also don't disagree with what you're saying, because the user did send a bad request. Of course, you could always do both :) Send the opinionated payload with a 400 error. As long as your consumer can reason about what went wrong, then I guess that's in line with the objective I was trying to get across.
- zinekeller 4y agoFair enough (and that's why edited the post to state that the document isn't clear about the intent of code 400). Just don't send the default servers' 4xx error, that'll confuse everyone where it went wrong.
- treis 4y ago>Which is not to say you are wrong if you return a 400 5XX - Server did something wrong 4XX - Client system did something wrong 2XX - Everything worked IMHO, what's missing is a code for "All systems worked but the user did something wrong" for scenarios like looking up a non existent ID or if their CC details don't work.
- thr0wawayf00 4y agoI've always interpreted 422 to fit this case: a well-formed request that cannot be processed due to the content in the payload. https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422
- stop50 4y agoWouldn't 426 be better for nonexisting api roots?
- that_james 4y agoSho, I don't know. Maybe? I've never seen one in the wild, but looking at the docs is that error not reserved for using the wrong protocol (such as 1/1 when the server only accepts 2 or something?)
- stop50 4y agoOfficial yes, but if you want to misuse statuscodes this would be a better solution in my eyes.
- landryraccoon 4y agoIn the case of a missing user ID, if a more verbose response is desirable, why not put the error message in the body of the response? 404: Route not found vs 404: User ID not found seems like it resolves all ambiguity, no?
- zinekeller 4y agoStraight answer: HTTP 2 and 3 killed that (they only send codes, not an arbitrary message)
- thr0wawayf00 4y agoI've definitely seen RFCs that specify HTTP status codes should be used to represent the actual application layer. The SCIM protocol, for example, specifies to return a 404 if a user cannot be found, not 200. That's definitely in the business domain, not the network domain. One other small gripe I have with this approach is that it reduces the visibility of a failing request in the application layer, which is useful for debugging. It's not uncommon for one of my apps to issue many requests to the app server and if everything returns a 200, I have to look through the various payloads to find the request that actually failed. I guess my question is: is it really abusing HTTP statuses if it works well overall? I rarely find myself wondering whether a request failed in the network layer or the application layer, I could probably count on one hand the number of times this scenario bitten me in my career. I don't really see the point in throwing out something that works over semantics in an RFC.
- kmeisthax 4y agoIn the original context of HTTP being a way to distribute interlinked documents, a web application backend sending a 404 for an invalid database identifier is equally as valid as sending a 404 for an unrouteable path. The HTTP spec does not support a distinction between the two cases. If you absolutely needed separate status codes for the two cases, you probably could use 410 Gone or 501 Not Implemented for unrouteable paths and 404 Not Found for bad IDs.
- dtech 4y agoNothing prevents you from setting both a non-2xx API header, and still making the difference of "invalid path" vs "entity doesn't exists" clear in the response body.
- ehnto 4y agoIn regards to interoperability it does matter I think, if it's non-standard then it won't "just work" in an application that's expecting the standard. Practically though, APIs are a wild west, and almost no-one implements status codes right. So the idea of standardized interoperability was out the window a long time ago.
- jstrong 4y agoin that vein: status is pretty useful, seems like a good solution is using two separate non-200 status codes to differentiate between employee not found vs typical 404 no such endpoint/url/path.
- dtech 4y agounfortunately HTTP doesn't have good status codes to make the distinction
- eadmund 4y ago> In regards to interoperability it does matter I think, if it's non-standard then it won't "just work" in an application that's expecting the standard. The standard is to return a 404 for a resource that is not found. The standard is not to return a 200. An application which expects a 200 for a not-found resource is not expecting standard behaviour, and is dead wrong.
- ehnto 4y agoI never suggested anyone send or expect a 200 for a resource not found, I suggested following the standards, so we're in agreeance.
- 4y ago
- hyperman1 4y agoI've started doing this after dealing with a 'smart' load balancer. The good old SOAP protocol requires all SOAP faults to return a 500 status error. And people tended to map SOAP Faults to programming language exceptions representing business errors. You know what happens next. If a client generates too much business errors, the load balancer tends to throw out a server, which doesn't bother the client in the slightest, except it has to login again for the next request. Keep this up for a few minutes, and it makes all but 1 servers disappear from the pool, and the only survivor to spend half its time negotiating connections.
- oron 4y agoMissing the point of HTTP for things like CDN's caching etc. Looks like an anti-pattern
- efficax 4y agoThis is a terrible idea for observability. It would be difficult or impossible with most observabilty platforms to make it parse the http response to determine error responses, but when my systems are sending out a ton of 422s or 400s that's a useful signal to me that something is going on. I'd highly recommend using status codes to indicate application level behavior.
- foreigner 4y agoI think I agree with the article, because I just got bitten by exactly this. My API was returning 404 as a fairly common response when a user makes a typo, but Twilio's observability code was treating that as a serious error it needed to alert me about.
- eadmund 4y ago> My API was returning 404 as a fairly common response when a user makes a typo, but Twilio's observability code was treating that as a serious error it needed to alert me about. Then Twilio's obesrvability code is broken. Requesting a non-existent resource is not a serious error if resources are requested by hand-constructed URLs.
- shadowgovt 4y agoThis is one of those tricky philosophical points on web admin, because there are two sources of 404s: - random clients on the Internet poking around at stuff, which you have no control over and causes no harm so should not alert - an error in the links or code of the pages you ship to clients, which will cause a consistent degraded user experience and you should be notified about. In practice, the best way to solve this I'm aware of is not via observation of server-emitted 404s at all... It's via a back-channel for clients to report errors (possibly authenticated, so randos on the web can't fudge your stats) and then tie alerting to that back channel. So you don't track 404s to /foo, but you do alert on your clients screaming at you that they got a 404 trying to access /foooo. Of course, this solution requires your end-users to enable JavaScript.
- Demonsult 4y agoI've been low-key abusing emojis in binary protocols: result=0x3a29
- vasilakisfil 4y ago> Returning a 2xx code immediately tells the client that the HTTP response contains a payload that they can parse to determine the outcome of the business/domain request. There is nothing in the protocol that mandates only 2xx status codes are parsable. Instead, the Content-Type pinpoints to the client what kind of content the body has, regardless the status code. And the content-type will most probably be what the client asked (if not, then the server announces that the response content is not subject to negotiation, something that you see rarely nowadays..) In general I think this post reflects one of the mentalities that appeared around 10 years ago regarding HTTP APIs (another was the extreme hypermedia-driven APIs). But I really think nowadays we can do much better, we can deliver a practical API that works 99% of the times. By the way in the latest HTTP RFC (RFC9110) status code 422 is finally officially defined (previously was part of webdav extention), which means that the war 422 vs 400 for validation and other semantic errors is over and we have a clear winner. But 200 vs 404 for something that was not found? Well that was has ended long ago I think..
- zinekeller 4y ago> By the way in the latest HTTP RFC (RFC9110) status code 422 is finally officially defined (previously was part of webdav extention), which means that the war 422 vs 400 for validation and other semantic errors is over and we have a clear winner. Unfortunately, the way 422 is written implies that the sent body/content has error(s) and not the header. It's close, but I still feel that for GET requests it's wrong.
- bbatha 4y agoThe standard says nothing about bodies: > The 422 (Unprocessable Entity) status code means the server understands the content type of the request entity (hence a 415(Unsupported Media Type) status code is inappropriate), and the syntax of the request entity is correct (thus a 400 (Bad Request) status code is inappropriate) but was unable to process the contained instructions. For example, this error condition may occur if an XML request body contains well-formed (i.e., syntactically correct), but semantically erroneous, XML instructions. https://datatracker.ietf.org/doc/html/rfc4918#section-11.2 https://datatracker.ietf.org/doc/html/rfc4918#section-11.2 Additionally, bodies are allowed on GET requests by the standard though are not commonly used because of bad middleboxes. However, many GET requests include query params or other parts of the url to be parsed, and its completely reasonable interpretation of the standard to return 422 if those are not well-formed according to application rules.
- grose 4y agoImagine that we didn't have a fancy dynamic system and we were still using files for web pages like the good old days. In that scenario, what would happen if someone accessed the non-existent `/api/v1/employees/100` path? It would return 404 Not Found. From that perspective, I think it's hard to justify that the original intentions of HTTP would want you to return a 200.
- Spivak 4y agoIt's the difference between "There is nothing handling this route at all" and "there is something handling this route but the object isn't found." To me the former is an HTTP 404 and the latter is an application "Not Found." But I also very much don't like playing the "if you expose your app over HTTP you should assimilate HTTP semantics and do a fuzzy lossy map of your application to HTTP verbs and HTTP status codes." I leave the HTTP semantics to the front end web server and live on top.
- zeveb 4y ago> It's the difference between "There is nothing handling this route at all" and "there is something handling this route but the object isn't found." 'Nothing handling this route' has no meaning, because routes have no meaning in a hypertext application. Clients should not generally be constructing URLs by hand; they should be receiving them from the servers they communicate with. In the example in the article, an application dealing with employees should not be constructing URLs by appending employee IDs to strings; rather, every reference to an employee in the application should be to a URL rather than an ID. So when it requests a list of employees, it receives the equivalent of {/api/v1/employees/1, /api/v1/employees/2 … /api/v1/employees/N} rather than {1, 2 … N}. > I also very much don't like playing the "if you expose your app over HTTP you should assimilate HTTP semantics and do a fuzzy lossy map of your application to HTTP verbs and HTTP status codes." If you are building a hypertext application, then you should build a hypertext application. It's completely possible. Off the top of my head, protocols such as ACME (used by Let's Encrypt) are good examples to follow.
- NoGravitas 4y ago
- nailer 4y ago> This approach throws ambiguity straight out of the window. (+) This approach reduces ambiguity between failed routes and missing resources (-) This approach adds ambiguity by making missing resources look like they exist
- speedgoose 4y agoThat’s how GraphQL is doing when used over HTTP and it feels a bit wrong in the beginning but I also think it is better. And you don’t have to fit your errors into HTTP semantics.
- ElectricalUnion 4y agoGraphQL does that [everything status 200] because it is designed to work with Browser agents on-purpose self-inflicted technical limitations. Browsers don't return (in XHR/fetch requests) any response in the 400-599 status range.
- speedgoose 4y agoI think you are not correct on this.
- eadmund 4y ago> That’s how GraphQL is doing when used over HTTP and it feels a bit wrong in the beginning GraphQL is maldesigned; it is an abuse of HTTP and should not be emulated.
- speedgoose 4y agoWhy do you think so?
- ironmagma 4y agoThis reminds me of the “HTML should be semantic” and “CSS class names should be semantic” crowd. People aren’t robots, and we can’t/don’t write crystalline code, nor arguably should we. The status codes are extraordinarily convenient, both to set and to read, and the ship has sailed already on whether to use them this way.
- deleted 4y ago[deleted]
- ninefathom 4y agoSo as a client, being able to check whether I likely have a usable result without parsing out the body has no value whatsoever? I'm not sold. I think we need to be using HTTP error codes better at the API layer, not ditching them entirely. Building on this example, for an invalid user, how about an HTTP 204 and an empty response body? That gives us a clear "no such user exists" without having to parse JSON out first. EDIT: for the record, I think that on some level, REST APIs themselves are an abuse of HTTP. JSON didn't even exist for much of the public Internet's first decade, and certainly the early work on HTTP never mentioned such a thing as a REST API. We're already using HTTP for so many things in so many ways that this is somewhat of a Lilliputian discussion.
- qwertox 4y agoNot only that, but you're using nonstandard ports for your WebSocket connection (wss://blog.slimjim.xyz:1313/livereload) Since this makes my firewall dialog pop every couple of seconds after I close it, I prefer to close the page without reading it. Why not just serve it over 443 as well?
- ninefathom 4y agoHeh - indeed. Methinks the emperor has no clothes.
- brightball 4y agoYes! This is absolutely one of my biggest pet peeves. A 404 should not be shown on a valid path with an empty result. That’s exactly what 204 is meant for. Moreover, a 404 in many REST clients will bubble up an error in the application code because it thinks there’s something wrong (you’re trying to reach something in the wrong place). That’s not the case for an empty result, any more than an empty list is an error.
- simiones 4y agoFrom the perspective of an HTTP server, a resource either exists, or doesn't. There's no concept of a "valid path". If you do GET /a/b/c, if /a/b/c exists you get a 200, if it doesn't you get a 400 (and if you're not allowed to access it you get a 401 or 403 depending on some other details, and if the server has a problem while replying you get a 500 etc). This is a very important concept about what an HTTP URL means, and how it is understood by HTTP servers and middle-boxes (caches, proxies etc).
- brightball 4y agoThat’s just it though, the resource does exist. The resource is the API to retrieve a result. The result being empty doesn’t mean the resource doesn’t exist, in the context of an API.
- simiones 4y ago> That’s just it though, the resource does exist. The resource is the API to retrieve a result. That is not true if the API is RESTful - in that case, the API is the resource. An item with the requested URL either exists or doesn't. If it doesn't, then return 404 and (if possible) give some more information.
- heeton 4y agoReading the spec, that's definitely not what 204 is meant for. https://httpwg.org/specs/rfc7231.html#rfc.section.6.3.5 https://httpwg.org/specs/rfc7231.html#rfc.section.6.3.5 > The 204 (No Content) status code indicates that the server has successfully fulfilled the request and that there is no additional content to send in the response payload body. I don't see how that could be intepreted to mean "You have requested a resource which does not exist". It's intended for use cases like "Your request to save the file has been received, no further content will be sent". Or am I misreading the spec?
- rhesa 4y agoStatic web servers return 404 for resources that don't exist on the file system. For example, https://news.ycombinator.com/s.gif https://news.ycombinator.com/s.gif -> 200 But https://news.ycombinator.com/t.gif https://news.ycombinator.com/t.gif -> 404 I don't see why /api/v1/employees/3.1415926 shouldn't return a 404 either.
- grumple 4y agoWhy is this person making up their own rules rather than following the standard? https://developer.mozilla.org/en-US/docs/Web/HTTP/Status https://developer.mozilla.org/en-US/docs/Web/HTTP/Status You should return the best-fitting error code and an error response in an expected format for the end user you are serving (either your own frontend, or your own api users). Using 200 for everything means you can't use a whole lot of enterprise server / error tracking software to monitor failures related to the business logic, which are generally as important as most technical failures.
- dlisboa 4y agoI don't see how using 404 is abusing it. If the client requested `/api/v1/employees/<non-existing-employee>` this means that the entire resource (identified by the full URL, including authority) is non-existing (i.e. Not Found). Both the technical and business requirement are the same here: what the client is trying to access cannot be found. There's no difference between that and `/api/v999/employees/1`, as that resource also does not exist, even if `/api/v1/employees/1` does. You should detail the error further in the 404 response, and you can say "this entire path prefix could not be found" or just "the path prefix is fine, but this specific employee could not be found". The spec does not prevent you from informing that only the representation of that specific resource is not found. [1] Using 200 for that is a bit of a cop out, you lose all of the semantics of the response and it's all pushed to out-of-band definitions (like your API's documentation). It could just as easily be argued in the article's example that every single 4xx error is just a 200 with a '{"result": false, ...}' body. --- [1] - https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4 https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4
- brodo 4y agoExactly. And the same goes for all other status codes.
- oceanplexian 4y agoI tend to agree with this perspective. It's not HTTP's responsibility to distinguish between infinite permutations of non-existing resources, only that the resource does or doesn't exist. Returning a 200 is counter-productive because now any sort of logging, metric system, or middleware is going to be ineffective unless you write some custom code to parse out the body and decode whatever custom error resource the developer decides to invent. And in that case why are you even using HTTP as a transport protocol in the first place if you're going to violate 30+ years of precedent.
- gls2ro 4y agoI think the article is missing some points with regards to the REST. If the API that the author is building is a REST API then the response for a non-existing resource is 404. In case of REST the main idea is that if you try to GET a resource by ID then you assume that the resource exists. Thus if it does not exists => 404. It does not matter too much which part of the URL is the one that is causing the URL to be wrong. Thus `api/v11/employees/1` and `/api/v1/employees/100` both are wrong resources. In the first case, asking for `/api/v11/employees/1` is not a search or find to see if there is a V11 and if inside there is employee 1. Building an URL is an intentional choise that includes assumptions, like there is an 'api', that has a 'v11' and inside there are employees and one should have the ID 1. The same goes for later case with employee ID 100. If you ask for an employee with an ID that means you know (or assume that you know) that employee exists. Thus if it does not it should be very level clear => 404. In both cases responding with 200 means something like "I kind understand that you want an URL that is similar with some that I have so it is almost okish". But in REST this is not the case. It is like you are serving some static folders and you want to get the file 100 under /api/v1/employee and that file does not exists. Nobody is stoppping anyone to add response body to a 404 to indicate which nested resource is invalid. That can be added as a debug message for the developer for example. Of course this is IMHO and I am only addressing REST API. If the API is not supposed to be REST then do whatever you want but make sure you document it well and be consistent.
- treis 4y ago>If the API that the author is building is a REST API then the response for a non-existing resource is 404. The problem is that this error then overlaps with server path routing issues, DNS problems, and general network issues. Even if it's logically correct it makes dealing with your API annoying. >Nobody is stoppping anyone to add response body to a 404 to indicate which nested resource is invalid. But then we've lost standardization which is the whole point of the error codes to begin with.
- 0xCMP 4y agoExactly the point I bring up to not do this. However, a viable (but often difficult or unsupported) way to solve this would be changing the HTTP Message so it's clearer the intent of the error. Most frameworks like Django hardcode the response messages.
- smcl 4y agoOpening with "You're using X wrong" or "You're doing X wrong" is a good way to get a reader to ctrl-w and move on. In my experience it is more often than not followed by questionable advice or incorrect assumptions so my instinct is to just go " ah one of these" and tab away.
- throwaway_ocr 4y agoThe whole argument falls apart for me when it's acceptable to 404 when an api version isn't found, while it's not acceptable to 404 when an employee isn't found. Why would we handle not finding an employee differently from not finding an API version? If we're saying employees/100 should return 200 because it "could" exist, but doesn't at the moment, then why would we ever return anything other than 200? Any route "could" come to exist at a future point.
- jstrong 4y agoI am ambivalent about 200 for employee not found, but that is clearly a different category of error compared to an api version not existing. for employee not found, the error is purely in data: there is no responsive data, but the request itself was made to an existing endpoint/url/route correctly. no api version existing is an improperly made request: whether or not there is data cannot be determined as the request itself was problematic, so we never even got there.
- bennyp101 4y agoDepends on the API I guess. If you are using graphql or jsonrpc or something, then sure return a 200 "The request was ok, but go look in errors" If you are using REST then I think it is fine to return a 404 - "The thing you wanted doesnt exist". That seems correct to me, the thing you wanted to get doesn't exist. The fact that /non-existant-path also returns a 404 is also correct, the thing/resource you wanted doesn't exist.
- dncornholio 4y agoAuthor thinks only a 200 response can contain a body or something
- that_james 4y agoThat's not the takeway I was after, I was pretty certain I had clearly described my intention of debating the usage of HTTP status codes as domain error messages. Not really sure why you think I'm of the opinion only 2xx codes can have responses, perhaps I could have been clearer.
- dncornholio 4y agoIt's because of these things: 1. If I were to call /api/v11/employees/1 I would get the exact same error. 2. Your examples on the bottom of the post shows a response code and body for 2xx but not a body for the 404.
- zzbzq 4y agoI agree with this, and in a way where I don't really see anyone who disagrees as a peer. We can agree to disagree, but it's condescending on my part. On one team we once also came up with a compromise where we would use 2 status codes. 200 for all success, 400 for all errors. However I would prefer HTTP status codes just go away altogether. Those are for the web browser software to do things like navigation, not for APIs. Same with HTTP verbs. Aside from the theoretical reasons why status codes aren't good for APIs, there's also a practical reason. Using the same status code for all intentional results is best because it allows the clients to write simpler error handling code. If they get a 200, then they also get a payload describing success or failure. If you get any other HTTP status, it's an unexpected or unknown error. If you were to instead use many HTTP statuses, each one comes in the known and unknown varieties, where the error description payload may or may not be there. In the simplest cases, this is equivalent. But in a more complicated case, where you many have more than 2 HTTP errors and also more than 2 application-level errors described in the payload, it becomes difficult to handle all the errors correctly. Since I'm obsessed with telemetry data, as any seasoned engineer eventually will be, the conflation between HTTP error data and application error data is vexing in logs and telemetry. In older APIs I work with, the errors are all conflated in this way--errors were being tracked and categorized in some cases simply by their HTTP status. But there's 400s, and then there's 400s. There were unexpected 500s, and then there were 500s we understood and could have added data to. Sometimes a transient 500 could actually be understood as a 400. The results are not correct, and they would never be correct because it is not manageable. People get so enamored with trying to make REST APIs into this rigorous thing. The REST thesis was an old academic trick where Fielding just described the way web browsers work, but changed all the specific nouns into spooky abstract ones. Most of the ceremony around HTTP trappings like status codes and verbs are there for the browser and are white elephants, obstacles, when trying to write an API.
- eadmund 4y ago> I agree with this, and in a way where I don't really see anyone who disagrees as a peer. Oddly enough, I disagree with this, and in a way where I don't really see anyone who agrees as a peer. Why? This approach fundamentally misunderstands the HTTP model. HTTP is not RPC. Again, HTTP is not RPC. Nor is it, as the article claims 'mostly just TCP with extra steps.' HTTP is a protocol for hypertext transfer which enables one to design applications not as APIs, but as state machines. > If they get a 200, then they also get a payload describing success or failure. If you get any other HTTP status, it's an unexpected or unknown error. That's just incorrectly written server software. A 400-series error may/should carry a payload. A 500-series error may/should carry a payload. Use HTTP the way it is intended and designed, and these things are easy; misuse it and they are hard (and as you note, unmanageable). So … don't do that!
- tauntz 4y agoThe suggestion at the end looks inconsistent: /api/v1/employees/100 -> StatusCode: 200 vs /api/v11/employees/1 -> StatusCode: 404 Should these be both "$thing not found" errors and indicated in the same way? The first example is "employee not found" and the 2nd is "API version not found" :)
- tekkk 4y agoWell I suppose this is solves a problem with differentiating between malformed payloads and such compared to API-specific errors. But in reality, you're just multiplexing the errors inside the 200 response without standard categorization. HTTP errors are nice because it gives the user immediate direction where to debug their problem. If an API returned 200 with error: 'unavailable to process a request' I'd argue that's way worse than returning 403 with no message at all. Ultimately I think why this is so confusing is because HTTP error codes are confusing and they should be clearly defined and/or include more codes specific to application errors rather than the network. It makes life much easier when you can see immediately the type of error you receive and if I had to depend on my API producer to write sensible error messages using 200 return codes I'd lose my mind.
- cpfohl 4y agoBoy. This is the best explanation of this belief I've come across yet. It's coherent and reasonable, even if I disagree. My highest scoring StackOverflow answer [1] (and my most controversial) is on exactly this topic and _no one_ in any competing answer have given _any_ sensible explanation of their reasoning. I've genuinely never understood their thought process, despite trying really, *really* hard. This explains it a bit, even though I totally disagree. There are other tools for helping differentiate different varieties of 404 or 401. There are status codes, there's _nothing_ preventing you from returning a payload with explanation or overriding the Reason Phrase. If your HTTP client is too fragile to handle a very common usage pattern for HTTP status codes then pick a different one. --- [1] - https://stackoverflow.com/questions/11746894/what-is-the-proper-rest-response-code-for-a-valid-request-but-an-empty-data/11760249#11760249 https://stackoverflow.com/questions/11746894/what-is-the-pro...
- stackbutterflow 4y agoReading this discussion on hn and stackoverflow, I feel sometimes software development is more philosophy than engineering.
- NateEag 4y agoYes, software dev absolutely is closer to philosophy than engineering. There is such a thing as software engineering, but that isn't what most of us do (nor is it what we should be doing, IMO). NASA needs software engineers. Low-level critical systems need them, so the G-MAFIA has a few. For most business purposes, though, the iterative exploratory "let's get a UI out there and see how it works" is exactly what we need. It's a useful, cost-effective strategy when lives aren't on the line, and it isn't engineering. (My current job title is "Software Alchemist")
- dncornholio 4y agoOnly if you let it. Developers tend to overthin things. Be careful on where you spend you energy on. Because honestly the answer is, as long as you are consistent, it really doesn't matter if you use status codes or not.
- 4y ago
- TrianguloY 4y agoThere is a standard for the "solution" in the article, passing the parameter on the body of a POST, like a remote call execution. That way you always return 200 if the call is correct, and the content tells you the result or errors. The REST standard uses the error codes as part of it, and using a standard wrong will mean that you can't use most standard tools, which is probably worst.
- aikah 4y ago> The REST standard There is no such thing as a "REST standard" and that's why we've been having all these pointless arguments for the last 15 years. If Fielding really wanted web developers to use REST he would have wrote a normative spec about REST. Seems to me that REST was never about developers writing web API. The whole "smart client" capable of API discovery through "hyperlinks" is... a browser with a user clicking on those links. Except that API aren't consumed by browsers but other apps that are neither as smart nor complex.
- cxr 4y ago> Seems to me that REST was never about developers writing web API. You are correct. <https://news.ycombinator.com/item?id=23672561 https://news.ycombinator.com/item?id=23672561>
- hn_throwaway_99 4y agoEveryone who is responding "well, the author just doesn't really understand REST", is pretty much correct, but I feel like they're ignoring the broader point of practicality. I absolutely have seen the problem the author describes, not to mention other issues that come from the fact that using response codes in REST doesn't really separate "technical" problems from "business/domain" problems. Just another reason I'm a big fan of GraphQL - error handling is much more clearly defined, with responses returning a 200 and the body defining the error.
- davewritescode 4y agoI don't want to be too hard on author here but this article is really misguided. Status codes are a very useful tool for observability and error handling and abusing HTTP 200 is a quick path to making your life difficult. Response bodies should disambiguate some of the different cases for the error (this path is wrong vs this employee doesn't exist) In general, applications using REST should follow these semantics. 2XX - Request was understood and we found what you were looking for 4XX - Something was wrong with the request on the client side. The client should take some action before retrying. 5XX - Something was wrong with the server and you can retry this request sometime later and it may work.
- revscat 4y ago> Status codes are a very useful tool for observability and error handling Adding to this, it is far easier for clients to parse response codes than bodies. TFA makes the following claim: > Returning a 2xx code immediately tells the client that the HTTP response contains a payload that they can parse to determine the outcome of the business/domain request. Which is wrong. Response codes say nothing about the body. Parsing the response body requires much more work: determining the format of the response received by the client, parsing it, validating it, handling edge cases, and so forth. Further, it is possible, however unlikely, that the client does not accept application/json. Now what? Now there is yet another problem you have to solve. And don’t forget the server has to generate all this as well. All of this could have been avoided simply by returning a simple 404.
- d1sxeyes 4y ago> Which is wrong. Response codes say nothing about the body. Actually, that's not correct. A response code can say a fair bit about the body, especially about whether it exists or not, but in some cases also the nature of the body. For example, a 203 says that the body of this request is not the same as the body sent by the origin server. A 204 tells the user agent that there's no body. 400s and 500s SHOULD have a body which includes an explanation of the error.
- 4y ago
- trymas 4y agoReminds me about a comment on graphql couple days ago: https://news.ycombinator.com/item?id=32037909 https://news.ycombinator.com/item?id=32037909
- kordlessagain 4y agoI'd really like to see the 402 method implemented with Lightning on a machine learning model site for authentication and use of the model. This way, other models could make calls on behalf of the queries they receive to their networks. https://github.com/lightninglabs/aperture https://github.com/lightninglabs/aperture https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/402 https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/402
- NoGravitas 4y agoNah, this guy is wrong. The reason that he's wrong is that URLs represent resources, such as, in his example API, employee data. If you submit a request for a resource that doesn't exist, then 404 is the correct response. His problem is in thinking that HTTP is just a transport layer for arbitrary applications to use for whatever they want. It's not; it's a framework for particular types of applications, using REST and HATEOAS. He's trying to write some kind of RPC application using REST syntax, but ignoring REST semantics.
- JackFr 4y agoThis. REST isn't RPC lite. If your mental model is that an HTTP GET is a function call, you think of 4xx's as exceptions, but that is not the correct mental model.
- that_james 4y agoREST is not HTTP! I didn't mention REST for a reason :) But that's a good point about the transport layer.
- dragonwriter 4y ago> REST is not HTTP! REST over HTTP is HTTP used according to spec, because the REST architectural style involves using the underlying protocol(s) per spec, but only the subset of such protocols that corresponds to REST semantics (but in HTTP’s case, since HTTP/1.1 is itself designed around REST principles and HTTP/2 and HTTP/3 maintain HTTP/1.1 semantics, that’s pretty much the whole spec.)
- donatj 4y agoThe endpoint you called doesn’t exist. 404. If you want to do something the author suggests, just abandon REST pathing and do a service call like api/v1/getemployee?id=100 Then the endpoint api/v1/getemployee exists, there’s just no results for your query.
- dragonwriter 4y agoThe query is part of the URL used, along with the path, for specifying the primary resource. The part of a URL representing a secondary resource and whose representation is dependent on the result of a retrieval action on the primary resource is the fragment, not the query; see RFC 3986 (query) https://www.rfc-editor.org/rfc/rfc3986.html#section-3.4 https://www.rfc-editor.org/rfc/rfc3986.html#section-3.4 (fragment) https://www.rfc-editor.org/rfc/rfc3986.html#section-3.5 https://www.rfc-editor.org/rfc/rfc3986.html#section-3.5
- willcipriano 4y agoPrinciple of least surprise would have you follow the spec. How much easier is it when you have to explain to everyone who uses your API that you have your own interpretation of how status codes are supposed to work?
- innocenat 4y agoI don't understand. Using the definition in the article, wouldn't other 4xx statuses like 401 or 403 also be business/domain error and and not technical error?
- vbezhenar 4y agoIMO it does not matter much. You'll write some middleware around API anyway. Handling HTTP codes or handling JSON fields - the only thing that I wish is consistency, so I can handle it all in one place. But what does matter is expectations. People naturally consume lots of HTTP APIs everywhere. And making things like everyone else's doing is important. Everyone's using HTTP codes for their API. That's reality. So I think that it's better to stick to this format until we'll go some circle and get back to SOAP 3.0 or something. If you would ask me, I'd prefer distinct set of statuses for APIs. So nginx will never return it, no matter what (mis)configuration happened. Right now if I misconfigure my reverse proxy, it'll return 404 for everything. This should be gateway error, but it's not and some clients will happily work thinking that everything not found which is not true. So something like 1200/1404/whatever for APIs and 200/404 for other things. And author's approach will not help, because it's equally easy to misconfigure nginx to return 200 with some garbage SPA HTML for every request. Well, at least there's hope that failing to parse it as JSON will signal to client that something's not quite right.
- dgb23 4y agoI very much disagree with this. This is both semantically wrong and unpractical at the same time. You can and probably should provide a payload for further processing in some cases, but that's an orthogonal concern. You are also writing a HTTP server, which provides resources, expects certain formats, provides certain formats, tells intermediate layers and clients about what to cache for how long, says whether a new resource has been created or _will be_ created. You might be redirecting or you might be telling a client that they need to retry a request because there has been a conflict. Your response might be processed by different layers, some of which only care about the status codes, while others will inspect the payload to make further decisions. The list goes on. Status codes are there so you can do all of these things and more in a standardized fashion. Even just from a human consumption perspective it is useful to just look at the code and immediately have an intuition of what happened. Also many of the status codes make no sense at all if an application server doesn't provide them. A reverse proxy doesn't know whether there has been a transactional issue on the application server, so it cannot say 409 without that information. A caching layer doesn't know about the domain model so it cannot say whether a specific resource can be cached forever.
- that_james 4y agoI think the issue is here is that I should have explicitly stated this was about HTTP RPC (I am steering clear of REST for a reason). In the context of RPC, is it still impractical? It feels clearer to me, but I might just be continuing my own confusion now
- dgb23 4y agoMy comment makes sense if you lean on REST/HTTP and want to leverage the uniform communication and layered architecture. The more bespoke you get, the less you want to care about what I said above. I interpreted your post as general if that makes sense. In any case, consistency is key. Personally I think it is easier to lean on standards in order to be consistent. Which is why I made some examples of status codes that can only be formed by an application server. I think "how do I avoid surprises, workarounds and inconsistencies" is a good question to make decisions around what we're discussing here.
- perrygeo 4y agoFeels like a strawman argument - returning a 200 with an error payload vs an opaque 404 are not the only options. If you need to differentiate between a technical error and a business domain error, we have other tools: A whole list of 4xx errors can be used for slightly different semantics (400 for "Bad request", 422 for "Unprocessable entity", etc). And we can attach a response body to a response of any status code, not just a 200. I'd definitely try these tools first rather than break HTTP semantics.
- imdsm 4y agoI disagree that returning a 404 for `/api/v1/employees/100` is wrong. If `/api/v1/employees/100` is the resource that is being requested, yet the record doesn't exist, then the resource doesn't exist. Much like `/some_photo.jpg` not existing would return 404 if missing too. What if we changed `/some_photo.jpg` to `/photo?id=some_photo` and it didn't exist? 404? Okay, now what if we change `/photo?id=some_photo` to `/photo?id=100`? What if then change that to `/photo/100`? At which point does it no longer become okay for the request to be tied to the resource? `/api/v11/employees/1` may be 404, and `/api/v1/employees/100` maybe 404 too, because neither of them are found. If anything, the problem is that HTTP status codes are limited and haven't really kept up with technology. We have a few additional codes, like with Cloudflare, but for the most part, there is no community project or standard for expanding HTTP Status Codes. Perhaps there should be.
- qainsights 4y agoExactly, my thought as well. Particularly, from QA perspective (automation or performance testing), testers often assume `200` means everything is fine; let's move on. But in reality, it is not.
- that_james 4y agoFor me it boils down to this: If you ask for an employee that doesn't exist, is it a failure? Or is it just a negative, but expected response? Perhaps that's a stupid question though.
- deleted 4y ago[deleted]
- wizofaus 4y agoIf you write an API designed to return a single entity matching the path provided, it's absolutely an error if it doesn't exist. If your API returns the simplest possible JSON representation in a success case, what JSON should it return if it doesn't exist?
- treis 4y agoThe spec says: >The 404 (Not Found) status code indicates that the origin server did not find a current representation for the target resource or is not willing to disclose that one exists. So when you say: >yet the record doesn't exist, then the resource doesn't exist. So you do have a current representation for the target resource. Namely, that it doesn't exist.
- light_hue_1 4y agoThis is like saying that if /web/index.html exists we shouldn't return 404 when /web/junk.html does not exist. That's clearly nonsense and not how http was designed to work.
- that_james 4y agoThat was what I meant by "actual web servers" :D poorly described though, fair enough. I should have been clearer about this relating to HTTP RPC. I updated the post. That said, after reading the responses here, I can see that what I've actually achieved is making the response harder to determine for the client, which is antithetical to the objective.
- moonchrome 4y ago> HTTP is just a protocol defining behavior that belongs in layer 7. It’s not a transport layer in a technical sense, but from the perspective of an API it’s mostly just TCP with extra steps That's basically arguing against REST and for RPC over HTTP
- kiernanmcgowan 4y agoHard pass - this is something that may be narrowly/semantically correct, but I’m not going to decode an HTTP response to figure out what business logic should happen after a bad lookup. I’ll also point to other status codes like 400 Bad Request or 401 Unauthorized which give a clear reason for why something didn’t work as expected. Under the author’s framework all 4xx responses should be 200.
- deleted 4y ago[deleted]
- revskill 4y agoREST stands for Representational State Transfer, so http status code has business meaning to the "state" itself. 404 is not ambigous. The real ambiguity is from 404, 400, 422 errors that you choose to return based on your validation logic.
- scelerat 4y agoThe author simply disagrees with the the HTTP RFC and a couple decades and many of thousands of implementors. There's nothing wrong with that, and many implementors and applications have certainly taken the tack the author suggests -- i.e. use HTTP strictly as a simple transport layer rather than a representation of application state -- GraphQL is a great example. But the rfc is pretty darn clear: "The 404 (Not Found) status code indicates that the origin server did not find a current representation for the target resource or is not willing to disclose that one exists." I can't find a reasonable interpretation of this that somehow means if the server can't find the document you asked for, you should still return 200.
- intelijstupide 4y agoThe author doesn't disagree with the HTTP RFC at all. All the author disagrees with is mixing the http server layer and application client layer status codes in the same client response being given to users. The only argument anyone should be having here is if they believe their api should speak HTTP to their client or if their api should speak through HTTP to their client. Personally? I think trying to shove business level response logic in HTTP (or REST for that matter) to be monkey butt level stupidity. HTTP was built as a RESTful protocol to serving files up to web browsers. And it works great for that. However, it was not designed to handle the 25 unique and user-readable or not-user-readable errors my one /authenticate route can throw back at the user at different times. REST and HTTP by extension excels at simple CRUD or file serving operations. But it's just not suitable for these kinds of API's. Here's an example: If my HTTP speaking API needs to respond with an error, how should my client handle it? How should I differentiate errors that should be displayed to users and errors that should be used only for debugging? If you need to suggest that my client needs to parse out the errors or differentiate between server and api errors in order to be able to show these errors to users then you've sort of proved my point that HTTP isn't a suitable solution to this problem - you're already wrapping an extra non-HTTP protocol inside HTTP, you're just only doing it for errors instead of all application responses.
- mesozoic 4y agoHave they never heard of codes other than 2xx and 4xx?
- that_james 4y agoI even put 500 in the post :) I was hoping to start a discussion around using HTTP status codes as domain error codes, as opposed to an opinionated payload. Maybe not as clear as I could have made it.
- UI_at_80x24 4y agoI've been in a SysAdmin/SRE type role for many years and I've used 200 status codes & non-200 codes as part of an automated site-check script for most of it. I've used the below script to automate scanning of 3000+ sites. (other optimizations exist, but this gets the job done quite nicely) I like to do 2 layers of checks, (1) Is the site returning a 200? (2) Can the site return a specific file? This script will send me an email alert if the site is not returning a 200 status code, but it's easy to daisy-chain other commands too. Here's an example of (1): #!/usr/local/bin/bash INPUT="/usr/sync/urls.txt" #DOWN="down.txt" TODAY="$(date +'%Y-%m-%d_%R:%S')" while read line ; do status_code=$(/usr/local/bin/curl -o /dev/null --silent --head --write-out '%{http_code}\n' $line) if [ $status_code -ne "200" ] then echo "$line is down since $TODAY" |mail -s Site_DOWN email@example.com #echo "$line at $TODAY is DOWN" >> $DOWN #example to output to a file. #You can add further actions here fi done </usr/sync/urls.txt
- btbuildem 4y agoI think this is contrary to what most would expect, and therefore "wrong" or at best annoying to work with. You can still include a payload with a 404 describing what is missing or why it wasn't found. If I see a 2xx status code, I assume the request succeeded. OP's way forces an additional check, more code, more potential for bugs.
- AtNightWeCode 4y agoI think this is essentially incorrect. However. It can sometimes be practical to wrap a model in a result object. I agree with that. The thing with response codes is that they are often logged in contexts where it is unpractical to read and parse the response body. Like for rate-limit rules in layer 7 proxies and so on. 404 are overused though. Sometimes it is better to design around the problem to only get “real” 404:s so to say. 204:s can also be used even though any REST fanatic will disagree with that.
- AtNightWeCode 4y agoIt is always baffling what you get downvoted for on HN. To have a get by id endpoint that does not return 404 if the entity is not found. Well, I fired people for less than that.
- hexer303 4y agoIt's all logical until you are dealing with an outage when your clients can't tell the difference between a wrong endpoint and a missing item. Example: https://news.ycombinator.com/item?id=31849488 https://news.ycombinator.com/item?id=31849488
- npteljes 4y agoOn the contrary, I was happy that with REST, we finally didn't put something on top of the current already kinda big pile, but used the topmost layer in a sensible way. 4xx responses can also have a body, which could explain to the caller whether it's the entity or the endpoint that doesn't exist.
- lapser 4y agoMany moons ago I worked with a system like this. An XML based API that returned 200 because "the server and/or service isn't broken" but the body would contain errors. I understand what the owner of the API is trying to convey. "My service is up, and the URL itself exists" (status 200), but in the body "the data you requested doesn't exist" (result: true, error: "doesn't exist). If you are stubborn enough, you can even think you're right. But by Odin's Beard this was one of the hardest APIs I had to work with. It doesn't help that most HTTP clients assume a sane* API expecting error codes based on the resources. Note: I say sane, but within this context, this is of course subjective. For me, error codes corresponding to the resources is sane, but to OP this is clearly not the case.
- adhoc_slime 4y agoI'm with the author here, I've had this conversation with the other devs on my team a few times and each time it lands on 404 if there's no data.. Its just not as useful as the dev who makes the client side applications. Its more convoluted because the server had no error though it returns an error code so as a client side developer, I'm left to decide was my URL invalid and the server really did 404? or was there no payload for the request so I received a 404. Ideally, the bad paths all are caught during dev-time but that just can't be assumed to always happen, it doesn't account for any of the risk involved with other systems changing and then 404's popping up. It could happen for other unexpected reasons so why should we assume it won't, it seems like we're just covering our eyes to the problem. for a short example, Imagine a brand-new account was made in a system. should it make some sort of GET request for a specific component of its account that is created asynchronously to the rest of the account creation, eg a 3rd party billing service, and receive a 404 from the server, there are different interpretations that the client could have around this error code! does it mean one such component was truly not created and needs to be? could it be that as a client we know implicitly of this delay and fudge a 404? using 404 as a catch all for these cases seems error prone and less informative than returning the true results of the GET request. I posit that any good client-side software handles 0 results from a request with an empty state, and 1 or more with a results state and 4XX with the error state(s). its more clear, trustworthy and easier to debug.
- ec109685 4y agoThere’s a 202 response code for “not yet fully created” https://stackoverflow.com/questions/11746894/what-is-the-proper-rest-response-code-for-a-valid-request-but-an-empty-data/11760249#11760249 https://stackoverflow.com/questions/11746894/what-is-the-pro...
- deleted 4y ago[deleted]
- EdSharkey 4y agoAs a Service Provider, the ambiguity of 404's we return is purposeful given that callers can be adversarial. All of these borked protocols were designed when we were all young and naive and all was peace and love. We have to live with them and deal with them as they are. If you care about your callers and they're internal trusted partners, you can return a traceId header they can use to diagnose the 404 to find out if the request was malformed somehow or the requested ID was truly missing or something else bad happened. Splunk can be a pretty rad thing when everyone is on the OpenTelemetry bandwagon. Otherwise, assume the caller is evil and treat them accordingly.
- petesergeant 4y agoRight, but why stop there: going to different URLs for different resources is also nuts. REST is fundamentally broken from this conflation of concerns across different layers. GraphQL — where you are sending and receiving data (and application errors) to a specific endpoint is much much saner.
- NoGravitas 4y agoNo, REST is good, but you should either use it or not. If you're not going to use it, then you really ought to be sending and receiving data to a specific endpoint. And once you're doing that, it actually starts to be questionable whether you need or want HTTP at all (ignoring the hellworld of middleboxes we live in).
- dragonwriter 4y ago> going to different URLs for different resources is also nuts It’s not, and if you want to know why it is not, think about what “URL” stands for.
- bluesnowmonkey 4y agoHere’s the issue: /api/v1/employees/<employee_id> These URLs, which philosophically we’re trying to convince each other are opaque resource identifiers, are actually structured requests. There is an API being provided, specifying how to craft the URL, among other things. It’s not a “resource” at all, it’s a function call, and the difference becomes extremely evident as soon as you start needing to add variations to the request using query parameters. Good APIs have few nouns and many verbs. That’s exactly the opposite of REST, which says you should have many nouns (URLs) and few verbs (HTTP methods). REST is a misguided idea that an academic had years ago. It doesn’t work well and it certainly doesn’t represent some philosophical ideal. We should stop pursuing it.
- lucideer 4y ago> Why do I get a 404 here? > [...] > As an API consumer, all I want to do here is raise my middle finger. Why? As an API consumer this makes sense to me. The author doesn't go into any detail on why it presumably doesn't make sense to them, nor what problems it presents from a client implementation standpoint? > RFC 7230 defines HTTP as an Application Layer protocol, which means it should represent application logic, right? No. What? No. Why on earth would it mean that. It's a protocol at the application layer - it has nothing to say or do w.r.t. the internal implementation details of that application. Why on earth would it? None of this article makes any sense whatsoever. The author is making some wild assumptions that seem entirely unique to themselves - I've certainly never seen anyone else face these logical challenges. > In any API call, there are 2 problems to solve as a client when you are processing the response: > 1: Did the technical request succeed? > 2: Did the business/domain request succeed? Where in your client code are you trying to solve these problems? Is it in the same place? If so, you've over-generalised / over-DRY-ed your error handling. If you've approached writing your client in a sane manner, you have business/domain context at the request site - you can tell the granularity of your request validity by the code location where you're handling the response. As a developer of client code, 4xx means "I did something wrong". It's not the server's responsibility to know how I've implemented URL-building and whether or not I'm using dynamic version strings. Take the author's example: /api/v1/employees/100 -vs- /api/v11/employees/1 Here's 2 ways I could build that URL (pseudocode): base = '/api/v1' section = '/employees' url = '$base/$section/$id' v = '1' url = '/api/v$v/employees/$id' The author's assuming something like the 2nd way, and supposing the API should be aware your client code could have supplied an invalid value for $v. But what if it's the 1st and you've supplied an invalid value for $base? Expecting knowledge of your client implementation on the side of the API implementers is nonsense. --- As for the solutions proposed, there's two problems: Problem 1 (harmless enough): The error context provided in the 200 response body will only be specifically useful for the author's example client implementation - many different clients will consume your API and their implementations will differ. For some of them, the context will be of less/no use (coupled with added developer confusion from the unexpected statuscode). Problem 2 (harmful): This kind of context is going to enable OSINT user & metadata enumeration in a very large number of API applications, so recommending this as general advice is actively dangerous.
- b0afc375b5 4y agoI encountered this type of architecture in one of our company's internal services. At first I thought the developers just didn't know about other status codes, but now I read this and I can see there is some rationality behind it. My take is that this kind of architecture might be fine for personal applications, but once you start working with other people this can get infuriating very fast (I know I was). Others already mentioned why this would be a bad idea (caching, etc.), but for me the most important thing is that it's not conventional. I don't know of any open source web framework that handles http statuses this way out of the box. If someone does please comment so I can have a look.
- ranger207 4y agoI agree with the author. HTTP is used as a transport protocol for your API, not the API itself. It's one layer lower. You don't expect to have to look at what TCP flags you got as part of the API, nor should you care about what HTTP errors you get as part of your API HTTP transports content, and the content is your API. HTTP should not be part of your API
- SNosTrAnDbLe 4y agoI think the best way to use REST is to not be cultish about it which just results in more discussions and unread RFCs on your org wiki. I always endup with erring on the side of more documentation
- HatchedLake721 4y agoIf you’re a public API, please don’t do this. I’ve personally integrated with almost 50 different SaaS APIs in the last 2 years. The worst ones to work with were the ones returning errors in 200. I don’t want to parse and write switch statements for your strings to understand whether it’s authentication, authorization, not found or any other error. Now I have logic tied to strings you return and I can’t wait until someone decides to change the error messages they return. People rarely assume there’s an API contract in error messages.
- that_james 4y ago> People rarely assume there’s an API contract in error messages But then what's the point of an API contract if it's not describing the returned data? What I'm arguing is the opinionated payload provides a lot of the same value. Or am I missing something? The monitoring systems would naturally not be stoked to see 2xx codes containing errors, but I'd just ping the monitoring system out of my application server anyways. Not sure if that's better or worse though. I've done like little to no platform engineering and I may be gravely underestimating the consequences of doing this, but it works well with prometheus. Perhaps I am a fool though :) but how else would I find out if I didn't put my ideas out there :D
- titusjohnson 4y agoNot who you're replying to, but I have input. But then what's the point of an API contract if it's not describing the returned data? What I'm arguing is the opinionated payload provides a lot of the same value. Or am I missing something? I guarantee you that you have not actually contracted your error messages. You have a typo, you'll change the phrasing to better describe the problem, you'll need to add more info for ancillary problems, etc. I doubt you'll bump the rev on your API when "The resource you requested was not found" gets tweaked to "We could not find anything by that ID!" in the name of 'friendlyness', but that's what you would need to do for me, as a client, for me to not have to ship a hotfix because you changed your error contract and now my users are seeing "Something went wrong, our engineers are looking at it" instead of my nice branded "404 - Not Found" page. You try employee 1, fantastic, it works! You try employee 100, not fantastic, it 404’d. Huh? Why do I get a 404 here? The path is clearly correct, otherwise employee 1 wouldn’t have worked either. “Ah”, you may be thinking “but it clearly means that the employee wasn’t found!” No, there’s nothing clear about that. If I were to call /api/v11/employees/1 I would get the exact same error. As an API consumer, all I want to do here is raise my middle finger. But as an API producer, this results in a conundrum: What am I supposed to do then? To start off with, stop worrying about your clients fat-fingering your API namespace. That is not your concern. You don't give a shit if they spend a _week_ hammering the wrong domain and getting 404s, why the hell would you care about them hitting a non-existent v11? You don't do anything beyond direct this confused user to your docs. If for _some reason_ you want to give more info for requests to `/api/v11/*` or whatever other non-paths you want to handle, just serve a 400 response back and let your consumers figure out what they screwed up; but I argue you have more important things to do with your time. Also, why are you worried about differentiating your server errors from client network failure? That's for the client app developer to handle. Don't worry about it. Your obligation begins and ends with a connection to your service. Opionated payloads should be mandatory Returning a 2xx code immediately tells the client that the HTTP response contains a payload that they can parse to determine the outcome of the business/domain request. That is to say - client checks HTTP response is valid (2xx status) - client can confidently parse the response and make a domain oriented decision, as opposed to a techinical one This makes your client happy. Very, very happy. Using our above examples, here is what we would see: If I was reviewing an API for an integration and I ran across this blog post and/or descriptions of this behavior in the API docs, your service would go straight into the "won't integrate with" pile. I'm simply not interested in the problems this paradigm will generate for us. I've gone down this road many times, these days it's use the HTTP Spec or GTFO. My API is clean, easy to understand and easy to debug. A client no longer needs to send me a request to ask for clarity on an endpoint that sometimes returns a 200 and other times returns a 404. Ah, there's the nut. Instead of adding descriptive error bodies to your 404 responses you threw the paradigm out the window and added descriptive error bodies to 200 Success responses. If you're not providing API packages for your users to hide this unexpected behavior, they are not "very, very happy". No one is "very, very happy" as they add yet more Magic Strings with which to infer what their remote resource means when it says "200 Success: Failure"
- that_james 4y agoY'all have given me a lot to chew on. I would like to point out a few of the detractors are conflating REST and HTTP RPC, I avoided using the term REST for a reason :) BUT, that being said, lots of good arguments against this stance. I appreciate the feedback :)
- dtertman 4y agoThe idea described here is implemented in e.g. TikTok's API, and as a consumer of that API (in Java at least), it has been awful to code against. Because most people abuse status codes, most tooling is built around status codes. All of the Java HTTP libraries, for instance, throw very nice and easily-handle-able exceptions for non-200 status codes, but do nothing at all for 200 codes. So, now, we need to: * try/catch the call anyway, because it might fail * parse the result * figure out if the result is an error or OK * throw / return early from error results * separate that throw from the try above It's all so much hassle to avoid a mistake that _client libraries don't make_ : using the wrong URL in the first place.
- hcarvalhoalves 4y ago404 means "this URL doesn't point to a known resource". HTTP doesn't and shouldn't make a distinction between "this URL maps to a known route on my API backend but the ID doesn't exist on the database" and "this URL is not mapped on my API backend", this is leaking implementation details to the client. This not "abusing" status codes, this is the expected behaviour for an HTTP server.
- ISV_Damocles 4y agoThe immediate rebuttal to this whole article is that you can have custom response bodies on any status code, not just when the status code is 200, so you can satisfy both kinds of users by having `/api/v1/employees/100` with `404` as the status code and `{ "success": false, "reason": "No employee with ID 100 exists" }` while `/api/v11/employees/1` can fail with `404` as the status code and `{ "success": false, "reason": "No such path: /api/v11", }` Now for those who are exploring your API with curl or are reading logs from their service they can see the human-legible information that may help them debug the error, while their application logic can continue to use `2xx` for success, `4xx` for my own failure and `5xx` for a server failure that I should retry after some back-off time, allowing it to do the most sensible thing it can in the situation, rather than explode in an uncontrolled fashion when an endpoint the developers only got successful 200 responses from during development suddenly encounters a 200 response with an error payload.
- dragonwriter 4y ago> The immediate rebuttal to this whole article is that you can have custom response bodies on any status code Not according to the spec, though you can for most status codes. (Consider, for example, 205.)
- bern4444 4y agoReturning a 404 makes more sense and you can still include a response body status: 404, body: { "result": false, "errorMessage": "No employee found for ID 100" }
- jameshart 4y agoSo, to address this on its own terms, talking about HTTP as an 'application programmer', ignoring browsers and REST and the whole value add of the HTTP stack in terms of caching and security and content negotiation... just thinking of HTTP as a way for a client to call a server and get back a message: This approach is the HTTP-server equivalent of exposing a function that sometimes returns null. When you call a function like that in your code, the call succeeds and returns, but the caller needs to now inspect the returned object to see if it is actually useful. In this case, the HTTP call succeeds, but you now have to parse the result to figure out whether you got something useful. In code, there are a few alternatives to having a function returning null: throwing an exception, the null object pattern, or the optional type/maybe monad. Using an HTTP status code lets your client choose an HTTP client implementation that manifests as one of these, according to their preference, simplifying their code. So just from an application design point of view... if you wouldn't build a function with that return signature, why would you build an HTTP endpoint that does that?
- jwlake 4y agoIsn't the solution to this just 400 for bad path, and returning a not found is 404 plus body of "entity not found"? Having to always check result: true is by biggest annoyance in APIs designed like that.
- mariusor 4y agoI think the first problem with the examples is that cool URIs don't change. This goes double for API paths. Putting the API version in the path of a request is not a good solution. Doing it introduces exactly this kind of ambiguities that could be avoided without them.
- that_james 4y agoPosting another comment rather than updating my last one: Thanks to all for the feedback. After knocking it around in my head, I concede I am wrong :) HTTP is, after all, an Application layer protocol. Whilst I remain unconvinced by some of the arguments, that one that got through to me was mostly about the reasoning behind using an application layer protocol in the first place: standards. And this breaks the crap out of those standards. The correct answer is probably closer to a combination of status codes and a _clear_ response message (as well as the correct Content-Type header!) An empty 404 is ambiguous, which is surprising to nobody :) fair points all round
- Karellen 4y agoI wouldn't say you're using status codes wrong - I'd say there's a mismatch between how you're using URLs and how you're using status codes. If your API was: `GET /api/v1?employee=1`, then returning 200 with a `{ result: false }` for employee 100 makes sense, because a resource identified by the URL `/api/v1` exists, but the parameter asks for something that doesn't. And `GET /api/v11?employee=1` returning 404 is also consistent, as there is no `/api/v11` resource. However, by setting your API to `GET /api/v1/employees/1` you're saying that each employee is resource identified by a URL, and so using HTTP status codes to say whether that resource exists is consistent with the way that API uses URLs. The way you're using status codes isn't intrinsically wrong. It would make absolute sense if your API used URLs in a way that complemented that usage. The only problem is that you aren't. I am wondering, do you have one endpoint per API version, or one endpoint per resource type? i.e. Should `GET /api/v1/xyzzy/1` return 200 or 404 if there is no `xyzzy` resource type? What if you decide to change your architecture and split a monolithic API into multiple services? Or do the opposite?
- theginger 4y agoI fundamentally disagree with this as being the correct code, a http get request is give me this resource, not does this resource exist, however it is all about context. It is your API, it is your decision to decide if you have looked for the resource and were unable to find it, so return an error, or you have looked for the resource and found a placeholder that says there could be a resource here, but currently isn't and successfully return that placeholder. To suggest people are wrong or abusing the protocol for not choosing to implement placeholders in their application is incorrect. To suggest people may benefit from implementing a placeholder instead of a 404 error is useful. My personal experience is that the error status code here is much more useful than the message. It's a big red/orange flag that says don't do what you normally do with this resource. But if your API lends itself particularly to being queried for things that often don't exist then those red flags become noise that mask other things you really do want to flag as errors.
- miohtama 4y agoIt really doesn’t matter how the API behaves unless there are external users. When you need to convince other people to use your API then those people will bring their own expectations how the API behaves and then you need to manage those expectations. If your API is esocentric on its design choices other people might find it unexpected, curse and move to do something else.
- arithma 4y agoWhat I like about this approach is that if you are using any other protocols for transport, everything becomes instantly portable. Imagine having the same logic serving objects per IDs through HTTP and websockets. Rest makes things more clumsy. Though I would concede doing things the REST way, as in the app-server participating in the http headers/status enables caching and all sorts of things that would be hell to do through otherwise.
- throwaway787544 4y agoslow clap
- imetatroll 4y agoSorry but this just sounds like "hey if you could talk to my server in any capacity then its 200!". Which means that all http status codes immediately collapse into a weird state of 200. No thank you.
- jalfresi 4y agoIn HTTP, URLs are opaque identifiers. If you want to isolate semantics from a URL, then use query parameters.
- dordoka 4y agoWhy people keep trying to reinterpret HTTP? Honestly it's even worrying that so many of these kind of articles pop up lately.
- apeace 4y agoI have been doing things the same way for a while. In my RPC API everything is POST (even for "getting" something), and everything returns 200. I don't like the design of REST. I don't think status codes should have meaning to an application. Why? Because there is not a defined status code for every type of problem. One thing I have often run into is: what status code are you supposed to use when business rules aren't followed? Let's say the resource exists, so it's not a 404, and the request is properly formatted as JSON and all the fields have valid values, so it's not a 400. But the user is trying to do an invalid action, like booking an appointment slot that is already full. What status code are you going to use? For this reason, I've seen two types of code bases that use status codes. The first one is error handling soup: if (res.statusCode == 404) { // Display a "not found" error to user } else if (res.statusCode == 400) { // There will probably be some validation errors in the body, so display those } else if (res.statusCode == 403) { // show a "forbidden" error to user } else if (res.body.error) { // Aha! We have some error that doesn't have a defined status code. // This block will contain a completely different type of error handling, // based on information found in the body. } And it's just a mess. The second one is a bit better, where they only care about 200 or not-200, and pass error information through the body: if (res.statusCode != 200) { // Error handling reading information from res.body } But why put in the work to use correct status codes on the server side if it essentially comes down to a boolean value? So my solution is to always have a boolean value called "ok", and if "ok" is not true there's always a human-readable error you can show to the user. if (!res.body.ok) { // Show res.body.error to the user } There's a bit more to it since I also account for passing back field-by-field validation errors, but the point is that I'm always returning 200 and I am always reading error information out of the body. If some resource doesn't exist, the error message will say that. If the user is forbidden from doing something, the error message will say that. If the appointment slot is full, the error will say that. There are only two places where I use status codes. If some unexpected error happens (like the database is down), I return 500. If my frontend ever sees 500 it sends the error to my error reporting system. If the user is not authenticated I return 401. If the frontend ever sees 401 it automatically redirects the user to the login screen. Importantly, both of these things are hidden away in a library I wrote, so my application code never thinks about them. It just thinks about !ok. Status codes work for things that are generic from the point of view of the client, in the sense that the client doesn't care if my database is down or if I misconfigured something or if I ran out of memory. It only cares if "something bad happened that needs to be reported to the devs", which is 500, or "this user is no longer logged in so they need to log in", which is 401. For everything else, the client does care about the specifics of what the error means, so I need to pass it that information. Status codes don't work for that.
- cheradenine_uk 4y agoClear, unambiguous and - wrong.
- Komodai 4y agoStupid post
- zihotki 4y agoThere is a wonderful RFC which solves the same problem - Problem Details for HTTP APIs. This approach is quite popular in good quality APIs. I was quite surprised that nobody mentioned it before. - https://datatracker.ietf.org/doc/html/rfc7807 https://datatracker.ietf.org/doc/html/rfc7807
- davecheney 4y agoThe OP is neglecting the important property that non 2xx responses can contain a body.
- akullpp 4y agoIn typical business applications you often have two endpoints: One to fetch a single resource and one to fetch all resources of that type. Why not merge them and work with lists, I think a lot people would intuitively understand the following: GET /users?ids=1 { "users": [] } GET /users?ids=1,2,3,4 { "users": [ { "id": 3 } ] } Transport the ids in any way you like, that's not my point.