Location Availability is changing with Inventory Changes

Hello, I am using Square SDK and posting inventory changes for products by location. I grab the inventory at location A OR Location B, then when I do a transaction on my back office system, I use the tool to reduce inventory at a location, or if I refund something, I will then add inventory at the location. I’ve got six locations.

My problem is I’m running into the situation where after I post an update to an inventory at 1 location, it changes the LOCATION AVAILABILITY OF A PRODUCT. I want to IGNORE the product availability at a location. I do not want it to change. But every time I make an inventory change at a location, the AVILABILITY is also changes. API is working fine where I am posting items, and quantity to go up or down. After a transaction is completed, I’m finding myself with a location availability challenge.

Here is what we are doing. Can you tell me what is missing here?

$client = new SquareClient([
‘accessToken’ => OUR_API_TOKEN
‘environment’ => Environment::PRODUCTION,
]);

		$inventoryApi = $client->getInventoryApi();			

		$body = new BatchChangeInventoryRequest;
		$body->setIdempotencyKey(uniqid('MCCWC-'));
		$body_changes = [];

		$body_changes[0] = new InventoryChange;
		$body_changes[0]->setType(InventoryChangeType::ADJUSTMENT);
		$body_changes[0]->setAdjustment(new InventoryAdjustment);
		$body_changes[0]->getAdjustment()->setCatalogObjectId( $item_variation_id );
		$body_changes[0]->getAdjustment()->setFromState( $from_state );
		$body_changes[0]->getAdjustment()->setToState( $to_state );
		$body_changes[0]->getAdjustment()->setLocationId( $location_id );
		$body_changes[0]->getAdjustment()->setQuantity( $qty );
		$body_changes[0]->getAdjustment()->setOccurredAt(date('c'));

		$body->setChanges($body_changes);

		$body->setIgnoreUnchangedCounts(true);

		$apiResponse = $inventoryApi->batchChangeInventory($body);

		if ($apiResponse->isSuccess()) {
		    $res = $apiResponse->getResult();
		} else {
		    $res = $apiResponse->getErrors();
		}

STATE CHANGE VARIABLES for above code :
To mark qty as sold:

from : InventoryState::IN_STOCK
to: InventoryState::SOLD

To re-stock qty back to the respective location from where we took off (marked as sold) :
from: InventoryState::UNLINKED_RETURN
to: InventoryState::IN_STOCK

:wave: I’m Square Dev AI, here to help answer your question!
Based on the information you’ve provided, the following documentation will help answer your question:

Additional Documentation

Inventory API
Enable Stock Conversion
Locations API

If this documentation helped you find a solution, please check the box indicating that this reply solves the problem.
Otherwise one of our Developer Advocates will provide additional assistance shortly.

The issue you’re experiencing is likely due to the fact that Square’s inventory system is designed to automatically adjust the availability of an item at a location based on the inventory count. When the inventory count for an item at a location reaches zero, the item is marked as sold out at that location, which could be affecting the availability of the item.

However, you can control the visibility of an item at a location using the present_at_location_ids and absent_at_location_ids fields of the CatalogItem object. By setting these fields, you can manually control which locations an item is available at, regardless of the inventory count.

In your case, after making an inventory change, you could make an additional API call to update the CatalogItem object for the item, setting the present_at_location_ids field to ensure the item remains available at the desired locations.

Remember that any changes to the CatalogItem object should be made using the Catalog API’s UpsertCatalogObject endpoint. Also, keep in mind that these changes will affect the visibility of the item at the specified locations across all Square applications, including the Square Dashboard and Point of Sale app.

This answer was reviewed by @Bryan-Square.

But this is not the case that is happening here. As the moment we add location again, we get the inventory count of that location.

For eg: We have a item available at all locations and after some time or few hours we see that the same item is now not available at many locations, where as during this period we made several API requests for inventory change (InventoryState::IN_STOCK - InventoryState::SOLD or InventoryState::UNLINKED_RETURN - InventoryState::IN_STOCK).

Now, when we see this item and it is not available at many locations (randomly) and we again make it availabe at all locations, it also increases stock qty as per locations, where as according to you it should show us ZERO stock fro this particular location as because of that it made it unavailable.

Here, I do understand that an additional API request will force it to be available at all locations, but this is not the solution as doing so will be performance concerning. Like making 2 API request for each order.

Also, the logic is still not clear.

Please help.

Thanks!

Is the item being updated by the seller that’s making the item no longer available for the location. Also you can listen to catalog.updated webhook events to listen for when item have been updated. :slightly_smiling_face:

Actually the reason is not clear like what is making it unavailable at locations and to what location, How and when ?
It is not clear and I am not able to regenerate the issue.
Yes we can try integrating webhooks and do appropriate changes as needed.
I want to regenerate the issue.
May be someone from SQUARE TECH team can look into our account and see logs or something to check what exactly making these changes.

Do you have an item_id that changed and an approximate time frame? We would need to narrow this down to look at the logs. Also there are a lot of places where the availability of an item can be updated. :slightly_smiling_face:

Sure SKU : 736530891930
This is one Item for your reference there are many others as well.
Time frame is not very sure but it was around 21st Nov 2024 may be.
Please check any help would be helpful here :slight_smile:

Hi @smthur ,
Stepping in for Bryan here.

I’d need one more thing: The related merchant Square account’s merchant id or location id.
Thanks for the SKU (I could try to start investigating with this and a location id).

Optionally: If I could get the Catalog id instead of the SKU. What is the API catalog id of one of these items where the qty randomly reduced hours later? If you’re in the Seller Dashboard, if you click into the item, if you could give me the URL will have the ID I’m looking for.

I want to update only present_at_all_locations of item. But in reference it is doing a lot
Update Catalog Objects.
Kindly suggest.

Also, could there be problem with below code as here I set locations to get Inventory counts ?

		$body = new BatchRetrieveInventoryCountsRequest();
		$body->setCatalogObjectIds( $ITEM_VARIATION_IDs );
		$body->setLocationIds( SELF::$WAREHOUSE_LOCATION );

		$inventoryApi = $client->getInventoryApi();
		$apiResponse = $inventoryApi->batchRetrieveInventoryCounts($body);

Sure, I will share details you want. But is it not too much of information here, which is public ?

Also, I am trying to use API to update Item for Location only but it seems it also reqyires me to update price as well ?

Below is the code for your reference :

          $price_money = new \Square\Models\Money();
          $price_money->setAmount(76);
          $price_money->setCurrency('USD');
          
          $item_variation_data = new \Square\Models\CatalogItemVariation();
          $item_variation_data->setItemId('#ID_STRING');
          $item_variation_data->setPricingType('FIXED_PRICING');
          $item_variation_data->setPriceMoney($price_money);
          
          $object = new \Square\Models\CatalogObject('ID_STRING');
          $object->setType('ITEM_VARIATION');
          $object->setVersion(VERSION_STRING);
          $object->setPresentAtAllLocations(true);
          $object->setItemVariationData($item_variation_data);
          
          $body = new \Square\Models\UpsertCatalogObjectRequest('RANDOM UNIQUE STRING', $object);
          
          $api_response = $client->getCatalogApi()->upsertCatalogObject($body);
          
          if ($api_response->isSuccess()) {
              $result = $api_response->getResult();
          } else {
              $errors = $api_response->getErrors();
          }

Please suggest!

Sure, I will share details you want. But is it not too much of information here, which is public ?

Application id, location ids, and merchant ids are safe to share publicly. What should never be shared is the access token. But if you’d like, we can continue the conversation over email. You can reach the Developer Success Engineers through the Support dropdown options in the Square Documentation. To start an email, you can choose “Contact Support”. Ask for Lance and link this forum discussion.

I want to update only present_at_all_locations of item.
…Also, I am trying to use API to update Item for Location only but it seems it also requires me to update price as well ?

The Catalog API’s Upsert is a bit difficult to work with. I know you’ve referred to this throughout this discussion, so sorry if I’m missing your point. If I misunderstanding, looking at the history of an example catalog id will help me later.

The general trick for catalog upsert is to just RetrieveCatalogObject right before the upsert. Just remove any read only fields, like timestamps, and just change what you’re looking to change.

Can you clarify what exactly you’re trying to do?

  • What is present_at_all_locations currently set to.
  • What would you like it to be? Where is the item present or absent from?

It is a bit complicated, since if this field changes, another field like present_at_location_ids might need to be adjusted to then clarify where the item is present.

You shouldn’t need to update price if you do not want to. You can adjust an example reference code to suit your needs. You should be able to remove the two pricing code lines from that reference.

Also, could there be problem with below code as here I set locations to get Inventory counts ?

Nothing odd sticks out to me at a glance.

So, I tried below code to update ITEM and make it available at all locations.

    $item_variation_data = new \Square\Models\CatalogItemVariation();
    $item_variation_data->setItemId('#ITEM_ID');
    
    $object = new \Square\Models\CatalogObject('#CATELOG_OBJECT_ID');
    $object->setType('ITEM_VARIATION');
    $object->setVersion(1732311025969);
    $object->setPresentAtAllLocations(true);
    $object->setItemVariationData($item_variation_data);
    
    $body = new \Square\Models\UpsertCatalogObjectRequest('idempotency_key', $object);
    
    $api_response = $client->getCatalogApi()->upsertCatalogObject($body);
    
    if ($api_response->isSuccess()) {
        $result = $api_response->getResult();
    } else {
        $errors = $api_response->getErrors();
    }

Below is the response :

{
  "errors": [
    {
      "category": "INVALID_REQUEST_ERROR",
      "code": "INVALID_VALUE",
      "detail": "Invalid object `ZKYJN3LIAB3WDZKQYNJNKPN4`: [merchant_token=7JDRW4GA3EDSY] Variation with FIXED_PRICING pricing type missing global price value.",
      "field": "price"
    }
  ]
}

What I am doing wrong.
Please suggest, its been long and all I am trying to do is to write a code to update ITEM and make it available for all locations through API.
If I am wrong please suggest correct working code.

Thanks!

The Catalog API upserts are tricky.

I was able to find your application id since that’s a very specific error. From there I could find your API Logs. More insight for me than your code would be your request JSON body.

The error is saying that your missing the item variation pricing amount money. You cannot just set the pricing_type FIXED_PRICING without noting the price.

But the real issue is your request body is missing a ton of the object. What you need to do is call RetrieveCatalogObject right before the upsert. Just remove any read only fields, like timestamps, and just change what you’re looking to change.

Your request body should be all of the object’s writable fields.

Why do you need everything? Because if you exclude a field in a upsert - The assumption is you’re deleting it.

Here’s a related doc on updating catalog object.

It is not working out for me, whichever way I am trying it is failing so not sure exactly what I am missing where I am wrong.
Also, it makes no sense to update entire PRODUCT FIELDS when I only want to update 1 field which LOCATION. The process you are suggesting is way to expensive in terms of performance and in implementation.
Like at first I have to identify a product to update
then I have to make an API call to pull complete OBJECT
then I have to find fields not to update like timestamps etc.
then you want me to pass entire/all fields along with field I want to update.

Don’t you think it is scary and not suggested to update all fields for no reason.
Also, there must be some setting which I can configure for a product so that you doesn’t update or change LOCATION AVAILABILITY in any situation. I understand that it automatically makes unavailable for a location when it goes out of stock for that particular location. But what if I don’t want it to be unavailable at any location for any reason.

For Example: Look at product 2020 Sofia’s Star (Aged 3yr) SKU 736530891930 and tell me Why it is not available only at Solvang location where as per API we have stock quantity at all other locations ?
And then Why API is not giving me correct info, I should get what I see in UI ?
Why API is saying that we have quantity counts for all locations where as UI is showing it for Solvang only ?

I have shared API response and screenshot of UI where I am seeing that UI says that product is not available at any other location and you told me that if a product is not available at any particular location that means it is out of stock for locations respectively. BUt at the same time API is showing quantity for all locations meaning we have quantity to sell.

Which is confusing me and I am not sure what to understand with this data in hand.

Also, I will not share my account IDs or Catalog IDs or any such details here in public because of security concerns.
You have my email and and I have only one account here at square. You can find all accounts associated with my this email address.

Can I please talk to someone from technical team ?
Here it is too slow and we are talking back and forth, which is costing me both in terms of time and money. Because of this only I am unable to sell my products correctly because of this visibility issues.

Kindly suggest better way of communication and proper solution along with with exact code snippet. I know your docs and references which I have already gone through.

I need to get it fixed ASAP.

Please help me.

Thanks!

  Square\Models\BatchRetrieveInventoryCountsResponse Object
  (
      [errors:Square\Models\BatchRetrieveInventoryCountsResponse:private] => 
      [counts:Square\Models\BatchRetrieveInventoryCountsResponse:private] => Array
          (
              [0] => Square\Models\InventoryCount Object
                  (
                      [catalogObjectId:Square\Models\InventoryCount:private] => #ITEM_ID
                      [catalogObjectType:Square\Models\InventoryCount:private] => ITEM_VARIATION
                      [state:Square\Models\InventoryCount:private] => IN_STOCK
                      [locationId:Square\Models\InventoryCount:private] => #LOCATION_ID
                      [quantity:Square\Models\InventoryCount:private] => 37
                      [calculatedAt:Square\Models\InventoryCount:private] => 2024-11-20T20:51:33.416Z
                  )
  
              [1] => Square\Models\InventoryCount Object
                  (
                      [catalogObjectId:Square\Models\InventoryCount:private] => #ITEM_ID
                      [catalogObjectType:Square\Models\InventoryCount:private] => ITEM_VARIATION
                      [state:Square\Models\InventoryCount:private] => IN_STOCK
                      [locationId:Square\Models\InventoryCount:private] => #LOCATION_ID
                      [quantity:Square\Models\InventoryCount:private] => 14
                      [calculatedAt:Square\Models\InventoryCount:private] => 2024-11-26T01:03:15.641Z
                  )
  
              [2] => Square\Models\InventoryCount Object
                  (
                      [catalogObjectId:Square\Models\InventoryCount:private] => #ITEM_ID
                      [catalogObjectType:Square\Models\InventoryCount:private] => ITEM_VARIATION
                      [state:Square\Models\InventoryCount:private] => IN_STOCK
                      [locationId:Square\Models\InventoryCount:private] => #LOCATION_ID
                      [quantity:Square\Models\InventoryCount:private] => 1106
                      [calculatedAt:Square\Models\InventoryCount:private] => 2024-12-01T11:33:06.963Z
                  )
  
              [3] => Square\Models\InventoryCount Object
                  (
                      [catalogObjectId:Square\Models\InventoryCount:private] => #ITEM_ID
                      [catalogObjectType:Square\Models\InventoryCount:private] => ITEM_VARIATION
                      [state:Square\Models\InventoryCount:private] => IN_STOCK
                      [locationId:Square\Models\InventoryCount:private] => #LOCATION_ID
                      [quantity:Square\Models\InventoryCount:private] => 578
                      [calculatedAt:Square\Models\InventoryCount:private] => 2024-11-30T23:14:25.147Z
                  )
  
              [4] => Square\Models\InventoryCount Object
                  (
                      [catalogObjectId:Square\Models\InventoryCount:private] => #ITEM_ID
                      [catalogObjectType:Square\Models\InventoryCount:private] => ITEM_VARIATION
                      [state:Square\Models\InventoryCount:private] => IN_STOCK
                      [locationId:Square\Models\InventoryCount:private] => #LOCATION_ID
                      [quantity:Square\Models\InventoryCount:private] => 67
                      [calculatedAt:Square\Models\InventoryCount:private] => 2024-12-01T23:43:39.46Z
                  )
  
          )
  
      [cursor:Square\Models\BatchRetrieveInventoryCountsResponse:private] => 
  )

When updating a catalog object using the Square API, you must pass the entire object because the API uses a “replace” model for updates. This means that the update operation replaces the existing object with the new object you provide. Here are some reasons for this approach:

  1. Consistency: By requiring the entire object, Square ensures that the catalog remains consistent. Partial updates could lead to incomplete or inconsistent data if not handled correctly.
  2. Simplicity: The replace model simplifies the API design. Instead of having to handle partial updates and merging logic on the server side, the API treats each update as a full replacement, which is straightforward to implement and understand.
  3. Atomicity: Replacing the entire object ensures that the update is atomic. Either the entire update succeeds, or it fails, which prevents partial updates that could lead to data corruption.
  4. Versioning and Conflicts: Square’s catalog objects often have version numbers. When you update an object, you specify the version you’re updating, which helps prevent conflicts from concurrent updates. By sending the entire object, you ensure that the server has all the necessary information to handle versioning correctly.
  5. Flexibility: This approach allows clients to make any changes they need in a single API call, rather than having to make multiple calls to update different fields.

When updating a catalog object, make sure to retrieve the current version of the object, make your modifications, and then send the entire modified object back to the API. This workflow ensures that your updates are applied correctly and that you are working with the most recent version of the object. :slightly_smiling_face:

Okay, I will try this flow of implementation.
What about the other issues which I mentioned multiple times now ?

  1. Why product is unavailable at locations when we have quantity counts available at location.
  2. Why API is giving me quantity counts for location when square UI shows its unavailable at location.

I have already shared API response and screenshot above in my previous messages.

Great! As for the availability of an item that is something entirely different then inventory itself. To monitor the availability of an item variation you’ll follow our guidance for Monitoring Sold-out Item Variations or Modifiers. Also we recommend listening to webhooks since sellers can update the availability of items in our first party products. :slightly_smiling_face:

Bryan,

Unfortunately we are not getting a solution here. We need your help to sort this out. Soyuz said clearly…

The original question is still pending…

@Bryan-Square or @Lance-Square we are not getting an answer here. :frowning: Instead, you are giving me how to manage the catalog or how to retrieve the catalog, which is not what we said at all.

But our problem is to find the reason why LOCATION AVAILABILITY IS CHANGING every time we post a qty change.

In the example above… Why is Sofia Star unavailable at locations when we have qty counts for that location? We have already shared screenshots and API responses in the thread above.

We need a real reply as to the issue we are facing.

Are they changing the availability in the Dashboard or in the app? There are multiple places where the availability of an item can be changed. :slightly_smiling_face: