Configuring the C# SDK for the Selling Partner API


#1

Hello all. I’m working with a C# SDK generated by Swagger for the new Selling-Partner API, and I’m having trouble setting up a Configuration object, which continually results in an “Unauthorized” error - no other details, just “Unauthorized”. Amazon gives good documentation for Java, but I have not found anything for C#, and the libraries for the two languages are significantly different - the C# library has no AWSAuthenticationCredentialsProvider type. Below is my current code:

			LWAAuthorizationCredentials authorizationCreds = new LWAAuthorizationCredentials
			{
				ClientId = "...",
				ClientSecret = "...",
				RefreshToken = "...",
				Endpoint = new Uri("https://api.amazon.com/auth/o2/token")
			};
			AWSAuthenticationCredentials authenticationCredentials = new AWSAuthenticationCredentials
			{
				AccessKeyId = "...",
				SecretKey = "...",
				Region = "us-east-1"
			};
			Configuration config = new Configuration();
			config.AuthenticationCredentials = authenticationCredentials;
			config.AuthorizationCredentials = authorizationCreds;

			ReportsApi reportsApi = new ReportsApi(config);
			var response = reportsApi.CreateReport(new CreateReportSpecification(null, "_GET_MERCHANT_LISTINGS_DATA_", DateTime.UtcNow, DateTime.UtcNow.AddDays(-3), new List<string>() { "ATVPDKIKX0DER" }));
			AuthorizationApi authApi = new AuthorizationApi(config);
			Console.WriteLine(response.Payload.ToString());

I have successfully used the LWAAuthorizationCredentials information in cURL, so I know it’s accurate and that we are successfully registered. The only other options seem to be that either my AWSAuthenticationCredentials are incorrect or that I’m missing something.

Below is the default constructor for Configuration, which is the one I am using. I have not been able to find any references to ApiKeys or ApiKeyPrefixes in the documentation.

    public Configuration()
    {
        UserAgent = "Swagger-Codegen/1.0.0/csharp";
        BasePath = "https://sellingpartnerapi-na.amazon.com";
        DefaultHeader = new ConcurrentDictionary<string, string>();
        ApiKey = new ConcurrentDictionary<string, string>();
        ApiKeyPrefix = new ConcurrentDictionary<string, string>();
    }

Has anyone else worked with the C# SDK yet? Any help would be much appreciated! Please let me know if more information is needed.


#2

Did you ever get this figured out? We are struggling as well. I have yet to be able to make the most basic api call with C#. The MWS sdk is fantastic. The SP-API is not well documented at all.


#3

Hi, also interested in this. Bumping the question.
Also, did you guys try to use the “Login With Amazon API” to authenticate with SP-API?


#4

LWA is required with SP-API. I had someone test my code and it worked but I can’t get it to work. I suspect my LWA/IAM credentials are not configured 100% correct but I can’t get anyone at Amazon to help me out. If you would like a copy of my C# code I can provide it. Send me a message here or give me some way to reach out to you. I will throw it up on github or something.


#5

I’d love to see your working code, I’ve tried generated the client library for dotnet 4.5 or .net core and there’s all kinds of issues, for example their mustache template is using some out of maintenance (since 2016) portable fubar library. whats your github?


#6

I also would appreciate a look at the code.


#7

Hi, I’m trying to get this working as well and having trouble. Was just following the test code they have in the project. Has anyone gotten this to work?
Thanks,
Tom


#8

I get the Access Token, but when making an API call, it whines that the signature does not match. I checked, it does, so I think it must be some credentials issue or something, but can’t figure it out. I opened a case w/Amazon, but have not heard back in two weeks.


#9

OH wow so you get the refresh token? So you are past me then. I’m at the point where i’m trying to get refresh token and it says refresh token parameter missing. Here is my code below so far.

        RestClient restClient = new RestClient("https://sellingpartnerapi-na.amazon.com");
        IRestRequest restRequest = new RestRequest("orders/v0/orders", Method.GET);

        AWSAuthenticationCredentials AWSCredentials = new AWSAuthenticationCredentials();
        AWSCredentials.AccessKeyId = “....";
        AWSCredentials.SecretKey = "....";
       AWSCredentials.Region = "us-east-1";
       
        LWAAuthorizationCredentials LWACredentials = new LWAAuthorizationCredentials();
        LWACredentials.ClientId = "amzn1.application-oa2-client…… ";
       LWACredentials.ClientSecret = "……";
        LWACredentials.Endpoint = new Uri("https://api.amazon.com/auth/o2/token");
        LWACredentials.RefreshToken = "";

        restRequest = new AWSSigV4Signer(AWSCredentials).Sign(restRequest, restClient.BaseUrl.Host);

        restRequest = new LWAAuthorizationSigner(LWACredentials).Sign(restRequest);

Can you send me some code sample if possible of yours?

Thanks,
Tom


#10

I was able to get an access token two ways. Both are below.
I just cannot get access to the endpoint.

This uses the APIAA Amazon Library:

String resource = "/fba/inbound/v0/shipments";//?marketplace=ATVPDKIKX0DER";

IRestRequest restRequest = new RestRequest( resource, Method.GET);

JsonParamaters jsonParameters = new JsonParamaters();

restRequest.AddJsonBody(jsonParameters);

// https://github.com/amzn/selling-partner-api-docs/blob/main/guides/developer-guide/SellingPartnerApiDeveloperGuide.md
// Step 4. Create and sign your request


// https://stackoverflow.com/questions/64468573/how-to-fix-issue-calling-amazon-sp-api-which-always-returns-unauthorized-even

restRequest.AddParameter("MarketPlaceIds", "ATVPDKIKX0DER", ParameterType.QueryString);

restRequest.AddParameter("CreatedAfter", DateTime.UtcNow.AddDays(-5), ParameterType.QueryString);

LWAAuthorizationCredentials lwaAuthorizationCredentials = new                 
LWAAuthorizationCredentials {
		ClientId = AmazonLWA_Client,
		ClientSecret = AmazonLWA_Secret,

		RefreshToken = AmazonLWA_RefreshToken),

		Endpoint = new Uri("https://api.amazon.com/auth/o2/token")
	};

restRequest = new LWAAuthorizationSigner(lwaAuthorizationCredentials).Sign(restRequest);


AWSAuthenticationCredentials awsAuthenticationCredentials = new AWSAuthenticationCredentials {
		AccessKeyId = "AKIAT66Q7ZL6GTFTISKE"
		, Region = "us-east-1"
		, SecretKey = "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"
	};


RestClient restClient = new RestClient("https://sellingpartnerapi-na.amazon.com/");


restRequest = new AWSSigV4Signer(awsAuthenticationCredentials).Sign(restRequest, restClient.BaseUrl.Host);


IRestResponse response = restClient.Execute(restRequest);

String json = response.Content;

This is the code that does not use the Selling Partner APIAA library which gets the Access Token

String accessToken = TokenHandler.getAccessToken();

public class TokenRequest
{
    public String refresh_token;
    public String client_id;
    public String client_secret;
    public String grant_type = "refresh_token";
}

public class TokenResponse
{
	public String access_token;
	public String token_type;		// \":\"bearer\",
	public int    expires_in;		// \":3600}"
}



public class TokenHandler
{
private static DateTime expDateTime = DateTime.Now;
    private static TokenResponse accessToken;

    public static string getAccessToken()
    {
        //Validate the return anti-forgery token against the user's session to prevent confused deputy security exploits such as CSRF and XSS

        DateTime now = DateTime.Now;

        // Access Tokens are valid for one hour. Re-use token if not expired

        if (accessToken == null || DateTime.Compare(now, expDateTime) > 0)
        {
            TokenRequest tokenRequest = new TokenRequest()
            {
                refresh_token = AmazonLWA_RefreshToken,
                client_id     = AmazonLWA_Client,
                client_secret = AmazonLWA_Secret,
            };

            RestClient  client   = new RestClient("https://api.amazon.com");

            RestRequest request = new RestRequest("/auth/o2/token");

            request.AddJsonBody(tokenRequest);

            IRestResponse response = client.Post(request);


			String json = response.Content;
			if (response.IsSuccessful) {
                accessToken = JsonConvert.DeserializeObject<TokenResponse>(json);
                expDateTime = DateTime.Now.AddSeconds(accessToken.expires_in - 10); // Subtract 10s as buffer
			}
			else {
                accessToken = new TokenResponse();
                accessToken.access_token = "";
            }
        }
        
        return accessToken.access_token;
    }

`


#11

Hi @NutritionGuy, So are you putting something in RefreshToken? or is it blank when you call " LWAAuthorizationSigner" ?
Thanks,
Tom


#12

The Refresh Token is a value that is issued one time only, which is used to get an Access Token once every hour. The Access Token is what is used to make api calls on a recurring basis.

The refresh token value comes from Seller Central when you self-authorize your app. Or it would come from amazon in the client authorization process.

As documented here:


#13

Yeah I was figuring that as I was writing the reply back that the refreshtoken I can get of the site one time and use that to get another token in the code. I thought thats what it was getting in the code was the refresh token. Anyway Thanks I was able to get to same point you are now. Also, The Json Parameters class your using. Where do you get that from and is that really needed?
Thanks,
Tom


#14

Ok, great, so now we are both stuck at the same point?

The JsonParameters class is just the request body of the call. This varies depending on the api call.

Here is mine:

public class JsonParamaters
{
	public List<String> ShipmentStatusList = new List<string> { "WORKING" } ;	// 		optional 	A list of ShipmentStatus values. Used to select shipments with a current status that matches the status values that you specify. 	< enum (ShipmentStatusList) > array
										/* WORKING 	The shipment was created by the seller, but has not yet shipped.
											SHIPPED 	The shipment was picked up by the carrier.
											RECEIVING 	The shipment has arrived at the fulfillment center, but not all items have been marked as received.
											CANCELLED 	The shipment was cancelled by the seller after the shipment was sent to the fulfillment center.
											DELETED 	The shipment was cancelled by the seller before the shipment was sent to the fulfillment center.
											CLOSED 	The shipment has arrived at the fulfillment center and all items have been marked as received.
											ERROR 	There was an error with the shipment and it was not processed by Amazon.
											IN_TRANSIT 	The carrier has notified the fulfillment center that it is aware of the shipment.
											DELIVERED 	The shipment was delivered by the carrier to the fulfillment center.
											CHECKED_IN 	The shipment was checked-in at the receiving dock of the fulfillment center.
											*/

	public DateTime ShipmentIdList;		// optional 	A list of shipment IDs used to select the shipments that you want. If both ShipmentStatusList and ShipmentIdList are specified, only shipments that match both parameters are returned. 	< string > array
	public DateTime LastUpdatedAfter;	// optional 	A date used for selecting inbound shipments that were last updated after (or at) a specified time. The selection includes updates made by Amazon and by the seller. 	string (date-time)
	public DateTime LastUpdatedBefore;	// optional 	A date used for selecting inbound shipments that were last updated before (or at) a specified time. The selection includes updates made by Amazon and by the seller. 	string (date-time)

	public String QueryType = "SHIPMENT";	/* required 	Indicates whether shipments are returned using shipment information
								 (by providing the ShipmentStatusList or ShipmentIdList parameters),
								 using a date range (by providing the LastUpdatedAfter and LastUpdatedBefore parameters),
								 or by using NextToken to continue returning items specified in a previous request. 	enum (QueryType)
								 
								SHIPMENT 	Returns shipments based on the shipment information provided by the ShipmentStatusList or ShipmentIdList parameters.
								DATE_RANGE 	Returns shipments based on the date range information provided by the LastUpdatedAfter and LastUpdatedBefore parameters.
								NEXT_TOKEN
								*/
	public String NextToken;		// optional 	A string token returned in the response to your previous request. 	string
	public String MarketplaceId = "ATVPDKIKX0DER";
}

#15

Did you try to make an API call? I think my problem is some credentials/configuration issue. I have been hoping that the next step, your API call would work, then that would confirm my suspicions.

Or are we both stuck at the same point?

The JsonParameters class is just the request body of the call.

Yeah, ok, not the best class name.


#16

Hi @NutritionGuy, Yes and getting " access to requested resource is denied". I have read somewhere and I’m trying to locate again, is there is something in the Documentation that is not clear or is missing saying there is a step that supposed to include the IAM Role info in some call or something like that. I have gone back over all the steps and I have everything setup according to the documentation. Not sure if maybe the account gets put in review after setting up? I’ll let you know if I find something or get it working.
Thanks,
Tom


Request review through the API
#18

Hi @NutritionGuy, Yes I do the self authorization to get refresh token and use that. That part seems to work. So here is some links below that talk about my issue, which I think is the issue. Also, if you look at the java library. it has one more call for credentials which is “AWSAuthenticationCredentialsProvider”. There is no equivalent in C# library? And this is what that link below, all those people have this issue.

import com.amazon.SellingPartnerAPIAA.AWSAuthenticationCredentials;

AWSAuthenticationCredentialsProvider awsAuthenticationCredentialsProvider=AWSAuthenticationCredentialsProvider.builder()
 .roleArn("myroleARN")
 .roleSessionName("myrolesessioname")
 .build();

#19

I am using the project I found on GitHub which is basically a small C# library. It only does the authentication. It is called: “Amazon.SellingPartnerAPIAA”. I got to it through the docs.
I think this is only good for authentication, which isn’t working.

I am fine w/that, I can go through the docs and use RestSharp to build each call, but I just wish they would respond to my case which has been open for three weeks. For a company of their size, you would think they could find someone to do that.

I have tried access with two different apps using different roles, still not working.

It’s good to know others are having the same problem.

Reading through the thread now.


#20

@NutritionGuy, So I finally got it working. Although it is returning no orders for me which is not correct. But I’m getting back OK response with content, just nothing in there. Here is the code below. I had to NuGet a couple AWS librarys. The runtime AWS library and also the AWS Security Token library. So I take the credentials that I get back from assumeRole and I use those for the aws credentials. and also set a new header security token with assumeRole sessiontoken.

‘’’
AssumeRoleResponse assumeRoleResponse = null;
DateTime checkDate = DateTime.UtcNow.AddDays(-5);

        Task.Run(async () =>
        {
            assumeRoleResponse = await GetAssumeRoleTokenDetail();
        }).GetAwaiter().GetResult();

        RestClient restClient = new RestClient("https://sellingpartnerapi-na.amazon.com");
        IRestRequest restRequest = new RestRequest("orders/v0/orders", Method.GET);

        restRequest.AddQueryParameter("MarketplaceIds", "ATVPDKIKX0DER");
        restRequest.AddQueryParameter("CreatedAfter", checkDate.ToString("yyyy-MM-ddTHH:mm:ssZ"));


        AWSAuthenticationCredentials AWSCredentials = new AWSAuthenticationCredentials();
        AWSCredentials.AccessKeyId = assumeRoleResponse.Credentials.AccessKeyId;
        AWSCredentials.SecretKey = assumeRoleResponse.Credentials.SecretAccessKey;
        AWSCredentials.Region = "us-east-1";

        LWAAuthorizationCredentials LWACredentials = new LWAAuthorizationCredentials();
        LWACredentials.ClientId = "LWA credentials from developer area";
        LWACredentials.ClientSecret = "....LWA credentials from developer area";
        LWACredentials.Endpoint = new Uri("https://api.amazon.com/auth/o2/token");
        LWACredentials.RefreshToken = ".....from self authenticate - generating refresh token";

        restRequest = new LWAAuthorizationSigner(LWACredentials).Sign(restRequest);

        restRequest.AddHeader("X-Amz-Security-Token", assumeRoleResponse.Credentials.SessionToken);

        restRequest = new AWSSigV4Signer(AWSCredentials).Sign(restRequest, restClient.BaseUrl.Host);

        IRestResponse response = restClient.Execute(restRequest);

        String json = response.Content; 

‘’’


#21

Ok, that’s great! Give me some time to try it out.
In their library, in Utils.cs, Hash(), I found that I needed to add the following line
to get the the correct result, but still not in.
data = data.Replace("\r", “”).Trim();

I will look at your code above.