Working with the bit.ly API to shorten URLs
Join the DZone community and get the full member experience.
Join For FreeBit.ly is a quite popular URL shortening service. On Tiwtter, almost all of the links I see provided by the people I follow are posted as bit.ly shortcuts. If you’ve used a Twitter client, you probably already know that some of them (if not almost every single of them) offers URL shortening as a built-in capability.
Now, you can implement the same functionality, thanks to the fact that bit.ly offers a public API to do this. But let’s start with coding.
First of all, all data that is passed to the service is transferred via HTTP requests. The response generated by the service is by default formatted as a JSON document, however the developer can explicitly specify that XML data should be returned. A request to the bit.ly shortening service requires authentication, and the username and API key are required to be passed as parameters. This means that in order to use the service, a bit.ly account is needed (it is free). The API key can be found here (http://bit.ly/account/your_api_key) once the user registered.
Shortening
The first (and probably the most important method) is the one that actually shortens the URL and it is called /v3/shorten. V3 at the beginning stands for the API version (that is 3.0 at the moment, so don’t worry about that).
This method accepts 5 parameters:
• format – determines the output format for the request
• longUrl –determines the long URL that needs to be shortened
• domain – [optional] the domain used for shortening – either bit.ly or j.mp (default: bit.ly)
• x_login – the user ID (although in the documentation it is indicated as optional, it is not)
• x_apiKey – the user API key (although in the documentation it is indicated as optional, it is not)
Let’s look at the method I’ve written to shorten the URL:
enum Format
{
XML,
JSON,
TXT
}
enum Domain
{
BITLY,
JMP
}
string ShortenUrl(string longURL,
string username, string apiKey, Format format = Format.XML, Domain domain = Domain.BITLY)
{
string _domain;
string output;
// Build the domain string depending on the selected domain type
if (domain == Domain.BITLY)
_domain = "bit.ly";
else
_domain = "j.mp";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(
string.Format(@"http://api.bit.ly/v3/shorten?login={0}&apiKey={1}&longUrl={2}&format={3}&domain={4}",
username, apiKey, HttpUtility.UrlEncode(longURL), format.ToString().ToLower(), _domain));
using (WebResponse response = request.GetResponse())
{
using (StreamReader reader = new StreamReader (response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
There are two enums that hold the possible data formats as well as the domain names. This is made for safety reasons – if I would pass these as string parameters, there is a higher chance the end-user will pass the wrong string, and then the function will fail.
The code is based on a single HttpWebRequest that creates a HTTP request to the URL that is built according to the data passed to it. Then, I am getting the response stream and passing the string representation to the returned string variable.
Notice the fact that I am explicitly returning a string value for this method. In fact, I could either return a JsonDocument instance (requires a third-party library to use this class) or XmlDocument. But since there are two possible formats for the request to handle, it is better to return this as a simple string and then let the developer decide what he wants to do next.
Once called, the function will return data similar to this:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status_code>200</status_code>
<status_txt>OK</status_txt>
<data>
<url>http://j.mp/crnexS</url>
<hash>crnexS</hash>
<global_hash>msft</global_hash>
<long_url>http://www.microsoft.com</long_url>
<new_hash>0</new_hash>
</data>
</response>
Or this (for JSON):
{ "status_code": 200, "status_txt": "OK", "data": { "long_url": "http:\/\/www.microsoft.com", "url": "http:\/\/j.mp\/crnexS", "hash": "crnexS", "global_hash": "msft", "new_hash": 0 } }
Or this (for TXT):
http://j.mp/crnexS
I am using a custom domain here, but as you see – the format and domain are optional parameters. I can leave them with default values without actually passing them to the function, and then the only values that need to be indicated are the user ID, API key and the long URL.
Decoding
There is also a way to decode the URL to its initial state from what was the shortened one. The method is called /v3/expand and is used in a similar manner as the shortening one. In this method, I am also using the Format enum to specify the output format:
string DecodeUrl(string[] urlSet, string[] hashSet,
string username, string apiKey, Format format = Format.XML)
{
string output;
string URL = string.Format(@"http://api.bit.ly/v3/expand?login={0}&apiKey={1}&format={2}",
username, apiKey, format.ToString().ToLower());
if (urlSet != null)
{
foreach (string url in urlSet)
URL += "&shortUrl=" + HttpUtility.UrlEncode(url);
}
if (hashSet != null)
{
foreach (string hash in hashSet)
URL += "&hash=" + hash;
}
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);
using (WebResponse response = request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
It works a bit different though. As you can see, I am requesting the user to pass two arrays – one with URLs and one with hashes. One of them can be null, therefore the URL can be decoded either by the hash or by the shortened URL. The user can pass both arrays, and get a result similar to this:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status_code>200</status_code>
<status_txt>OK</status_txt>
<data>
<entry>
<short_url>http://j.mp/crnexS</short_url>
<long_url>http://www.microsoft.com</long_url>
<user_hash>crnexS</user_hash>
<global_hash>msft</global_hash>
</entry>
<entry>
<hash>crnexS</hash>
<long_url>http://www.microsoft.com</long_url>
<user_hash>crnexS</user_hash>
<global_hash>msft</global_hash>
</entry>
</data>
</response>
The URLs are sanitized inside the function – I am not assuming that the user will pass the encoded URL. In fact, the developer should never assume that the user will pass the correct value – the code should be as foolproof as possible.
User validation
If you work on an application that depends on the URL shortening service, it would be a good idea to validate the user before making the API calls. Bit.ly provides a method for this as well and it is called /v3/validate. It only requires three parameters – the username, the API key and the output format (that is in fact optional).
The C# implementation for this method looks like this:
string ValidateUser(string username, string apiKey, string userToCheck, string keyToCheck, Format format = Format.XML)
{
string output;
string URL = string.Format(@"http://api.bit.ly/v3/validate?x_login={0}&x_apiKey={1}&login={2}&apiKey={3}&format={4}",
userToCheck, keyToCheck, username,apiKey, format.ToString().ToLower());
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);
using (WebResponse response = request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
A bit of confusion can be caused by the fact that there are x_ -prefixed copies of login and API key. You need to pass your ID and API key to verify someone else’s account validity. X_ -prefixed parameters represent the end user.
The output should look similar to this:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status_code>200</status_code>
<status_txt>OK</status_txt>
<data>
<valid>1</valid>
</data>
</response>
Count clicks
Bit.ly provides click statistics, so once you shorten a URL, you can track its basic usage. Statistics are available through the /v3/clicks method. It doesn’t have a TXT output format, so you will have to avoid using that (or create a separate enum, that is the best choice).
The implementation for it looks like this:
string GetClicks(string[] urlSet, string[] hashSet,
string username, string apiKey, Format format = Format.XML)
{
string output;
string URL = string.Format(@"http://api.bit.ly/v3/clicks?login={0}&apiKey={1}&format={2}",
username, apiKey, format.ToString().ToLower());
if (urlSet != null)
{
foreach (string url in urlSet)
URL += "&shortUrl=" + HttpUtility.UrlEncode(url);
}
if (hashSet != null)
{
foreach (string hash in hashSet)
URL += "&hash=" + hash;
}
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);
using (WebResponse response = request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
Same as for the Expand method, an array of URLs and hash codes can be passed and statistics will be generated for multiple entries at once.
The response looks like this (in XML format):
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status_code>200</status_code>
<data>
<clicks>
<short_url>http://j.mp/crnexS</short_url>
<global_hash>msft</global_hash>
<user_clicks>0</user_clicks>
<user_hash>crnexS</user_hash>
<global_clicks>2230</global_clicks>
</clicks>
<clicks>
<user_clicks>0</user_clicks>
<global_hash>msft</global_hash>
<hash>crnexS</hash>
<user_hash>crnexS</user_hash>
<global_clicks>2230</global_clicks>
</clicks>
</data>
<status_txt>OK</status_txt>
</response>
Note that the XML won’t be indented by default.
Check for PRO domain
Bit.ly offers pro, customizable domains. That means, that not only bit.ly and j.mp can be used for shortening, but user-defined domains as well. The /v3/bitly_pro_domain method allows to check whether a domain is bit.ly PRO-powered or not. It is very similar to the user validation method, but it accepts the domain name instead of the user credentials.
The C# implementation looks like this:
string CheckPro(string username, string apiKey, string domain, Format format = Format.XML)
{
string output;
string URL = string.Format(@"http://api.bit.ly/v3/bitly_pro_domain?login={0}&apiKey={1}&domain={2}&format={3}",
username, apiKey, domain, format.ToString().ToLower());
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);
using (WebResponse response = request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
Once called, the output looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status_code>200</status_code>
<data>
<domain>nyti.ms</domain>
<bitly_pro_domain>1</bitly_pro_domain>
</data>
<status_txt>OK</status_txt>
</response>
URL lookup
Bit.ly also allows the lookup of long URLs. For example, you might want to find if there is an existing short URL for the existing long URL. To do this, there is the /v3/lookup method.
The implementation is quite simple and as other methods, it has the same base structure:
string Lookup(string username, string apiKey, string[] url, Format format = Format.XML)
{
string output;
string URL = string.Format(@"http://api.bit.ly/v3/lookup?login={0}&apiKey={1}&format={2}",
username, apiKey, format.ToString().ToLower());
foreach (string _url in url)
URL += "&url=" + HttpUtility.UrlEncode(_url);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);
using (WebResponse response = request.GetResponse())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
output = reader.ReadToEnd();
}
}
return output;
}
The XML response looks similar to this for a positive result (there is a URL found):
<?xml version="1.0" encoding="UTF-8"?>
<response>
<status_code>200</status_code>
<data>
<lookup>
<url>http://www.dreamincode.net</url>
<short_url>http://bit.ly/mviGY</short_url>
<global_hash>mviGY</global_hash>
</lookup>
</data>
<status_txt>OK</status_txt>
</response>
Notice that I can pass an array of URLs to be checked. Mind, though, that the maximum number of URLs that can be passed to the method is 15.
With the methods described above, you can harness the power of bit.ly and bring it to your .NET application (the code can easily be ported to any .NET-compatible programming language).
For official documentation, you can take a look here.
Opinions expressed by DZone contributors are their own.
Comments