I've been playing with Twitter's REST API to collect stats for work. Data like: follower counts, what Tweets are being faved, etc… There are libraries for working with the API but no good examples for using it directly. So, I wrote my own1. It's not super complicated, but there are some tricky parts. I'm posting my code here for posterity's sake.

Each example follows the same basic flow outlined in Twitter's Application-only Authentication approach.

  1. Combine the Consumer Key and Consumer Secret (generated when you create an app) to make a Bearer Token.
  2. Base64 encode the Bearer Token (making sure newlines aren't introduced).
  3. Use the Bearer Token to obtain an Access Token.
  4. Use the Access Token to make the actual API call (in this case the basic info for my Twitter account).
  5. Print a dump of the JSON data that's returned.

Step 5 being where real work would actually happen. It's also where looping for to make additional API requests would take place since the Access Token only needs to be pulled once2.

Here's the code:

Ruby

require "base64"
require "json"
require "net/http"
require "uri"

### Setup access credentials

consumer_key = "YOUR_CONSUMER_KEY_STRING"
consumer_secret = "YOUR_CONSUMER_SECRET_STRING"

### Get the Access Token

bearer_token = "#{consumer_key}:#{consumer_secret}"
bearer_token_64 = Base64.strict_encode64(bearer_token)

token_uri = URI("https://api.twitter.com/oauth2/token")
token_https = Net::HTTP.new(token_uri.host,token_uri.port)
token_https.use_ssl = true

token_request = Net::HTTP::Post.new(token_uri)
token_request["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8"
token_request["Authorization"] = "Basic #{bearer_token_64}"
token_request.body = "grant_type=client_credentials"

token_response = token_https.request(token_request).body
token_json = JSON.parse(token_response)
access_token = token_json["access_token"]

### Use the Access Token to make an API request

timeline_uri = URI("https://api.twitter.com/1.1/users/show.json?screen_name=TheIdOfAlan")
timeline_https = Net::HTTP.new(timeline_uri.host,timeline_uri.port)
timeline_https.use_ssl = true

timeline_request = Net::HTTP::Get.new(timeline_uri)
timeline_request["Authorization"] = "Bearer #{access_token}"

timeline_response = timeline_https.request(timeline_request).body
timeline_json = JSON.parse(timeline_response)

puts JSON.pretty_generate(timeline_json)

Python

import base64
import json
import urllib2

### Setup access credentials

consumer_key = "YOUR_CONSUMER_KEY_STRING"
consumer_secret = "YOUR_CONSUMER_SECRET_STRING"

### Get the Access Token

bearer_token = "%s:%s" % (consumer_key, consumer_secret)
bearer_token_64 = base64.b64encode(bearer_token)

token_request = urllib2.Request("https://api.twitter.com/oauth2/token") 
token_request.add_header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
token_request.add_header("Authorization", "Basic %s" % bearer_token_64)
token_request.data = "grant_type=client_credentials"

token_response = urllib2.urlopen(token_request)
token_contents = token_response.read()
token_data = json.loads(token_contents)
access_token = token_data["access_token"]

### Use the Access Token to make an API request

timeline_request = urllib2.Request("https://api.twitter.com/1.1/users/show.json?screen_name=TheIdOfAlan")
timeline_request.add_header("Authorization", "Bearer %s" % access_token)

timeline_response = urllib2.urlopen(timeline_request)
timeline_contents = timeline_response.read()
timeline_data = json.loads(timeline_contents)

print json.dumps(timeline_data, indent=2, sort_keys=True)

Perl

use strict;
use Data::Dumper;
use HTTP::Request::Common;
use JSON;
use LWP::UserAgent;
use MIME::Base64;
use Mozilla::CA; # Gets HTTPS working on Mac OSX (10.10)

### Setup access credentials

my $consumer_key = "YOUR_CONSUMER_KEY_STRING";
my $consumer_secret = "YOUR_CONSUMER_SECRET_STRING";

### Get the Access Token

my $bearer_token = "$consumer_key:$consumer_secret";
my $bearer_token_64 = encode_base64($bearer_token, "");

my $user_agent = LWP::UserAgent->new;

my $token_request = POST(
  "https://api.twitter.com/oauth2/token",
  "Content-Type" => "application/x-www-form-urlencoded;charset=UTF-8",
  "Authorization" => "Basic $bearer_token_64",
  Content => { "grant_type" => "client_credentials" },
);

my $token_response = $user_agent->request($token_request);
my $token_json = decode_json($token_response->content);

my $timeline_request = GET(
  "https://api.twitter.com/1.1/users/show.json?screen_name=TheIdOfAlan",
  "Authorization" => "Bearer " . $token_json->{access_token}
);

my $timeline_response = $user_agent->request($timeline_request);
my $timeline_json = decode_json($timeline_response->content);

print Dumper $timeline_json;

A few things to keep in mind with these examples:

  • Authentication credentials should not be hard coded in source files.
  • They are very high level. Just one step beyond pseudo-code.
  • The code is straight procedural and almost certainly not how you should actually do it.
  • There is no error handling in these examples.
  • There is no accounting for throttling based on the API rate-limits.
  • No step to transform strings into RFC 1738 (which the docs say to do) was added since there is no change at this point.
  • Probably some other caveats that I'm forgetting.
  • Authentication credentials should not be hard coded in source files (in case you missed it the first time).

Generally, I'm all for using code libraries to make life easier. Working with APIs is a different story. Unless whoever makes the API is also providing the library, I'd rather use the service directly. Avoiding third-party dependencies is well worth the little extra work it takes. And, if there are good examples available there's not a huge difference between the levels of effort.


Footnotes

  1. The person I'm helping with this is interested in using the pandas data analysis library which is written in Python. I wrote the first version in that language. I decided to port it to Ruby since that's my go-to language these days. Once I started down the path of multiple versions it was easy to thrown in Perl (my previous language of choice). This was the first time in years that I've written any Perl. It's also the first time I've ever done anything beyond "Hello, World" in Python. All said, it was a good language exercise. It's fun to see the difference between them.

  2. The Access Token only needs to be pulled once for as long as it's valid. It should stay that way unless a specific request to invalidate it occurs.