Scrape any 1688 category from one English keyword: English titles, MOQ price ladders, landed cost, dropship scores, compliance hints. Or watch a shortlist of offers for price drops and stock changes — daily snapshots, alerts, weekly trend report. Failed runs are never charged.
Full release history lives in git commit messages (git log --oneline); this file records release-visible changes from 0.1.17 onward.
0.1.39 (2026-11-11)
Search-to-detail/monitoring follow-through and monitoring persistence safety. No PPE price, API default, search/crawler, SEO or Task change.
Successful searches now end with an executable next step. Settled, non-monitor runs with delivered rows add nextSteps to both SUMMARY and OUTPUT; SUMMARY_SCHEMA_VERSION moves 1.0.0 → 1.1.0. For a single-keyword card-only run, nextSteps.detailSearch.input is a copyable 20-item full-detail request whose complete FREE-tier delivery estimate is $0.17. nextSteps.priceMonitor.input carries up to 10 valid, deduplicated offer IDs from the actual delivered prefix, in dataset order; 10 IDs estimate $0.16 per run and $4.80 for 30 daily runs, with an explicit manual Schedule suggestion. Multi-keyword, direct-offerId, full-detail/mixed and missing-valid-ID cases disclose why a suggestion is unavailable; zero-delivery, monitor, unsettled and explicit-zero-cap runs omit the block.
Budget and credential boundaries are preserved. An explicit positive maxTotalChargeUsd is carried into both suggested inputs and accompanied by estimateExceedsCap when applicable. Suggested proxy configuration is rebuilt from the public residential-CN whitelist rather than copied from input or result rows, so proxy URLs, credentials, sessions and unknown fields cannot leak. Estimates reuse FALLBACK_EVENT_PRICES_USD; no second price table, automatic run, automatic Schedule or new billing event was introduced.
A history-write failure now withholds the entire monitor batch and charges nothing. Previously a non-OK PriceSnapshotStore.write() only logged a warning, after which successful snapshots could still be pushed and billed. A false return, thrown write or mid-batch failure now produces KV_UNAVAILABLE, historyReadOk: false, zero snapshot/trend/base charges, no paid snapshot dataset push and no webhook alerts from withheld rows. The normal path remains billed only for snapshots actually persisted and delivered.
The shared-store token no longer travels in the URL. Price-history reads and writes use Authorization: Bearer …; record URLs contain no token query parameter, and transport errors still never echo the credential.
Verification: repository npm test — 1153 tests, 1153 passed, 0 failed. New coverage includes exact follow-up estimates, ID boundaries/deduplication/cap, proxy-secret negative controls, SUMMARY/OUTPUT parity, budget-truncated delivered prefixes, write false/throw/mid-batch failures with trend and webhook armed, and Bearer-only REST transport.
package.json moves 0.1.38 → 0.1.39. This entry does not claim a cloud deployment or a latest change.
0.1.38 (2026-11-09)
First-run conversion and scoring-evidence repair. No PPE price change, no API-default change and no Actor SEO change.
First run moves from 30 detail-enriched rows (~$0.25 on FREE) to 10 card-only rows ($0.06). Only Console prefill changes: maxItems 30 → 10 and includeDetailstrue → false. API callers that omit those fields retain maxItems.default = 40 and includeDetails.default = true. The Actor-level exampleRunInput, Console schema and README now carry the same phone case / 10 / card-only / US / relevant / residential-CN input.
Dropship scoring now uses only four observable inputs: one-piece dropship support, MOQ, supplier composite score and repeat-purchase rate (supplier 3-month rate first, product rate as fallback). Their original 30:20:10:5 proportions are renormalized over the four inputs. pickupRate48h, distributorCount and orders30d remain raw output fields but no longer affect score, grade, breakdown or dataQuality; missing factors still receive neutral 50, with full / partial / minimal covering 0 / 1–2 / 3–4 neutral inputs. scoreConfidence remains in the output contract.
Field semantics are explicit.quantityPrices is the standard quantity-break ladder; optional skuDetails is normally empty under standard public inputs and is not a detail-fetch failure signal. dataQuality measures scoring-evidence completeness, not detail delivery; detail status remains in SUMMARY. The README sample is a real owner-verification row from run VsBB4dpPw797yXslj, including its actual three-tier ladder, instead of a fabricated skuDetails placeholder.
The starter Task is prepared, but its public replacement is blocked by the platform's Task cap.1688-starter-10-card-search (TQok4Zw7FSSDX7nS5) uses the exact starter input, build: latest, 1024 MB and 3600 s; FREE-tier cost is 10 × $0.005 + $0.01 = $0.06, with zero detail-item events. The old 1688-product-selection-quickstart Task is retired from the public listing. Fourteen legacy public Tasks remain, above Apify's limit of 10, so publishing the starter is rejected with too-many-public-actor-tasks-per-actor; it remains private and is not linked from the README. No additional Task was retired, and the other 14 public Tasks are unchanged.
Verification: repository npm test — 1138 tests, 1138 passed, 0 failed. Regression tests pin the four-factor scorer, prefill/default split, search-first quick-start and field semantics. A default-path cloud run submitted without a build parameter resolved to 0.1.38, SUCCEEDED, delivered one card-only row, and reconciled search-item 1 + run-base-fee 1 with zero detail-item; platformCountsReconciled: true and billingInvariantViolation: false.
package.json moves 0.1.37 → 0.1.38. No PPE price, API default or Actor SEO field changed.
0.1.37 (2026-10-28)
Documentation-and-examples release from the operations brief 价格监控订阅化 (qwen_strategy/2026-10-28_51_需求-价格监控订阅化-高流量actor-交开发agent.md). No src/ change; no price change; nothing about search, detail, monitor or billing behaviour moves. The brief's R1/R3 items are answered in the review rather than in code — see "Not done, and why" at the end.
The monitor quick-start was built on offer IDs that had never been run. README's copy-paste block read ["994632611553", "951309417091", "700000000001"]. None of the three had ever been delivered by a cloud run of this Actor, and 700000000001 appears in no evidence file, no schema and no test — it is a made-up round number. A buyer who copied the block got three snapshot_failed rows and an empty history curve. They are unbilled, so nothing ever complained. This is the same defect class the amazon-fba bridge shipped in its Try-button prefill for eight days (fixed there in 0.1.9 the same day).
Replaced with the three IDs the live daily template actually watches — 781924595627 / 659979401746 / 993816303199 — verified delivering on build 0.1.36 in run TTAUZX3djQ38T2cKa (3 of 3 snapshotted, snapshot-item 3 + run-base-fee 1, historyReadOk: true, SUCCEEDED). The README block is now byte-for-byte the input of that public Task, so "click the template" and "copy the JSON" are the same run. includeTrendReport is false in it, deliberately: at three offers the $0.25 report is 82% of the bill.
offerIds.example and the ID quoted in both field descriptions are now run-backed too.777995455975 (cited by the 0.1.32 entry as "taken from a live delivery", but not archived anywhere this test can see) and 994632611553 are replaced by 1059578993224 and 781924595627, both delivered today. dataset_schema.json's example values are left alone on purpose: they illustrate a column nobody executes, and swapping in a real ID would put it next to an unrelated LED-strip sample title.
New: a real monthly-cost table for a standing watchlist (3 / 5 / 10 / 20 / 50 / 200 offers × one run, daily, weekly, weekly-with-trend-report), computed from the registered FREE-tier prices with the $0.01 run base fee and the $0.25 trend report shown as separate line items rather than folded into a per-offer rate. The operations brief's figures for this table were both wrong and both low: it quoted "$0.53 to watch 5 offers for a week" (actual $2.345 — it dropped the base fee and the trend report it asked to prefill) and "$1.6/month for 5 offers daily" (actual $2.55; $1.65 is the 3-offer figure). The table now states the two non-obvious consequences: the base fee is per run not per offer, so 3 offers weekly ($0.24) is ~7× cheaper than daily ($1.65); and the trend report costs more than snapshotting 16 offers, so it belongs on a wide weekly watchlist, not a small daily one (daily-with-trend on 5 offers is $10.05/month versus $2.55 without).
New: "Where the offer IDs come from." The product-side half of the brief's cross-sell. Points at this Actor's own search rows and at 1688 Image Search Scraper , naming the exact path — results[].offerId on each imageResult row, one row per query image with the matches nested inside — so a reverse-image search converts directly into a price watch. The image-search side of that cross-sell is not in this release; see below.
New cloud-tests/evidence/proven-offerids.json + cloud-tests/build-proven-offerids.mjs. Every ID in the corpus was delivered by a named run, and each run's dataset and run header are archived beside it, so a reviewer can open the file instead of trusting the list. Rebuilding is idempotent and carries prior entries forward.
New tests/doc-offerid-provenance.test.js (6 tests) makes "proven" executable: corpus entries must be backed by an archived run; every offer ID in a README input block and in the offerIds / watchlistOfferIdsprefill / example / description must be in the corpus; the quick-start must name the run that verified it and must not prefill a trend report at ≤5 offers; and the cost table plus the two worked examples are recomputed from the registered prices. Output-sample blocks are excluded by shape (offerId + scrapedAt) but must still be introduced as coming from a real run. Both negative controls were run: restoring the three fabricated IDs fails tests 2 and 4; substituting the brief's $1.60 into the 5-offer daily cell fails test 5 with table says $1.6, current prices give $2.5500.
package.json moves 0.1.32 → 0.1.37. It was left at 0.1.32 through 0.1.33–0.1.36 because none of those builds changed src/ relative to 0.1.32 (see the three entries below).
Not done, and why (all three are answers to the brief, not omissions):
R1's image-search changes are deliberately not in this release, and should not be built casually. image-search's live latest is 0.3.34 while its local tree is 0.3.33 — and 0.3.34 is not a code release at all: its build record's changelog still starts ## 0.3.33. It was a rebuild whose only purpose was to bake a version envVar into the build. cloud-tests/results/rollout-review-20261016/platform-after-100pct.json records version 0.3 carrying NOBROWSER_ROLLOUT_PCT=100 from 2026-10-16T14:19Z, while src/rollout.js:13 defaults to 30 and the local .actor/actor.json has no envVars field at all. So a docs-only rebuild of image-search from this tree would silently drop the browser-free card-only path from 100% to 30% — a margin regression on the portfolio's only cash cow, caused by a README paragraph. Worse, the same version carries MTOP_KV_API_TOKEN as a secret envVar, and Apify secret envVars are write-only: once a version PUT drops one, it cannot be read back. R1.2 waits for the next image-search release that has a real code change, and that release must re-send all three envVars explicitly.
R1.1 needed nothing: offerId is already in both definitions.card and definitions.detail, and the card's description already says "Feed it back via the offerIds input to re-fetch this exact product." R1.3 is not implementable as written — offerId is nested inside results[] on a one-row-per-query-image dataset, so it cannot become a top-level column; the only route is a view that flattens results, and because enrich-mode runs push progressive snapshots into an append-only dataset (src/main.js:855: "consumers take the LAST imageResult block", "re-pushing here would duplicate every imageResult") such a view would show the same match several times. A counting-wrong view is worse than no view.
R3 adds no field. The monitor SUMMARY already carries mode: "monitor", watchlistSize, monitorEvents, itemCount, truncatedOffers, invalidOffers and economics.chargedEvents; the brief's three requested numbers are watchlistSize, chargedEvents["snapshot-item"] and chargedEvents["trend-report"] > 0. A watchlist: {} block would be a second source of truth for figures that already have one. More importantly the brief's stated purpose is unreachable by any SUMMARY field: another user's run details are not visible to the owner over REST, so per-run shapes cannot be enumerated. The meter that does work already exists and needs no code — snapshot-item is emitted only by the monitor branch (src/price-monitor.js is the sole consumer), so the platform's own aggregate snapshot-item / trend-report counts are the subscription-shape meter, readable in Insights without enumerating runs. The brief's §3 acceptance criterion should be restated as "weekly snapshot-item events ≥ 10", which is measurable, instead of "≥10 watchlist-shaped runs per week", which is not.
R2.1 does not create a third Task. product already publishes two monitor-shaped Tasks, and weekly-price-drop-radar is within a word of the requested weekly-price-watch; adding another would fragment the discovery surface for no new capability. Both existing Tasks were instead verified live on 0.1.36 (TTAUZX3djQ38T2cKa 3/3 offers, MtqCm2nffCkc2ndC0 4/4 offers + trend-report 1 with offersTracked: 4 and two ranked drops) — all seven of their offer IDs deliver, so unlike the README none of them had rotted. weekly-price-drop-radar did have two live storefront defects, fixed by Task PUT the same day: its seoDescription was truncated mid-word at the platform's 160-character cap ("...the biggest price drops and the faste"), and its title/seoTitle were the lowercase generic "Weekly price-drop radar" with no "1688", unlike the other 14 public Tasks on this Actor. Both now read 1688 Weekly Price-Drop Radar — Trend Report + Alerts (52/60) with a complete 126/160 sentence; input, options, description, datasetView and inputSchemaFields were re-sent verbatim and read back unchanged.
Tests 1131 → 1137.
Deployed and verified 2026-10-29. Build 1xwXoCwfcP7qUbmeu = 0.1.37, SUCCEEDED, tagged latest (was 0.1.36 / 3R0sQpbitc3d1gD0d). Deployed by the new cloud-tests/deploy.mjs, which uploads the live source manifest rather than the working tree, so the change is content-only and auditable against the diff: 132 manifest files + 1 added (tests/doc-offerid-provenance.test.js, since the live source already carries all of tests/) = 133, with four content changes (README.md, .actor/input_schema.json, package.json, CHANGELOG.md) and 59 local-only cloud-tests/ files deliberately not uploaded — cloud-tests/results/ alone is 3.2 MB of run evidence.
Default-path verification run r0rIzTIRe7U1YQ92r, submitted with no build parameter using the README's monitor quick-start block byte-for-byte, 9/9 checks green: the platform resolved options.build: "latest" to 0.1.37 on its own; SUCCEEDED in 23 s; all three documented IDs delivered (781924595627 / 659979401746 / 993816303199); snapshot-item 3 + run-base-fee 1, trend-report 0 and search-item 0; list-price sum $0.05500, matching the new cost table's first row exactly. SUMMARY confirms historyReadOk: true, kvAvailable: true, platformCountsReconciled: true, billingInvariantViolation: false — so the canary auditor's billingIdentity row stays green and the 09:00 canary will pick 0.1.37 up unpinned. Evidence: cloud-tests/evidence/verify-0.1.37-default-path-{run,dataset}.json.
Correction to the image-search claim above (2026-10-29, proven wrong). This entry asserted that a docs-only rebuild of image-search would "silently drop the browser-free card-only path from 100% to 30%" and that a version PUT would destroy its write-only MTOP_KV_API_TOKEN. Both halves are false. A version PUT that omitsenvVars preserves every existing entry, secret and plain alike. Proven twice on throwaway Actors and then again on this very deploy:
probe 1 — a secret envVar set via POST /versions/0.1/env-vars, then a source-only PUT: valueHash identical before and after.
probe 2 — the same PUT with both a secret and a plain ROLLOUT_PCT_PROBE=100: both survived with the plain value still "100". This is the exact shape of the NOBROWSER_ROLLOUT_PCT worry.
production — this Actor's MTOP_KV_API_TOKEN (htlzOA) and PRICE_KV_API_TOKEN (pVPdTu) kept identical hashes across the 0.1.37 PUT, and historyReadOk: true proves the price-monitor token still authenticates.
The mechanism, from the Apify CLI's own source: apify push sends envVars: transformEnvToEnvVars(actorJson.environmentVariables, ...)only when actor.json declares environmentVariables; otherwise the value is undefined and JSON.stringify drops the key, so the platform never sees an envVars field to clear. The related project-memory note remains correct on its own terms — a whole-object PUT carryingvalueHash is 400-rejected, and the incremental env-vars endpoint is still the right way to add one — it just does not imply that omission deletes.
What survives of the recommendation: image-search still should not be rebuilt for a docs-only change, but on the honest ground that there is no buyer-visible benefit worth a build, not because a rebuild is hazardous. NOBROWSER_ROLLOUT_PCT=100 is safe across a rebuild. Do not carry the old constraint forward.
0.1.36 (2026-10-28)
The first build promoted to latest since 0.1.31, so this is the release that actually put the 0.1.32 agent-readiness work (REQ-1/2/3) in front of buyers. Source is the tree at commit 97eba80 — the same tree 0.1.32/0.1.33 were built from — with exactly one change:
.actor/actor.jsondefaultRunOptions.timeoutSecs2400 → 3600, to match what was already live. The deploy path PUTs defaultRunOptions from the local manifest, so pushing the stale local value would have silently cut every buyer's default run window by 20 minutes. Caught in the pre-flight metadata diff, before the push; title, description, seoTitle, seoDescription, categories, isPublic and all seven pricingInfos records were diffed at the same time and were unchanged.
Verification, on the default path rather than a pinned one: an unpinned run resolved options.build: "latest" → buildNumber 0.1.36 and returned SUMMARY.schemaVersion 1.0.0 with the economics block present; the zero-traffic arm passed 14/14 assertions. Operations independently probed 0.1.36 from a secondary account (run 52QfUYGnhHe9nV22Y): the plural keywords alias normalised and delivered 3 rows, and SUMMARY.economics reconciled three ways.
0.1.34 / 0.1.35 (2026-10-24, beta tag only — never served a buyer)
Recorded for completeness because the cloud build numbers ran ahead of this file, not because anything shipped.
Both were built with the beta tag, so defaultRunOptions.build: "latest" never resolved to them: buyers ran 0.1.31 from 2026-10-20 until 0.1.36 was promoted on 2026-10-28. beta still points at 0.1.35 (dqkdUzHk72KwTE4Ak).
What can be established from the cloud side: their build records' inputSchema, readme and changelog are byte-identical to 0.1.32/0.1.33's — all three still begin ## 0.1.32, i.e. neither wrote its own entry. Their imageDigest values differ from each other and from every other build, but imageDigest changes on every build and is not a content fingerprint, so it is not evidence of a source change.
What cannot be established: GET /v2/actor-builds/{id} does not return sourceFiles, so whether their src/ differed from the committed tree is not determinable, and it is not recoverable — the images are gone and the repo never held them. An earlier note in project memory claimed a "three-way version mismatch" here; that claim rested on comparing a JavaScript string .length against a UTF-8 byte count for content containing Chinese characters and emoji, which produces a phantom difference. The semantic diff above is the corrected reading.
Practical consequence, and the reason this entry exists: beta is not a valid A/B baseline for this Actor. Pin ?build=0.1.36 (or whatever latest currently is) instead, or rebuild from a committed tree. Going forward the rule is commit-before-build, so git log is the provenance record — and never apify push -b beta, which retags the version to beta and leaves latest pointing at an older build.
0.1.32 (2026-10-24)
Agent-readiness release (operations REQ-1/2/3). Prices, limits, failure semantics and the mtop identity flywheel are unchanged; every change is additive to the input surface and the SUMMARY/OUTPUT contract.
Lenient input entry. The canonical keywords field arriving as a STRING was silently dropped, so {"keywords":"phone case","maxItems":5} exited NO_INPUT with nothing charged and nothing explained (run Y2i1qGredT5uIRfPU, secondary account, 2026-10-23). keywords/keyword/searchKeywords/searchKeyword/searchTerms/searchTerm/query/search/q are now all read, each as a string, an array, or one comma/newline/semicolon-separated string; offerIds accepts the same shapes. Precedence is unchanged (canonical field first, an empty one never shadows a usable alias). New module src/input-normalize.js, no change to the search pipeline.
Normalisation is disclosed, never silent.SUMMARY.keywordFieldUsed, maxItemsClamped / maxItemsRequested / maxItemsApplied, maxItemsIgnored and invalidOfferIds report what we made of the input. Invalid offerIds are still attempted (they fail unbilled exactly as before) and are additionally named.
Measured correction to the brief: the platform validates the input schema before a run exists.maxItems: 5000 and sort: "cheapest" never reach the Actor — the API answers 400 invalid-input with Field input.maxItems must be <= 200 and Field input.sort must be equal to one of the allowed values: "relevant", "sales", "price" (matrix cases b and d). No run, no compute, no charge. The in-Actor clamp and the maxItemsClamped disclosure therefore cover only the inputs that bypassed schema validation (a hand-edited INPUT record, a local apify run), and the README/schema now say so instead of promising a clamp at the API boundary.
Alias callers get generic-actor defaults. A run that reaches us through an alias field now behaves like a keyword run (dropshipOnly=false, excludeFoodProducts=false, default batch 40) instead of inheriting the pet-era narrow filters. The canonical keywords array path is untouched.
OUTPUT is written on every exit path. A successful keyword or offerIds run wrote SUMMARY only, so the key the listing and the other exits advertise did not exist (live evidence: run NxlMEV7Hhx9RM71Ir's KV holds DELIVERY_STATE, INPUT, PENDING_RESULTS, SUMMARY). A caller reading OUTPUT got nothing — the origin of the "the ledger is null" report.
Spend self-report. Every SUMMARY/OUTPUT now carries schemaVersion (1.0.0, semver, additive within a major), itemCount, chargedEvents and an economics block: unitPrices (the tier prices this run actually applied), actualEvents, expectedCharge (= Σ events × unit prices), chargedUsdTotal, deliveredRows, totalEvents, rowsMatchRowEvents, platformEvents + platformCountsReconciled (the SDK ChargingManager's own counters) and settled. The flat keys previously existed only on the NO_INPUT / BUDGET_EXHAUSTED / monitor exits; on a successful search run they were absent altogether (runs 4tEcjb14l8M4dQsox, O9w6iB3sPrYncAFFm, NxlMEV7Hhx9RM71Ir each delivered 20 rows and 41 charged events with neither key).
NO_INPUT explains itself. The message now names the accepted fields and shapes, and the report carries acceptedKeywordFields plus keywordFieldSeen (the field the caller used, when its value was unusable). Still SUCCEEDED, still $0.00.
README "For AI agents / MCP" gains the capability boundary (1688.com only; no login, no ordering, no image search, no supplier profiles), the minimal call, a cost table whose worst case is $1.61 (200 × $0.005 + 200 × $0.003 + $0.01 — the operations brief's $1.01 omitted detail-item), the full errorCode table with the charge for each, and the economics reference.
Schema text: keyword documents the accepted aliases, offerIds gains two real example IDs (777995455975, 1059578993224, taken from a live delivery) and the invalid-ID disclosure, maxItems documents the clamp and the worst-case spend; the key-value-store schema describes both records as written on every path.
Tests: tests/input-normalize-0132.test.js (33 cases over the coercion/clamp/economics layers, including a reproduction of the live 20+20 ledger) and tests/main-agent-input-0132.test.js (13 end-to-end main() runs, including the four-case "no FAILED" acceptance matrix from the brief). Suite: 1131 tests, 0 failures. tests/task273-card-only-fixbatch.test.js keeps mocking price-monitor.js and now re-exports isValidOfferId, which input-normalize.js imports.
Cloud builds for this change set: 0.1.32 (first cut, beta tag) and 0.1.33 (identical src/, README and input-schema wording corrected after the boundary-validation finding above). Promote 0.1.33; latest stayed on 0.1.31 throughout. Acceptance evidence: cloud-tests/results/verify-0.1.32/.
0.1.31 (2026-10-20)
Bugfix release for two defects proven on 0.1.30; prices and limits unchanged.
Migration idempotency (defect B): after a platform migration the replacement process used to re-deliver and re-charge every offer from scratch (canary run charged 28 search + 28 detail events for 8 unique offers). The run's default KV now keeps a DELIVERY_STATE ledger: delivered offerIds re-seed the global dedup set, already-charged amounts re-scope the billing plan, and the run base fee is collected at most once per run even across processes. A replacement process whose contract is already fully delivered closes out with zero scraping, zero pushes and zero charges (SUMMARY.resumedAfterMigration).
Genuine no-result keywords no longer fail (defect A): 1688 pads the result counter with fuzzy-match recommendations (a gibberish keyword returned found=60, offers=0), which the old found===0 gate mislabeled as a search failure and escalated to the full anti-bot fallback ladder, ending FAILED. A well-formed empty response with found <= 100 is now classified as a business NO_MATCH — the run ends SUCCEEDED with errorCode=NO_MATCH after a single search request. Real risk-control interception (RGV587/x5sec/TMD) keeps failing the run. Classification rules are documented in docs/failure-semantics.md.
0.1.30 (2026-10-17)
Keyword search and product details now run first on browser-free HTTP identities: each identity is one sticky residential session whose cookies and mtop token are obtained over plain HTTP. Search uses mobile request headers, details use desktop headers, and an identity that hits risk control is replaced immediately (per-identity call quotas, three consecutive dead identities switch that tier off for the run). The browser mint is no longer started at run start; it starts only when a fallback needs it.
The previous browser-backed chain (in-page mtop, minted-jar HTTP, HTML ladder, browser search, browser detail groups) remains the fallback for whatever the HTTP tier cannot serve. A recent pool-health lock now skips only the browser detail fallback instead of downgrading the whole run to cards.
Fresh-identity recovery and the browser search fallback are allowed once per user keyword (0.1.29 allowed them once per run, which starved later keywords in multi-keyword runs). SUMMARY adds httpIdentity counters and searchVia: mtop_identity. PRODUCT_HTTP_IDENTITY=off restores the browser-first behaviour.
Includes the 0.1.29 candidate changes below. Event prices and limits are unchanged.
0.1.29 (candidate only, 2026-10-17)
Never selected as the public default build; superseded by 0.1.30.
Keyword detail searches start a fresh bootstrap rather than repeatedly checking the minter's rejected shared jar. Product token writes use an isolated record key; monitoring and direct-offer entry points retain cache reuse.
An in-flight bootstrap is not replaced by a competing lazy bootstrap. The first search wait covers the bootstrap's existing 60-second bound, and retries of dead tokens bypass the rejected cache.
Fresh full-chain recovery and browser search fallback each have a single run-wide allowance. Two blocked search results stop further synonymous queries. SUMMARY.searchGuard exposes the network/time cost estimate, admission limit and stop reason; this estimate is not an exact platform bill or a mid-request hard limit.
Collected rows are checkpointed in PENDING_RESULTS after offer batches and on persistState. Final delivery retains global sorting and factual event settlement; checkpoints are not billing receipts or automatic resume states.
A risk-control or infrastructure zero-delivery run now ends FAILED, while genuine NO_MATCH and empty-input runs remain uncharged.
The input form exposes optional direct offerIds without rejecting existing numeric IDs. SEO title uses Product Scraper, Wholesale Prices, MOQ and Price Monitor together. Event prices and the 200-product input limit are unchanged.
0.1.28 (2026-10-17)
Docs only; code identical to 0.1.27, prices unchanged.
Corrected the keyword claim. README and the keyword field said every English keyword is "translated to Chinese search terms internally"; in fact only pet-supply keywords are mapped (the built-in dictionary is pet-only), and every other English keyword is sent to 1688.com as typed, where 1688's own search matches it (e.g. phone case and stainless steel tumbler return relevant results in cloud runs on 2026-10-07/08). The copy now says exactly that.
Titles are described as machine-translated (free translation API chain with a dictionary fallback, as the Translation honesty rule already stated) instead of "AI-translated"; the dataset schema says titleEn is empty when no clean translation exists.
Removed the link to the retired 1688 Pet Supplies Scraper and merged the two "For AI agents" sections into one.
Local actor.json now mirrors the live Store description and SEO fields (push never syncs them).
0.1.27 (2026-10-07)
Corrected exact-budget accounting: eventChargeLimitReached=true is not an invariant violation when the requested count was fully charged; it only means later charges of that event are blocked.
Includes the complete 0.1.26 candidate repair below; 0.1.26 was never selected as the public default build.
0.1.26 (candidate only, 2026-10-07)
Corrected SDK atomic-push accounting: Actor.pushData(items, eventName) can return an aggregate chargedCount spanning the explicit event and the platform dataset event. Delivery accounting now uses the explicit event's ChargingManager.getChargedEventCount() delta, so SUMMARY, pushed-prefix selection and platform reconciliation cannot double-count rows.
Includes the complete 0.1.25 candidate repair below; 0.1.25 was never selected as the public default build.
0.1.25 (candidate only, 2026-10-07)
Budget and delivery safety repair without a price change: current subscription-tier event prices and the platform run cap are read from ChargingManager; the effective cap is the lower of input/platform limits; detail and run-base-fee are reserved; and product/monitor rows use the SDK atomic pushData(rows, eventName) path (budget limit → push prefix → charge). Detail/base settle only after a successful row push, factual ChargeResult counts drive the ledger, and terminal retries are single-flight so one batch is not pushed or charged twice.
Small includeDetails=true requests now auto-downgrade to card-only before proxy/browser startup instead of returning DETAILS_MIN_20. SUMMARY discloses requested/effective/actual delivery shape and reason.
Cloud warm-cache hits now pass one browser-channel signed canary gate before reuse. Rejections close once, never clear shared KV, and fall through to self-mint; raw acceptance and later business success are reported separately.
Search zero outcomes now distinguish NO_MATCH, RISK_CONTROL_ZERO, and infrastructure failures. Only risk-control zero receives one fresh-identity retry.
SUMMARY now includes deliveryShape, tokenPath, billingPlan, factual charged counts, errorBuckets, platform-count reconciliation and costGuard.freeRows=0.
Event prices are unchanged.
0.1.24 (2026-09-16)
Listing copy changes from this release remain below.
Store title/description/SEO rewritten around what buyers actually search for: the title leads with 1688 Scraper — Product & Supplier Search, MOQ & Landed Cost instead of a brand-style name plus a price tag. meta.description (145 chars) and store-copy.md (300-char Store description) are kept byte-identical to each other so the three disclosure surfaces cannot drift.
New README FAQ section answering the long-tail questions buyers arrive with: no 1688 account or Chinese needed, how to search in English, how to find factories, why 20 items is the enrichment minimum, what dropshipReadyScore and the landed-cost estimate actually mean, and that failed or zero-result runs cost $0.
Cheaper starter prefill: the input form now proposes phone case / 30 items instead of led strip lights / 40, so a first trial lands near $0.25. maxItems.default stays 40 — API callers who omit the field are unaffected.
One-click Task list expanded to 11 entries, leading with the new keyword-matrix tasks (wholesale search, supplier lookup, MOQ & price-range scan, US landed cost, dropship readiness, shortlist price monitor). The price-range task is the first one to exercise priceMin/priceMax.
Cross-Actor CTA to the TikTok Shop Scraper in the sourcing-toolkit section.
Buyer-facing copy no longer names competitors — the comparison table describes capability differences instead, and a source comment lost its competitor reference.
meta.categories realigned to ["ECOMMERCE","AGENTS"] to match the live Store listing. The local file declared only ECOMMERCE, so a future push would have silently dropped the AI-agents category placement.
0.1.20 (2026-09-05)
Fix (P1-4 fallout, found by the R2M-7b-equivalent run): the dataset schema rejected card-level rows whose detail response carries no weight module — weightKg: null failed "type": "number" (forensics run RPahJyL8ctKR7pvul: 2 of 3 delivered rows 400-rejected, run FAILED with 0 items; keyword card-only never tripped it only because the search-card parser omits the key entirely, while the offerIds card-only path always materialises weightKg: null). A full all-null schema audit surfaced three more fields with the same code-contract-vs-schema mismatch and they are fixed together: price.min (extractPriceFromContext ships {min:null,…} when no price module; price.max already allowed null — the original author covered max but missed min), salesTotal (parseTransactionCount returns null for listings without a transaction count) and quantityPrices[].minQuantity (tiers whose beginAmount is null are kept). All four now accept null; wrong types (strings) are still rejected. Pinned by tests/task273-schema-null-tolerance.test.js (N1–N8) using the real ajv validator.
Ops: data-safety.js flush failures now log the platform's invalidItems payload (bounded JSON dump) so future schema rejections are diagnosable from the run log without a forensics build.
0.1.18 (2026-09-05)
Fix (P1-4, 方案 A): offerIds + includeDetails=false no longer silently delivers 0 products (run ui0FeqEeUkejFXArA). The combination now means CARD-LEVEL delivery — every offerId goes through the mtop-primary transport (fetchDetailsParallel) and is delivered at card level only (no detail enrichment, no description images), billed search-item × N + run-base-fee exactly like the keyword card-only path (5 IDs ≈ $0.035). Offers the transport cannot resolve are honestly absent and never billed; SUMMARY.detailStatus reports card_only.
Fix (P1-1): card-level paths with no search phase (Price Monitoring snapshots and the new offerIds card-only path) now seed the cached minter jar into a live warm page (Crawler1688.seedWarmCardPage) BEFORE the batch fetch — previously a settled cache-hit token stayed page-less, so fetchDetailsParallel skipped tier-1 (warm-page probe) and tier-1.5 (retained reuse) and fell straight to tier-2 fresh mtopdet* sessions (R2M-1: 3/3 RGV587, Cliff breaker TRIPPED, while the SAME token seeded by the keyword search tier delivered 20/20 — R2M-6b). A teardown leak guard (releaseSearchFallbackBrowser) closes any unconsumed seeded page on both paths.
0.1.17 (2026-09-04)
Docs (P1-2 semantic clarification): the README input table now documents the offerIds direct-fetch path and states explicitly that includeDetails=true (details enrichment) requires maxItems ≥ 20 — smaller batches fall to the DETAILS_MIN_20 guard (honest refusal, $0 charged); use includeDetails=false for small card-only lookups — and that Price Monitoring (watchlistOfferIds) is a separate mode not subject to that guard (any watchlist size 1–200). The wall itself is unchanged: it is the 0.1.7 economic red line (detail enrichment breaks even only at batch scale).