I have an Etsy listing with 1 product in draft
state. When I try to update the inventory of this listing through the updateInventory
API endpoint (docs: https://www.etsy.com/developers/documentation/reference/listing#method_updatelisting) I get errors.
I followed the example in 'Updating a Listing with one product' section to try to update the price, quantity, and SKU with no luck: https://www.etsy.com/developers/documentation/getting_started/inventory
I get either an oauth signature_invalid
error, or an expected products property to be present
error.
Finally, I was able to make a request that returned a 200 OK. But no data in the listing was updated. Here is that request (I added new-lines in the body for readability, the original request has no spaces or new-lines in the body):
POST https://openapi.etsy.com/v2/listings/808984070/inventory?method=PUT HTTP/1.1
Host: openapi.etsy.com
Authorization: OAuth oauth_consumer_key="xxxxxxxxx",oauth_nonce="xxxxxxxxx",oauth_signature="xxxxxxxxx",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1591886701",oauth_token="xxxxxxxxx",oauth_version="1.0"
Accept: application/json, text/json, text/x-json, text/javascript, application/xml, text/xml
User-Agent: RestSharp/106.11.4.0
Connection: Keep-Alive
Request-Id: |38197ee2-416cf452906ba6a9.4.
Accept-Encoding: gzip, deflate
Content-Type: application/json
Content-Length: 192
{
"products": [{
"product_id": 4415387567,
"sku": "TEST1",
"property_values": [],
"offerings":[{
"offering_id": 4627746384,
"price": "50.00",
"quantity":100,
"is_enabled":1,
"is_deleted":0
}],
"is_deleted":0
}]
}
Why is Etsy not updating the inventory, even though it returns a 200 OK? More specifically, what should change in this request to get Etsy to update the inventory data?
P.S. Some of the things I tried before arriving at the above request:
signature_invalid
error.x-www-form-urlencoded
formatting for the body by setting the Content-Type
header to that value, and setting the body to products=<url-encoded string of JSON representation of products array>
. Got a signature_invalid
error.The issue stemmed from my misunderstanding how to use the C# library that I was using (RestSharp). After some tweaks, I was able to generate a request that works. Here is what it looks like:
PUT https://openapi.etsy.com/v2/listings/834145978/inventory HTTP/1.1
Host: openapi.etsy.com
Authorization: OAuth oauth_consumer_key="xxxxx",oauth_nonce="xxxxx",oauth_signature="xxxxx",oauth_signature_method="HMAC-SHA1",oauth_timestamp="xxxxx",oauth_token="xxxxx",oauth_version="1.0"
Accept: application/json, text/json, text/x-json, text/javascript, application/xml, text/xml
User-Agent: RestSharp/106.11.4.0
Connection: Keep-Alive
Request-Id: |feb37503-4e0cc9e4a34b242a.4.
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 654
products=%5B%0D%0A%20%20%7B%0D%0A%20%20%20%20%22product_id%22%3A%204638831553%2C%0D%0A%20%20%20%20%22sku%22%3A%20%22N27%22%2C%0D%0A%20%20%20%20%22property_values%22%3A%20%5B%5D%2C%0D%0A%20%20%20%20%22offerings%22%3A%20%5B%0D%0A%20%20%20%20%20%20%7B%0D%0A%20%20%20%20%20%20%20%20%22offering_id%22%3A%204740581353%2C%0D%0A%20%20%20%20%20%20%20%20%22price%22%3A%20%2265.00%22%2C%0D%0A%20%20%20%20%20%20%20%20%22quantity%22%3A%201%2C%0D%0A%20%20%20%20%20%20%20%20%22is_enabled%22%3A%201%2C%0D%0A%20%20%20%20%20%20%20%20%22is_deleted%22%3A%200%0D%0A%20%20%20%20%20%20%7D%0D%0A%20%20%20%20%5D%2C%0D%0A%20%20%20%20%22is_deleted%22%3A%200%0D%0A%20%20%7D%0D%0A%5D
The parts to note are:
PUT
Content-Type
header is application/x-www-form-urlencoded
products=[
{
"product_id": 4638831553,
"sku": "N27",
"property_values": [],
"offerings": [
{
"offering_id": 4740581353,
"price": "65.00",
"quantity": 1,
"is_enabled": 1,
"is_deleted": 0
}
],
"is_deleted": 0
}
]
Not exactly related, but here is the code I use to create a new listing in Etsy, using C# and RestSharp. This is a work-in-progress and needs cleanup, but it works:
public async Task<ListingModel> PostListing(ListingCreateModel createModel)
{
// Create authenticator for the URL we will need to use.
_restClient.Authenticator = MakeAuthenticatorForProtectedResource();
// Update the create model, making sure we set the ShippingProfileId - this way
// you won't need to set it manually in every listing afterwards.
createModel.Quantity = createModel.Quantity;
createModel.ShippingProfileId = ShippingProfileId;
// Create a basic listing first, with minimal properties.
var basicListing = ExecuteAndDeserialize<ListingListModel>(
CreatePostRequest("listings", createModel)).Results.First();
// Get the current products for this listing (we'll need to modify that object
// and send it back to Etsy.)
var productList = ExecuteAndDeserialize<dynamic>(
CreateGetRequest($"listings/{basicListing.ListingId}/inventory"));
// set SKU, price, and quantity (just experimenting, you can set any
// supported properties you need here)
productList.results.products[0].sku = createModel.Sku;
productList.results.products[0].offerings[0].price =
createModel.Price.ToString("0.00");
productList.results.products[0].offerings[0].quantity =
createModel.Quantity;
// Now that we have an updated products list, call the PUT endpoint to
// update the listing.
// The CreatePutRequest method sets the 'Content-Type' header to
// 'application/x-www-form-urlencoded'.
// RestSharp will take care of serializing the `new { productList.results.products }`
// object to the form-urlencoded format.
var updatedListing = ExecuteAndDeserialize<dynamic>(
await CreatePutRequest($"listings/{basicListing.ListingId}/inventory",
new
{
productList.results.products
}));
var newListingList = ExecuteAndDeserialize<ListingListModel>(
CreateGetRequest($"listings/{basicListing.ListingId}"));
return newListingList.Results.First();
}
private async Task<IRestRequest> CreatePutRequest<TBody>(string resource, TBody body)
{
var request = new RestRequest(resource)
{
Method = Method.PUT
};
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddObject(body);
return request;
}
//-------- NOT SO RELEVANT TO THE QUESTION ----------
private IRestRequest CreateGetRequest(string resource)
{
var request = new RestRequest(resource)
{
Method = Method.GET
};
return request;
}
private OAuth1Authenticator MakeAuthenticatorForProtectedResource()
{
var auth = OAuth1Authenticator
.ForProtectedResource(_authRepo.AuthInfo.AppKey, _authRepo.AuthInfo.SharedSecret,
_authRepo.AuthInfo.AccessToken, _authRepo.AuthInfo.AccessTokenSecret);
return auth;
}
private T ExecuteAndDeserialize<T>(IRestRequest req)
{
var response = _restClient.Execute(req);
if (response.IsSuccessful)
{
return JsonConvert.DeserializeObject<T>(response.Content);
}
if ((int) response.StatusCode >= 400 && (int) response.StatusCode < 500)
{
throw new ValidationException(response.Content);
}
throw new InvalidOperationException(response.Content);
}