Search code examples
etsy

Etsy API updateInventory responds with 200 OK, but doesn't update inventory data


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:

  • Tried sending a PUT request without a "?method=PUT" in the URL. Got a signature_invalid error.
  • Tried using 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.
  • Tried with and without a SKU - I get back a 200 OK, but SKU doesn't get set.

Solution

  • 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:

    • the request method is PUT
    • the Content-Type header is application/x-www-form-urlencoded
    • here is what the request body looks like before being url-encoded for the request:
    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);
    }