Leveraging private Uber APIs in a Chrome extension
I used to use Mint for budgeting until it shut down in March 2024. As a replacement I swapped over to Monarch, which I've loved even more and would recommend if you're looking to do any sort of budget tracking. 1 Not affiliated in any way, but if you're interested in signing up this link gets you 50% off the first year (and much more importantly gets me $30).
A key part of how I use it is manually going through each transaction. The automatic categorization is remarkably good,
2
They use LLMs to great effect providing merchant explanations and categorizing transactions from new merchants. Combined with easily customizable rules and a deference to historical changes it's a pretty good system.
but I like to tag if I should split the payment with people or reimburse through work, as well as jot down some short notes to remind myself the context. I mostly do this audit at the end of each month
3
Citation needed, given that I recently closed my backlog...going back to April 2024. 🫣
and usually have good recall—except for Uber transactions, where I struggled to remember exactly where I went and who with from the dollar amount alone. I would pull out my phone and scroll through transactions, but I figured that there should be some way to automate this.
4
So let's dig into the Uber APIs!
Uber APIs
Uber has a developer dashboard which felt like a good place to start. Unfortunately while it let me create an application, when it came time to actually get an auth code that I could use to hit the API it told me to reach out to my Uber point of contact. 5 Maybe I'm just behind the times, everyone else have one? The API documentation also seemed more geared towards requesting rides and managing a driver experience than seeing historical rider information, so I abandoned this as a dead end.
Private Uber APIs
We know that there's some API, because the Uber app is getting the information to display from somewhere. While I could reverse engineer the app like I have in the past it tends to be easier to check the web version first. Luckily unlike Lyft, 6 Well, kind of unlike Lyft, WIP post™ about that. Uber allows you to see this information in a browser at https://riders.uber.com/trips.
We know that some request is surfacing a canceled Uber ride from São Paulo Airport, and we can easily find it by reloading the page with DevTools open and searching for the text, revealing a call to https://riders.uber.com/graphql
, with an Activities
query for the 5 most recent rides.
Copying the request (complete with headers and cookies) into a Python script and bumping up the requested limit
from 5 to 100 returned the 50 most recent rides (seemingly hitting an internal cap) with pagination tokens. We're in business! We can also click into 'Details' to see a separate request with a GetTrip
query to get waypoints, a map of the route, and fare/tip information, which I combined for the script.
Uber Eats
We can do something similar for Uber Eats. Navigating to https://ubereats.com/orders shows multiple orders, along with their prices and item breakdowns. Checking DevTools shows that this isn't backed by GraphQL, but is backed by a paginable https://www.ubereats.com/_p/api/getPastOrdersV1
API which is almost as good (though it's limited to 10 results at a time instead of the 50 we saw for rides).
Copying this into a script also works, noting that despite the identical login affordances for Uber Eats and Uber rides they have different authorization tokens and cookies.
Monarch
The last API piece of the puzzle that we need to interface with the Monarch transactions is Monarch itself. While they currently don't have a public API
8
They do want one eventually, and they're hiring! (Once again, I am not affiliated. But what's the point enjoying a great product if you don't evangelize a little?)
they do use a GraphQL interface that many people reverse engineered, https://api.monarch.com/graphql
.
9
Now did I use any of these libraries? Of course not, reinventing the wheel is such a time-honored past time that there are two programming sections in the reinventing the wheel Wikipedia page.
While I originally used hardcoded merchant IDs from my search, the original merchants all start with UBER *EATS
and UBER *TRIP
(and UBER *ONE
for the subscription service), so we can just fetch transactions with search: 'UBER *'
and needsReview: true,
and filter down to the two merchant strings we're handling. Again this needs a token for authorization, though Monarch tokens seem to have a very long expiration window (mine was still working seamlessly months later).
Matching transactions
We now have a list of Monarch transactions, and relevant data from Uber—time to match them up. This was substantially harder than I expected for a couple of reasons. First, tips can be sent after the ride finishes, in which case they show up as a separate transaction days later, 10 I do this all the time, I effectively only tip and rate when I open the app for the next ride. whereas the API will show it combined. Second, all charges recorded by Monarch are in USD, but Uber transactions in other countries that use different currencies like the British Pound or the Brazilian Real show up in the API in their native denomination. Thirdly, because of Uber One discounts or other deals the amount reported by the API doesn’t always match the amount actually charged, 11 Price differences could be a scam or misbilling issue, but the amount I was charged was always less than it showed in the Uber app. If that's a scam, please keep it coming. differing by anywhere from 5 cents up to in some cases more than $5.
You can see the matching code for this in the GitHub, but generally the way I solved this was splitting each transaction into three: combined, just tip, and without tip. Then I'd look for an exact match within a 2 day window, up to a 2 week window for tips, and finally a 10% wiggle value on absolute cost. For the weaker matches I'd add a "warn" flag that would give me a heads up so I could decide if it was a true or false positive.
Turning it into a Chrome extension
This started off as a Python script but I quickly converted
12
Language conversions are also a great application of AI. Syntax ✅, Novel thinking ⚠️.
it into an Electron app so that I could have an interactive UI that didn't feel crappy worked (it's still visually...barebones).
But that's okay, because like all projects the goal is to get it working first, and then get it pretty. Functionality-wise we're mostly good, 13 This was the first side project where I really used TypeScript and there's no way I go back. Infinitely infinitely better than raw Javascript, static typing should be tablestakes for any real programming. with one caveat—because all of the API calls are unofficial, using them requires me to manually open tabs and invoke some GraphQL requests, and then hardcode the headers from those requests into the app. What if instead we could do this automatically?
Using onBeforeSendHeaders
Chrome extensions can have a lot of control.
14
Unless you're getting in the way of Google's ad-supported business model.
One key API that we can leverage is chrome.webRequest.onBeforeSendHeaders.addListener
. This (combined with the webRequest
and cookies
Manifest permissions) allows us to listen to all requests to a given URL and extract the headers/cookies. By listening to the GraphQL endpoints we can trigger those tabs in the background and then clone the headers for our use. This way we don't need to hardcode anything, and the extension can automatically refresh headers if they're stale (though you will still need to manually log in).
I really like this approach. A lot of random side projects I do involve gluing APIs together, and this in-browser piggybacking feels like it speeds up a lot of them, and allows me to avoid having to implement an actual authentication system. I'll definitely be doing this again.
Viceroy
Now it's polishing time: I wanted to create something that someone else could use as opposed to a hardcoded mess. Making the header extraction automatic was a large part of this, but I had also hardcoded the merchant lookup for Uber transactions, the list of tags that you could tag transactions with, and location mapping for better automatic Uber ride descriptions. Getting rid of those dependencies, and polishing the UI turned into Viceroy!
This is slightly better than the original Electron app, cribbing pretty heavily from the design language of Monarch's extension. Note that this homage
15
Homage? Copy? I'll leave that to the Supreme Court and Andy Warhol to fight about (this is one of my favorite recent Supreme Court cases, if you haven't read it there's tons of snark about whether people really understand art, e.g. this footnote and this excerpt from Kagan's dissent).
extends to the name Viceroy, which is a different species of butterfly that's a visual mimic of the Monarch butterfly
16
As an interesting rabbit hole it's a Müllerian mimic instead of a Batesian mimic, the difference being that for Batesian mimicry a non-poisonous species mimics the appearance of a poisonous one so predators avoid it too, while as in Müllerian mimicry they're both poisonous. We only categorized Viceroy's as Müllerian in 1991 (palatability research is so funny, they just gave a bunch of butterfly abdomens to blackbirds and measured if they liked them). Also both Müller and Bates had really interesting lives, both absconding from Europe to Brazil for decades to catch and examine butterflies. Bates had a pet toucan!
(also referenced by the extension icon which is a 180° hue match and rotation of the Monarch icon).
In addition to the main Transactions page I also added in a Settings page, allowing me to remove the hardcoding for tags/location mapping and make it user configurable.
You can download Viceroy from my GitHub. The naming and branding is a little too confusing to fully publish to the Chrome extension store, but at least it's installable (and public for others, as this seems to be the first use of the undocumented Uber Eats API in GitHub code search).
-
Not affiliated in any way, but if you're interested in signing up this link gets you 50% off the first year (and much more importantly gets me $30). ↩︎
-
They use LLMs to great effect providing merchant explanations and categorizing transactions from new merchants. Combined with easily customizable rules and a deference to historical changes it's a pretty good system. ↩︎
-
Citation needed, given that I recently closed my backlog...going back to April 2024. 🫣 ↩︎
-
Maybe I'm just behind the times, everyone else have one? ↩︎
-
Well, kind of unlike Lyft, WIP post™ about that. ↩︎
-
Maybe
FacebookMeta's greatest contribution to the world. It's definitely not Myanmar politics and sure, React is nice and all, and yeah, okay, having actual images has blown Craigslist out of the water but getting everyone to use an easy-to-edit query language that can be repurposed to your own ends? Tough to beat. ↩︎ -
They do want one eventually, and they're hiring! (Once again, I am not affiliated. But what's the point enjoying a great product if you don't evangelize a little?) ↩︎
-
Now did I use any of these libraries? Of course not, reinventing the wheel is such a time-honored past time that there are two programming sections in the reinventing the wheel Wikipedia page. ↩︎
-
I do this all the time, I effectively only tip and rate when I open the app for the next ride. ↩︎
-
Price differences could be a scam or misbilling issue, but the amount I was charged was always less than it showed in the Uber app. If that's a scam, please keep it coming. ↩︎
-
Language conversions are also a great application of AI. Syntax ✅, Novel thinking ⚠️. ↩︎
-
This was the first side project where I really used TypeScript and there's no way I go back. Infinitely infinitely better than raw Javascript, static typing should be tablestakes for any real programming. ↩︎
-
Unless you're getting in the way of Google's ad-supported business model. ↩︎
-
Homage? Copy? I'll leave that to the Supreme Court and Andy Warhol to fight about (this is one of my favorite recent Supreme Court cases, if you haven't read it there's tons of snark about whether people really understand art, e.g. this footnote and this excerpt from Kagan's dissent). ↩︎
-
As an interesting rabbit hole it's a Müllerian mimic instead of a Batesian mimic, the difference being that for Batesian mimicry a non-poisonous species mimics the appearance of a poisonous one so predators avoid it too, while as in Müllerian mimicry they're both poisonous. We only categorized Viceroy's as Müllerian in 1991 (palatability research is so funny, they just gave a bunch of butterfly abdomens to blackbirds and measured if they liked them). Also both Müller and Bates had really interesting lives, both absconding from Europe to Brazil for decades to catch and examine butterflies. Bates had a pet toucan! ↩︎