Using curl with the Amazon Product Request API

Motivation

Amazon provides a handy Product Advertising API that allows you to fetch the price, reviews, description, and image (among other things) for products. There are several examples on how to use the API for Ruby, PHP, Python, and Java. But sometimes it's useful to use curl from the shell.

Amazon provides a guide and a few examples for the Product Advertising API. It is easy enough to follow until step 8.

8. Calculate an RFC 2104-compliant HMAC with the SHA256 hash algorithm using the string above with this example AWS secret key: 1234567890. For more information about this step, see documentation and code samples for your programming language.

It takes jumping through some hoops in order to calculate the RFC 2104-compliant HMAC with the SHA256 hash algorithm with Unix utilities.

Creating the Request

There are several types of requests exposed by the API, allowing searching for items, cart manipulation, or requesting info about particular items. The Amazon documentation uses the following request, which will be used throughout this post:

http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&Operation=ItemLookup&ItemId=0679722769&ResponseGroup=ItemAttributes,Offers,Images,Reviews&Version=2013-08-01

It should be easy enough to see that the request is an ItemLookup for the item with ASIN 0679722769 (Ulysses by James Joyce) and should return ItemAttributes, Offers, Images, and Reviews. There are a plethora of ResponseGroups available to request.

We'll store the request arguments as ARGS

ARGS="Service=AWSECommerceService&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE&AssociateTag=mytag20&Operation=ItemLookup&ItemId=0679722769&ResponseGroup=ItemAttributes,Offers,Images,Reviews&Version=2013-08-01"
Adding the timestamp

The current UTC timestamp needs added to the arguments list, which is easy enough to do with the date command.

TIMESTAMP=$(date -u "+%Y-%m-%dT%H:%M:%SZ")
ARGS="$ARGS&Timestamp=$TIMESTAMP"
URL Encoding ',' and ':'

The commas and colons can be substituted for their URL encoded counterparts with sed.

ARGS=$(echo $ARGS | sed 's/:/%3A/g;s/,/%2C/g')
Line break the argument pairs

The ampersands in the argument string can be "translated" to newlines with tr.

ARGS=$(echo $ARGS | tr '&' '\n')
Sorting the argument pairs

By default, sort sorts alphabetically. However, the sorting needs to happen in byte order, that is all uppercase parameters come before lowercase parameters. By setting LC_ALL=C sort will sort in byte order. Note that $ARGS is enclosed in double quotes so the newlines are interpreted properly.

ARGS=$(echo "$ARGS" | LC_ALL=C sort)
Rejoin with Ampersands

The newlines can be translated back to ampersands with tr. echo is setup to not print a trailing newline, which would be replaced with an ampersand.

ARGS=$(echo -n "$ARGS" | tr '\n' '&')
Create string to sign

The string to sign is made by appending the type of HTTP request, the target URL, and the REST endpoint.

STRING=$'GET\nwebservices.amazon.com\n/onca/xml\n'$ARGS
Create signature

The infamous Step 8. The request string needs to be signed with a AWS secret key. The command line utility openssl is capable of computing the HMAC with the SHA256 algorithm. The output from openssl then needs base64 encoded. Again, echo must be configure to not print a trailing newline.

SIGNATURE=$(echo -n "$STRING" | openssl dgst -sha256 -hmac "$SECRET_KEY" -binary | openssl enc -base64)
URL encode the signature

Base64 encoding allows '+' and '=' which need URL encoded.

SIGNATURE=$(echo $SIGNATURE | sed 's/+/%2B/g;s/=/%3D/g;')
Create Request URL

The API URL, arguments, and signature need combined into one URL.

REQUEST="http://webservices.amazon.com/onca/xml?$ARGS&Signature=$SIGNATURE"
Make curl post
curl -s $REQUEST

If successful, the API will return XML.

<?xml version="1.0" ?>
<ItemLookupResponse
	xmlns="http://webservices.amazon.com/AWSECommerceService/2013-08-01">
	<OperationRequest>
...
		<Item>
			<ASIN>0679722769</ASIN>
			<DetailPageURL>https://www.amazon.com/Ulysses-James-Joyce/dp/0679722769%3FSubscriptionId%3DAKIAJYK4ZM7MKHMVJ2RA%26tag%3Dfrdmtoplay-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D0679722769</DetailPageURL>
			<ItemLinks>
				<ItemLink>
					<Description>Technical Details</Description>
...
		</Item>
	</Items>
</ItemLookupResponse>
Parsing the output

The Product Advertising API returns XML which can be parsed in bash with a few tricks. The first is going to be a user defined function read_xml() which reads tags into $ENTITY and the corresponding content into $CONTENT

read_xml () {
    local IFS=\>
    read -d \< ENTITY CONTENT
}

Used in a while loop, read_xml can be used to search for specific XML entities, for example the PageURL.

while read_xml; do
	if [[ $ENTITY = "DetailPageURL" ]]; then
		URL=$CONTENT
	fi
done < curl -s $REQUEST
echo $URL

The complete bash script:

# User Defined Parameters
AFFILIATE_TAG=mytag-20
ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
ASIN=0679722769
SECRET_KEY=12345678

# Request arguments
ARGS="Service=AWSECommerceService&AWSAccessKeyId=$ACCESS_KEY&AssociateTag=$AFFILIATE_TAG&Operation=ItemLookup&ItemId=$ASIN&ResponseGroup=ItemAttributes,Offers,Images,Reviews&Version=2013-08-01"  

# Append Timestamp
TIMESTAMP=$(date -u "+%Y-%m-%dT%H:%M:%SZ")  
ARGS="$ARGS&Timestamp=$TIMESTAMP"  

# URL Encode
ARGS=$(echo $ARGS | sed 's/:/%3A/g;s/,/%2C/g')  

# Create line broken list of argument pairs
ARGS=$(echo $ARGS | tr '&' '\n')

# Sort arguments in ABCabc order
ARGS=$(echo "$ARGS" | LC_ALL=C sort)  

# Rejoin the arguments into one line
ARGS=$(echo -n "$ARGS" | tr '\n' '&')

# Build request string
STRING=$'GET\nwebservices.amazon.com\n/onca/xml\n'$ARGS

# Create signature
SIGNATURE=$(echo -n "$STRING" | openssl dgst -sha256 -hmac "$SECRET_KEY" -binary | openssl enc -base64)  

# URL Encode signature
SIGNATURE=$(echo $SIGNATURE | sed 's/+/%2B/g;s/=/%3D/g;')  

# Create Request
REQUEST="http://webservices.amazon.com/onca/xml?$ARGS&Signature=$SIGNATURE"

# Make request
curl -s $REQUEST  

Was this information useful? Are you going to buy something from Amazon in the next 24-hours? Please consider using my Amazon Affilliate URL as a way of saying thanks. Or, feel free to donate BTC (1DNwgPQMfoWZqnH78yt6cu4WukJa3h8P1f) or ETH (0xf3c4a78c24D34E111f272Ac2AC72b1f01ba52DF3).