Google Reader API

I love Google Reader, but one function of the service that seems SOOOO missing, is it’s API. I’ve found a few people online that have taken the time to reverse engineer the calls the app makes, and thankfully, they’ve documented their lessons learned while interacting with the service. Sadly, this information is many many years behind.

My first stop was at Niall Kennedy’s blog which definitely gave me hope that my app could communication with Reader, however, being the article was from Dec 2005, it doesn’t really help you get started.

My next find was pyrfeed which seems like a full document on the subject, but again, outdated. It’s last update (save maybe the comments) was from Jul 2007. And quite frankly, this document did nothing but confuse me.

So I kept looking and eventually found gPowered’s post about it. VERY helpful, but still, being from Aug 2007, outdated. Lot’s of the functions there still work, but not all of them.

Thus, I’ll post up what I know works as of March 29, 2009. It doesn’t cover everything you can do, but looking back at gPowered’s post, along with watching Google Reader in Firebug, will certainly give you a means to obtain any missing functionality from what I have.

Ok, Here are the things I need to do:

  • Get a list of unread items
  • Mark those items retrieved as “read” (so they don’t pull in when I next query for new items)
  • Subscribe to a feed
  • Unsubscribe to a feed

And of course, to do any of this, you need to authenticate your user. gPowered will detail this process for you, but just to eliminate confusing, you will find this script also makes calls to:

  • Authenticating a user
  • Getting a session id (SID) for the authenticated user.

Also you will see me importing “Communicator” and referencing it as “cm”. This is simply a URLLib library I’ve set up so I can make various http (POST and GET) calls in one easy place.

The general rules are this:

  • You authentic the user once.
  • Every call to a Reader action (get unread items, add a subscription) will require an updated SID
  • The rest is simple… you’ll call a url and post data to it and get results.

So, here is some Python code for doing that:

import re, simplejson, timefrom lib.communicator import Communicatorclass GReader(object): cm          = Communicator() login       = '' password    = 'your_password' source      = 'your_source' reader_url  = '' # url's we will be making calls to: url_for_login           = '' url_for_token           = '%s/api/0/token' url_for_reading_list    = "%s/api/0/stream/contents/user/06091295523448519803/state/" url_for_subscribing     = "%s/api/0/subscription/quickadd?ck=%s&client=contact:%s" url_for_unsubscribing   = "%s/api/0/subscription/edit?client=contact:%s" url_for_marking_read    = "%s/api/0/mark-all-as-read?client=contact:%s" def get_reading_list(self, SID):    url    = self.url_for_reading_list % (self.reader_url, int(time.time()), self.login)    result =, self._header(), None, self._cookie(SID))    if result: return simplejson.loads(    else: print 'Error: get_reading_list' def subscribe_to(self, SID, feed_url):    url    = self.url_for_subscribing % (self.reader_url, int(time.time()), self.login)    data   = { 'quickadd': feed_url.encode('utf-8'),               'ac': 'subscribe',               'T': self._get_token(SID)             }    result =, self._header(), data, self._cookie(SID))    if result: pass    else: print 'Error: subscribe_to' def unsubscribe_from(self, SID, feed_url):    url    = self.url_for_unsubscribing % (self.reader_url, self.login)    data   = { 's': "feed/%s" % feed_url.encode('utf-8'),               'ac': 'unsubscribe',               'T': self._get_token(SID)             }    result =, self._header(), data, self._cookie(SID))    if result: pass    else: print 'Error: unsubscribe_from' def mark_all_as_read(self, SID):    url    = self.url_for_marking_read % (self.reader_url, self.login)    data   = { 's': 'user/06091295523448519803/state/',               't': 'All%20items',               'T': self._get_token(SID),               'ts': int(time.time())             }    result =, self._header(), data, self._cookie(SID))    if result: pass    else: print 'Error: mark_all_as_read' def _get_SID(self):    data   = { 'Email': self.login,               'Passwd': self.password,               'service': 'reader',               'source': self.source,               'continue': "" }    result =, self._header(), data)    if result: return'SID=(S*)',    else: print 'Error: get_SID' def _get_token(self, SID):    result = % self.reader_url, self._header(), None, self._cookie(SID))    if result: return    else: print 'Error: get_token' def _cookie(self, SID):    return 'Name=SID;SID=%s;;Path=/;Expires=160000000000' % SID def _header(self):    return { 'User-agent' : self.source }