del.icio.us .:. tweet

Netflix Catalog API Explorer .:. kentbrewster.com

Netflix, in case you didn't know, is a marvelous place to rent movies and television series on DVD, and watch an increasing percentage online. Over the last ten years, Netflix has accumulated a treasure trove of information about movies and the people who make them, and has recently opened it all up with the Netflix Catalog API. (Full disclosure: yes, I work there, and yes, it's awesome.)

Here's a handy tool to help you get your feet wet without having to worry about OAuth.

Before You Begin

You'll need a developer account, from developer.netflix.com. Once you're signed up and logged in, the link to Consumer Key--below, to the left of the first text entry box--should take you right to the spot where you can copy your key and secret.

If you're feeling paranoid, please view source before clicking the Onwards! button. This thing just below that looks like a form really isn't a form, and is not going to submit your information anywhere except the Netflix API. The only reason why I'm using an all-client solution is so you, the developer, can actually try out your very own consumer key and shared secret, without me, the potentially-nefarious third party, ever actually knowing what they are.

Try Your Queries Right Here

Consumer Key:

Shared Secret:

Query:

Got your consumer key and shared secret filled in? Click me:

Sample Queries (Click to Try)

These are only a few examples; please see the REST API Reference for detailed information.

Things to Do and Notice

  • When your results come back, just about any link that starts with http://api.netflix.com/catalog can be copied and pasted right back into the query blank for further exploration.
  • A link to your OAuth-signed URL will magically appear under the iframe containing your results. If you click this it ought to open your results in a new page, so you can see exactly what OAuth did to your request string. This is very, very handy when you're wrestling with some of the more arcane aspects of the API, such as what needs to be URL-encoded.
  • Use Netflix's handy expand parameter to avoid extra calls to the API. Any of the link attributes in the main tree should be expandable; details are in the API docs, which I am currently in the process of refactoring.
  • To try out version 1.5 of the Netflix API, add v=1.5 to any query. Please note that expand behaves differently in version 1.5.

Don't Forget to Break It!

No, seriously. This is critical; you need to know what happens when things go wrong. Change your key or secret, or enter an URL that doesn't compute, and make note of the errors you get back.

Bad Ideas

  • Trying to retrieve the entire catalog. (Okay, I admit it: I tried this myself. The resulting bolus of data choked my browser, and I had to reboot and recover some lost stuff that I hadn't saved.) Client apps should never need the entire catalog; if you are thinking about doing this, you are doing it Wrong.
  • Using term search to power autocomplete. That's what /catalog/titles/autocomplete is for, folks; it's even been left free of OAuth requirements so people banging on it won't use up your daily quota of API calls.
  • Putting this script into production anywhere. Although OAuth calls can be made using nothing but JavaScript, doing so will reveal your consumer key and shared secret on every call, which could lead to trouble if either item is hijacked and used by someone who is not you.

Other Important Resources

  • If you'd like to play around with OAuth parameters, see the OAuth Test Page.
  • To run signed-in calls to the Netflix API--accessing things like a specific user's queue--you'll want Flixo.
  • Although it feels very GOOG-centric, the OAuth Playground has a spot under "Choose Your Scope" where you can enter any OAuth endpoint. Netflix works there.

The JavaScript Source

For the morbidly curious, here's how I got client-side OAuth to work in eighty lines of JavaScript. It may be worth looking at if you're a throwback like me who refuses to use a library he doesn't understand. The heavy lifting happens at the bottom of the script, from oAuthEscape on down, with much help from Paul Johnston's HMAC-SHA1 routine.

( function(d) { 
   var trueName = '';
   for (var i = 0; i < 16; i++) { 
      trueName += String.fromCharCode(Math.floor(Math.random() * 26) + 97); 
   }
   window[trueName] = {};
   var $ = window[trueName];
   $.f = function() {
      return {
         init : function() {
            $.v = '';
            var t = ['a','b','k','s','u','x','z','q'];
            for (var i = 0; i < t.length; i++) {
              $[t[i]] = d.getElementById(t[i]);
            }
            $.b.onclick = function() {
               $.f.runQuery();
            }
            var a = $.x.getElementsByTagName('A');
            for (var i = 0; i < a.length; i++) {
               a[i].onclick = function() {
                  $.q.value = this.rel;
               }
            }
         },
         runQuery : function() {
            if ($.q.value && $.q.value !== $.v) {
               $.z.style.display = '';
               $.u.style.display = '';
               var key = $.k.value;
               var secret = $.s.value;
               var url = $.q.value;
               var request = $.f.oAuthRequest(key, secret, url);
               $.u.href = request;
               $.z.src = request;
            }
         },
         oAuthEscape : function(r) {
            return encodeURIComponent(r).replace("!","%21","g").replace("*","%2A","g").replace("'","%27","g").replace("(","%28","g").replace(")","%29","g");
         },
         oAuthRequest : function(key, secret, url) {
            var timestamp = Math.floor(new Date().getTime()/1000);
            var nonce = '';
            for (var i = 0; i < 15; i++) { 
               nonce += String.fromCharCode(Math.floor(Math.random() * 10) + 48); 
            }            
            var u = url.split('?')[0];
            var q = url.split('?')[1];
            if (q) { 
               q = q.replace(/@/g, '%40').replace(/,/g, '%2C').replace(/:/g, '%3A').replace(/\//g, '%2F');
               var a = q.split('&'); 
            } else { 
               var a = []; 
            }            
            a.push('oauth_consumer_key=' + key);
            a.push('oauth_nonce=' + nonce);
            a.push('oauth_signature_method=HMAC-SHA1');
            a.push('oauth_timestamp=' + timestamp);
            a.push('oauth_version=1.0');
            a.sort();
            var theBody = a.join('&');            
            var theHead = 'GET&' + $.f.oAuthEscape(u) + '&';
            var sigBase = theHead + $.f.oAuthEscape(theBody);
            var theSig = $.f.b64_hmac_sha1(secret + '&', sigBase);
            var signedUrl = u + '?' + theBody + '&oauth_signature=' + $.f.oAuthEscape(theSig);
            return (signedUrl);
         },
         b64_hmac_sha1 : function(k,d,_p,_z){
            // heavily optimized and compressed version of http://pajhome.org.uk/crypt/md5/sha1.js 
            // _p = b64pad, _z = character size; not used here but I left them available just in case
            if(!_p){_p='=';}if(!_z){_z=8;}function _f(t,b,c,d){if(t<20){return(b&c)|((~b)&d);}if(t<40){return b^c^d;}if(t<60){return(b&c)|(b&d)|(c&d);}return b^c^d;}function _k(t){return(t<20)?1518500249:(t<40)?1859775393:(t<60)?-1894007588:-899497514;}function _s(x,y){var l=(x&0xFFFF)+(y&0xFFFF),m=(x>>16)+(y>>16)+(l>>16);return(m<<16)|(l&0xFFFF);}function _r(n,c){return(n<<c)|(n>>>(32-c));}function _c(x,l){x[l>>5]|=0x80<<(24-l%32);x[((l+64>>9)<<4)+15]=l;var w=[80],a=1732584193,b=-271733879,c=-1732584194,d=271733878,e=-1009589776;for(var i=0;i<x.length;i+=16){var o=a,p=b,q=c,r=d,s=e;for(var j=0;j<80;j++){if(j<16){w[j]=x[i+j];}else{w[j]=_r(w[j-3]^w[j-8]^w[j-14]^w[j-16],1);}var t=_s(_s(_r(a,5),_f(j,b,c,d)),_s(_s(e,w[j]),_k(j)));e=d;d=c;c=_r(b,30);b=a;a=t;}a=_s(a,o);b=_s(b,p);c=_s(c,q);d=_s(d,r);e=_s(e,s);}return[a,b,c,d,e];}function _b(s){var b=[],m=(1<<_z)-1;for(var i=0;i<s.length*_z;i+=_z){b[i>>5]|=(s.charCodeAt(i/8)&m)<<(32-_z-i%32);}return b;}function _h(k,d){var b=_b(k);if(b.length>16){b=_c(b,k.length*_z);}var p=[16],o=[16];for(var i=0;i<16;i++){p[i]=b[i]^0x36363636;o[i]=b[i]^0x5C5C5C5C;}var h=_c(p.concat(_b(d)),512+d.length*_z);return _c(o.concat(h),512+160);}function _n(b){var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s='';for(var i=0;i<b.length*4;i+=3){var r=(((b[i>>2]>>8*(3-i%4))&0xFF)<<16)|(((b[i+1>>2]>>8*(3-(i+1)%4))&0xFF)<<8)|((b[i+2>>2]>>8*(3-(i+2)%4))&0xFF);for(var j=0;j<4;j++){if(i*8+j*6>b.length*32){s+=_p;}else{s+=t.charAt((r>>6*(3-j))&0x3F);}}}return s;}function _x(k,d){return _n(_h(k,d));}return _x(k,d);
         }
      };
   }();
   $.f.init();
})(document);

If you'd like to learn more about the various snakes that bit me while I was figuring it out, please see True OAuth Confessions, or Why My Hand-Rolled Calls All Blew Chunks.

Have fun, please let me know how it goes, and don't forget to read the documentation, at http://developer.netflix.com/. It will be better soon, I promise! :)

Comments from before Disqus:

Ken Kennedy .:. 2009-11-16 21:04:46
Brad, the request string is in the location bar, as the full URL.
Brad Wilkinson .:. 2009-06-08 13:07:52
Kent,
Under "Things to Do and Notice" you mention that the OAuth-signed URL button will let me see what OAuth did to my request string. But what I am seeing is the results of the query. Am I missing something?

Thanks,
Brad

Copyright Kent Brewster 1987-2014 .:. FAQ .:. RSS .:. Contact