REST API - Authentication

To authenticate any API calls to QL's REST API, you must sign each request using the API credentials associated with the store's data you wish to access over the API.

Please Note

Unlike our legacy API, the REST API authentication is done at the Account level, and not at the Site level. For relevant API calls, you can specify the Site or Sites you wish to interact with.

API Key Pair

Your API credentials are made of two long strings, that are used together to sign and issue calls to our REST API. The public part of your key-pair is called an API_KEY, while the private part of the pair is called an API_SECRET.

As the names imply, the API_KEY can be safely shared and passed around in requests, while the API_SECRET must be kept safe from prying eyes.

Accessing your API Credentials

Your API Key / Secret pair are available on QL's online platform, in the "Account Settings" area. To protect your API credentials, you must reconfirm your login email and password to view them.

API Credentials

Time-stamping your request

To avoid request-replay attacks by 3rd-parties, you are required to pass a timestamp as part of the request query string with each request made to our API. This timestamp is used to ensure that the request was made within a 3 minutes window. Requests with stale or malformed timestamps will be denied.

Timestamps should be a UTC Unix timestamp in milliseconds and should be passed as in the query-string as the value of qts parameter.

For example, calling /api/v1/dummy?paramA=1&paramB=2 should be done using the following URL - /api/v1/dummy?paramA=1&paramB=2&qts=1414562585331

Signing your requests

For the purpose of this example, let's assume your API key-pair contains the following strings:

  • API_KEY - ccae6d912a41bfefd569a77b5cd86603cde92e53cdd45813cba9e5bf080b3734
  • API_SECRET - 0d66f4f97239b4530a5ab226eede766657933948c4fdd1ea22e14773c8342ee5

Let's assume you wish to make an API call to an imaginary end-point /api/v1/dummy?paramA=1&paramB=2. Before issuing the request, you need to generate a request signature, and pass that signature as part of the request headers.

To create the request signature, you need to create a SHA256 digest of the request path, request query-string and request body and salt it with your API_SECRET as follows:

Example signature generation (Ruby)
SHA256.hexdigest(request-path + query-string + request-body + API_SECRET)

Creating the signature

The API end-point /api/v1/dummy?paramA=1&paramB=2 is made up of the following components:

  • Request path - /api/v1/dummy
  • Query-string - paramA=1&paramB=2

Therefore, the request signature would be:

SHA256.hexdigest(
  "/api/v1/dummy" +
  #we added the `qts` parameter to the query-string so the request is timestamped
  "paramA=1&paramB=2&qts=1414562585331" +
  "0d66f4f97239b4530a5ab226eede766657933948c4fdd1ea22e14773c8342ee5"
)

The above example will generate a the following signature: 9f910f3354fd28e4d9b54d902ed881c8e5a912337bce02d51d5f24a1755b552d

Using the signature

Once we signed our request components using the above method, we can use the signature in our API call, by passing it in the custom HTTP header API_DIGEST.

If we were to use cURL as our HTTP client, our API call would look like this:

curl -XGET -H 'API_KEY: ccae6d912a41bfefd569a77b5cd86603cde92e53cdd45813cba9e5bf080b3734' \
-H 'API_DIGEST: 9f910f3354fd28e4d9b54d902ed881c8e5a912337bce02d51d5f24a1755b552d' \
'https://rest.quicklizard.com/api/v1/dummy?paramA=1&paramB=2&qts=1414562585331'

API Signature Checklist

You should note the following when signing the request:

  • Order is important. Please concat the request components in the following order: path + query-string + request-body + API_SECRET.
  • If your request doesn't have a body (usually GET requests), do not add the body to the concatenated string above.
  • Similarly, if your request doesn't have a query-string (usually POST/PUT), do not add it to the concatenated string above.
  • Your API_SECRET should always come last in the concatenated string.
  • Do not forget to timestamp your request, by adding the qts parameter to either your query string or request body

JSON Request Payload

When making POST and PUT requests, you may pass the request payload as JSON instead as a query-based request form.

To create an API signature for JSON payload, you must do the following:

  • Use the JSON payload string as the request body signature part
  • Pass the qts query-string parameter in the query string and not as part of the JSON payload

For example, to sign a POST request to /api/v1/dummmy with the JSON payload {"field":"value"} you should create a request signature as follows:

SHA256.hexdigest(
  '/api/v1/dummy' + # request path
  'qts=1414562585331' + # request query-string (contains only qts)
  '{"field":"value"}' + # request JSON payload as string
  '0d66f4f97239b4530a5ab226eede766657933948c4fdd1ea22e14773c8342ee5' # API secret
)

API Client Libraries

For your convenience, we created client libraries that simplify the use of our API by wrapping the authentication logic and HTTP request flow. These libraries are small enough to simply drop into your codebase and use without worrying about external dependencies.

Please take a look at our Bitbucket repo for more information.

Code Examples

Below are examples on how to generate an API digest in different programming languages.

Ruby

require 'digest'
require 'uri'

api_secret = '0d66f4f97239b4530a5ab226eede766657933948c4fdd1ea22e14773c8342ee5'
uri = URI('https://rest.quicklizard.com/api/v2/recommendations/all?page=1')
qts = Time.now.utc.to_i * 1000 #ruby time is in seconds, so we convert to ms by multiplying it by 1000
uri.query += "&qts=#{qts}" #append qts to query-string
digest = Digest::SHA256.hexdigest("#{uri.path}#{uri.query}#{api_secret}")
puts "Digest: #{digest} | API URL: #{uri.to_s}"

PHP

$api_secret = '0d66f4f97239b4530a5ab226eede766657933948c4fdd1ea22e14773c8342ee5';
$uri = parse_url('https://rest.quicklizard.com/api/v2/recommendations/all?page=1');
$qts = time() * 1000;
$query = $uri['query'].'&qts='.$qts;
$digest = hash('sha256', $uri['path'] . $query . $api_secret);
$url = $uri['scheme'] . '//' . $uri['host'] . $uri['path'] . '?' . $query;
echo 'Digest: ' . $digest . ' | API URI: ' . $url;

Java

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.TimeZone;

try {
  String url = "https://rest.quicklizard.com/api/v2/recommendations/all?page=1";
  String apiSecret = "0d66f4f97239b4530a5ab226eede766657933948c4fdd1ea22e14773c8342ee5";
  long ts = Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTimeInMillis();
  URI uri = new URI(url);
  String path = uri.getPath();
  String queryString = String.format("%s&qts=%d", uri.getQuery(), ts);
  String rawDigest = String.format("%s%s%s", path, queryString, apiSecret);
  MessageDigest md = MessageDigest.getInstance("SHA-256");
  byte[] hash = md.digest(rawDigest.getBytes("UTF-8"));
  StringBuffer hexDigest = new StringBuffer();
  for (int i = 0; i < hash.length; i++) {
    String hex = Integer.toHexString(0xff & hash[i]);
    if(hex.length() == 1) hexDigest.append('0');
    hexDigest.append(hex);
  }
  String apiDigest = hexDigest.toString();
  URI apiURI = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), queryString, uri.getFragment());
} catch (URISyntaxException e) {
  e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
  e.printStackTrace();
} catch (UnsupportedEncodingException e) {
  e.printStackTrace();
}