Uber recently announced the availability of a public API. I decided to take it for a spin and provide some commentary. The quick version is that it is a fairly standard HTTP API and that is both a good thing and a bad thing.
You can find the official documentation for the Uber API on their developer site. In general, the documentation is easy to read and it seems fairly comprehensive for what is, at the moment, a pretty simple API. From what I can see, it is just a read-only API at the moment, but they do advertise that “more endpoints are coming”.
The API reference document starts with this,
All API requests are made to https://api.uber.com/<version>, where version is currently v1
It is unfortunate that the Uber API developers have not spent more time at conferences like API Craft, RESTFest, APIDays, EndpointCon, where it is becoming increasingly common knowledge that putting a v1 at the front of your URI is not a best practice. There are many other more finely grained ways of doing versioning when you really must introduce breaking changes. Having a version number as a first path segment creates a huge cliff where potentially everything changes in v2 and can become a nightmare for client and server developers.
HTTP Status Codes
The documentation then lists the HTTP status codes. I’m really not sure why the writers of the documentation feel the need to rewrite what is supposed to be standardized. However, it is interesting that they chose to use 422 to distinguish between “invalid content” and a “malformed request”. It will be interesting to see how they make that distinction.
It does amuse me that they only include the 500 error in the 5XX range. I’m pretty sure most clients are going to need to account for 502, 503, and 504 as well. Enumerating out the response messages that a server supports is very misleading to client developers. Clients need to be built to account for every HTTP status code because you never know what other status code returning intermediary is going to be sitting between you and the origin server. There is no harm in defining actions based on ranges of status-code values. It’s not as if every status code needs explicit handling code.
Once again, we have another API that has decided they need to re-invent the “error message” wheel. There a number of efforts underway to create a standardized format, such as http-problem and vnd-error. API providers should get behind one and not invent their own.
It is great that Uber has chosen to use the new 429 status code to indicate that rate limits have been exceeded and it is commendable that they have re-used the headers from the Twitter API for indicating the rate limit status. However, it would have been nice if they could have dropped the X- as has been recommended in RFC 6648.
I’m not really sure why there is a need to return the rate-limiting information on every request. Why not provide a resource I can query when I want to know what the rate limit is. Why send those extra bytes over the wire?
Reading the authentication section is almost a delight because I almost don’t need to read it. For resources that a specific to a particular user, it is OAuth 2. This is the way it is supposed to be with HTTP APIs. By taking advantage of the uniform interface, all the infrastructure issues are just supposed to work the same way. I don’t need to learn anything new. At least I shouldn’t. Unfortunately, when testing the API we identified a number of nits related to the OAuth2 process. I suspect they will be ironed out soon enough though.
For resources that are not specific to a user then you use a server_token which is supplied when you register your application. Interestingly the documentation states that you MUST pass the token using an Authorization header using the authorization scheme Token. This is awesome, just the way it should be done. However, the reality is a bit different. If you look at the tutorial example the server_token is passed as a query string parameter. This is convenient but you may find your server_token littered throughout other people’s log files. Both ways work, so just do the right thing!
The API documentation chooses to call these “Endpoints” in the heading but then uses the term Resource within the actual documentation page. It’s one of those things that doesn’t matter once you already know what they mean, but it can be confusing for people who are new and trying to understand which words are significant and which are not.
I am never very good at getting a grasp of APIs from looking through a list of URIs. I much prefer a visual representation. I’m used to building hypermedia APIs which are a bit easier to draw pictures of because they naturally have links between resources. However, for non-hypermedia APIs like the Uber API, I have found that a hierarchical diagram of the URI space by path segment can be a good visualization tool.
From what I understand from the documentation, this is how the URI space is defined. The documentation does a decent job at explaining what each of these resources represent.
Unsurprisingly, the API simply returns application/json for all representations. This means that it would be difficult for clients to do anything other than use the request URI to identify the structure and semantics of the response. Even sniffing the content to determine semantics is tricky because resources like /v1/me don’t have a root object name. Coupling representation structure to the request URI is commonplace within the HTTP API world, I just wish developers had a better understanding of the price that they are paying for taking this shortcut.
I am disappointed that we don’t see any caching directives on the responses coming back from the API. As all of the requests require an Authorization header it makes sense to choose not to make the responses publicly cacheable. However, private caching would be perfectly viable for responses. Especially for things like a list of products. Even more so considering the current performance of the API. To retrieve the list of locations is currently taking around 350ms in my tests and once an HTTPS connection is established it drops down to around 150ms. For a single request that isn’t terrible, but I am doing this on a desktop with a decent broadband connection.
One mitigating factor is that there is an Etag on the products list, so at least it allows the client to perform conditional GET requests and spare the bytes over the wire. However, the current list of products weighs in at around 1KB so, chances are it would all fit in a network packet any way!
In general, it is a fairly simple HTTP API, that uses techniques that most API developers will be familiar with. I’m happy that we are starting to see some stabilization around security mechanisms, but I do wish that there was a little more forward-thinking in API design in general.
Interestingly, there is quite a bit of opportunity here to take advantage of hypermedia to define workflows in this kind of API. There are likely a few fairly well-defined paths through the process of selecting a product, verifying the prices, determining a time to arrival, and then selecting a car. Along the way, the client application accumulates state: where the user is located, what type of car they want, which driver they want, and finally accept a request for pickup. This is an ideal scenario for hypermedia as the engine of application state to accumulate the information selected by the user whilst leading them on to the next step of the process.