When GET Doesn’t Fit the Request

The key rule of HTTP GET is simple: it shouldn’t modify server state. This convention is what allows intermediaries like browsers and client libraries to make safe assumptions about a request. When a form is submitted via GET, for instance, a browser knows it can automatically retry the submission if the network drops. With POST, however, it may not be safe to retry, so the browser will prompt the user for confirmation before trying again.

APIs lean on this same distinction. An app using GET for a read-only API call can rely on its HTTP client library to retry a failed request without understanding the specifics of the call. The Dropbox API follows this practice for calls that don’t alter state, but it isn’t always possible, as a notable constraint soon surfaces: GET requests have no request body. Every parameter has to go into the URL or a header. While the HTTP spec doesn’t limit the length of URLs or headers, most HTTP clients and servers in practice cap them at somewhere between 2 KB and 8 KB.

Running Into the URL Ceiling

That practical limit usually doesn’t cause problems, but one Dropbox API call challenged it: the /delta endpoint. It’s a read-only operation, so GET would be the semantically correct method, but its parameters are sometimes too large to fit comfortably in a URL or header. At that point, we were facing an interesting design failure: in HTTP, the property of modifying server state is tightly coupled with the property of having a request body, even though there’s no inherent reason for that coupling in the logic of the function itself.

Rather than contorting the API’s design or making runtime tradeoffs to fit the HTTP worldview, we switched /delta to POST. The REST-style convention wasn’t worth the cost.

Decoupling Semantics From HTTP

HTTP was originally built for a fairly specific use case: hierarchical document storage and retrieval. It’s no surprise that it isn’t a perfect fit for every API function. The workaround we settled on was to stop letting HTTP’s restrictions dictate API design.

Instead, we define each function on its own merits: does it modify server state or not? The server then accepts GET requests only for functions that don’t modify state and don’t carry unwieldy parameters, while still accepting POST in the general case. That lets an API opportunistically take advantage of HTTP’s conveniences without becoming its hostage.