Reverse engineering Google Reminders for fun and profit

June 7, 2020

If you've got a Google device you've probably used google reminders before. It's built into a lot of google services including Calendar, Keep, Inbox (💀), Home and others. It's pretty simple, but like many other things in the Google ecosystem, it's absurdly inconsistent. You'll get different options and see different reminders in a completely different UI even though they are all "Google Reminders".

I use Todoist which is an awesome task management service. Naturally, I'd expected them to have some sort of integration with Google Reminders, but when I checked with their support, they said it wasn't possible as Google Reminders didn't have a public API.

I decided that I'd build my own tool to sync reminders from Google to Todoist
(todo-sync.atymic.dev for those interested).
First step, figure out how to query, update & delete google reminders.

Reverse Engineering Google's Reminders API

I jumped into Google Calendar with Chrome's dev tools to figure out how the API worked. Usually it's pretty easy to figure out how they work, even without access to the code. I was completely surprised when I saw what looked like a gibberish request and response format. For example, this is a "List" request:

{
  "1": {
    "4": "WRP / /WebCalendar/calendar_200531.18_p0"
  },
  "2": [{"1":3},{"1":16},{"1":1},{"1":8},{"1":11},{"1":5},{"1":6},{"1":13},{"1":4},{"1":12},{"1":7},{"1":17}],
  "5": 1,
  "6": 100,
  "24": "1599314400000",
  "25": "1583157600000"
}

Checking the Content-Type header (application/json+protobuf) tell us this is a protobuf request. Protobuf is typed data transfer protocol, used internally by lots of teams at google. Unfortunately, this made it a lot hard to figure out how we can interact with their APIs.

Finding the Protobuf Definitions

Jumping into the Sources tab in dev tools, I started looking through the Javascript files for code related to the reminder APIs. This was made more difficult by minified javascript but I managed to find the definitions burried in the code for part of the API Client. Here's what one of the looked like:

 FSc.Ra = FSc.prototype.Ra;
    TQ.prototype.Ra = function() {
        var a = ISc;
        a || (ISc = a = UH(TQ, {
            0: {
                name: "ListTasksResponse",
                fullName: "caribou.tasks.service.ListTasksResponse"
            },
            1: {
                name: "task",
                Ud: !0,
                Ia: 11,
                type: zQ
            },
            2: {
                name: "continuation_token",
                Ia: 9,
                type: String
            },
            4: {
                name: "continuation",
                Ia: 11,
                type: HQ
            },
            3: {
                name: "storage_version",
                Ia: 4,
                type: String
            },
            5: {
                name: "response_header",
                Ia: 11,
                type: KQ
            },
            6: {
                name: "skipped_storage_read",
                Ia: 8,
                type: Boolean
            }
        }));
        return a
    }
    ;

A mess, but clearly we're looking in the right place. I also found some other attempts to re-implement the API which were helpful in figuring the format out. By referencing all the objects in the google calendar javascript we can figure out the format of all the requests.

For example, when listing reminders in TodoSync, we make the following query:

{
  "5": 1, // include_archived = true
  "6": 500, // limit = 500
  "13": { // RecurrenceListOptions
      "1": 1 // collapse_mode = true
    }
}

I've also created a repository in github documenting how the API works for those interested.

Building TodoSync

The concept is pretty simple. Poll Google Reminders, and sync those across to Todoist. Naturally, I leaned on Laravel's awesome tooling to quickly get the tool up and running.

I used Laravel Socialite to quick add Oauth authentication for both Google and Todoist. Once you've logged in and set up the sync, a minutely scheduled task runs to sync each user's reminders.
To make this scale with more than a few users, we trigger a background job for each user so they can run asynchronously. Laravel Horzion manages the workers and provides alerts if there's any issues.

Synced Reminder

The code is all open source on Github if you'd like to see how an application like this works, or want to host it yourself!

Feel free to hit me up with any questions you have and i'll do my best to answer them.