Advertisement

The Ultimate Guide to Decoding the Flickr API

by

Flickr, being the biggest photo management and sharing site in the world, has an impressive API to let developers access and manipulate almost all of its data. Lets take a look at how to work with the API: at the lowest level possible.

A Word From the Author

In this Web 2.0 era, web applications which have an easy to use, intuitive API have a distinct advantage as it lets developers exploit and build for the platform and thus capture more users. As we move towards the social web and mashups, a good API is not a nice addition anymore: it is downright necessary. And remember too much abstraction is never a good thing. While there are a number of API kits out there to simplify working with the API in question, wouldn't it be cool to know what is actually going on under the hood? Wouldn't it be exciting to deconstruct the actual voodoo going on between the kit and the API? Yeah, I thought so! In this new series, we'll be taking a look at the APIs of some of the most popular services out there. Today, we take a look at the Flickr API.

The Sequence of Events

The tango between the developer and API begins and culminates in a series of well defined steps. I'll explain each step as we go.

Deciding the Type of Application

First of all, we need to decide on the type of application we are going to build. Desktop applications have to use the desktop model while a web application can use either of the models. The mobile model is beyond the scope of this article.

For this article I've chosen to go with the desktop model since the web model requires all the testing to be done on the domain on which the app is to be deployed. This might not necessarily be feasible for a lot of people. We choose the desktop model since it is devoid of this restriction.

Obtaining an API Key

The next step is obtaining an application key. Flickr uses this app key to keep tabs on our usage and other statistics. Head on over here and apply for your own API key.

Since our usage of this particular API key is purely educational we choose to obtain a non-commercial key.

Fill in all the details the form requires with special attention to the description of the project. The devs at Flickr actually read this description if your app misbehaves in some way to make sure it is legit. So spend that extra minute describing your masterpiece.

A successful registration yields you this page. Do note down the api key and the shared secret for later use.

Flickr API Basics

The Flickr API provides a number of methods which may or may not require authentication. Each method takes a number of arguments which modify its behavior and payload. Responses can be received in a number of formats including JSON, XML, SOAP and REST. All these requests can be made to end points corresponding the format you've chosen to make the request in. For example, we'll be using REST for the rest of this article and so our URL end point would be http://api.flickr.com/services/rest/.

Pulling in Public Data

There are a number of methods which pull in public data and thus require no authentication of any sort. We just need the api key we had obtained earlier along with any required arguments of the method in question. Lets take a look at an example.

The getPublicGroups method is an example of a method which doesn't require authentication and which pulls in public data. We pass in the user id of the user and our api key and the API responds in the format you requested with a list of groups the user is part of.

We'd send in a request to this URL.

http://api.flickr.com/services/rest/?method=flickr.people.getPublicPhotos&api_key=your_api_key&user_id=user_id_x

Replace your_api_key with the key we obtained earlier and user_id_x with a valid NSID. Since I like my responses to be in JSON, I can add another parameter asking the API to respond with a JSON payload.

http://api.flickr.com/services/rest/?method=flickr.people.getPublicPhotos&api_key=your_api_key&user_id=user_id_x&format=json

The API will send a response like so:

jsonFlickrApi({"photos":{"page":1, "pages":1, "perpage":100, "total":"2", 
"photo":[{"id":"3728895285", "owner":"40318902@N02", "secret":"df6dfee053", "server":"3466", 
"farm":4, "title":"opac", "ispublic":1, "isfriend":0, "isfamily":0}, 
{"id":"3729689790", "owner":"40318902@N02", "secret":"ea9c38a675", 
"server":"2531", "farm":3, "title":"scale", "ispublic":1, "isfriend":0, "isfamily":0},]}, 
"stat":"ok"})

Properly formatted, it is going to look like this.

jsonFlickrApi(
 	{"photos": {
    	"page": 1,
        "pages": 1,
        "perpage": 100,
        "total": "2",
        "photo": [
        	{
            	"id": "3729689790",
                "owner": "40318902@N02",
                "secret": "ea9c38a675",
                "server": "3466",
                "farm": 4,
                "title": "opac",
                "ispublic": 1,
                "isfriend": 0,
                "isfamily": 0
            },
            {
            	"id": "3729689845",
                "owner": "40318902@N02",
                "secret": "df6dfee053",
                "server": "2531",
                "farm": 3,
                "title": "scale",
                "ispublic": 1,
                "isfriend": 0,
                "isfamily": 0
            }
          ]
	  },
      "stat": "ok"
})

Pulling in Private Data

This is probably the reason you want to learn how to work with the Flickr API and so we'll go over each step slowly since this part has a tendency to confuse people.

Signing

To obtain private data, each method needs authentication and for authentication to work each of our calls has to be signed. Signing works like so:

Make an alphabetically sorted list of the arguments

For example, in the previous example our list would look like so:

  • api_key: xxx
  • format: json
  • user_id: yyy

Create the signature string

The signature string is created by taking the API secret we obtained earlier and then appending the list of arguments to it. For example, our signature string would look like so:

0123456789api_keyxxxformatjsonuseridyyy

Signing our call

The final step is the actual signing. Flickr expects us to take the MD5 hash of our signature string and append it to our original method call as a named parameter.

So any authenticated call has this general format

http://api.flickr.com/services/rest/?method=ourmethod&api_key=apikey&api_sig=hashedvalue

Obtaining a frob

Now with the signing out of the way, we can now move on to the actual authentication. Flickr uses a system similar to OAuth for authorization which means that a user who wants to use our app doesn't need to divulge his/her user credentials. Users are transported to the Flickr web site where the user is asked whether he/she wants to allow our app to access the user's data.

This is where a frob comes in. To create the login link which takes the user to an authorization page on Flickr, we need a way to identify a specific login session.

In order to obtain a frob to identify the session, we need to call the flickr.auth.getFrob passing our api key as a named argument. Our URL would look like so:

http://api.flickr.com/services/rest/?method=flickr.auth.getFrob&api_key=apikey&api_sig=hashedvalue

A JSON response looks like so:

frobcallback(
	{"frob":{
    	"_content": "xxx"
        }, 
     "stat":"ok"
     })

Constructing the login URL

Having successfully obtained a frob, we can now work on building the URL which lets the user authorize our application. The login URL has this general format:

http://flickr.com/services/auth/?api_key=apikey&api_sig=apisig&perms=perms&frob=frob

Replace api_key's value with the one we had obtained earlier, api_sig's value with a MD5 hash of our signature string and frob's value with the frob value returned by the API. The perms parameter defines the desired level of account access and has valid values of read, write and delete. Each access includes the rights of all its predecessors.

A valid login URL takes this form:

http://flickr.com/services/auth/?api_key=63b08e2efcc22de9900163f4d761fdbc&api_sig=663369798c695dbe2fd7e2af7576dd2b&perms=delete&frob=72157621742082858-8e995a1104e28114-870912

The authorization pages looks like so:


First, Flickr makes sure the user wasn't conned into authorizing the application.

Next, it is made sure the user knows the level of authorization the he/she is granting to the application.

Successful authorization!

Obtaining the auth token

Once the user has given authorization for our application, we can proceed forward. The final step in this process is obtaining an auth_token. An auth token ties a specific API key to a specific user ID i.e an auth token can be used to manipulate only a specific user's data whilst using a specific API key. An auth token is necessary for each and every API method call which requires authentication.

Obtaining an auth token is as simple as calling the flickr.auth.getToken method passing in the api key, frob and api signature as named parameters. The URL would look like so:

http://flickr.com/services/auth/?api_key=apikey&api_sig=apisig&frob=frob

A successful request nets us an auth token which can be used indefinitely to access a specific user's data using a specific api key.

Making the call

Now, that all the prerequisites have been met, we can go about retrieving data as needed. Remember, each of your authenticated calls needs to be signed and so each call must send in the api_key, auth_token and api_sig for the method call to work.

At the base minimum, the URL for your REST request must look like this. Other method specific parameters or parameters which modify the payload can be appended as needed.

http://flickr.com/services/auth/?api_key=xxx&api_sig=yyy&auth_token=zzz&method=method_name

While signing, make sure to also include the other arguments and their values. This is a frequent cause of error and headache and is easily rectified. Are you including a callback parameters in the URL to avoid the cross domain restriction in browsers whilst using AJAX? Those have to go in the signature string too!

Reconstructing the URLs

Let's take a look at an example response for a method which returns public photos.

jsonFlickrApi(
 	{"photos": {
    	"page": 1,
        "pages": 1,
        "perpage": 100,
        "total": "2",
        "photo": [
        	{
            	"id": "3729689790",
                "owner": "40318902@N02",
                "secret": "ea9c38a675",
                "server": "3466",
                "farm": 4,
                "title": "opac",
                "ispublic": 1,
                "isfriend": 0,
                "isfamily": 0
            },
            {
            	"id": "3729689845",
                "owner": "40318902@N02",
                "secret": "df6dfee053",
                "server": "2531",
                "farm": 3,
                "title": "scale",
                "ispublic": 1,
                "isfriend": 0,
                "isfamily": 0
            }
          ]
	  },
      "stat": "ok"
})

It's all fine and dandy but the response doesn't contain a URL we could just link to. Instead we have to construct a URL for the image in question based on the data sent back from the server. Here is how:

Ever image's URL on Flickr follows a well defined pattern. Unlock this and the response starts to make a lot more sense. Here is the URL of an image in my account.

http://farm3.static.flickr.com/2531/3729689790_ea9c38a675_b.jpg

The URL is made of a number of parts:

  • Farm ID. 3 in our case.
  • Server ID. 2531 here.
  • Photo ID - A way to uniquely identify each and every photo hosted by Flickr. 3729689845 in this case.
  • Photo secret - ea9c38a675
  • Image size - Defines the size of the image to be returned. Possible values include o for original, b for a width/height of 1024, m for 240, t for 100 and s for 75. When not specified it defaults to a width/height of 500.

In short, in order to construct the source of the image, the link would look like the one shown below if we were made to parse the JSON response where data is the variable which holds the response:

"http://farm" + data.photos.photo[i].farm + ".static.flickr.com/" + data.photos.photo[i].server + "/"+data.photos.photo[i].id + "_"+data.photos.photo[i].secret + ".jpg

Uploading to Flickr

Now that we've taken a look at how to retrieve data from Flickr using its API, it's time to take a look at how to send data back.

Flickr's upload API is distinct from its REST or SOAP based APIs in that there are no URL endpoints you could just access and retrieve data. Instead data has to be sent via a POST request to

http://api.flickr.com/services/upload/

Since it's outside the scope of this article to show you how to construct a POST query from scratch, we'll use a form element with a enctype value of multipart/form-data in order to generate all the code for us. Using this certain attribute lets us state that the form contains binary data and it has to be handled as such. A sample form would look like this.

<form enctype="multipart/form-data" method="post"  action="http://api.flickr.com/services/upload/">
<input type="file" name="photo"/>
<input type="submit" name ="submit" value="Upload"/>
</form>

But remember, we still need to send in a number of parameters to the service including the api key, auth token and the method signature. How do we do that? Its just a matter of creating a hidden text field and modifying their value to reflect the correct values. Like so:

<form enctype="multipart/form-data" method="post"  action="http://api.flickr.com/services/upload/">
<input type="file" name="photo"/>
<input type="hidden" name="api_key" value=""/>
<input type="hidden" name="auth_token" value=""/>
<input type="hidden" name="api_sig" value=""/>
<input type="submit" name ="submit" value="Upload"/>
</form>

Remember that while generating the MD5 hash of the signature string you need to upload every element of the form excluding the photo field. This includes the submit buttons value since contents of the entire form are posted to the URL. For the above example, the hash would have to be calculated like so:

var hash = MD5(secret + "api_key" + apikey  + "auth_token" + token + "submitUpload");

You are not entirely limited to those arguments. The upload API takes in a number of arguments including the title of the photo, its title and description. If you wanted you could just as easily let the user enter all those data along with privacy settings like so:

<form enctype="multipart/form-data" method="post"  action="http://api.flickr.com/services/upload/">
<input type="file" name="photo"/>
<input type="text" name="title" value=""/>
<input type="text" name="description" value=""/>
<input type="text" name="tags" value=""/>
<input type="text" name="is_public" value="0"/>
<input type="text" name="is_friend" value="1"/>
<input type="text" name="content_type" value="1"/>
<input type="text" name="hidden" value="2"/>
<input type="hidden" name="api_key" value=""/>
<input type="hidden" name="auth_token" value=""/>
<input type="hidden" name="api_sig" value=""/>
<input type="submit" name ="submit" value="Upload"/>
</form>

Commonly Used Methods

An article on how to work with a service's API would be clearly incomplete without a look at some of the most used API methods. With that in mind, here are a few API methods which should be very helpful irrespective of whether you are creating a mashup or just looking to retrieve your own data.

Remember, authenticated calls require valid values for the api_key, api_sig and auth_token parameters to work while normal calls may or may not require method specific parameters. All calls require the api_key parameter to be sent in. So if I mention the call requires authentication, the fact that the call requires the other arguments is implied implicitly. Arguments noted below are optional unless mentioned otherwise. Methods which return a list of data also take a page and per_page argument to define their namesakes.

I've included responses of each method to give you an idea about the data that is delivered back to us. I've went with JSON as the response format since most devs I work with like JSON better than XML.

flickr.activity.userPhotos
Returns a list of recent activity on photos belonging to the calling user.

Arguments: timeframe - Defines the time frame in which to look for updates.

Authentication: Yes

Response

{
    "items": {
          "item":[
               {
                   "type": "photo",
                   "id": "3728895285",
                   "owner": "40318902@N02",
                   "ownername": "lordtottuu",
                   "secret": "df6dfee053",
                   "server": "3466",
                   "farm": 4,
                   "title": {
                         "_content": "opac"
                        },
                   "commentsold": 1,
                   "commentsnew": 0,
                   "notesold": 0,
                   "notesnew": 0,
                   "views": 0,
                   "faves": 0,
                   "more": 0,
                   "activity": {
                         "event": [
                               {
                                   "type": "comment",
                                   "commentid": "40298554-3728895285-72157621628251433",
                                   "user": "40318902@N02",
                                   "username": "lordtottuu",
                                   "dateadded": "1248131143",
                                   "_content": "Demo image for my upcoming article on Net Tuts"
                               }
                            ]
                        }
                  }
            ],
          "page": 1,
          "pages": 1,
          "perpage": 10,
          "total": 1
      },
     "stat": "ok"
}

flickr.contacts.getList
Returns a list of contacts for the calling user.

Arguments: filter - Argument to filter out the list. Valid values include friends, family, both and neither.

Authentication: Yes

Response

{
     "contacts": {
           "page": 1,
           "pages": 1,
           "per_page": 1000,
           "perpage": 1000,
           "total": 2,
           "contact": [
               {
                  "nsid": "7488445@N05",
                  "username": "thegleek",
                  "iconserver": "179",
                  "iconfarm": 1,
                  "ignored": 0,
                  "realname": " Mike Poleski",
                  "friend": "1",
                  "family": "0",
                  "path_alias": null,
                  "location": ""
               }
            ]
         // Rest of the contacts
      },
     "stat": "ok"

flickr.favorites.getList
Returns a list of photos marked favorite by a specific user.

Arguments: min_fave_date, max_fav_date - Self explanatory.

Authentication: Yes

Response

{
    "photos": {
          "page": 1,
          "pages": 1,
          "perpage": 100,
          "total": "3",
          "photo": [
               {
                  "id": "2332823355",
                  "owner": "53555705@N00",
                  "secret": "e603be40a2",
                  "server": "2333",
                  "farm": 3,
                  "title": "Xbox 360 still life",
                  "ispublic": 1,
                  "isfriend": 0,
                  "isfamily": 0,
                  "date_faved": "1248134938"
               }
            ]
          // Rest of the photos
      },
    "stat": "ok"
}

flickr.people.getPublicPhotos
Get a list of public photos for the given user.

Arguments: nsid [required] - ID of the calling user, safe_search - To block out NSFW content.

Authentication: No

Response

{
    "photos": {
          "page": 1,
          "pages": 1,
          "perpage": 100,
          "total": "15",
          "photo": [
               {
                   "id": "3728895285",
                   "owner": "40318902@N02",
                   "secret": "df6dfee053",
                   "server": "3466",
                   "farm": 4,
                   "title": "opac",
                   "ispublic": 1,
                   "isfriend": 0,
                   "isfamily": 0
                }
            ]
        // Rest of the photos    
      },
     "stat": "ok"
}

flickr.groups.getInfo
To obtain information about a particular group.

Arguments: group_id [required]- The ID of the group about which you seek information.

Authentication: No

Response

{
    "group": {
           "id": "51035612836@N01",
           "iconserver": "1",
           "iconfarm": 1,
           "name": {
                 "_content": "Flickr API"
           },
           "description": {
                 "_content": string"A Flickr group for Flickr API projects. Driving awareness of the Flickr API, projects that use it and those incredible ideas that programmatically exposed systems produce. Think Google API + Amazon API + Flickr API with a bit of GMail thrown in. The developers of Flickr rightly pointed out they want to keep technical discussions directly related to the API on the mailing list."
           },
           "members": {
                 "_content": "7775"
           },
           "privacy": object{
                 "_content": "3"
           },
           "lang": null,
           "ispoolmoderated": 1,
           "throttle": object{
                 "count": "3",
                 "mode": "day"
           },
           "restrictions": object{
                 "photos_ok": 1,
                 "videos_ok": 1,
                 "images_ok": 1,
                 "screens_ok": 1,
                 "art_ok": 1,
                 "safe_ok": 1,
                 "moderate_ok": 0,
                 "restricted_ok": 0,
                 "has_geo": 0
           }
     },
     "stat": "ok"
}

flickr.photos.getExif
Extracts EXIF data of an existing photo .

Arguments: photo_id [required] - ID of the photo whose EXIF data is to be extracted.

Authentication: No

Response

{

    "photo": {
          "id": "2332823355",
          "secret": "e603be40a2",
          "server": "2333",
          "farm": 3,
          "exif": [
               	  {
                      "tagspace": "TIFF",
                      "tagspaceid": 1,
                      "tag": 271,
                      "label": "Make",
                      "raw": {
                            "_content": "Canon"
                        }
                  },
               	  {
                      "tagspace": "TIFF",
                      "tagspaceid": 1,
                      "tag": 272,
                      "label": "Model",
                      "raw": {
                            "_content": "Canon EOS 350D DIGITAL"
                        }
                  },
               	// Rest of the exif data
            ]
      },
    "stat": "ok"
}

flickr.photos.geo.getLocation
Returns the latitude and longitude of the place where a specific photo was taken.

Arguments: photo_d [required] - ID of the photo whose location is to be known.

Authentication: No

Response

{
     "photo": object{
         "id": string"229097925",
         "location": object{
                 "latitude": -33.856874,
                 "longitude": 151.214672,
                 "accuracy": "16",
                 "context": "0",
                 "locality": {
                       "_content": "Sydney",
                       "place_id": "p50kaZyYAJx9BZHQ",
                       "woeid": "1105779"
                 },
                 "region": object{
                       "_content":"New South Wales",
                       "place_id": "puGzSeubAphuNnF2",
                       "woeid": "2344700"
                 },
                 "country": object{
                       "_content": "Australia",
                       "place_id": "om3Zr2abAphqrm3jdA",
                       "woeid": "23424748"
                 },
                 "place_id": string"p50kaZyYAJx9BZHQ",
                 "woeid": string"1105779"
           }
     },
    "stat": string"ok"
}

flickr.photos.getFavorites
Returns a list of people who have marked the passed photo as a favorite.

Arguments: photo_id [required] - ID of the photo in question.

Authentication: No

Response

{
     "photo": {
           "person": [
               {
                   "nsid": "39011391@N06",
                   "username": "derek1960",
                   "favedate": "1243834286"
               },
               // Rest of the photos
            ],
           "id": "229097925",
           "secret": "13a21546fb",
           "server": "61",
           "farm": 1,
           "page": 1,
           "pages": 2,
           "perpage": 10,
           "total": "18"
      …},
     "stat": "ok"
}

flickr.places.getTopPlacesList
Returns a list of the 100 most tagged places for a day.

Arguments: place_type_id [required] - Numeric ID of a place to define how to cluster photos.

Authentication: No

Response

{
     "places": object{
           "total": number100,
           "place": [
               {
                   "place_id": "4KO02SibApitvSBieQ",
                   "woeid": "23424977",
                   "latitude": "48.890",
                   "longitude": "-116.982",
                   "place_url": "/United+States",
                   "place_type": "country",
                   "place_type_id": "12",
                   "_content": "United States",
                   "photo_count": "23654"
               },
               // Rest of the 99 countries
            ],
           "date_start": 1248048000,
           "date_stop": 1248134399
      },
     "stat": "ok"
}

flickr.tags.getHotList
Returns a list of most used tags for a given time period.

Arguments: period - Specifies the period for which to obtain tags. count - Specifies the number of tags to return in the response.

Authentication: No

Response

{
     "hottags": {
           "period": "day",
           "count": 20,
           "tag": [
               {
                  "score": "100",
                  "_content": "sundaystreets"
               },
               {
                  "score": "100",
                  "_content": "happymondayblues"
               },
               {
                  "score": "100",
                   "_content": "melbourneopenhouse2009"
               }
            ]
      },
     "stat": string"ok"
}

In conclusion

In this opening part of the series, we looked at how to work with the Flickr API including how to retrieve public and private data, authenticating with the API and how to upload data to the service. We also took a look at some of the most used API methods along with their JSON responses to better understand the structure of the data the API sends back.

Which API is covered next is entirely up to you. Here, at Net Tuts, we cater to popular demand and so we are going to let you, the readers, decide which service's API is going to be written about next. In your comment below, leave the name of the service and the API interface, if need be. We covered REST in this article but we'd be glad to cover SOAP based or XML-RPC based APIs if enough people want it.

Questions? Nice things to say? Criticisms? Hit the comments section and leave me a comment. Happy coding!