3

We use the Amazon Product Advertising API to do our repricing on their site. For those who don't know it basically lets you interogate the Amazon database without having to do time consuming operations like scraping web pages. It works very well for us, or has. They are now disbanding this API for sellers and moving us to a new API over at their MWS service.

The calls are subtly different as far as I can see. The two obvious differences are that the PA API uses HTTP and GET whereas the MWS is using HTTPS and POST.

I am not entirely sure the examples in the Amazon documentation are correct as they give examples of creating as hash signed URL request. That is the way we used to successfully do it on the PA-API where the GET method was used. But can you do that for POST? I don't think that is possible, although I may be wrong and that is certainly what the examples imply.

Anyway, creating a signed URL doesn't work! So I decided to look along the lines of using some kind of lower level HTTPS POST in Delphi.

I have searched on here and there are several different examples, but I cannot get any of them to work correctly. Some of the examples seem to use INDY 10, unfortunately we are stuck (for compatibility reasons) on INDY 9. I have also looked at the WININET wrapper type functions but I just cannot get a result back other than an exception or errors.

So that is why I am here asking for some help.

How can I make the correctly formatted call to Amazon MWS using Delphi? I have tried examples from pages like:

How to make an HTTPS POST request in Delphi?

etc etc all the ones I could find!

But I get errors back like 'Bad request' (exception).

I have tried using Fiddler (as suggested elsewhere) to look at what is happening but can't quite get my head around it yet (although I can compose a call in it that works!)

So I'm looking for some pointers or some direction on this. I don't really want to go upgrading INDY or adding new libraries. I would just prefer to keep things as they are and use what I have available. We are using D2007.

To give an idea what is required, I need to make this kind of call (the GetServiceStatus being the most simple):

POST /Products/2011-10-01?AWSAccessKeyId=<ACCESSKEY>
  &Action=GetServiceStatus
  &SellerId=<SELLERID>
  &SignatureVersion=2
  &Timestamp=2012-02-14T13%3A26%3A42Z
  &Version=2011-10-01
  &Signature=dtAvv595blmv%2FnV0h2Yr5bCGzKYXid0hkOuCmZOb3bc%3D
  &SignatureMethod=HmacSHA256

To this endpoint:

https://mws.amazonservices.co.uk/Products/2011-10-01

I think my problem is in all the examples I have tried I don't know how to set up the call correctly, which is probably why I get the Bad Request errors.

So, a nice simple solution would be most appreciated!


Documentation links and observations:

This is the developer guide for the API:

https://images-na.ssl-images-amazon.com/images/G/02/mwsportal/doc/en_US/bde/MWSDeveloperGuide._V161846143_.pdf

This is the particular part of the API we will be using (that replaces the current product advertising API):

https://images-na.ssl-images-amazon.com/images/G/02/mwsportal/doc/en_US/bde/MWSDeveloperGuide._V161846143_.pdf

The most basic of functions here is the GetServiceStatus function. It takes no parameters. But, it still needs to be authenticated and 'signed' using credentials from Amazon (Seller ID, MWS access key and secret key (for generating a signature). My feeling is that if I can get the most simplest of functions to work then the rest will follow. But the problem of authentification is what makes finding a solution so difficult. There's no test account. And also the timestamp (and therefore the signature for the call) expires and a few minutes.

There is also a 'migration guide':

https://images-na.ssl-images-amazon.com/images/G/02/mwsportal/doc/en_US/products/MWSProductsApiMigrationGuide._V140058392_.pdf

But this document does contain some errors. For example, the end points are incorrect.


My latest code inc ssl, cookie manager etc:

var
  LHTTP                : TIdHTTP;
  IdSSLIOHandlerSocket : TIdSSLIOHandlerSocket;
  IdCookieManager      : TIdCookieManager;
  LParams              : TStringList;
  LResponse            : string;

begin
  IdCookieManager:=TIdCookieManager.Create(self);
  IdSSLIOHandlerSocket:=TIdSSLIOHandlerSocket.Create(self);
  with IdSSLIOHandlerSocket do begin
    SSLOptions.Method := sslvSSLv3;
  end;

  LHTTP := TIdHTTP.Create(Self);
  with LHTTP do begin
    CookieManager:=IdCookieManager;
    AllowCookies:=true;
    IOHandler:=IdSSLIOHandlerSocket;
    Request.ContentType:='text/xml';
    Port:=443;
    HandleRedirects:=true;
    Host:='mws.amazonservices.co.uk';
    ProtocolVersion:=pv1_1
  end;


  LParams := TStringList.Create;
  try
    LParams.Add('AWSAccessKeyId=<ACCESSKEY>');
    LParams.Add('Action=GetServiceStatus');
    LParams.Add('SellerId=<SELLERID>)');
    LParams.Add('SignatureVersion=2');
    LParams.Add('Timestamp=2012-02-15T13%3A00%3A07Z');
    LParams.Add('Version=2011-10-01');
    LParams.Add('Signature=viPlDAbzEBwlTAwq4hNaZi%2Fa1Klf7qIXIP%2BKUsOcJTI%3D');
    LParams.Add('SignatureMethod=HmacSHA256');
    LResponse:=LHTTP.Post('https://mws.amazonservices.co.uk/Products/2011-10-01?', LParams);
    ShowMessage( LResponse );
  except
    on E: Exception do
      ShowMessage('ouch! ' + E.Message );
  end;
  LHTTP.Free;
  IdSSLIOHandlerSocket.Free;
  IdCookieManager.Free;
end;

This is based on what Amazon are expecting to receive as per their scratchpad. The user agent at the end is optional and not required and is not part of the signing process:

POST /Products/2011-10-01?AWSAccessKeyId=<ACCESSID>
  &Action=GetServiceStatus
  &SellerId=<SELLERID>
  &SignatureVersion=2
  &Timestamp=2012-02-15T13%3A00%3A07Z
  &Version=2011-10-01
  &Signature=viPlDAbzEBwlTAwq4hNaZi%2Fa1Klf7qIXIP%2BKUsOcJTI%3D
  &SignatureMethod=HmacSHA256 HTTP/1.1
Host: mws.amazonservices.co.uk
x-amazon-user-agent: AmazonJavascriptScratchpad/1.0 (Language=Javascript)
Content-Type: text/xml

I have tried to get the information from Fiddler, but it is not showing any traffic from the software but it must be communicating with Amazon to get the '400 bad request' error. Odd.

Community
  • 1
  • 1
Trevor
  • 63
  • 2
  • 8
  • You should probably link to the documentation for the service you're trying to use and select a very specific kind of query that you'd like to implement. Make it as easy as possible for someone to help, because other ways you're just waiting for someone that's both a Delphi developer AND an Amazon seller. If you put in enough work I (and others) might give this a try even if we're not Amazon sellers: for example, is there *any* query that can be made without being a seller? Does Amazon provide test accounts that we can use? – Cosmin Prund Feb 14 '12 at 20:24
  • Hi Cosmin. Thanks for comment. Of course, you are 100% correct. My brain has been like putty trying to sort this out & I realise now the documentation links would help. I have the links at work. When I can post again (seems I have a limit on posts being a newbie) I'll post up the links. Sadly their are no test accounts. And the signing process is very secure using seller id, mws access id & secret key to sign the call. So I appreciate this makes the whole process of getting to the bottom of this much harder. Add in a timestamp & it becomes harder still. But I will post the doc links tomorrow. – Trevor Feb 14 '12 at 23:51
  • You're not supposed to "post again", you're supposed to edit the current question and add the relevant details. You have an `edit` link under the tag list for your question. That's how this site works: it's not a forum for discussions, it's a Q&A site. – Cosmin Prund Feb 15 '12 at 07:11
  • Cosmin, my apologies. I did wonder as it did say I was answering my own question when I initially tried, which I thought a little strange. I assume I edit Dorins answer with the code that I created from his example? I will do that this time and append the code that I now have to his reply. Please advice if this is not the way to do it. I am sorry again, like I said I am new here and still getting a feel for the way it works. But thanks for the heads up on how things work on here - last thing I want to do is tread on anyones toes when I am looking for help. Thanks. – Trevor Feb 15 '12 at 09:07

3 Answers3

4

I would go with TIdHTTP, set it up using your credentials(if applies) in the HTTPOptions and then do:

procedure ...
var
  LHTTP: TIdHTTP;
  LParams: TStringList;
  LResponse: string;
begin
  LHTTP := TIdHTTTP.Create;
  LParams := TStringList.Create;
  try
    // setup params, basically you're doing key-value pairs that will be encoded in the post
    // as KEY1=VALUE1&key2=value2&KEY3=value3, etc.
    // in the URL, stuff after ? are parameters
    // you don't have to worry about encoding parameters, indy will do it for you
    LParams['AWSAccessKeyId'] := '<ACCESSKEY>';
    LParams['Action'] := 'GetServiceStatus';
    LParams['SellerId'] := '<SELLERID>'
    LParams['SignatureVersion'] := '2';
    // adjust timestamp
    LParams['Timestamp'] := '2012-02-14T13%3A26%3A42Z';
    LParams['Version'] := '2011-10-01'
    // adjust signature...
    LParams['Signature'] := 'dtAvv595blmv%2FnV0h2Yr5bCGzKYXid0hkOuCmZOb3bc%3D';
    LParams['SignatureMethod'] := 'HmacSHA256';
    LResponse := LHTTP.Post('https://mws.amazonservices.co.uk/Products/2011-10-01', LParams);
    ShowMessage( LResponse );
  except
    on E: Exception do
      ShowMessage('ouch! ' + E.Message );
  end;
end;

I think you need to replace "/Products/2011-10-01" with the date you're search, i.e. "/Products/2012-02-14" and so on...

Also, the SSL libs are required if I'm not mistaken...

  • Hi Dorin. Thanks for the reply. I've used your code, adapted it a bit for ssl calls. But still get 'bad request'. I was going to post the new code but I think there's a restriction on how quickly I can post again. The code is on my work laptop, I'll post it first thing when I get to work in the morning. Thanks, Trevor. – Trevor Feb 14 '12 at 23:37
  • damn, well... back to api documentation... have to tried to enable redirects ? –  Feb 15 '12 at 06:34
  • Dorin, I've added my code to your answer and the links to the documentation I have added to my original question. I have gone through the documentation again and I know the parameters and signature are correct as they work in scratchpad and also when I use Fiddler to compose the call. So it has to be something to do with the way I am creating the call from Delphi. – Trevor Feb 15 '12 at 09:31
  • here are a couple of things to test: enable AllowCookies, assign a cookiemanager(odIdCookieManagerNewCooke, set VAccept to true), enable HandleRedirects. Can you post the traffic that is generated using Fiddler? don't forget to hide sensitive information. –  Feb 15 '12 at 10:03
1

Just to let you know, I shared your Pain with this problem, eventually went to the GET format, finally read the manual

Sort the UTF-8 query string components by parameter name with natural byte ordering. The parameters can come from the GET URI or from the POST body (when Content-Type is application/x-www-form-urlencoded).

If you set content type as above in the header, it will work

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
Lowplex
  • 11
  • 1
1

Just to let everyone know I gave up trying to get the POST solution for this working - I was getting too frustrated with not being able to get any closer to a result and wasting too much programming time.

Instead, I went right back to basics and I have changed the string to sign to a GET instead of a POST and then created a parameterised URL which I then call to get the XML result.

Maybe not the best solution technically or aesthetically, but, hey, it works!

But thanks for eveyone's help and advice, appreciated.

Trevor
  • 63
  • 2
  • 8