Forums/Community: Question and Answers/Product API

Price Alerts API

Max Ciccotosto
posted this on May 23, 2011 09:15 pm

Alerts are treated as simple objects that can be created, destroyed, and viewed via a RESTful API.

Table of Contents 

 

Property

Description

Value

ApiKey

Identifies the partner

Assigned by Wishpot

Sig

Request signature

Computed signature.  See the last section “signing requests” for how this is implemented.

 

Also, a mandatory HTTP header is required with each request:

Accept: text/xml

 

 

Property

Description

Required?

Value

alert_type

The type of alert you’re creating

Yes

A string: PriceMatch, PriceProtection, PriceTrack, or PriceWatch

url

The URL of the item to start tracking

Yes

A single URL

postback_url

The URL to receive alerts at via an HTTP post

Yes

A single URL

alert_key

An arbitrary key which you can store inside the alert, which will be included with the alert when posted back.  Useful for storing your internal IDs.

 

A string less than 255 characters in length.

target_price

The price a user wants to be alerted about if it drops to or below it.

Only if type= PriceMatch.  Ignored otherwise.

A numeric value which is less than the product’s current price.

percentage_drop

The percentage a price has to drop in order to alert a user.

Only if type= PriceTrack. Ignored otherwise

A floating point number greater than zero and less than one.

schedule

The frequency with which a user wants to be alerted about a price

Only if type= PriceWatch.  Ignored otherwise.

A string: Daily, Weekly, Monthly, or Yearly.

expiration_date

The date at which a user is uninterested in price drops.

No: Automatically set to the merchant’s return policy (today + x days) but can be overridden.

A date  in MM/DD/YYYY format.

 

 

Send an HTTP POST request to http://alerts.wishpot.com/alerts with the requisite parameters.  The properties of the alert should be wrapped in alert[property] format.  The parameters required with every request should not be.  For example (with curl):

REQUEST:

curl -H "Accept: text/xml" -d "alert[url]=http://www.amazon.com/gp/product/0596529260/" -d "alert[alert_type]=PriceMatch" -d "alert[target_price]=5" -d "alert[postback_url]=http://localhost" -d "ApiKey=YOUR_KEY" -d "Sig=asdf" http://alerts.wishpot.com/alerts

SUCCESSFUL RESPONSE: HTTP CODE 201 (Created)

<?xml version="1.0" encoding="UTF-8"?>

<price-match-alert>

  <alert-key nil="true"></alert-key>

  <clicks type="integer">0</clicks>

  <created-at type="datetime">2008-06-04T15:20:21-07:00</created-at>

  <id type="integer">3</id>

  <initial-price type="float">26.39</initial-price>

  <postback-url>http://localhost</postback-url>

  <target-price type="float">5.0</target-price>

  <updated-at type="datetime">2008-06-04T15:20:21-07:00</updated-at>

  <bookmark>

    <id type="integer">27286</id>

    <product-image>

      <id type="integer">61198</id>

      <image-h type="integer">160</image-h>

      <image-uri>http://ecx.images-amazon.com/images/I/518TAgBUwKL._SL160_.jpg</i...>

      <image-w type="integer">122</image-w>

      <thumbnail-h type="integer">75</thumbnail-h>

      <thumbnail-w type="integer">57</thumbnail-w>

      <updated-on type="timestamp">Wed Jun 04 15:20:21 -0700 2008</updated-on>

    </product-image>  <url>http://www.amazon.com/exec/obidos/redirect?link_code=ur2&amp;ta...>

    <name>RESTful Web Services</name>

    <latest-price>26.39</latest-price>

    <name-detail>Paperback</name-detail>

  </bookmark>

  <initial-price>26.39</initial-price>

  <url>http://alerts.wishpot.com/r?alert_id=3</url>

</price-match-alert>   

 

A successful response will contain a typical successful HTTP Response code (201 Created).  It will also contain a wealth of information about the alert you just created, including:

·      id – the ID of the alert in our system.  Can be used to query the alert on demand.

·      Product information inside the “bookmark” node.  This includes the products name, image, etc. if available.  It also includes the latest price of the product.

·      Initial-price – This is the price on record when the alert was created.  This is always going to be equal to the latest price of the product at the time an alert is created.  Over time, however, the product’s price will change and this price will stay the same.

·      URL – There are 2 URLs in the response.  One is inside the “bookmark” node.  This is the direct URL to the item at the merchant.  The other is a property of the alert.  This is the URL that should be sent to users in their alerts.  This URL will ensure that clicks are tracked, and credit is given for purchases.

·      Clicks – This is a simple running total of the number of clicks this alert has received by users.  Clicks are incremented when the user follows a link from the alert to the merchant selling the item.  This figure says nothing about whether or not the user went on to complete a transaction at the merchant’s site.

A failed request/response will look like this (here we asked for a PriceMatch without specifying our target price):

REQUEST:

curl -H "Accept: text/xml" -d "alert[url]=http://www.amazon.com/gp/product/0596529260/" -d "alert[alert_type]=PriceMatch http://alerts.wishpot.com/alerts

FAILED RESPONSE: HTTP CODE 422 (Unprocessable Entity)

<?xml version="1.0" encoding="UTF-8"?>

<errors>

  <error>Target price can't be blank</error>

  <error>Target price is not a number</error>

</errors>

 

Rather than passing in a URL for the alert (the alert[url] parameter, above) you could alternatively pass:

alert[asin] – with the Amazon ID of the item

alert[sdcid] – with the Shopping.com ID of the item

Any of the above methods will also work for identifying an item.  Only one should be provided for identifying the item.  (Note: if you pass in an Amazon URL, behind the scenes the Alerting system will figure out the ASIN and use Amazon’s APIs to monitor the price.  So, there is no need to parse out the ASIN yourself if you’re given an Amazon URL).

Alerts can be retrieved by ID with an HTTP GET to /alerts/id.  For example:

curl -H "Accept: text/xml" “http://alerts.wishpot.com/alerts/2 ?ApiKey=YOUR_KEY&Sig=asdf”

They can also be retrieved via your customized alert key:

curl -H "Accept: text/xml" “http://alerts.wishpot.com/alerts?alert_key=internal_key &ApiKey=YOUR_KEY&Sig=asdf”

The output will be identical to the response output after creating an alert

Alerts can be deleted by ID with an HTTP DELETE to /alerts/id.  For example:

curl –X DELETE -H "Accept: text/xml" http://alerts.wishpot.com/alerts/3

Or, using your custom alert key,

curl -X DELETE -H "Accept: text/xml" "http://alerts.wishpot.com/alerts?alert_key=your_key "

The output will simply be an HTTP response code (ex. 200 ok).

If there is an error, the HTTP response code will be an error (ex. 404 if the resource is not found) and the error message details will be formatted similarly:

<?xml version="1.0" encoding="UTF-8"?>

<errors>

  <error>Couldn't find Alert with ID=3</error>

</errors>

 

Alerts can be updated by ID with an HTTP PUT to /alerts/id and passing in updated parameters.  For example, this changes a PriceMatch alert such that it has a target price of $6:

curl -X PUT -H "Accept: text/xml" "http://alerts.wishpot.com/alerts/3" -d "ApiKey=your_key" -d "Sig=asdf" -d "alert[target_price]=6.0"

SUCCESSFUL RESPONSE: HTTP CODE 200 (OK)

(no body in response)

FAILED RESPONSE: HTTP CODE 422 (Unprocessible Entity)

<?xml version="1.0" encoding="UTF-8"?>

<errors>

  <error>Target price is not a number</error>

</errors>

 

To test if a URL can be parsed for prices, send an HTTP POST to http://alerts.wishpot.com/alerts/check_url with a single ‘url’ parameter.

SUCCESSFUL RESPONSE: HTTP CODE 200 (OK)

<?xml version="1.0" encoding="UTF-8"?>

<bookmark>

  <price type="float">26.39</price>

  <url>http://www.amazon.com/gp/product/0596529260/</url>

  <name>RESTful Web Services</name>

  <name-detail>Paperback</name-detail>

</bookmark>

 

FAILED RESPONSE: HTTP CODE 422 (Unprocessible Entity)

<?xml version="1.0" encoding="UTF-8"?>

<errors>

  <error>Unable to fetch information for wish.  URL is not supported (http://foo.com)</error>

</errors>

You can pull up a list of all of the domains supported for price parsing with an HTTP GET to http://alerts.wishpot.com/alerts/supported_merchants.  The response will contain a lot of information about each of the supported merchants, including name, logo, domain, homepage, etc.

<merchants type="array">

  <merchant>

    <display-name>Amazon.com</display-name>

    <domain>amazon.com</domain>

    <homepage-blank-img>http://www.assoc-amazon.com/e/ir?t=wishlistingco-20&amp;amp;l=u...>

    <homepage-uri>http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=htt...>

    <id type="integer">6</id>

    <logo-alt>Amazon.com</logo-alt>

    <logo-blank-img nil="true"></logo-blank-img>

    <logo-h type="integer">60</logo-h>

    <logo-link>http://www.amazon.com/gp/redirect.html?ie=UTF8&amp;location=htt...>

    <logo-src>http://rcm-images.amazon.com/images/G/01/associates/2005/served-ban...>

    <logo-w type="integer">120</logo-w>

  </merchant>

  <merchant…

 

 

When an alert fires, a postback is sent to the URL specified in the alert.  The contents of this HTTP POST will be an XML document of the alert (same response as after creation of an alert).  The particular field of interest in all of the postbacks will be the “latest-price” field.   That is the new price that the user has expressed an interest in.  In every case except PriceWatch alerts, this value will be lower than the initial-price field.

They are fired at different times for the different alert types.

 

Alert Type

First Fires

After that?

PriceMatch

As soon as the price of the item drops to be less than or equal to their target price.

The alert will transform into a PriceTrack alert, and will notify the user for every 10% that the price drops.

PriceWatch

Will fire on a schedule as determined by the user.  Either daily, weekly, monthly, or yearly.

Will continue to fire until deleted.

PriceProtection

As soon as the price of the item drops by any amount lower than the initial price.  If this never happens, a special message will fire that the alert has expired (FORMAT TBD), and it will self destruct.

Will continue to fire on every price drop, until the return policy dictates that the item cannot be returned.  At that point the alert will self-destruct.

PriceTrack

As soon as the price dips by x% of the initial price, as determined by percentage_drop specified by the user.

Will continue to alert every time the price drops by another x%.  Will not alert users on price rises, only on new lows relative to their previous alerts.

 

 

In order to make it easier to test postbacks, you can call a method on any alert to force it to postback.  This will send the exact same message to the server that would be sent if the alert had been triggered normally.  It is expected that you would only use this in a testing scenario.

To force a postback, send a POST request to the force_postback action of an alert identified by it’s alert id.  For example:

curl -H "Accept: text/xml" -d "ApiKey=YOUR_KEY" -d "Sig=asdf" http://alerts.wishpot.com/alerts/1/force_postback 

This will both fire the postback, and return the alerts data upon success (which are the same as the contents of the postback).  Note that the postbacks will not have any faulty data in them.  The prices will be accurate as of the time the postback is fired.  So, by manually firing a postback on a PriceMatch alert, you will find that the price of the item probably hasn’t matched the target price.

All requests are to be signed using the partner private key. The signing algorithm works as follows.

1)   Take all name-value pairs of the request in unencoded form. For links these are the URL parameters. For REST requests, these are all the name-value pairs in the request body.

2)   Concatenate each name-value pair as a single string in the format: name=value

3)   Sort the above strings alphabetically and concatenate all the resulting strings into one.

4)   Append your private key.

5)   Compute the byte array corresponding to the string, in UTF-8 encoding.

6)   Encode the byte array using the MD5 hashing algorithm.

7)   Convert the hash to a string containing two lowercase hex digits for each byte.

Add the sig argument to your request, with the value just computed.

static string ComputeSignature(

                    System.Collections.Specialized.NameValueCollection nvc,

                    string privateKey)

{

    System.Collections.Generic.List<string> nvps = new System.Collections.Generic.List<string>();

    for(int i = 0; i < nvc.Count; i++)

    {

        string key = nvc.GetKey(i);

        if(String.Compare(key, "sig", true) == 0)

            continue;

 

        string[] values = nvc.GetValues(i);

        for(int j = 0; j < values.Length; j++)

        {

            string nvp = key + "=" + values[j];

            nvps.Add(nvp);

        }

    }

    nvps.Sort(StringComparer.InvariantCultureIgnoreCase);

 

    System.Text.StringBuilder sb = new System.Text.StringBuilder();

    for(int i = 0; i < nvps.Count; i++)

    {

        sb.Append(nvps[i]);

    }

    sb.Append(privateKey);

 

    System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();

    string s = sb.ToString().Trim();

 

    Encoding enc = System.Text.Encoding.UTF8;

    byte[] blobIn = enc.GetBytes(s);

    byte[] blobOut = md5.ComputeHash(blobIn);

 

    StringBuilder sbOut = new StringBuilder();

    for(int i = 0; i < blobOut.Length; i++)

    {

        byte b = blobOut[i];

        sbOut.Append(ToHexDigit((b & 240) >> 4));

        sbOut.Append(ToHexDigit(b & 15));

    }

 

    string result = sbOut.ToString();

    return result;

}

 

static char ToHexDigit(int b)

{

    Debug.Assert(b >= 0 && b < 16);

    if (b < 10)

        return (char)('0' + b);

    else

        return (char)('a' + (b - 10));

}

 

def self.calculate_signature(params, private_key)

    param_array = []

    flattened_params = self.flatten_hash(params)

    flattened_params.each{ |k,v| param_array<<"#{k}=#{v}"}

    param_string = param_array.sort{|a,b| a.downcase <=> b.downcase}.join<<private_key

    Digest::MD5.hexdigest(param_string)

end

 

  #Flattens a hash which may have nested hashes.

  def self.flatten_hash(hash, name=nil)

    flat_hash = {}

    hash.each do |k, v|

      if v.is_a?(Hash)

        flat_hash.merge!(flatten_hash(v, k))

      else

        name.nil? ? flat_hash[k] = v : flat_hash["#{name}[#{k}]"] = v       

      end

    end  

    flat_hash

  end