In my previous post I talk about adding support for reading and commenting on your Facebook news feed in RSS Bandit. This functionality is made possible by the recently announced Facebook Open Stream API. As I worked through the code in my few free moments I was alternately impressed and frustrated by the Open Stream API. On the one hand, the functionality the API provides is greatly enabling and has already unleashed a bounty of innovation as evidenced by the growing number of applications for interacting with your Facebook news feed on the desktop (e.g. Seesmic, Tweetdeck, bdule, etc). On the other hand, figuring out there are a few quirks of the API that make the web developer in me cringe and the desktop developer want to beg for mercy.
Below are my opinions on the Open Stream API, the purpose of sharing is so other developers who plan to use the API may avoid some of the pitfalls I did and also to share feedback with my peers at Facebook and elsewhere on best practices in providing activity stream APIs.
Good: Lots of options for getting at the data
Most API calls at Facebook have two entry points. You can either call a “REST-like” URL endpoint via HTTP GET or perform a SQL-like query via FQL on a generic end point. For interacting with the Facebook news feed, you have stream.get and stream.getComments as the REST-like methods for accessing the news feed and a comment thread for a particular feed item respectively. With FQL, you can perform queries against the stream (FQL) and comment (FQL) to get the same results.
Both mechanisms give you the option of getting back the data as XML or JSON. Below are example of what HTTP requests to retrieve your news feed look like using both approaches
stream.get REQUEST:
http://api.facebook.com/restserver.php?v=1.0&method=stream.get&format=XML&viewer_id={0}&session_key={1}&api_key={2}&call_id={3}&sig={4}
FQL REQUEST:
http://api.facebook.com/restserver.php?v=1.0&method=fql.query&format=XML&call_id={0}&session_key={1}&api_key={2}&sig={3}&query={4}
FQL QUERY:
select post_id, source_id, created_time, actor_id, target_id, app_id, message, attachment, comments, likes, permalink, attribution, type from stream where filter_key in (select filter_key FROM stream_filter where uid = {0} and type = 'newsfeed') and created_time >= {1} order by created_time desc limit 50
I ended up using stream.get
over FQL because the only benefit I see to using FQL is being able to filter some of the result fields or combine data from multiple tables, neither of which I needed in this instance. I chose XML as the output format over JSON because I transform the results of the Facebook API request into an Atom feed and then hand it down to the regular Atom feed parsing code in RSS Bandit. It was much easier for me to handle this transformation using XSLT over XML results as opposed to procedural code over a JSON result set.
There’s also a third mechanism for getting news feed data out of Facebook that would have been perfect for my needs. You can access the news feed using the nascent activity streams standard which returns the data as an Atom feeds with certain extensions specific to social network activity stream. I couldn’t get this to work probably due to user error on my part (more on this later) but if I had, the structure of the GET request would have been along the lines of
Activity Streams REQUEST:
http://www.facebook.com/activitystreams/feed.php?source_id={0}&app_id={1}&session_key={2}&sig={3}&v=0.7&read&updated_time={4}
If not for the initial problems I had figuring out which parameters to pass to APIs and the concern about building on an API that isn’t yet at version 1.0, the Activity Stream API would have been perfect for my needs.
Kudos to the Facebook team for providing such a rich and varied set of options for getting the news feed out of their system. There’s something for every temperament.
Bad: Misuse of HTTP status codes
The Facebook API documentation describes the platform as “REST-like” and not RESTful. They sure weren’t kidding. For the most part, when I’ve discussed the problems with APIs that aren’t completely RESTful the negatives to such an approach have seemed aesthetic in nature as opposed to being practical problems. Below are some of the practical problems I faced because the Facebook APIs do not use HTTP status codes in a manner consistent with the rest of the Web.
In HTTP, there are existing error codes which clients can interpret in a consistent manner and provide consistent feedback to users when they occur. Now consider this excerpt from the documentation on using Facebook’s activity stream API
Response Codes
Like most HTTP responses, Facebook Activity Streams responses include a response header, which always includes a traditional response code and a short response message. The supported response codes include:
- 200 Code provided whenever the Facebook servers were able to accommodate the request and provide a response.
- 304 Code provided whenever the request header included If-Modified-Since and no new posts have been generated since the specified time.
Note: Code 304 will never be returned if If-Modified-Since isn't included in the request header. - 401 Code provided whenever the URL omits one or more of the required parameters.
- 403 Code provided whenever the URL is syntactically valid, but the user hasn't granted the required extended permission.
- 404 Code provided whenever the URL is syntactically valid, but the signature is incorrect, or the session key is invalid.
You might notice that an HTTP 401 is used to indicate that the request is improperly formed. However, let’s see what RFC 2616 has to say about the 401 status code as well as how to communicate badly formed arguments
400 Bad Request
The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications.
401 Unauthorized
The request requires user authentication. The response MUST include a WWW-Authenticate header field (section 14.47) containing a challenge applicable to the requested resource. The client MAY repeat the request with a suitable Authorization header field (section 14.8). If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials. If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity might include relevant diagnostic information. HTTP access authentication is explained in "HTTP Authentication: Basic and Digest Access Authentication"
According to the HTTP specification excerpted above, the status code to return on missing parameters should be 400 not 401. The practical problem with returning HTTP 401 in this case is that applications like RSS Bandit may have code paths that prompt the user to check or re-enter their credentials because an authentication error has occurred. We now have to special case getting a 401 from Facebook’s servers versus any other server on the Web. Thankfully, this error should be limited to development time unless Facebook changes their API in a backwards incompatible manner by requiring new parameters to their methods.
A bigger problem is that Facebook returns HTTP 200 which traditionally means success in regular occurring error conditions. Specifically, when a user’s session key expires a successful response is sent containing an error document. In RSS Bandit, we already have a processing pipeline that hands off successful responses containing an XML document to the RSS/Atom parsing layer. With Facebook’s behavior I had two choices
-
Modify the RSS/Atom parsing layer to understand Facebook error documents and then kick back an error up to the user interface asking the user to re-enter their credentials. This was particularly hacky because that layer doesn’t really have a connection to the main UI thread nor should it.
-
Pre-process the XML document before handing it to the RSS/Atom parsing layer. This implies an intermediate step in between dispatching on the response status and actually processing the document.
Option #2 proved more palatable since there was already an intermediate step needed to transform the results of stream.get
to an Atom feed. If I’d used the Activity Streams API as was my initial plan then the decision may have been harder to make.
Good: Well thought out model for authorizing access to user data
A number of application platforms work by giving a user all-or-nothing access to the user’s data. My favorite example of this bad practice is Twitter which for a long time made this situation even worse by requiring the applications to collect the person’s username and password. The controversy around Twply earlier this year shows exactly why giving applications more access to a user’s data than they need is bad. Twply only needed access to a user’s @replies but given that there was no way to only give it access to just that aspect of a user’s Twitter data, it also got the ability to post tweets on their behalf and did so in a spammy manner.
The Facebook API has a notion of extended permissions which are access rights that require special opt-in from the user given that the data or functionality is sensitive and can be abused. The current set of extended permissions in the Facebook API is provided below
Permission | Description |
publish_stream | Lets your application or site post content, comments, and likes to a user's profile and in the streams of the user's friends without prompting the user. This permission is a superset of the status_update, photo_upload, video_upload, create_note, and share_item extended permissions, so if you haven't prompted users for those permissions yet, you need only prompt them for publish_stream. Note: At this time, while the Open Stream API is in beta, the only Facebook users that can grant your application the publish_stream permission are the developers of your application. |
read_stream | Lets your application or site access a user's stream and display it. This includes all of the posts in a user's stream. You need an active session with the user to get this data. |
email | This permission allows an application to send email to its user. This permission can be obtained only through the fb:prompt-permission tag or the promptpermission attribute. When the user accepts, you can send him/her an email via notifications.sendEmail or directly to the proxied_email FQL field. |
offline_access | This permission grants an application access to user data when the user is offline or doesn't have an active session. This permission can be obtained only through the fb:prompt-permission tag or the promptpermission attribute. |
create_event | This permission allows an app to create and modify events for a user via the events.create, events.edit and events.cancel methods. |
rsvp_event | This permission allows an app to RSVP to an event on behalf of a user via the events.rsvp method. |
sms | This permission allows a mobile application to send messages to the user and respond to messages from the user via text message. |
status_update | This permission grants your application the ability to update a user's or Facebook Page's status with the status.set or users.setStatus method. Note: You should prompt users for the publish_stream permission instead, since it includes the ability to update a user's status. |
photo_upload | This permission relaxes requirements on the photos.upload and photos.addTag methods. If the user grants this permission, photos uploaded by the application will bypass the pending state and the user will not have to manually approve the photos each time. Note: You should prompt users for the publish_stream permission instead, since it includes the ability to upload a photo. |
video_upload | This permission allows an application to provide the mechanism for a user to upload videos to their profile. Note: You should prompt users for the publish_stream permission instead, since it includes the ability to upload a video. |
create_note | This permission allows an application to provide the mechanism for a user to write, edit, and delete notes on their profile. Note: You should prompt users for the publish_stream permission instead, since it includes the ability to let a user write notes. |
share_item | This permission allows an application to provide the mechanism for a user to post links to their profile. Note: You should prompt users for the publish_stream permission instead, since it includes the ability to let a user share links. |
Bad: Too many prompts for desktop applications
Although I’ve praised the extended permissions model, it currently leads to a cumbersome experience for desktop applications. Installing Facebook desktop applications like bdule or Facebook for Adobe Air requires running through three separate permissions screens. The user has to login, then grant the read_stream extended permission followed by the publish_stream extended permission. Granted, the latter two only need to be done once but they still affect the out of box experience fairly negatively in my opinion.
In RSS Bandit, I’ve attempted to reduce this by delaying the prompt for publish_stream permission until the first time a user tries to comment on a news feed item from within the application. Streamlining this experience will be a boon for application developers who want the entire experience to be smooth and painless. The documentation on the documentation the Open Streams API page states that there are options for streamlining these requests but they only apply to Web applications not desktop apps.
Bad: Plethora of application identifiers and authentication requirements is a stumbling block for beginners
The official documentation on the Facebook API doesn’t do a good job of connecting all the dots when it comes to understanding how to make calls to the service. For example, when you read the documentation for stream.get it is not obvious from that page what endpoint to make requests to OR that every Facebook API call has a set of required parameters beyond the ones listed on that page. In fact, I was stumped for about a week or so trying to figure out the magical incantations to get the right set of parameters for API calls and the right data to put in these parameters until I stumbled on a Facebook forum post creating the 'sig' parameter which solved my problems. I believe I once saw this information in the Facebook API documents but after ten minutes of searching just now I can’t seem to find it so perhaps it was in my imagination.
Part of the problem is the varied number of identifiers that you have to keep straight as an application developer including your
- application ID
- API key
- application secret
- session key
- client secret
The fact that various APIs take different combinations of the above lead to more than one confusing moment for me. Eventually I figured it out but I felt like I was being hazed as I was going through the process.