Dropbox starts using POST, and why this is poor API design.
Today Dropbox announced in a blogpost titled
“Limitations of the GET method in HTTP” that it will start allowing
requests for APIs that would otherwise only be accessible using
It’s an interesting post, and addresses a common limitation people run into when developing RESTful webservices. How do you deal with complex queries? Using URL parameters is cumbersome for a number of reasons:
- There’s a limitation to the amount of data you can send. Somewhere between 2KB and 8KB apparently.
- URL parameters don’t allow nearly enough flexibility in terms of how you can define your query. The percent-encoded string doesn’t really have a universal way to define in what character set its bytes are,
- The URL is not nearly as versatile and expressive as JSON, and let alone XML.
Their solution to this problem is to now allow
POST requests on endpoints
that traditionally only alowed
Is this the best solution? Well, it’s certainly a pragmatic one. We’re clearly running into artificial limitations here that are poorly solved by existing technology.
The problem with
POST discards a number of very useful features though.
is defined as a non-safe, non-idempotent method. This means that if a
request fails, an intermediate (such as a proxy) cannot just assume they can
make the same request again.
It also ensures that HTTP caches no longer work out of the box for those requests.
The HTTP specification has an extension that defines the
this spec is picking up some steam, and a lot of people are starting to use it
to solve common problems in API design.
In the same vein, there’s been another standard HTTP method for a while with
REPORT, which specifically addresses some of the issues with
- Can have a request body
- Is safe
- Is idempotent
It appears in the IANA HTTP Method list and is actually quite great for this use-case. The main reason it’s off people’s radar, is because it originally appeared in a WebDAV-related spec a long time ago.
However, its semantics are well defined and it works everywhere. I would love to see more people start picking this up and adding it to their HTTP API toolbelt.
GET with a request body
Whenever this topic comes up on Hacker News, there’s almost guaranteed to be a
comment about using
GET with a request body.
I wondered about this myself (6 years ago now apparently!) and it’s my top question on stackoverflow. Clearly a lot of people have the same thinking process and wonder about this.
Using a request body with
GET is bad. It might be allowed, but it’s
specifically defined as meaningless. This means that any HTTP server, client or
proxy is free to discard it without altering the semantic meaning of the
request, and I guarantee that some of them will.
Furthermore, the benefits of using
GET are then completely gone. Caching is
not based on request bodies, and these requests are not addressable with a
Literally the only reason why anyone would do this is because
nicer, it’s an aesthetic decision, and nothing more.
GET requests are great: addressability
Whether you use
POST or the superiour
REPORT request, you still miss the
biggest advantage of using
GET query is always a URI. Anyone can link from it. Parts of your service
can link to specific results. Even external services can integrate with you by
referring to specific reports.
POST query can not be linked and neither can a
REPORT query. All we can
do is explain that a certain URI accepts certain http methods with certain
media-types, but this is not nearly as elegant as a simple URI. Linking rocks.
An alternative approach
One way to solve this issue entirely and fix all problems related to this, is disconnect the query you are doing from its result.
To do this, you could create a
/queries endpoint where you allow clients to
POST requests to, with request bodies containing all the details of
This operation could create a new ‘query resource’ and responds by saying that
the result of this query can be found at
/queries/1 using a
Then to fetch the result of the query, you can just issue a
GET request on
/queries/1. This means that you:
- Don’t break the Web!
- Resources in your API are still addressable and can be linked to.
- The results are still cacheable, safe and idempotent.
We’re still using a
POST request here though, but there’s a fundamental
difference: we are using
POST to create a new ‘query resource’ and we don’t
use it to do the query itself.
The drawback? It’s definitely a bit more complicated to design this API and it requires storage on the server (for the query and/or the result of the query).
But then, REST services are not meant to be simple. They are meant to be robust and long-lasting, just like the web itself.