The Yodlee API and Rails, Part 1: Starting the Conversation

Dubbed by CNN the "800-pound gorilla of the internet", Yodlee is used by banks and financial institutions worldwide to aggregate consumer data from across the financial spectrum: it gathers and collates everything from student and mortgage loans, to credit cards, 401(k), checking, and savings accounts.

The Yodlee API has become something of a hobby for us at NYC Dev Shop. In this tutorial, I will walk you through the process of setting up a working API connection between your Rails app and the Yodlee API service.

Prerequisites

I am going to make heavy use of Savon, a Ruby gem for interacting with SOAP APIs which allows us to write our SOAP requests in plain Ruby, and Nokogiri, a gem for parsing the returned XML. Add Savon and Nokogiri to your Gemfile and run bundle install. It's also a really good idea to install SoapUI, which shows you the data requirements for each API call. If you have never used Savon or SoapUI before, you really need to watch this Railscast.

And of course, you will need a Yodlee account.

Getting Started

Begin by creating a new Yodlee model. I will be putting all our Yodlee methods into this model. To be clear, this is not an ActiveRecord model and should not inherit from ActiveRecord::Base.

Let's start by adding constants.

class Yodlee

  # Constants. The ones below are FAKE examples. 
  # Replace with the real data you receive from Yodlee once your account is approved.
  COBRAND_ID = "1000000"
  APP_ID     = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234"
  USERNAME   = "username"
  PASSWORD   = "password"

  # Yodlee Class Methods.
  class << self

    # All of our methods will go here.

  end
end

Authenticating Your App

All Yodlee interaction begins with authenticating your app. Once you authenticate your app Yodlee will return in its SOAP response a "cobrand context". A "cobrand" is Yodlee lingo for an approved organization or app, and a "cobrand context" is an XML block that contains the app's login credentials. You will need to include this cobrand context with most subsequent API calls.

Let's set up an API call with Savon. Every Savon API call starts with a block where you specify the call's endpoint and namespace. With Yodlee, the endpoint will always contain the same base URL (https://sdwhatever99.yodlee.com/yodsoap/services/ in our example; Yodlee will tell you which to use for your account) but it's ending (example: CobrandLoginService) changes to match the WSDL you are interacting with. The namespace, on the other hand, will change constantly depending on the WSDL being used and can be located by loading the WSDL in SoapUI. An example Savon Client block looks like this:

client = Savon::Client.new do |wsdl|
    wsdl.endpoint  = "https://sdwhatever99.yodlee.com/yodsoap/services/CobrandLoginService"
    wsdl.namespace = "http://cobrandlogin.login.core.soap.yodlee.com"
  end
end

You then use the assigned variable to send data:

response  = client.request :request_action_here do |soap|
  soap.body = {
    # Your data goes here
  }
end

But try running this code and you'll run into an error right away. Savon needs an SSL certificate to interact with Yodlee, and unless all your lucky stars are aligned, Savon won't be able to find a valid SSL certificate on your local computer to use. To get around this, you need to download a certificate and tell Savon where to find it. Search online for the ca-bundle.crt, download it, and store it in your Rails app. When you initiate the Savon code block, you can add a http parameter and use that to tell Savon where to find the certificate.

client = Savon::Client.new do |wsdl, http|
    wsdl.endpoint              = "https://sdwhatever99.yodlee.com/yodsoap/services/CobrandLoginService"
    wsdl.namespace             = "http://cobrandlogin.login.core.soap.yodlee.com"
    http.auth.ssl.ca_cert_file = '/path/to/your/ca-bundle.crt'
    http.auth.ssl.verify_mode  = :peer
    http.open_timeout          = 120
    http.read_timeout          = 120
  end
end

An important note: On your production environment you should use your server's SSL certificate, which can usually be found in /etc/ssl/certs.

You'll also notice that I also upped Savon's timeout limit to 120 seconds, to deal with cases when we are expecting huge amounts of data returned from Yodlee.

Now that we know how to initiate a request with Savon, let's look at what the actual Yodlee call to authenticate your cobrand looks like.

def login_cobrand
  client = Savon::Client.new do |wsdl, http|
      wsdl.endpoint              = "https://sdwhatever99.yodlee.com/yodsoap/services/CobrandLoginService"
      wsdl.namespace             = "http://cobrandlogin.login.core.soap.yodlee.com"
      http.auth.ssl.ca_cert_file = '/path/to/your/ca-bundle.crt'
      http.auth.ssl.verify_mode  = :peer
      http.open_timeout          = 120
      http.read_timeout          = 120
    end
  end

  response  = client.request :login_cobrand do |soap|
    soap.body = {
      :cobrand_id            => COBRAND_ID,
      :application_id        => APP_ID,
      :locale                => {
        :country             => "US",
        :language            => "en",
        :"variant/"          => ""
      },
      :tnc_version           => 2,
      :cobrand_credentials   => {
        :login_name          => USERNAME,
        :password            => PASSWORD,
        :attributes!         => {
         :login_name         => { "xsi:type" => "xsd:string" },
         :password           => { "xsi:type" => "xsd:string" },
        }
      },
      :attributes!           => {
        :cobrand_credentials => { "xsi:type" => "ns1:CobrandPasswordCredentials", "xmlns:ns1" => "http://login.ext.soap.yodlee.com" }
      }
    }
  end

  Nokogiri::XML(response.to_xml).xpath("//loginCobrandReturn").children.to_xml
end

Let's quickly run through this line by line.

First we create a new Savon::Client, passing in the namespace and URL we want to connect to. Next we define the request body, which Savon will convert from our Ruby hashes into XML. Notice that we specify the action we want, the login_cobrand action, when we write response = client.request :login_cobrand do |soap|, as there are always other actions available at this URL too.

We pass in our Cobrand ID and Application ID. We also specify the locale. If we wouldn't specify the locale, the cobrand context returned to us by Yodlee would also omit the locale snippet, and it is required for some of the calls we will be making later on. Notice the slightly bizarre :"variant/" key? Under the hood, Savon uses Gyoku to convert the Ruby hashes into XML, and :"variant/" is Gyoku syntax for creating a self-closing tag: <variant /> rather than <variant></variant>.

TNC version is the version of Yodlee's Terms and Conditions that we are agreeing to.

Finally we pass in our username and password login credentials.

We then use Nokigiri to parse the SOAP response, slice out the cobrand context, and return it.

It's time to take a moment to discuss the attributes hashes. Yodlee oftentimes requires specific attributes to be added to the XML. Here, for example, we can't simply have our XML state <login_name>x</login_name>, it needs to be <login_name xsi:type="xsi:string">x</login_name>. When Yodlee requires these attributes, it gets particular about them. Omit the attribute and Yodlee will return a cryptic error. Adding the attributes hashes admittedly uglifies our code, but they are needed.

The attributes hashes always end with the bang ! operator. Be default, Gyoku escapes data when it converts it into XML. Adding a bang to the hash key tells Gyoku to leave the data as-is.

It's time to put our code to the test. Fire up the console, and run Yodlee.login_cobrand. You should see our SOAP request being sent to Yodlee, the SOAP response received, and finally the cobrand context snippet which Nokogiri has parsed.

Housekeeping

At this point, there are two problems with our code.

First, the cobrand context we are receiving is going to be needed for many of our upcoming API calls. I don't want to login constantly to get this cobrand context, so let's add a method to cache the cobrand context response.

def cobrand_context
  @cobrand_context ||= login_cobrand
end

Second, every API call is going to require a Savon::Client block, and duplicating that block in each method isn't DRY. Let's move it into a private method at the bottom of our model. And since we are cleaning things up, let's pretty it up a little bit by moving our base SOAP URL (https://sdwhatever99.yodlee.com/yodsoap/services/) into a constant. At this point, our complete Yodlee model looks like this:

class Yodlee

  # Constants. The ones below are FAKE examples. 
  # Replace with the real data you receive from Yodlee once your account is approved.
  BASE_URL   = "https://sdwhatever99.yodlee.com/yodsoap/services/"
  COBRAND_ID = "1000000"
  APP_ID     = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234"
  USERNAME   = "username"
  PASSWORD   = "password"

  # Yodlee Class Methods.
  class << self

    def cobrand_context
      @cobrand_context ||= login_cobrand
    end

    def login_cobrand
      service   = "CobrandLoginService"
      namespace = "http://cobrandlogin.login.core.soap.yodlee.com"
      client    = client service, namespace

      response  = client.request :login_cobrand do |soap|
        soap.body = {
          :cobrand_id            => COBRAND_ID,
          :application_id        => APP_ID,
          :locale                => {
            :country             => "US",
            :language            => "en",
            :"variant/"          => ""

          },
          :tnc_version           => 2,
          :cobrand_credentials   => {
            :login_name          => USERNAME,
            :password            => PASSWORD,
            :attributes!         => {
             :login_name         => { "xsi:type" => "xsd:string" },
             :password           => { "xsi:type" => "xsd:string" },
            }
          },
          :attributes!           => {
            :cobrand_credentials => { "xsi:type" => "ns1:CobrandPasswordCredentials", "xmlns:ns1" => "http://login.ext.soap.yodlee.com" }
          }
        }
      end

      Nokogiri::XML(response.to_xml).xpath("//loginCobrandReturn").children.to_xml
    end



    private

      def client service, namespace
        Savon::Client.new do |wsdl, http|
          wsdl.endpoint              = BASE_URL + service
          wsdl.namespace             = namespace
          http.auth.ssl.ca_cert_file = '/path/to/your/ca-bundle.crt'
          http.auth.ssl.verify_mode  = :peer
          http.open_timeout          = 120
          http.read_timeout          = 120
        end
      end

  end
end

Coming Up

In our next tutorial in the Yodlee API series, I will look at Yodlee's method of interacting with banks and financial services. I will explore the best method of finding which banks and services are compatible with Yodlee, and how to store and use that information within our Rails app.