# Changelog of AliExpress Product Details Scraper & Shipping (`myth84/aliexpress-product-scraper`) Actor

- **URL**: https://apify.com/myth84/aliexpress-product-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/myth84/aliexpress-product-scraper.md

### Changelog

History of `aliexpress-product-scraper` (folder `actors/aliexpress-product`). Versions are the Apify build numbers.

#### Unreleased (agent-facing polish: field order, first screen)

- Brief `docs/cloud-tasks/2026-10-24_studio-agent-facing-polish.md` (2026-10-24): what an AI agent reads first. No change to the code, the defaults, the prices or the output.
- **Input field order** (the Apify MCP search card lists the first 20 fields): unchanged: every field is among the first 20 and the proxy and crawl settings already close the form. Nothing renamed or removed.
- **README first screen**: under the title, "Verified daily by an automated run on Apify with the default input", the Free-plan and Business-plan prices per 1,000 results of the pricing file in force and the $0.00005 start fee per run (Apify's minimum); a one-line review request before the legal note. A test binds these figures to the pricing file and forbids "to confirm" and "no start fee" in the input schema and the README.

#### Unreleased (quality score: input documented for AI agents)

- Brief `docs/cloud-tasks/2026-10-20_studio-quality-score-insights.md` (2026-10-21): the input schema passes the shared checks of `@scraper-studio/core/testing` (`runInputQualityChecks`, `runSearchTextChecks`), the rules of Apify's quality score and of what an AI agent receives through the Apify MCP server.
- Descriptions rewritten with the reason of each default: `country`, `maxResults`, `maxVariants`, `proxyConfiguration`, `concurrency`, `productsPerIp`, `delayMs` (0 disables the pause), `debugStoreResponses`.
- Prefilled input = daily check input: `productUrls` is prefilled with the five stable products of `inputs/daily-check.json` (two more than before, the reserve) and `maxResults` with 3 (the default stays 100), so the prefilled run Apify tests daily asks for 3 rows, like our daily check. `inputs/daily-check.json` is unchanged (it already parsed to that input).
- `store.json`: `seoDescription` opens with "AliExpress product details" (was "Scrape AliExpress products"), same length and promise; new `searchSynonyms` (AliExpress product scraper, dropshipping, stock tracking, shipping cost), all within the first 3,000 README characters. README input table: the prefills of `productUrls` and `maxResults`.
- No code change: rows, logs and charges are identical.

#### Unreleased

- **Products no longer for sale, robust release checks** (brief `docs/cloud-tasks/2026-10-20_aliexpress-product-repair-unavailable.md`): the second release stopped at the control step (run `JilSRLWJtOlbzCM4F`: 98 rows for 100 products, "no crawler errors: 5 error(s)"), while the crawl itself was fine.
  - Every `SITEM_BAN*` code other than `SITEM_BAN` (not sold to the country), such as `SITEM_BAN_NO_AVAIL_SKU` (still listed, no variant left for sale), now means **unavailable**: no row, not charged, not tried again, the code and its meaning in the log, a new counter `RUN_STATS.extra.unavailable`. It used to be a gateway error, queued again three times, then counted as four errors (product `1005012707077513`).
  - A session whose opening call gets no token (connection failure, e.g. `ConnectError … tls handshake eof`) counts as **refused at the start** (`sessionsRefused`), like a refused IP, and no longer as an error: the next session takes over.
  - `errors` now counts the **products finally not obtained** only: given up after three tries, skipped, or never tried because the crawl stopped before the result limit. Failed attempts that another try or another session made up for go to the new counter `retries`. Products left in the queue once `maxResults` (or the run's charge limit) is reached are counted in the new `productsLeftAtLimit`, without a "not fetched" warning; `productsNotFetched` keeps only the products the crawl stopped without (refusals, `maxRequests`).
  - Release inputs carry a **reserve**, since products disappear from AliExpress every day and the release script expects exactly `maxResults` rows: `inputs/control-scale.json` lists 115 products for `maxResults` 100 (`1005012873168087`, not sold to the US, and `1005012707077513`, no variant for sale, removed; 17 products added from the committed US search captures), `inputs/daily-check.json` 5 products for 3 rows, `inputs/validation.json` the same 5 products for 3 rows plus the unknown id, now second so that it is still fetched before the limit (README input block updated). The example tasks *earbuds-new-shopper-deals*, *earbuds-variants-us* and *earbuds-shipping-germany* listed one of the two removed products: replaced by products of the same captures (still 50 or 60 products, one row each).
  - Test fixture `fixtures/aliexpress/product-details/pdp-no-available-sku-derived.2026-10-20.json`: **derived, not a capture** (the "not sold to the country" capture with the code the control run logged, `_meta.derivedFrom`).
  - README, input and output schema descriptions: products no longer for sale are free and do not count; tip "list a few more products than `maxResults`" for an exact number of rows.
- **Release fix** (brief `docs/cloud-tasks/2026-10-20_aliexpress-fix-release.md`): the first release stopped at the validation step (run `pWIXuM7nHgOIMVr1X`: 3 rows, 10 expected). The run itself was right: `inputs/validation.json` lists three products and a deliberately unknown id (no row, not charged), but asked for `maxResults` 10, and `scripts/release-actor.mjs` expects exactly `maxResults` rows from an Actor whose `store.json` says `maxResultsPerSearch: false`. `maxResults` is now 3 (and in the README input block). No change to the scraping code.
- The same check would have stopped the control step: `inputs/control-scale.json` (country US, `maxResults` 100) listed `1005012030698792`, the product captured as not sold to the United States (`SITEM_BAN`, no row). It is replaced by `1005012775245422` from the same US search captures; the US example tasks *earbuds-new-shopper-deals* and *earbuds-variants-us* listed it too and now use `1005013003743000` and `1005012791830960` (still 50 products, one row each).
- **Input `productsPerSession` renamed `productsPerIp`** (form title "Products per IP address", same default and limits): the Store checks of the studio refuse an input name containing "session" since #54 (Apify's quality score reads it as a secret), which turned `npm run check` red on `main`. The Actor is not public yet, so the former name is not kept as an alias.
- Tests: `maxResults` of every release input equals the products that give a row, and no example task lists a product the captures show unknown or not sold to its country (`rowlessProductIds` in `src/test-utils.ts`, read from the `pdp-not-found` and `pdp-unavailable-in-country` fixtures).

#### 0.1 — 2026-10-20

- First version, written offline on the fixtures captured by the probe on 2026-10-20 (brief `docs/cloud-tasks/2026-10-20_aliexpress-product-build.md`, audit `docs/audits/aliexpress-product.md`): one signed call per product to AliExpress' product gateway (`mtop.aliexpress.pdp.pc.query` on `acs.aliexpress.com`): an unsigned first call per session obtains the anonymous `_m_h5_tk` token, then `sign = md5(token&t&appKey&data)`. Product URLs of every AliExpress form and bare ids are accepted; aliexpress.us ids are converted to the global id (minus 2^51); aliexpress.ru is refused.
- Output schema `ecommerce-product/1.2` (new, `packages/core/src/schema/ecommerce-details.ts`): every `ecommerce-product/1.1` column, same order, then `priceMax`, `newShopperDeal`, `variantCount`, `variants`, `shippable`, `shippingOptions`, `imageUrls`, `videoUrl`, `categoryId`, `categoryIds`, `storePositiveFeedbackPercent`, `storeFollowers`, `wishlistCount`, `reviewsAggregated`, `language`.
- Sessions: a sticky proxy session per IP with its own cookie jar and the impit transport (Chrome profile), at most `productsPerSession` (5) products per session with `delayMs` (1,000 ms) between two calls, `concurrency` (4) sessions in parallel; a refused IP is dropped (the verification page is never solved) and replaced; a product refused in the middle of a session goes back to the queue (3 tries at most); a missing or expired token is renewed once per session; the crawl stops after 30 refused sessions in a row. Default proxy: Apify `RESIDENTIAL` group without a country (the gateway refuses datacenter IPs; the data follows the requested country, not the IP).
- Pay-per-event: `result` per product pushed. Unknown products (`SITEM_NOT_EXIST`) and products not sold to the country (`SITEM_BAN`) give no row and no charge (`RUN_STATS.extra.notFound`, `unavailableInCountry`). Explicit failure on empty output, unless every product asked for was unknown or not sold to the country.
- New-shopper prices are flagged per variant (`newShopperDeal`, `newShopperSaving`) and on the row, never hidden.
- No personal data: seller identity, seller ids, compliance contacts, description URLs and reviews are never read; the store is named, and the product video (`videoUrl`, whose address contains the uploader's account number) kept, only when the gateway says the seller is not a local seller (`storeType: business`); every row goes through the privacy filter (a specification named like personal data, such as "Gender", is dropped).
- Not included in this version: the description text (a second request), category names, reviews and questions, the seller's identity.
- Decisions local to the Actor: Store title shortened to "AliExpress Product Details Scraper: Variants" (the 50-character limit of the Store texts), three categories like the other Actors of the studio, no mention of proxy tiers or blocking techniques in the README (studio rule of 2026-10-20); the language pairs other than en_US, en_GB, fr_FR and de_DE are not verified yet.
