429 "Catalog locked by prior request" on first BatchUpsertCatalogObjects request in production

We are currently implementing a full catalog synchronization between our e-commerce platform and Square for one of our clients.

We are using the Catalog API and BatchUpsertCatalogObjects to synchronize products from the e-commerce website to Square through the PHP SDK.

Our implementation works correctly in the Sandbox environment, and all our tests have been successful.

However, when using the Production environment, we systematically receive the following error:

429 Catalog locked by prior request

The issue occurs immediately on the very first batch request, containing 100 products. Therefore, there should not be any previous catalog request from our synchronization process still running or locking the catalog.

We have tested the synchronization multiple times and consistently encounter the same behavior in Production, while the same implementation works as expected in Sandbox.

Could you please help us understand:

  • What can cause the catalog to already be locked when sending the first BatchUpsertCatalogObjects request?
  • Can the catalog be locked by another Square process or operation, outside of our API requests?
  • Is there any difference in catalog locking behavior or limitations between Sandbox and Production?
  • Is there a way to determine which request or process is currently holding the catalog lock?
  • Are there any additional recommendations or constraints we should follow when performing an initial/full catalog synchronization using batch upserts?

If useful, we can provide the request IDs, timestamps, payload structure, and any additional information required for investigation.

Thank you for your help.

Hi @DevNateev - Could you share your Application ID and timestamps for when you made the requests?

Hi,

My Application ID is: sq0idp-VAg7qcLLo5dnAL3bFFmPJg

And this is one of the requests timestamps that produced the error: 2026-08-12 08:45:48 CEST

Hi @DevNateev - Thanks for the details.

I can confirm that it is possible to get a 429 error upon an initial batch upload call due to an internal job that is also triggered while Square processes the overall request.

I would suggest implementing exponential backoff into your retry logic (see here) - it seems like all of the requests around the given timestamp were within seconds of each other. Given the size of your upload, it would take longer than a few seconds for the internal job to finish and release the catalog lock, but retrying within a few seconds would continue to return the same error.

Let me know if this works, or if you continue to run into other issues!

Thank you for your response and for investigating the requests.

However, I don’t think retrying with exponential backoff is a suitable solution for the issue we’re experiencing.

The main problem is that when the BatchUpsertCatalogObjects request returns a 429 Catalog locked by prior request, some; and possibly all of the products from that request are still successfully synchronized to the catalog.

This makes the response ambiguous from the API client’s perspective. When receiving a 429, we cannot determine:

  • whether the request was actually rejected;
  • whether it is still being processed internally;
  • whether only part of the batch was processed;
  • or whether the entire batch was successfully processed despite the error response.

In this situation, retrying the same request after a delay doesn’t really solve the problem, because we don’t know whether the original request has finished processing or whether it needs to be retried at all.

I believe the issue is therefore on the Square side rather than with the retry strategy on our side. If the operation continues asynchronously after the HTTP request, the API should ideally provide a way to know when that processing has completed and whether it was successful. Alternatively, if the endpoint is intended to behave synchronously, it should return a successful response once the operation has actually been accepted/processed rather than returning a 429 for an operation that is nevertheless being applied.

Due to our project deadline, I eventually had to work around the issue by synchronizing the entire catalog one product at a time instead of using batch requests. The full synchronization completed successfully this way and I did not encounter the same locking issue.

This allowed us to meet our deadline, but obviously sending products one by one is not an ideal long-term solution and defeats much of the purpose of BatchUpsertCatalogObjects.

Could you please investigate why a batch request can return 429 Catalog locked by prior request while its objects are nevertheless being synchronized, and clarify what the expected behavior is in this situation?

In particular, is there any reliable way for an API client to determine whether a batch that returned this 429 was partially or fully processed before deciding whether it should be retried?

@DevNateev - For the 4 situations that you described:

A 400 error would occur if the request was entirely rejected.

A 429 error occurs when the catalog is locked during processing - this covers the second and third situations. You are correct that this can happen without the client knowing up to what point it had processed the request, but this can be handled by using the right idempotency key.

In the logs, your attempts correctly re-use the same idempotency key when encountering a 429 error, which tells the system to treat this request as one that is identical to a previous request with the same key. When the server receives this request after throwing a 429 error, it picks up the remaining items, without the need for the client to specify which items have already been processed.

If the entire batch was successfully processed, and a 429 error still occurs, my advice still stands to use the same idempotency key with exponential backoff.

Hope this helps!