Unofficial API

I’m in the process of moving away from a Mac to Linux, and one of the things that I’m looking for is a desktop application that manages my tasks and also syncs with an Android application. In my opinion, there is no better Android task application than, however the only thing that they have that even remotely resembles a desktop application is a Chrome extension. While that’s helpful, it doesn’t provide the level of desktop integration that I prefer, so I decided to try to write one.

Unfortunately, somebody has already tried to go down this path and it ended with the search for the API, as detailed by this Quora question. I’m generally not one to take “no” for an answer, so I kept digging until I found the file contained in their chrome extension. This file just happens to detail the REST API that the Chrome extension uses, so I’m reposting it here with the hope that it might help somebody.



Any.DO client-server REST API Documentation

Introduction & API Notes

Clients use REST API calls over HTTP to synchronize tasks, categories, user preferences and sharing information with the server.

There are 5 types of requests: GET, POST, PUT, DELETE, and GET with a specific ID.

Here are the supported request types:

GET /endpoint

GET /endpoint/id

POST /endpoint

PUT /endpoint/id

DELETE /endpoint/id

  • All API is currently placed under the “/state-manager” path.
  • All JSON requests (all requests except the login) must have the application/json content type.
  • The API supports sending the data in dictionary mode, with flattened tasks, called “flatDict”.

    To return the data using a dictionary with the global ID as the key of every value, use responseType=flatDict

    as a GET parameter to every request.

  • In JSON requests, all data sent using POST and PUT requests must be JSON encoded data in the body of the request.
  • All requests use a globally unique identifier encoded with URL safe base64 encoded. The globally unique ID

    is a 16 byte random binary string. After base64 encoding, the ID will be a 24 byte string.

  • in PUT, DELETE, and optionally GET requests, you need to add the 24 byte global ID to the URL.

Code to create the global ID:

function encodeSafeBase64(str) {
    return str.replace("+", "-").replace("/","_")

function createGlobalId()  {
    var randomString = "";
    for(var i = 0; i < 16; i++) {
        randomString += String.fromCharCode(Math.random() * 256);
    return encodeSafeBase64(encodeBase64(randomString));

GET requests

There are two types of GET requests – with or without the ID.

In flatDict mode, the response is a dictionary with all of the tasks, using their UUID as the key and a dictionary

of the data as the value. If a GET request was done with the ID, the task dictionary is returned in the body as is.

POST requests

To create new objects, a list with dictionaries of objects is sent to the server. If you need to create only one

item, put a single entry in the list. Note that the global ID must be created by the client and not the server!

PUT requests

To update an object, send a PUT request with the ID in the URL. The body should have the new object in the body.

DELETE requests

To delete an object, simply send a DELETE request with the ID in the URL.


  • Right now, the only form encoding call done to the server is the login process.
  • Only POST is supported.
  • The parameter _spring_security_remember_me should always be specified.
  • Password is not encoded/encrypted in the request.

Endpoint: /state-manager/j_spring_security_check

POST parameters:

    j_username: <email>,
    j_password: <password>,
    _spring_security_remember_me: "on"


The users table contains all user info and preferences.

Endpoint: /user


    name: <string>,
    username: <email>,
    password: <plaintext password>,
    emails: [<email>],
    phoneNumbers: [<string>, <string>, ...],
    instDetails: {
        version_code: <int>,
        widget_1x4: <boolean>,
        widget_4x4: <boolean>,
        current_view: <string>,
        gtasks_logged_in: <boolean>,
        versionSDK: <int>,
        country: <string>,
        language: <string>,
        c2dmReceiverId: <string>,
        additionalData: <string>,
        installationId: <string>,
        completed_tasks: <int>,
        platform: <string>,
        todo_tasks: <int>,
        done_tasks: <int>,
        shake: <boolean>,
        pnsToken: <string>


The tasks endpoint.

Endpoint: /me/tasks


    id: <uuid>,
    parentGlobalTaskId: <uuid>,
    ownerPuid: <uuid>,
    title : <string>,
    category: <string>,
    creationDate: <date>,
    dueDate: <date>,
    expanded: <boolean>,
    priority: "Normal" / "High",
    status: "UNCHECKED" / "CHECKED" / "DELETED" / "DONE",
    repeating: <boolean>,
    latitude: <string>,
    longitude: <string>,
    shared: <boolean>,
    subTasks: [] # not used in flat mode
    sharedFriends: {
            puid: <uuid>,
            name: <string>,
            status: "PENDING" / "ACCEPTED" / "REJECTED" / "NEW",
            sharedMethod: {
                shareMethod: "ANYDO" / "EMAIL" / "PHONE",
                value: <uuid>


The task categories endpoint.

Endpoint: /me/categories


    id : <uuid>,
    name : <string>,
    listPosition : <int>,
    isDefault : <boolean>


To any staff that happens to see this: if you would rather that I don’t repost this information, I’d ask that you post it somewhere on your site instead. What is the harm in allowing users to use the same API that your applications do? The worst that can happen is that you have many more applications that integrate with your service (on platforms that you may not want to support).

I hope this helps somebody!

13 comments on “ Unofficial API

  1. sean said on June 23, 2013 at 1:52 pm: Reply

    Hi there!

    Thanks for reverse engineering this API. I’m having some trouble authenticating, however.

    I’m POSTing this URL which returns me a 500 page with a generic HTML response:

    Any thoughts?

    • cweagans said on July 8, 2013 at 2:50 pm: Reply

      I have no idea why it’s doing that. Honestly, I haven’t played with it that much. My recommendation would be to get a copy of Charles ( and inspect the requests that the Chrome extension makes. This will make it easy to see what data needs to be passed, and how to replicate it for your own applications.

    • Srinivasan Gopalan said on September 9, 2013 at 1:10 pm: Reply

      Same result here. Some sort of py call trace in the 500 response.

  2. Seth Alves said on August 28, 2013 at 7:20 pm: Reply

    try Posting to

    with content-type application/x-www-form-urlencoded and fields:


    That should get you two cookies called


    Then GETing things like:

    should work.

  3. Yatao Li said on October 18, 2013 at 2:37 am: Reply

    Great! Never thought it’s written there. :-D Thanks for the information!

  4. said on November 15, 2013 at 10:05 am: Reply

    Hi, I’m not a programer, but would love an any do desktop client. Dumb question, but how do I use this code to make an any do desktop client? Just plug it into terminal?

    • cweagans said on November 25, 2013 at 11:04 pm: Reply

      This information is really for programmers. There’s unfortunately no easy way to have an desktop client based on this information, however, if you use Chrome, there is a Chrome Web App that you can install that acts very much like a desktop client. On a Mac, it’s even a separate application that you can Cmd-Tab to.

  5. Kenny Saelen said on January 6, 2014 at 10:22 am: Reply

    I want to do this from within C#, has anyone tried this yet. I can’t seem to get past the Unauthorized exception… :|

  6. Kenny Saelen said on January 6, 2014 at 10:37 am: Reply

    I am not getting past the unauthorized thing with C#.

    WebRequest req = WebRequest.Create(@””);
    req.ContentType = “application/x-www-form-urlencoded”;
    req.Method = “POST”;

    using (var writer = new StreamWriter(req.GetRequestStream()))

    HttpWebResponse resp = req.GetResponse() as HttpWebResponse;

    it ironically gives me the exception : unauthorized :)

    • Bob Lamoureux said on January 28, 2014 at 9:29 pm: Reply

      You need to first do a GET to the /me URL so that a JSESSIONID cookie gets returned

      Also, you need to create a CookieContainer so that the cookies that get returned are managed. This cookie container should then be used for all subsequent requests

      Here is a working Powershell script (as close to C# as you should need)

      Stick your username and password into the $postData line near the top

      $encoding = new-object Text.ASCIIEncoding;
      $postData = “j_username=##USERNAME-HERE##&j_password=##PASSWORD HERE##&_spring_security_remember_me=on”;
      $data = $encoding.GetBytes($postData);

      $_cookieContainer = new-object Net.CookieContainer;
      [Net.HttpWebRequest]$myRequest = [Net.WebRequest]::Create(“”);
      $myRequest.UserAgent = “Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36″;
      #$myRequest.Proxy = new WebProxy(SessionControlForm.ProxyServer);
      $myRequest.CookieContainer = $_cookieContainer;
      $myRequest.Timeout = 180000;

      $myRequest.Accept = “text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8″;
      $myRequest.Headers.Add(“Accept-Encoding”, “gzip,deflate,sdch”);
      $myRequest.Headers.Add(“Accept-Language”, “en-US,en;q=0.8″);
      $myRequest.Headers.Add(“Cache-Control”, “max-age=0″);

      $myRequest.Method = “GET”;
      [Net.HttpWebResponse]$response = $myRequest.GetResponse()

      [Net.HttpWebRequest]$myRequest = [Net.WebRequest]::Create(“”);
      $myRequest.UserAgent = “Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36″;
      #$myRequest.Proxy = new WebProxy(SessionControlForm.ProxyServer);
      $myRequest.CookieContainer = $_cookieContainer;
      $myRequest.Timeout = 180000;

      $myRequest.Accept = “text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8″;
      $myRequest.Headers.Add(“Accept-Encoding”, “gzip,deflate,sdch”);
      $myRequest.Headers.Add(“Accept-Language”, “en-US,en;q=0.8″);
      $myRequest.Headers.Add(“Cache-Control”, “max-age=0″);

      $myRequest.Method = “POST”;
      $myRequest.ContentType = “application/x-www-form-urlencoded”;
      $myRequest.ContentLength = $data.Length;
      $newStream = $myRequest.GetRequestStream();

      $newStream.Write($data, 0, $data.Length);

      [Net.HttpWebResponse]$response = $myRequest.GetResponse()
      if ($response -ne $null)

      • asyba said on March 8, 2014 at 11:49 pm: Reply

        you know how i can do this in JavaScript in XMLHttpRequest and without other scripts ? the cooki part that is my problems how i send cookies in a GET request.

  7. Yatao Li said on March 8, 2014 at 10:46 am: Reply

    Hi, following this thread, I wrote some primitive code that can go pass the authorization and pull back all the tasks.
    Not sure if I can create/update entries.

    Anyone interested in building this together?

  8. Tim Janke said on October 29, 2014 at 8:21 pm: Reply

    Hi, Cameron,

    This is effing brilliant. Thanks for doing all this hard work.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>