The Flickr API in General

What are some approaches to learning the Flickr API? My first suggestion is to look around the documentation and glance through the list of API methods here:

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

While you are doing so, you should think back to all the things you know about Flickr as an end user (aspects I discussed in Chapter 2) and see whether they are reflected in the API. For example, can you come up with an API call to calculate the NSID of your own account? What is a URL to return that information? Hint: flickr.people.findByUsername.

Perhaps the best way to learn about the API is to have a specific problem in mind and then let that problem drive your learning of the API. Don’t try to learn commit the entire API to memory—that’s what the documentation is for.

As I argued earlier, calls that require neither signing nor authorization (such as flickr.photos.search) are the easiest place to start. How would you figure out which calls those are? You can make pretty good guesses from the names of methods. For instance, you won’t be surprised that the method flickr.photos.geo.setLocation would need authorization: you would be using it to change the geolocation of a photo, an act that would require Flickr to determine whether you have the permission to do so. On the other hand, the method flickr.groups.pools.getPhotos allows you to retrieve photos for a given group. A reasonably proficient Flickr user knows that there are public groups whose photos would be visible to everybody, including those who are not logged in to Flickr at all. Hence, it’s not surprising that this method would not require signing or authorization.

Using flickr.reflection Methods

You can get fairly far by eyeballing the list of Flickr methods for ones that do not require any permission to execute. (Recall the levels of permissions within the Flickr API: none, read, write, and delete.) It turns out that the Flickr API has a feature that you won’t find in too many other web APIs: the Flickr API has methods that return information about the API itself. flickr.reflection.?getMethods returns a list of all the Flickr methods available. flickr.reflection.getMethodInfo takes a given method name and returns the following:

  • A description of the method

  • Whether the method needs to be signed

  • Whether the method needs to be authorized

  • The minimal permission level needed by the method (0 = none, 1 = read, 2= write, 3=delete)

  • The list of arguments for the method, including a description of the argument and whether it is optional

  • The list of possible errors arising from calling the method

For example, let’s look at what the Flickr API tells us about flickr.photos.geo.setLocation. You can use this format:

http://api.flickr.com/services/rest/?method= flickr.reflection.getMethodInfo&api_key={api-key}&method_name={method-name}
         

Specifically, you can use this:

http://api.flickr.com/services/rest/?method=flickr.reflection.getMethodInfo&api_key={api-key}&method_name=flickr.photos.geo.setLocation
         

to generate this:

<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok">
<method name="flickr.photos.geo.setLocation" needslogin="1" needssigning="1"
        requiredperms="2">
  <description>Sets the geo data (latitude and longitude and, optionally,
the accuracy level) for a photo.

Before users may assign location data to a photo they must define who, by default,
may view that information. Users can edit this preference at &lt;a
href=&quot;http://www.flickr.com/account/geo/privacy/&quot;&gt;http://www.flickr.com
/account/geo/privacy/&lt;/a&gt;. If a user has not set this preference, the API
method will return an error.</description>
</method>
<arguments>
  <argument name="api_key" optional="0">Your API application key. &lt;a
href=&quot;/services/api/misc.api_keys.html&quot;&gt;See here&lt;/a&gt; for more
details.</argument>
  <argument name="photo_id" optional="0">The id of the photo to set location
data for.</argument>
  <argument name="lat" optional="0">The latitude whose valid range is -90 to
90. Anything more than 6 decimal places will be truncated.</argument>
  <argument name="lon" optional="0">The longitude whose valid range is -180
to 180. Anything more than 6 decimal places will be truncated.</argument>
  <argument name="accuracy" optional="1">Recorded accuracy level of the
location information. World level is 1, Country is ~3, Region ~6, City ~11, Street
~16. Current range is 1-16. Defaults to 16 if not specified.</argument>
</arguments>
<errors>
  <error code="1" message="Photo not found">The photo id was either invalid
or was for a photo not viewable by the calling user.</error>
  <error code="2" message="Required arguments missing.">Some or all of the
required arguments were not supplied.</error>
  <error code="3" message="Not a valid latitude.">The latitude argument
failed validation.</error>
  <error code="4" message="Not a valid longitude.">The longitude argument 
failed validation.</error>
  <error code="5" message="Not a valid accuracy.">The accuracy argument
failed validation.</error>
  <error code="6" message="Server error.">There was an unexpected problem
setting location information to the photo.</error>
  <error code="7" message="User has not configured default viewing settings
for location data.">Before users may assign location data to a photo they must
define who, by default, may view that information. Users can edit this preference at
&lt;a href=&quot;http://www.flickr.com/account/geo/privacy/&quot;&gt;
http://www.flickr.com/account/geo/privacy/&lt;/a&gt;</error>
  <error code="96" message="Invalid signature">The passed signature was
invalid.</error>
  <error code="97" message="Missing signature">The call required signing but
no signature was sent.</error>
  <error code="98" message="Login failed / Invalid auth token">The login
details or auth token passed were invalid.</error>
  <error code="99" message="User not logged in / Insufficient
permissions">The method requires user authentication but the user was not logged in,
or the authenticated method call did not have the required permissions.</error>
  <error code="100" message="Invalid API Key">The API key passed was not
valid or has expired.</error>
  <error code="105" message="Service currently unavailable">The requested
service is temporarily unavailable.</error>
  <error code="111" message="Format &quot;xxx&quot; not found">The requested
response format was not found.</error>
  <error code="112" message="Method &quot;xxx&quot; not found">The requested
method was not found.</error>
  <error code="114" message="Invalid SOAP envelope">The SOAP envelope send in
the request could not be parsed.</error>
  <error code="115" message="Invalid ?XML-?RPC Method Call">The ?XML-?RPC request
document could not be parsed.</error>
</errors>
</rsp>
         

Note specifically that the following:

<method name="flickr.photos.geo.setLocation" needslogin="1" needssigning="1"
        requiredperms="2">
         

confirms what we had surmised—that it needs authorization and signing because it requires a minimum permission level of write. Compare that to what we would get for flickr.photos.?search, which is the method that we have used throughout this chapter as an easy place to start in the API:

<method name="flickr.photos.search" needslogin="0" needssigning="0"
        requiredperms="0">
         

These reflection methods give rise to many interesting possibilities, especially to those of us interested in the issue of automating and simplifying the way we access web APIs. Methods in the API are both similar and different from the other methods. It would be helpful to be able to query the API with the following specific questions:

  • What are all the methods that do not require any permissions to be used?

  • Which methods need to be signed?

  • What is an entire list of all arguments used in the Flickr API? Which method uses which argument? Which methods have in common the same arguments?

[Caution]Caution

These reflection methods in the Flickr API are useful only if they are kept up-­to-date and provide accurate information. In working with the reflection APIs, I have run into some problems (for example, http://tech.groups.yahoo.com/group/yws-flickr/message/3263) that make me wonder the degree to which the reflection methods are a first-­class member of the APIs.

Querying the Flickr Reflection Methods with PHP

As a first step toward building a database of the Flickr API methods that would support such queries, I wrote the following PHP script to generate a summary table of the API methods. First there is a flickr_methods.php class that has functions to read the list of methods using flickr.methods.?get Method s and, for each method, convert the data from flickr.reflection.getMethodInfo into a form that can be serialized and unserialized from a local file.

<?php
# flickr_methods.php
# can use this class to return a $methods (an array of methods) and $methods_info --
# directly from the Flickr API or via a cached copy

class flickr_methods {

  protected $api_key;

  public function __construct($api_key) {
    $this->api_key = $api_key;
  }

  public function test() {
    return $this->api_key;
  }

# generic method for retrieving content for a given url.
  protected function getResource($url){
    $chandle = curl_init();
    curl_setopt($chandle, CURLOPT_URL, $url);
    curl_setopt($chandle, CURLOPT_RETURNTRANSFER, 1);
    $result = curl_exec($chandle);
    curl_close($chandle);

    return $result;
  }

# return simplexml object for $url if successful with specified number of retries
  protected function flickrCall($url,$retries) {
    $success = false;
    for ($retry = 0; $retry < $retries; $retry++) {
      $rsp = $this->getResource($url);
      $xml = simplexml_load_string($rsp);
      if ($xml["stat"] == 'ok') {
        $success = true;
        break;
      }
    } // for
    if ($success) {
      return $xml;
    } else {
      throw new Exception("Could not successfully call Flickr");
    }
  }

# go through all the methods and list

  public function getMethods() {

    // would be useful to return this as an array (later on, I can have another
    // method to group them under common prefixes.)

    $url = "http://api.flickr.com/services/rest/?method=flickr.reflection.getMethods&api_key={$this->api_key}";
    $xml = $this->flickrCall($url, 3);
    foreach ($xml->methods->method as $method) {
      //print "${method}\n";
      $method_list[] = (string) $method;
    }
    return $method_list;
  }

# get info about a given method($api_key, $method_name)

  public function getMethodInfo($method_name) {

    $url =
    "http://api.flickr.com/services/rest/?method=flickr.reflection.getMethodInfo&api_key={$this->api_key}&method_name={$method_name}";
    $xml = $this->flickrCall($url, 3);
    return $xml;
  }

# get directly from Flickr the method data
# returns an array with data
  public function download_flickr_methods () {

    $methods = $this->getMethods();

    // now loop to grab info for each method

# this counter lets me limit the number of calls I make -- useful for testing
    $limit = 1000;
    $count = 0;

    foreach ($methods as $method) {

      $count += 1;
      if ($count > $limit) {
        break;
      }

      $xml = $this->getMethodInfo($method);
      $method_array["needslogin"] = (integer) $xml->method["needslogin"];
      $method_array["needssigning"] = (integer) $xml->method["needssigning"];
      $method_array["requiredperms"] = (integer) $xml->method["requiredperms"];
      $method_array["description"] = (string) $xml->method->description;
      $method_array["response"] = (string) $xml->method->response;
    // loop through the arguments
      $args = array();
      foreach ($xml->arguments->argument as $argument) {
        $arg["name"] = (string) $argument["name"];
        $arg["optional"] = (integer) $argument["optional"];
        $arg["text"] = (string) $argument;
        $args[] = $arg;
      }
      $method_array["arguments"] = $args;

    // loop through errors
      $errors = array();
      foreach ($xml->errors->error as $error) {
        $err["code"] = (string) $error["code"];
        $err["message"] = (integer) $error["message"];
        $err["text"] = (string) $error;
        $errors[] = $err;
      }
      $method_array["errors"] = $errors;

      $methods_info[$method] = $method_array;
    }

    $to_store['methods'] = $methods;
    $to_store['methods_info'] = $methods_info;
    return $to_store;

  } // download_Flickr_API

# store the data
  public function store_api_data($fname, $to_store) {

    $to_store_str = serialize($to_store);
    $fh = fopen($fname,'wb') OR die ("can't open $fname!");
    $numbytes = fwrite($fh, $to_store_str);
    fclose($fh);
  }

# convenience method for updating the cache
  public function update_api_data($fname) {

    $to_store = $this->download_flickr_methods();
    $this->store_api_data($fname,$to_store);
  }

# restore the data

  public function restore_api_data($fname) {

    $fh = fopen($fname,'rb') OR die ("can't open $fname!");
    $contents = fread($fh, filesize($fname));
    fclose($fh);
    return unserialize($contents);

  }

} //flickr_methods
?>
         

This form of serialization in the flickr_method class provides some basic caching so that you don’t have to make more than 100 calls (one for each method) each time you want to display a summary table—which is what the following code does:

<?php

  require_once("flickr_methods.php");
  $API_KEY = "[API_KEY]";

  $fname = 'flickr.methods.info.txt';

  $fm = new flickr_methods($API_KEY);

  if (!file_exists($fname)) {
    $fm->update_api_data($fname);
  }
  $m = $fm->restore_api_data($fname);

    $methods = $m["methods"];
    $methods_info = $m["methods_info"];

    header("Content-Type:text/html");
    echo '<?xml version="1.0" encoding="utf-8"?>';
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Flickr methods</title>
    <meta ?http-?equiv="Content-Type" content="text/html;charset=utf-8" />
  </head>
  <body>
    <table>
      <tr>
        <th>method name</th>
        <th>description</th>
        <th>needs login</th>
        <th>needs signing</th>
        <th>permissions</th>
        <th>args (mandatory)</th>
        <th>args (optional)</th>
      </tr>
<?php
  foreach ($methods_info as $name=>$method) {
    $description = $method["description"];
# calc mandatory and optional arguments
    $m_args = "";
    $o_args = "";
    foreach ($method["arguments"] as $arg){
      //print "arg: {$arg['name']}\n";
      //print_r ($arg);
      // don't list api_key since it is mandatory for all calls
      if ($arg['name'] != 'api_key') {
        if ($arg["optional"] == '1') {
          $o_args .= " {$arg['name']}";
        } else {
          $m_args .= " {$arg['name']}";
        }
      } //if
    }
    print <<<EOT
      <tr>
        <td>
          <a href="http://www.flickr.com/services/api/{$name}.html">{$name}</a>
        </td>
        <td>{$description}</td>
        <td>{$method["needslogin"]}</td>
        <td>{$method["needssigning"]}</td>
        <td>{$method["requiredperms"]}</td>
        <td>{$m_args}</td>
        <td>{$o_args}</td>
      </tr>
EOT;
  }
?>
    </table>
  </body>
</html>
         

What Else Can Be Done with Reflection?

There’s certainly a lot more you can do with this code. Let me suggest a few ideas:

  • Store the data in a database (relational or XML) that can support a query language for making the queries that I listed earlier. (A poor person’s approach is to copy the output of the script into a spreadsheet and work from there.)

  • Create your own version of the Flickr API Explorer, perhaps as a desktop application, to help you learn about pieces of the API as you have specific questions.

  • Use the reflection methods as the basis of a new third-­party API wrapper that is able to update itself as the API changes.

[Note]Note

In all the examples I have shown of the Flickr API, I have used HTTP GET because none of the examples so far has required any write or delete permissions. If your calls do require write or delete permissions, you must issue your Flickr call with HTTP POST.