It's really great to see this on HN. Major kudos should go to Eric Mill, Gray Brooks, and everyone else who contributed[1] to these standards (as well as the White House, whose API standards 18F forked[2]). There is a blog post[3] that further explains 18F's goals for these standards, as well as a bit on how 18F evaluates what becomes a standard and what does not.
These were discussed out in the open, and meaningful feedback, suggestions, or pull requests are certainly welcome.
I'm not a big fan of reproducing data which probably belongs in headers and shoving it inside the URL, in case of a request, or the JSON body, in case of a response. For example: range/offset requests, status codes when there are an exception, dates or transaction IDs in the request when If-Modified-Since (or If-Unmodified-Since when doing compare and set) is appropriate.
Also, the underscores for JSON thing isn't really solved. Google's own JSON style guide recommends camelCase:
Interesting re header data. Could you elaborate as to what kind of data belongs in headers, and explain why putting this data in headers should be a bright line requirement or a "sane default"?
First off: Because this functionality is in HTTP spec itself, I believe it should be leveraged when it makes sense. Otherwise, why even bother with HTTP and response codes?
Secondly, because you don't need to parse a JSON response to make decisions based on the response, both clients and servers can potentially be simpler and faster. If you got a 206 response and 1000 items back, but you made the request without an accept range header, not knowing if you were going to receive 10 items or 10000 items, you don't need to parse the JSON to find this out. This is opposed to just retrieving a 200 response, parsing and processing 1000 items, getting to the end to find a "next" property in your JSON you weren't expecting, and then firing off another request, when you could have already queued a second request if you had read the header first before processing your response. (RFC2616 Section 3.12 gives you some leverage in specifying range units, so its easy to define the range in items instead of bytes.)
However, I don't think that creating new headers is a good idea. My recommendation is simple: If it's in the HTTP spec and you are can use it according to how it's defined (like my items range example), then you should use it. If it's not in the spec, just go ahead and put it in the URI/response. The exceptions to that is maybe custom authentication mechanisms (i.e. HMAC or cookie based authentication), or anything which may be performance sensitive. For example, I have endpoints that are used in browser and by batch machines. It's easy to just use cookies for the browser and HMAC for batch machines.
Would you mind expanding a bit on the usage of Range?
The simplest case involving items is clearly `items=10-20`, but that has its own set of problems (your set must be ordered; I assume you'd use the key you're sorting over as your `ranges-unit`).
The place where this solution seems less than ideal is when you want to start from an id, but get a certain number of items. That is, the "infinite scroll" use case: If you simply do "sort by date, discard 10, return next 10 items", then you have a race condition where you do your first request, a second agent adds a new item, and you do your second request: the last item from the previous page is now the first item from the current page.
So, ideally, I want to request: "Ordered by date, where id > <last-id>, 10 items".
If you're strict about the definitions of `Range`, this doesn't seem possible.
Maybe the best way to do this would be `Range: items=decacfb98eface89+10` or similar?
Should the use of GET (when client is sending data) be discontinued completely? It seems a number of exploits have used data in the URL query string as part of attacks. Some of Homakov's hijacks come to mind..
Also the fact a URL query string may be more likely to logged who knows where, at least more likely than a POST body.
Les Hazlewood's talk on REST API design is one of the best on this topic I've seen, really got me confident in designing my first API: https://www.youtube.com/watch?v=hdSrT4yjS1g
17 comments
[ 3.6 ms ] story [ 50.0 ms ] threadGreat! That means you can build little static apps on github.io that use the APIs.
Also, would love it if this gave guidance on what style and format of documentation you need to have for the API.
[1] https://github.com/18F/api-standards/issues/19
These were discussed out in the open, and meaningful feedback, suggestions, or pull requests are certainly welcome.
[1] https://github.com/18F/api-standards/blame/master/README.md
[2] https://github.com/WhiteHouse/api-standards
[3] http://18fblog.tumblr.com/post/91868213233/hot-off-the-press...
I will point out that JSON is recommended as a default, and if there is a compelling reason not to use it in a specific case, that should be fine[2].
[1] https://github.com/18f/api-standards/issues or https://github.com/18F/api-standards/issues/8
[2] https://github.com/18F/api-standards#just-use-json
Also, the underscores for JSON thing isn't really solved. Google's own JSON style guide recommends camelCase:
https://google-styleguide.googlecode.com/svn/trunk/jsoncstyl...
Secondly, because you don't need to parse a JSON response to make decisions based on the response, both clients and servers can potentially be simpler and faster. If you got a 206 response and 1000 items back, but you made the request without an accept range header, not knowing if you were going to receive 10 items or 10000 items, you don't need to parse the JSON to find this out. This is opposed to just retrieving a 200 response, parsing and processing 1000 items, getting to the end to find a "next" property in your JSON you weren't expecting, and then firing off another request, when you could have already queued a second request if you had read the header first before processing your response. (RFC2616 Section 3.12 gives you some leverage in specifying range units, so its easy to define the range in items instead of bytes.)
However, I don't think that creating new headers is a good idea. My recommendation is simple: If it's in the HTTP spec and you are can use it according to how it's defined (like my items range example), then you should use it. If it's not in the spec, just go ahead and put it in the URI/response. The exceptions to that is maybe custom authentication mechanisms (i.e. HMAC or cookie based authentication), or anything which may be performance sensitive. For example, I have endpoints that are used in browser and by batch machines. It's easy to just use cookies for the browser and HMAC for batch machines.
The simplest case involving items is clearly `items=10-20`, but that has its own set of problems (your set must be ordered; I assume you'd use the key you're sorting over as your `ranges-unit`).
The place where this solution seems less than ideal is when you want to start from an id, but get a certain number of items. That is, the "infinite scroll" use case: If you simply do "sort by date, discard 10, return next 10 items", then you have a race condition where you do your first request, a second agent adds a new item, and you do your second request: the last item from the previous page is now the first item from the current page.
So, ideally, I want to request: "Ordered by date, where id > <last-id>, 10 items".
If you're strict about the definitions of `Range`, this doesn't seem possible.
Maybe the best way to do this would be `Range: items=decacfb98eface89+10` or similar?
Javascript is usually drinkingCamelCase, and JSON that isn't looks really weird.
Also the fact a URL query string may be more likely to logged who knows where, at least more likely than a POST body.