Any.do 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 Any.do, 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 Any.do 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 README.md file contained in their chrome extension. This file just happens to detail the Any.do 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.

Login

  • 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"
}

Users

The users table contains all user info and preferences.

Endpoint: /user

DTO:

{
    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>
    }
}

Tasks

The tasks endpoint.

Endpoint: /me/tasks

DTO:

{
    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>,
    repeatingMethod: "TASK_REPEAT_OFF" / "TASK_REPEAT_DAY" / "TASK_REPEAT_WEEK" / "TASK_REPEAT_MONTH",
    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>
            }
        },
        ...
    }
}

Categories

The task categories endpoint.

Endpoint: /me/categories

DTO:

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

 

To any Any.do 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!

12 comments on “Any.do 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:

    http://any.do/state-manager/j_spring_security_check?j_username=email%40example.com&j_password=password&_spring_security_remember_me=on

    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 (http://www.charlesproxy.com/) 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

    https://sm-prod.any.do/j_spring_security_check

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

    j_username=*login-name*
    j_password=*password*
    _spring_security_remember_me=on

    That should get you two cookies called

    JSESSIONID
    SPRING_SECURITY_REMEMBER_ME_COOKIE

    Then GETing things like:

    https://sm-prod.any.do/me
    https://sm-prod.any.do/me/categoriesresponseType=flat&includeDeleted=false&includeDone=false
    https://sm-prod.any.do/me/tasksresponseType=flat&includeDeleted=false&includeDone=false

    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. ryan@reichenfeld.com 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 Any.do 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(@”https://sm-prod.any.do/j_spring_security_check”);
    req.ContentType = “application/x-www-form-urlencoded”;
    req.Method = “POST”;

    using (var writer = new StreamWriter(req.GetRequestStream()))
    {
    writer.Write(“j_username=mymail@gmail.com”);
    writer.Write(“j_password=mypassword”);
    writer.Write(“_spring_security_remember_me=on”);
    }

    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(“https://sm-prod.any.do/me”);
      $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(“https://sm-prod.any.do/j_spring_security_check”);
      $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);
      $newStream.Close();

      [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?

    https://github.com/v-yadli/all.do

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>