# Changelog of Shopify Products API & Scraper (`sourcing-data-studio/shopify-products-api`) Actor

- **URL**: https://apify.com/sourcing-data-studio/shopify-products-api/changelog.md
- **Full Actor documentation**: https://apify.com/sourcing-data-studio/shopify-products-api.md

## Changelog

### 0.1.8 (2026-10-10)

- The Actor now checks the security certificate of every store it reads. A store whose certificate does not verify is now reported as failed and not read. Before, the check was skipped, because the crawler library skips it unless it is told not to. `www.allbirds.com`, `www.colourpop.com` and `www.deathwishcoffee.com`, the three stores that the Console form is prefilled with, passed this check from the development machine on 2026-10-10. The build went live only after test runs on Apify's servers had read all three with the check switched on. The default is no proxy. With Apify Proxy the check stays on unless the proxy reports that it decrypts traffic, and on 2026-10-10 it did not report that.
- The code that starts the crawler again when a request is left waiting is now the same file as in the studio's shared template. No change in behaviour.
- No change to the input or the output.

### 0.1.7 (2026-10-09)

- README: the Python example now works with version 3 of Apify's Python client, the version `pip install apify-client` installs today (it needs Python 3.11 or later). The example read the dataset id as `run["defaultDatasetId"]`. Client 3 returns a typed object, so that line stopped with `TypeError: 'Run' object is not subscriptable`, after the run had finished and been charged. It now reads `run.default_dataset_id`, and the line above it says `pip install "apify-client>=3"`.
- The faulty example was added on 2026-10-09 and had not been run before it was published. The corrected one was run against this Actor the same day with client 3.3.0. The JavaScript example was run the same day and is unchanged.
- No change to the code, the input or the output.

### 0.1.6 (2026-10-09)

- What the platform showed after 0.1.5 went live (2026-10-09): of 54 single-store runs started nine at a time, one ended with the catalogue request still waiting; the Actor started the crawler again (`run.pending` in the log) and the store was read, 10 products saved. Build 0.1.4, in the same test, left 2 of 54 runs with an unread store and the false message. All 54 single-store runs started one after another, on either build, read their store. So the early end shows when several runs start together, it also happens without requests at the front of the queue (the guess in the 0.1.5 entry does not explain it), and the new start of the crawler is what covers it.
- README: the opening now states the price: what one saved row costs on the Free tier and what a typical query costs, from the same figures as the cost section. The sentence above the sample rows no longer names a "quick-test example".
- No source change.

### 0.1.5 (2026-10-09)

- Fixed a false message. On the platform, 2 of 10 single-store runs ended as succeeded with the status message "The stores list no products. No product results were charged." and saved nothing, although the store lists products. The store had not been read: the first request (`/meta.json`) ran, the request for the catalogue that follows it was never run, and the crawler ended with it still waiting. Nothing was charged, but the message was false and the run counted as a success. The other 8 runs, among them six reruns of the same input, saved every product they were asked for, so the failure comes and goes. The cause is not fully established. We suspect that a request added at the front of the queue is sometimes missing from the queue's answer when the crawler asks whether it is finished, but this has not been observed directly and it may be something else. The changes below do not depend on that guess.
- New store state `not-read` in the run summary: nothing was read from the store although nothing stopped the run. The words "list no products" are now used only for a store that answered with an empty first page (`empty`).
- A run that saved nothing and left a store unread now ends as failed. Its status message names the store and says that running again usually works. Nothing is charged. If other stores gave products, the run still ends as succeeded and the message names the stores that were not read.
- When the crawler ends while requests are still waiting in its queue, and nothing stopped the run (no limit, no time limit, no abort), the Actor starts the crawler again for them, at most three times. Each start is logged as `run.pending`, and the run summary counts them (`totals.extraPasses`) and the requests that were still left at the end (`totals.requestsLeft`). With a proxy it does not start the crawler again: that would use a new proxy session, so a new address, and a run keeps one address from start to end. There the waiting requests are only reported.
- Requests are no longer added at the front of the queue. The stores now take turns: the first page of every store, then the next page of every store. What changes for you: the rows of several stores are saved mixed, page by page, and no longer one store after the other. The `position` of a product is still its place in the catalogue of its own store, and the limit per store works as before. If **Maximum products in total** is lower than the stores together would give, the stores now share it page by page; before, the first store was read first and could fill it alone. Several stores can be in the middle of their catalogue when a limit is reached or the run is stopped; each of them is listed as `stopped`.
- A store that answers 429 still gets the pause it asks for, and the other stores are still read meanwhile: the pause now goes back to the end of the queue while other requests wait, instead of holding them up.

### 0.1.4 (2026-10-09)

- The title is now "Shopify Products API & Scraper". The Store address and the technical name are unchanged. The Store description leads with what you get; the sentence that the Actor is unofficial and not affiliated with Shopify stays.
- README: the output example now comes right after the feature list; the API section shows a curl, a JavaScript and a Python example; the cost section adds the price of a typical query. No change to the code, the input or the output.

### 0.1.3 (2026-10-06)

- The price of a product result is now $0.001 (Free and Bronze), $0.0009 (Silver) and $0.0008 (Gold), set as the first price before the Actor is published. In the earlier draft it was $0.0015, $0.0015, $0.00135 and $0.0012. 1,000 products cost $1.00 on the Free tier.
- A server error (5xx) with a `Retry-After` header is now waited out as the store asks, plus one second. Before, the pause was cut to 60 seconds and to the time left before the time limit of the run, so the store was asked again sooner than it said. Now a store that asks for more than 60 seconds, or for a pause that would end after the time limit of the run, is not asked again in this run and is listed as `failed` with that reason. This is how the answer 429 was already treated.
- The output field `scrapedAt` is now called `retrievedAt`, like the same field of the open-data Actors. The value is the same: the ISO 8601 time when the product was collected. If you read `scrapedAt` in a script, a spreadsheet or an integration, change it to `retrievedAt`. No other field changed.
- With a proxy, a run now uses one address (one proxy session) from start to end. Before, every request got a session of its own, so a store that answered 429 could be asked again after its pause from another address, and so could a store that answered a server error. Now every retry leaves from the same address. The address changes only when the connection through the proxy fails, never because of what a store answered. Without a proxy nothing changes.
- A 429 answer with the header `x-ratelimit-remaining: 0` now ends that store at once: the store says that no request is left, so it is listed as `rate-limited` and is not asked again in this run, whatever `Retry-After` says. Before, it got the pause and was asked again up to twice.
- A proxy that answers 407 (it refuses your proxy credentials) is no longer tried again and again: the page is recorded once as `failed`, and the run summary names the status line of the proxy only. Before, it was retried as often as your "Retries per page" setting allows.

### 0.1 (2026-10-05)

- First version: reads the public product catalogue of up to 50 Shopify stores and saves one row per product with price range, currency, variants, images and availability.
- Reads the route that Shopify stores list for read-only software agents in their /agents.md: /collections/all/products.json.
- Runs without a proxy by default: stores limit requests per IP address, and shared proxy addresses are usually over the limit already.
- A store that answers 429 gets the pause it asks for (at most 60 seconds) and is asked again at most twice; the other stores are read meanwhile. After that it is listed as rate-limited in the run summary.
- Two addresses of the same shop are read once; the second is listed as duplicate.
- E-mail addresses and phone numbers are also removed from title, vendor, product type and variant titles, in more formats.
- The run ends by itself before its time limit, so the run summary is always written.
