Google Reader API series
- Part 1 – Programming to the API
- Part 2 – Listing API
- Part 3 – Editing API
In part 3 of this series on using the Google Reader API I will discuss using the POST API to edit your Google Reader data. Although the REST architecture and HTTP in general doesn’t enforce it, Google appears to have chosen to make any commands which alter the state of your Reader account POST (as opposed to GET) requests.
In Part I I went over how to program for the HTTP POST requests so for now I’ll just go over the API. Arguments to POST calls are stored in a separate message body, unlike GET arguments which are stored in the URL exclusively. This is the main differentiating factor between the two types of calls. All POST calls to the Google Reader API take a single GET argument in the URL string – the client argument, which is the same as the client argument seen in all of the API calls in Part II of this series – just use a unique string identifying your own software package if in doubt.
For the purposes of this tutorial I’ll split the calls into a number of categories.
- Subscription editing – adding, editing and removal of subscriptions and their folders/tags
- Item editing – editing of items, such as adding/removing tags and stars, “liking” items, sharing (broadcasting) items, keeping items unread, etc.
- Email – Emailing items using your Google Gmail account
Subscription Editing
URL: http://www.google.com/reader/api/0/subscription/edit?client=[your client]
POST Data
| Argument | Description |
| s | The feed identifier. This is the URL of the feed preceeded by feed/, for example feed/http://blog.martindoms.com/feed/ for this blog’s RSS feed. |
| ac | The action to take on this feed. Possible values are subscribe to subscribe, unsubscribe to unsubscribe and edit to edit the feed. |
| t | The title to give the feed, only relevant in subscribe and edit actions. |
| r | A label to remove from the feed. This is in the format user/[UserID]/label/[LabelName]. As usual UserID can be replaced with a single dash. For example, to remove the label “MyLabel” you’d use the string user/-/label/MyLabel |
| a | A label to add to the feed. See the notes for the r argument above. |
| T | Your token (see Part I if you’re unsure of what this is). This is an argument in every POST call. |
Examples
Adding a feed – let’s add this blog’s main RSS feed to our subscriptions, but give it the title “Mr Doms’ Blog”. We’ll POST the following data to the relevant URL (at the top of this section). You’ll need to URL encode this data before posting but I’ve left it unencoded for ease of reading. Let’s assume I’ve already got myself a token which looks like abcde.ABCD1234abc
s=feedhttp://blog.martindoms.com/feed/&ac=subscribe&t=Mr Doms’ Blog&T=abcde.ABCD1234abc
Editing a feed – we want to add this feed to our “Infrequently Updated Blogs” folder on Google Reader to keep things organized. POST this data:
a=user/-/label/Infrequently Updated Blogs&s=feed/http://blog.martindoms.com/feed/&ac=edit&
T=abcde.ABCD1234abc
Removing a feed – finally, let’s remove this blog from our subscriptions out of frustration at Martin’s lack of regular updates.
s=feed/http://blog.martindoms.com/feed/&ac=unsubscribeT=abcde.ABCD1234abc
Item Editing
URL: http://www.google.com/reader/api/0/edit-tag?client=[your client]
POST Data
| Argument | Description |
| a | A new tag to add. This can take one of several formats depending on the type of tag you’re adding to the item. See the notes at the bottom of this table for details. |
| r | Remove an existing tag. This tag can take one of several formats depending on the type of tag you’re removing from the item. See the notes at the bottom of this table for details. |
| async | Can be either true or false. This appears to be a utility argument for Google’s own website, as it doesn’t seem to make a difference to my API calls. |
| s | The feed that this item belongs to. See the previous section for information on the format of this argument. |
| i | The item you want to tag. This is in the format tag:google.com,2005:reader/item/[item identifier]. |
| T | As usual this is your token. See Part I if you’re unsure about what this is. |
Tag identifier formatting – to add or remove a tag on an item you need to use a tag identifier string in the a or r argument. Google doesn’t seem to have a fully consistent way of formatting these tag identifiers, but here’s what I’ve figured out.
- Add/remove a custom tag (item label): user/-/label/[NewTag]
- Mark an item read or unread: user/-/com.google/read
- Star an item: user/-/state/com.google/starred
- Add or remove a “keep unread” tag: user/-/state/com.google/tracking-kept-unread
- “Like” or un-”like” an item: user/-/state/com.google/like
- Share or unshare an item with Google Reader friends: user/-/state/com.google/broadcast
Examples
To use any tag features you’ll need to retrieve an item identifier. Presumably if you’re displaying items in your software then you’ve already got an item identifier. If you don’t know how to retrieve these items, see Part II for the listing API. For the purpose of these examples I’ll be working with an item that represents Part I of this series, which happens to be item 0816b6dbac1d768d. We’ll also be assuming a token string abcde.ABCD1234abc as before.
Add a star – to add a star we send the POST data a=user/-/state/com.google/starred&async=true&s=feed/http://blog.martindoms.com/feed/&i=tag:google.com,2005:reader/item/0816b6dbac1d768d&T=abcde.ABCD1234abc
Mark as unread – we want to remove the “read” tag to mark an item as unread. Use this POST data: a=user/-/state/com.google/read&async=true&s=feed/http://blog.martindoms.com/feed/&i=tag:google.com,2005:reader/item/0816b6dbac1d768d&T=abcde.ABCD1234abc
The item tagging doesn’t get any more complicated than that. Easy!
Emailing
URL: http://www.google.com/reader/email-this?ck=[CurrentUnixTime]&client=[YourClient]
(current Unix time argument is optional)
POST Data
| Argument | Description |
| i | The item identifier. See the previous section for details. |
| emailTo | The email addresses to send the item to. You can send to multiple recipients. These addresses are in the format
|
| comment | The email body |
| subject | The email subject |
| ccMe | Whether to send the email to your own GMail account also. Can be true or false. | T | Your token. See Part I if you’re unsure of what this is. |
Example
Here’s some example POST data to send an email about Part I of this series of articles to the address example@example.com
i=i=tag:google.com,2005:reader/item/0816b6dbac1d768&emailTo= <example@example.com>, &comment=Check this out&subject=Google Reader API&ccMe=false&T=abcde.ABCD1234abc
By Martin Doms : Using the Google Reader API – Part 2 January 20, 2010 - 11:10 am
[...] Part 3 – Editing API [...]
By Martin Doms : Using the Google Reader API – Part 1 January 20, 2010 - 11:10 am
[...] Part 3 – Editing API [...]
By Rosebush January 24, 2010 - 10:02 pm
Does this url “http://www.google.com/reader/api/0/subscription/edit?client=[your client]” can use now?
This is my code, it works normally until 2010/01/, but after that date, it always return error 401.The SID and token are all right.
who can help me? Thank you first!
*********************************************************
public int addnewfeed(String feedurl,String foldername) {
int res = -1;
String posturl = “http://www.google.com/reader/api/0/subscription/edit”;
List nvps = new ArrayList();
if (GoogleLoginSID == null || GoogleLoginSID.compareTo(“”)==0)
{
return res;
}
else
{
nvps.add(new BasicNameValuePair(“Cookie”,”SID=”+mySID));
}
nvps.add(new BasicNameValuePair(“client”,”-”));
nvps.add(new BasicNameValuePair(“s”,”feed/” + feedurl));
nvps.add(new BasicNameValuePair(“ac”,”subscribe”));
nvps.add(new BasicNameValuePair(“T”,getToken()));
if (foldername != null)
{
nvps.add(new BasicNameValuePair(“a”,”user/-/label/” + foldername));
}
nvps.add(new BasicNameValuePair(HTTP.CONTENT_TYPE, “application/x-www-form-urlencoded”));
// HttpResponse response = gPostMethod(posturl, nvps, HTTP.DEFAULT_CONTENT_CHARSET);
HttpResponse response = gPostMethod(posturl, nvps, HTTP.UTF_8);
if (response == null)
{
return -1;
}
return response.getStatusLine().getStatusCode();
}
By Martin Doms January 25, 2010 - 3:29 pm
Hmm, your code looks OK to me. Are you properly URL encoding the data? I can’t seem to reproduce the 401 error. 401 usually indicates an authentication issue, are your SID and token valid?
By AJ February 19, 2010 - 6:54 am
Hi Martin,
Great article. However, I can’t seem to get Item Editing EDIT-TAG to work. I always get a 401. I am using C#. Do you have any code that is working?
Thanks.
AJ
By Edwin Lee February 21, 2010 - 7:57 am
Thanks! Great Stuff! Got a question though…
Using the web-based Google Reader interface, i can either open a feed for *real* reading, or just “mark it as read” (using the “mark all as read” function).
The difference it seems between these 2, is how it is refkected in the Trends. Items that are actually opened for reading are counted as “Items Read”, while items that are just “marked as read” are not.
In terms of the API, how are these 2 actions differentiated? (Because, what i understand is that to mark an item as read is to add a “read” state to the item, or is there a different way?)
Thanks!
By ann April 9, 2010 - 10:19 pm
inyteresting
By Daniel Baulig April 21, 2010 - 12:56 am
Hi, nice work so far! I am currently working on a google reader api implementation myself. I’m just wondering if you found anything out about the lifetime of the token. For how long will it remain valid usually? Keep it up!
By Martin Doms April 28, 2010 - 7:41 pm
Regarding token lifetime I have never discovered a good answer to this. If anyone knows please let me know. I’ve never personally experienced an expired token but I’ve also never set out to explicitly test it either.
By cosina1985 June 1, 2010 - 1:26 am
hi. good job!
but i found ‘ot’ value it’s not just firstitemmsec/1000.
do you have any new idea about this paramter?
By Andy June 24, 2010 - 4:15 am
I was using this flawlessly for just under 2 months, and then yesterday, it suddenly stopped working and errors with a 401 Permission Denied response.
The initial Auth, via getSid works fine and it retrieves an SID just as one would expect.
When the SID is passed to the “http://www.google.com/reader/api/0/token” URL to obtain the token the google responds with the 401 permission denied.
My initial thought were they don’t like us accessing the reader api but they aren’t basing the 401 denial on IP as if I use the same server to login to Reader via chrome/IE it’s fine.
I fired up fiddler and had a look at what the reader is current doing, it appears to use a number of different URLs, I wonder if they have changed the api slightly.
I’m going to try and modify your great example to work around the 401 error, will let you know if I get anywhere.
Thanks for the get article.
By Andy June 24, 2010 - 4:23 am
Update : Reading the google group http://groups.google.com/group/fougrapi/ it appears that a bunch of people are getting the same error.
But what’s even better, there’s now OAuth support for Reader. Whoopie!
By Martin Doms June 24, 2010 - 3:17 pm
Hey thanks for letting me know Andy! I will update my articles with OAuth information as soon as I have time (in the midst of exams right now!). Again, thanks so much for letting me know.
By Andy June 26, 2010 - 3:35 am
Update ::
Okay I’ve managed to get it to work.
Although I’ve implemented OAuth and have had been able to get some of the features to work it appears that it not quite perfect and I’m waiting on a response from the Unofficial Group on Google, in the mean while I followed through some other post on there and have seen there’s another way, w/o using OAuth.
Much the same as you have Martin, only you’re sending an AccessToken in the Header on the Request, and there’s no need for the cookie that’s passed.
The getSid method is much the same, with the exception that the body now contains an additional “Auth=” parameter, this is what you use in the header over the cookie’s SID.
Here’s a quick hack where I’m modified your original getSID code:
private void getSid()
{
string requestUrl = string.Format(“https://www.google.com/accounts/ClientLogin?accountType=GOOGLE&service=reader&Email={0}&Passwd={1}”, _username, _password);
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(requestUrl);
req.Method = “GET”;
HttpWebResponse response = (HttpWebResponse)req.GetResponse();
using (var stream = response.GetResponseStream())
{
StreamReader r = new StreamReader(stream);
/* TODO : Use propper SubString & IndofOf over Split & Replace */
string cSID = “”;
string cLSID = “”;
string cAUTH = “”;
foreach (string cLine in r.ReadToEnd().Split(‘\n’))
{
if (cLine.StartsWith(“SID=”)) { cSID = cLine.Replace(“SID=”, “”); }
if (cLine.StartsWith(“LSID=”)) { cLSID = cLine.Replace(“LSID=”, “”); }
if (cLine.StartsWith(“Auth=”)) { cAUTH = cLine.Replace(“Auth=”, “”); }
}
_sid = cSID;
_lsid = cLSID;
_auth = cAUTH;
}
}
There’s no longer a need for the SID or LSID either.
Initially I thought there wasn’t a need for the getToken, but my initial tests’ still return 401 if I don’t obtain a token, even if I don’t actually pass it. So in order to make getToken work replace the cookie with the following request Header & Content Type :
……
string url = “http://www.google.com/reader/api/0/token”;
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Method = “GET”;
req.ContentType = “application/x-www-form-urlencoded”;
req.Headers.Add(“Authorization”, “GoogleLogin auth=” + _auth + “”);
HttpWebResponse response = (HttpWebResponse)req.GetResponse();
……
Then the common httpGet / httpPost methods that are used for the other interaction require the same modifications.
Here’s httpGet, as that’s all I’ve had a chance to test so far :
private HttpWebResponse httpGet(string requestUrl, string getArgs)
{
string url = string.Format(“{0}?{1}”, requestUrl, getArgs);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = “GET”;
request.ContentType = “application/x-www-form-urlencoded”;
request.Headers.Add(“Authorization”, “GoogleLogin auth=” + _auth + “”);
return (HttpWebResponse)request.GetResponse();
}
Hope that helps anyone else who wants to make use of the API.
Once I get time / If I get any feedback on the OAuth mechs, I’ll update you with my findings, personally I’d rather use OAuth as I’m not particularly happy with keeping Username & Passwords, much nicer to use the OAuth system.
Cya
By Eric Mann June 29, 2010 - 1:06 pm
Andy, thanks for such a quick and easy-to-understand solution for this. I guess this is what we get for building on top of an “unofficial” undocumented API. Though I find it incredibly frustrating that they would change the authentication process on the fly like that.
In any case, I look forward to seeing what you put together with oAuth. Hopefully that will prove more stable in the longrun. I’ve rewritten much of this tutorial in PHP on my own site (for use in plug-ins for WordPress) and my own tutorial could benefit from an overview of using oAuth with the Google API.
By Andy June 30, 2010 - 8:13 pm
Eric
The Google Group (http://groups.google.com/group/fougrapi/) does have some Googlers on it and there was a post on there sometime ago that announced the change ahead of time.
If you do intend to use the API then I recommend you join the group to be sure of receiving any future announcements / changes.
By Fred. July 10, 2010 - 8:31 am
A C# wrapper class to get unread count on a Google Reader Account…
http://www.chapleau.info/cs/blogs/fchapleau/archive/2010/07/09/google-reader-api-to-check-for-unread-article.aspx
By sam October 11, 2010 - 11:32 pm
Very useful,thanks!
Is there an API to post comment or notes for feed item??
By rajani April 26, 2011 - 10:35 pm
how to post a note to google reader from android app.Is there an API to post comment or notes ?
By rajani April 26, 2011 - 10:36 pm
how to post notes to google reader from android application.Is there an API to post comment or notes ?
By rajani April 26, 2011 - 10:37 pm
how to post notes to google reader from android application.Is there an API to post comment or notes ?
….Rajani
By rajani April 26, 2011 - 10:37 pm
how to post notes to google reader from android application?
By aze August 5, 2011 - 6:56 pm
Very useful,thanks!
By thaison November 19, 2011 - 3:39 pm
I have th 400 BAD Request when add a new feed?Does anybody help me?
StringBuilder buf = new StringBuilder();
HttpsURLConnection request = null;
OutputStreamWriter post = null;
try {
URL url = new URL(StaticValue.URL_ADDFEED);
//URL url = new URL(“https://www.google.com/reader/api/0/subscription/quickadd”);
request = (HttpsURLConnection) url.openConnection();
request.setDoOutput(true);
request.setDoInput(true);
request.setUseCaches(false);
buf.append(“s”).append(“=”).append(“feed/http://vnexpress.net/RSS/GL/trang-chu.rss”);
buf.append(“&ac”).append(“=”).append(“subscribe”);
//buf.append(“&a”).append(“=”).append(“user/18030984262532758053/label/tab4″);
buf.append(“&t”).append(“=”).append(“TrangChu”);
buf.append(“&T”).append(“=”).append(getGoogleToken(auth, sid));
/*buf.append(“quickadd”).append(“=”).append(URLEncoder.encode(“http://vnexpress.net/RSS/GL/trang-chu.rss”));
//buf.append(“&ac”).append(“=”).append(“subscribe”);
buf.append(“&T”).append(“=”).append(getGoogleToken(auth, sid));*/
request.setRequestMethod(“POST”);
request.setRequestProperty(“Content-Type”, “application/x-www-form-urlencoded”);
request.setRequestProperty(“Content-Length”, buf.toString().getBytes().length+”");
request.setRequestProperty(“Authorization”, “GoogleLogin auth=” + auth);
request.setRequestProperty(“Cookie”, “SID=” + sid);
post = new OutputStreamWriter(request.getOutputStream());
post.write(buf.toString());
post.flush();
/* BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream()));
buf = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
buf.append(inputLine);
}*/
post.close();
//in.close();
Log.e(“Content-Length:” , buf.toString().getBytes().length+”");
Log.e(“response from server:” , buf.toString());
int code = request.getResponseCode();
Log.e(“response code: “, String.valueOf(code));
Log.e(“response message: “, request.getResponseMessage());
}
catch (ProtocolException e) {
// TODO: handle exception
Log.e(“e0″, e.getMessage());
}catch (MalformedURLException e) {
// TODO: handle exception
Log.e(“e1″, e.getMessage());
}
catch (FileNotFoundException e) {
// TODO: handle exception
e.printStackTrace();
Log.e(“e2″, e.getMessage());
}
catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
Log.e(“e3″, e.getMessage());
}
catch (Exception e) {
Log.e(“exception”, e.getMessage());
//TODO: do something about it
}