Access Keys

To communicate with the InfoSpace Search API, you must use an access key. This page describes the process of programatically signing each request with your access key. To get an access key contact your Partnership Manager.


Overview

To use an access key for search requests to the Infospace API you must sign all request URLs. This allows the API to validate that the requests originated from your application, and prevents unauthorized access to the API using your credentials. Without a valid signature, the requests will be rejected, and no results returned.
Prerequisites:

  • An InfoSpace search cobrand/feed
  • An InfoSpace access key
  • The system clock on each server that will be signing requests to be accurate within a maximum deviation of 1 minute.

Each URL is signed by appending a signature parameter to the end of the URL. This signature is a specially formed binary hash of the following information:

  • Request date and time
  • Access key/token
  • Request query string

Get the Access Key

Contact your Partnership Manager to obtain an Access Key.

Failures

If a request fails to authenticate, for whatever reason, the InfoSpace search API will return the following error message:

<search-results version="7.0">
    <error description="Not authorized." />
</search-results>

Algorithm

The basic algorithm for creating the signature is as follows.

    Create a timestamp as follows:

  • Start with the UTC/GMT time
  • Round to the nearest minute (30 seconds or greater rounds up, otherwise round down)
  • Format the time as a string: yyyyMMddHHmm
  • Legend
    Key Description Example Values
    yyyy 4-digit year 2013
    MM 2-digit month 01 to 12
    dd 2-digit day 01 to 31
    HH 2-digit hour (24-hour clock) 00 to 23
    mm 2-digit minute 00 to 59
  • Extract the query-string component from the request’s URL, excluding any leading ‘?’.
  • Concatenate the values together in the following order:
    1. timestamp
    2. access key
    3. query-string value
  • Decode the concatenated string into its binary representation according to whatever encoding it follows (likely UTF-8).
  • Perform a SHA-1 hash of the binary value.
  • Encode the hashed value using the url-safe base-64 encoding method, see http://tools.ietf.org/html/rfc4648#section-5

The resulting signature is then appended to the request’s URL as follows:

http://[partner subdomain].infospace.com/[cobrand id]/wsapi/results?some_query_string&signature=SIGNATURE_VALUE

An example of a request URL, given the SubDomain of ‘partnercompanyinc’ the CoBrand ID of ‘partnerco’, and a keyword search for ‘cars’ would look like:

http://partnercompanyinc.infospace.com/partnerco/wsapi/results?query=cars&category=web&qi=21&enduserip=71.164.114.232&X-Insp-User-Headers=USER-AGENT%3A%20Mozilla%2F5.0%20(Windows%20NT%206.1%3B%20WOW64%3B%20rv%3A10.0.2)%20Gecko%2F20100101%20Firefox%2F10.0.2%0Areferer%3A%20http%3A%2F%2Fwww.somewebsite.com%2Fsearch.php&signature=5R4fHtJLRFHigT54MP1cz3mUpDY

Important Considerations

Keys will change, design accordingly

Each access key issued by InfoSpace is assigned specifically to a partner. If that key is compromised, it may be deactivated and a new key issued. If this happens, all requests signed with the previous key would no longer be valid, and would be rejected by our API. This will result in a period of no results until requests are signed with the new access key. For that reason, you should design your system to allow keys to be quickly changed.

Requests are time-sensitive

Since each request URL is signed with the date and time it was created, is is imperative that your server clocks remain synchronized to internet time. If they are inaccurate by 1 minute or more, the requests may be rejected due to an invalid signature. More information on synchronizing a server clock to internet time:

Additionally, each request will only be valid if received by the InfoSpace search API within a very close time period of the request being signed. If the request takes more than a minute to reach the InfoSpace search API it will be considered invalid and rejected.

Query String Parameters

The order of query string parameters in the URL must not be changed, the order that is signed must be the order that is sent in the request URL.

Signing a URL must always happen last

The request URL is an integral component of the signing process for the request; changing even a single character in the URL will result in a different signature for the request. The request’s URL must NOT be modified after it has been signed. If it is modified, then the request may be considered invalid and rejected.

The “signature” parameter is reserved

The signing process appends a parameter to the request URL with the name “signature”. The InfoSpace search API will expect a single instance of this parameter for a request. If a request includes any additional parameters in the URL named “signature”, the request may be considered invalid and rejected. Important that you url-safe base-64 encode and not do the following:

  • URL encode the signature
  • Base 64 encode and then URL encode the signature

Example Code

The following section includes code-snippets for common languages that demonstrate how to correctly sign a request.

C#

using System;
using System.Security.Cryptography;
using System.Text;
using System.Web;

class InfoSpaceRequestSigner
{
    private readonly string token;

    public InfoSpaceRequestSigner(string token)
    {
        this.token = token;
    }

    public string SignUrl(string url)
    {
        return url + "&signature=" + GetSignature(url);
    }

    public string GetSignature(string url)
    {
        string value =
        GetTimeToNearestMinute() +
        this.token +
        GetQueryString(url);

        return HashValue(value);
    }

    private string GetTimeToNearestMinute()
    {
        return DateTime
        .UtcNow
        .AddSeconds(30)
        .ToString("yyyyMMddHHmm");
    }

    private string GetQueryString(string url)
    {
        return new UriBuilder(url)
        .Query
        .TrimStart('?');
    }

    private string HashValue(string value)
    {
        byte[] bytes = Encoding.UTF8.GetBytes(value);

        using (SHA1 hash = SHA1.Create())
        {
            bytes = hash.ComputeHash(bytes);
        }

        return EncodeUrlSafeBase64(bytes);
    }

    private string EncodeUrlSafeBase64(byte[] bytes)
    {
        string signature = HttpServerUtility.UrlTokenEncode(bytes);

        // Remove padding integer (C# specific):
        return signature.Remove(signature.Length - 1);
    }
}

And to use the above signer:

InfoSpaceRequestSigner signer = new InfoSpaceRequestSigner(MY_TOKEN);
string signedUrl = signer.SignUrl(REQUEST_URL);

Java

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
import org.apache.commons.codec.binary.Base64;

public class InfoSpaceRequestSigner
{
    private final String token;
    
    public InfoSpaceRequestSigner(String token)
    {
        this.token = token;
    }
    
    public String signUrl(String url)
    {
        return new StringBuffer(url)
        .append("&signature=")
        .append(getSignature(url))
        .toString();
    }
    
    private String getSignature(String url)
    {
        String value =
        getFormattedDateString() +
        this.token +
        getQueryString(url);
        
        return hashValue(value);
    }
    
    private String getFormattedDateString()
    {
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmm");
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        
        return format.format(getTimeToNearestMinute());
    }
    
    private Date getTimeToNearestMinute()
    {
        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        
        cal.add(Calendar.SECOND, 30);
        
        return cal.getTime();
    }
    
    private String getQueryString(String url)
    {
        return URI.create(url).getRawQuery();
    }
    
    private String hashValue(String input)
    {
        byte[] bytes = decodeUTF8(input);
        
        bytes = hashSHA1(bytes);
        
        return encodeUrlSafeBase64(bytes);
    }
    
    private byte[] decodeUTF8(String input)
    {
        try
        {
            return input.getBytes("UTF-8");
        }
        catch (UnsupportedEncodingException e)
        {
            return input.getBytes();
        }
    }
    
    private byte[] hashSHA1(byte[] input)
    {
        try
        {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            
            return digest.digest(input);
        }
        catch (NoSuchAlgorithmException e)
        {
            return null;
        }
    }
    
    private String encodeUrlSafeBase64(byte[] input)
    {
        return Base64.encodeBase64URLSafeString(input);
    }
}

And to use the above signer:

InfoSpaceRequestSigner signer = new InfoSpaceRequestSigner(MY_TOKEN);
string signedUrl = signer.signUrl(REQUEST_URL);

PHP

<?php
 
/**
 * PHP implementation of InfoSpaceRequestSigner
 *
 * @see http://sdk.infospace.com/access-keys/#ExampleCode
 *
 */
class InfoSpaceRequestSigner {
    private $token;
 
    /**
     * Constructs the object and saves the token
     *
     * @param string $token
     */
    public function __construct($token) {
        $this->token = $token;
    }
 
    /**
     * Signs a URL. 
     *
     * @param string $url
     *      URL to sign
     *
     * @return string
     *      Signed URL
     */
    public function signUrl($url) {
        return $url . '&signature=' . $this->getSignature($url);
    }
 
    /**
     * Gets the signature portion for a URL
     *
     * @param string $url
     */
    private function getSignature($url) {
        $value = $this->getFormattedDateString() . $this->token . $this->getQueryString($url);
        return $this->hashValue($value);
    }
 
    /**
     * Gets the date/time to the nearest minute as YYYYMMDDHHMM
     *
     * @return string
     */
    private function getFormattedDateString() {
        // Save the current timezone, get a date as GMT and reset timezone
        $timezone = date_default_timezone_get();
        date_default_timezone_set('GMT');
        $datetime = date('YmdHi', $this->getTimeToNearestMinute());
        date_default_timezone_set($timezone);
 
        return $datetime;
    }
 
    /**
     * Gets the date/time +30 seconds as a unix timestamp
     *
     * @return int
     */
    private function getTimeToNearestMinute() {
        return time() + 30;
    }
 
    /**
     * Gets the query portion of a URL
     *
     * @param string $url
     *
     * @return string
     */
    private function getQueryString($url) {
        // Type-cast to string so we can't return false or null
        return parse_url($url, PHP_URL_QUERY);
    }
 
    /**
     * Gets a base64 encoded SHA1 hash
     *
     * @param string $input
     *
     * @return string
     */
    private function hashValue($input) {
        $bytes = $this->decodeUTF8($input);
        $bytes = $this->hashSHA1($bytes);
 
        return $this->encodeUrlSafeBase64($bytes);
    }
 
    /**
     * Decodes a UTF8 string into ISO-8859-1
     *
     * @param string $input
     *
     * @return string
     */
    private function decodeUTF8($input) {
        // Not tested since input data isn't utf-8 anyway
        return utf8_decode($input);
    }
 
    /**
     * Gets a SHA1 hash
     *
     * @param string $input
     *
     * @return string
     */
    private function hashSHA1($input) {
        // Trial and error shows this must be binary result
        return sha1($input, true);
    }
 
    /**
     * Creates a URL safe base64 encoded string
     *
     * @param string $input
     *
     * @return string
     */
    private function encodeUrlSafeBase64($input) {
        // Apache code replaces + with -, / with _ and trims padding (=)
        return str_replace(array('+', '/'), array('-', '_'), trim(base64_encode($input), '=='));
    }
}

And to use the above signer:

$signer = new InfoSpaceRequestSigner($token);
$signedUrl = $signer->signUrl($requestUrl);

Alternative base64url encoders

While the preceding code sample demonstrates an implementation of the Apache Commons base64url encoder, there are several other alternative implementations that may be more appropriate. Also, if your commons-codec jar is from a Google jar (e.g. Adwords API) ensure you have the updated version. Previous versions of the commons-codec will cause a java.lang.NoSuchMethodError on runtime (compilation is just fine).

Apache Commons
http://commons.apache.org/proper/commons-codec//apidocs/org/apache/commons/codec/binary/Base64.html#encodeBase64URLSafeString(byte\[\])


import org.apache.commons.codec.binary.Base64;
Base64.encodeBase64URLSafeString(byte[]);

Android
http://developer.android.com/reference/android/util/Base64.html


import android.util.Base64;
Base64.encode(byte[], NO_PADDING | URL_SAFE);

Guava
http://docs.guava-libraries.googlecode.com/git-history/v14.0/javadoc/index.html?com/google/common/io/BaseEncoding.html


import com.google.common.io.BaseEncoding;
BaseEncoding.base64url().omitPadding().encode(bytes[]);

Next: Querying for Content

Back to: Getting Started