Amazon Catalog Variation Quality Auditor
Pricing
from $10.50 / 1,000 catalog assessments
Amazon Catalog Variation Quality Auditor
Audit Amazon product datasets before catalog import. Flag price, field and variation inconsistencies in each catalog assessment while keeping missing values, currencies and source scope explicit.
Pricing
from $10.50 / 1,000 catalog assessments
Rating
0.0
(0)
Developer
Tim Zinin
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Amazon Catalog Variation Quality Auditor — turn a refreshed product batch into a pre-ingestion quality audit
Feed it an existing Amazon product dataset and get back one catalog assessment per ASIN + observed marketplace — missing price/currency/stock, conflicting duplicate listings, and parent/variant hierarchy issues — before that batch reaches your catalog import.

This Actor does not scrape Amazon. It reads a product batch you already have — most naturally from Amazon Product Scraper and/or E-commerce Scraping Tool — and turns it into one catalog assessment per unique ASIN + verified marketplace host: whether the listing has a usable price, currency, stock status, and required catalog fields, whether two source rows sharing that ASIN actually agree with each other, and whether its parent/variant references are internally consistent. It is a pre-ingestion quality gate for teams who already have a product batch — not a scraper, not a pricing engine, and not a claim of exhaustive variation coverage.
What you get
For each distinct ASIN + observed marketplace found across your source batch, you get one assessment record containing:
entityId—<uppercased ASIN>::<marketScope>. The ASIN alone does not identify a marketplace offer: the same ASIN can legitimately be a different listing on amazon.com and amazon.co.uk, so the verified marketplace host is part of the identity, never inferred from currency.asin/marketScope— the bare uppercased ASIN, and the verified marketplace host (e.g.amazon.com,amazon.co.uk) this assessment is scoped to, or the literalunknown-marketwhen no source row for this ASIN carried a URL that verifiably references it on a real Amazon marketplace host.status—complete,complete_scope_qualified,incomplete_catalog_data,conflicting_duplicate, orvariant_hierarchy_issue.complete_scope_qualifiedmeans every observed field is complete but the source row never captured avariants/variationsfield at all, so full variation coverage is never claimed.priceState/listPriceState—dataset_missing|observed_absent|zero|present|unparseable. A negative number or an overflow string is alwaysunparseable, never accepted as a real price; an explicit$0iszero, kept distinct from a genuinely missing price.price/listPrice—{ value, currency, currencySymbol }when present or zero, otherwisenull.currencyis only ever a resolved ISO-4217 code (ornullwhen the symbol was ambiguous, like a bare$or¥— see Evidence and boundaries);currencySymbolpreserves the raw signal even when it couldn't be resolved.stockState—dataset_missing|observed_absent|present|unrecognized. Only a real boolean, or a recognized status string (including the schema.orgInStock/OutOfStockURLs), ever yields a knowninStockvalue.canonicalUrl/canonicalUrlSource— the source's own validated product URL when it actually references this ASIN on a known Amazon marketplace host (source_verified), otherwise a generatedhttps://www.amazon.com/dp/<ASIN>convenience link that never proves anything about the actual marketplace (generated_us_assumed).missingAttrs— which expected catalog fields (title,brand,price,currency,stockStatus,attributes,stars,reviewsCount,breadCrumbs) were absent, blank, or uninterpretable on this row.statuscan never becomplete/complete_scope_qualifiedwhile this is non-empty.variantParent— an object describingparentAsin/variantspresence, format validity, self-reference and duplicate-entry issues. A listing that lists its own currently-selected ASIN among its own variants (selfInVariants) is common, legitimate scraper output and is recorded as informational only, never an error.duplicateSourceRowCount— how many source rows shared this exact ASIN and market scope.evidence— the matched field, value, and rule behind every claim, tied to a source row index.- Honest diagnostics for everything else — an invalid row, a source-reported failure, a missing ASIN, a malformed ASIN, or an identity with no interpretable audit dimension are all free, separate rows.
What this Actor is explicitly not: no profit, price-history, or currency-conversion inference; no invented stock status; no claim of a complete or exhaustive variation enumeration.
Who uses it
- Ecommerce operations teams validating a refreshed competitor or supplier Amazon product batch before it reaches catalog ingestion, instead of spot-checking rows by hand.
- Catalog/data-quality teams who need a defensible, evidence-linked reason a listing is flagged incomplete or conflicting, not an opaque score.
- Pricing and merchandising teams who need missing/ambiguous currency and price signals surfaced explicitly, rather than silently defaulted to USD.
- Marketplace-expansion teams auditing the same ASIN across multiple Amazon marketplace hosts, using
marketScopeto keep amazon.com and amazon.co.uk listings for the same product properly separated rather than collapsed into a false conflict.
Not a scraper: point it at an existing Amazon product Dataset producing the row shape in Input contract, most naturally Amazon Product Scraper (users30d=2053, successfulRuns30d=327,736) or E-commerce Scraping Tool (users30d=656, successfulRuns30d=121,595). This Actor never starts either one, and never fetches a page from the open web.
How to run
- Console (fastest for a one-off run). Open the Actor, go to Input, paste rows into Source records (inline) or fill in Source Dataset, leave defaults, hit Start.
- From an existing product Dataset. Put its Dataset ID in
datasetId— read with your own run's permissions, no separate authorization step. - Chained via Integrations. Add this Actor as an "Actor/task run" integration on your Amazon-scraper run; map
{{resource.defaultDatasetId}}into this Actor'sdatasetIdfield explicitly (see Integration recipes) for aREADgrant — a bare Dataset ID or webhook payload alone does not grant access. - Via the API. Call
POST /v2/acts/tXq8nALhVK7jrXa7R/runs(orrun-sync-get-dataset-items) withdatasetId(oritems) and, for a spending ceiling, the run optionmaxTotalChargeUsd. - Set your batch size.
maxInputRows(default 100, max 1000) bounds source rows examined;maxResults(default 10, max 1000) bounds delivered assessments independently of spend. - Read the results. One row per unique ASIN + marketplace lands in the run's default Dataset, with a matching
OUTPUT/SUMMARYrecord.
Every run is independent processing — see Operating guide for what that means for re-running against the same batch.
Pricing
Pay-per-event, one event type: result-found ("Catalog assessment" in the platform's pricing UI — the primary pricing noun used throughout this listing, the Dataset terminology, and the pricing record itself) — one delivered catalog assessment (one unique ASIN + marketplace). There is no start fee, and every free diagnostic (an invalid row, a source-reported failure, a missing/malformed ASIN, a budget stop, an empty batch) requests no charge event at all.
| Tier | Price per catalog assessment | Discount |
|---|---|---|
| Free (default) | $0.015 | — |
| Bronze | $0.0135 | 10% |
| Silver | $0.012 | 20% |
| Gold | $0.0105 | 30% |
| Platinum | $0.0105 | 30% |
| Diamond | $0.0105 | 30% |
These are Apify's platform-wide account pricing tiers — not a volume schedule this Actor invents — assigned automatically by your account's usage; you don't select one. One catalog assessment is one unique ASIN + marketplace, never billed per duplicate source row.
A note on the evidence in this README: every output example below comes from the product owner's own verification runs against this Actor's accepted build — cost to the owner, not sales or customer revenue. Source-side dates shown in the underlying evidence (_exampleProvenance) are the original, retained snapshot metadata, not a fresh scrape performed for this README.
Set the platform's own Max total charge (USD) run option to cap spend. If testing the zero-paid path via a raw API call, do not rely on literal maxTotalChargeUsd: 0 — use a small positive value below every tier's price (e.g. $0.005, below Gold/Platinum/Diamond's $0.0105) instead, and confirm the run's actual effective cap after it starts rather than trusting the requested value silently — see Evidence and boundaries.
Input contract
| Field | Type | Default | Notes |
|---|---|---|---|
items | array | curated public prefill | Inline source rows; overridden by datasetId/runId/payload when set. Max 1000 items. |
datasetId | string (resourcePicker) | — | Existing source Dataset, read with your run's permissions. Overrides inline rows. |
runId | string | — | Alternative to datasetId: reads that run's own default Dataset with your run's permissions. |
payload | object | — | Optional ACTOR.RUN.SUCCEEDED webhook payload with resource.defaultDatasetId/resource.id. A bare payload does not grant access. |
maxInputRows | integer | 100 (max 1000) | Source rows examined before grouping; also bounded to 4 MiB total, 64 KiB/row. |
maxResults | integer | 10 (max 1000) | Useful catalog assessments delivered, independent of maxTotalChargeUsd. |
Source precedence (never merged): explicit datasetId/payload.resource.defaultDatasetId > runId/payload.resource.id > inline items. A malformed or conflicting reference is rejected outright, not silently ignored.
Supported adapter fields (the actual keys this Actor reads — from either upstream source above, or any dataset shaped the same way): asin/ASIN, url, title, price (a bare number, or {value, currency}), listPrice (same shape), a top-level currency (used only as a fallback when the price value itself carried no currency of its own), inStock/availability, brand, stars, reviewsCount, breadCrumbs, attributes (array), variants/variations (array of ASIN strings or {asin} objects), parentAsin. A row missing both asin and ASIN is a free missing_asin diagnostic; a value that isn't exactly 10 alphanumeric characters is a free invalid_asin_format diagnostic.
Output examples
Example 1 — a complete-but-currency-ambiguous listing (final-prefill, real public snapshot, exact Dataset row).
{"entityId": "B0FC5SJNQX::amazon.com","found": true,"status": "incomplete_catalog_data","action": "complete_missing_catalog_fields","confidence": 0.9,"sourceRowIndexesTruncated": false,"asin": "B0FC5SJNQX","marketScope": "amazon.com","canonicalUrl": "https://www.amazon.com/dp/B0FC5SJNQX","canonicalUrlSource": "source_verified","marketplaceHost": "amazon.com","title": "Logitech MX Master 4 Ergonomic Wireless Mouse with Haptics - Graphite","brand": "Logitech","price": { "value": 119.99, "currency": null, "currencySymbol": "$" },"priceState": "present","listPrice": null,"listPriceState": "observed_absent","inStock": true,"stockState": "present","stars": 4.3,"reviewsCount": 2019,"breadCrumbs": "Electronics > Computers & Accessories > Computer Accessories & Peripherals > Keyboards, Mice & Accessories > Mice","attributeCount": 33,"missingAttrs": ["currency"],"variantParent": {"hasParentField": false, "parentAsin": null, "parentAsinValidFormat": false,"hasVariantsField": false, "variantsState": "dataset_missing", "variantCount": 0,"variantAsins": [], "invalidVariantAsinCount": 0, "selfReferentialParent": false,"selfInVariants": false, "hasDuplicateVariants": false, "issue": null},"duplicateSourceRowCount": 1,"evidence": [{ "field": "currency", "value": null, "rule": "currency_unknown: a price value is known but its currency was not (checked both price.currency and a top-level currency field), so it must not be assumed or compared cross-currency", "sourceRowIndex": 0 },{ "field": "variants", "value": null, "rule": "variants_dataset_missing: source row has no variants/variations field; sibling-variant existence is unknown, not confirmed absent", "sourceRowIndex": 0 }],"businessPartial": true,"sourceRowIndexes": [0],"partial": true,"sourceCoverage": { "complete": true, "reason": null, "droppedOversized": 0 },"product": "amazon-catalog-variation-quality-auditor","schemaVersion": "1.0","sourceDatasetId": null,"sourceRunId": null,"suppliedSourceRunId": null,"sourceRunIdVerified": null,"processedAt": "2026-09-23T03:37:36.048Z"}
Note price.currency: null even though price: { value: 119.99, currency: "$" } was on the source row: a bare $ is used by USD, CAD, AUD and more — it is never resolved to a specific ISO code, only preserved verbatim as currencySymbol. missingAttrs: ["currency"] reflects exactly this.
Example 2 — the same listing under a controlled incomplete-source test ("partial-columns" scenario), exact Dataset row. This actor's own adversarial-provenance.json states: "Historical label only. Actual source values retained; explicit found:true/partial:true injected as a controlled incomplete-source flag. This is a derived adversarial scenario, not observed upstream incompleteness or actual column removal." Every field below is unedited from the actor's own real partial-columns receipt — only the injected partial:true source flag (and its consequence, one added evidence entry and a lower confidence) distinguishes it from Example 1; no column was actually removed from this fixture:
{"entityId": "B0FC5SJNQX::amazon.com","found": true,"status": "incomplete_catalog_data","action": "complete_missing_catalog_fields","confidence": 0.8,"sourceRowIndexesTruncated": false,"asin": "B0FC5SJNQX","marketScope": "amazon.com","canonicalUrl": "https://www.amazon.com/dp/B0FC5SJNQX","canonicalUrlSource": "source_verified","marketplaceHost": "amazon.com","title": "Logitech MX Master 4 Ergonomic Wireless Mouse with Haptics - Graphite","brand": "Logitech","price": { "value": 119.99, "currency": null, "currencySymbol": "$" },"priceState": "present","listPrice": null,"listPriceState": "observed_absent","inStock": true,"stockState": "present","stars": 4.3,"reviewsCount": 2019,"breadCrumbs": "Electronics > Computers & Accessories > Computer Accessories & Peripherals > Keyboards, Mice & Accessories > Mice","attributeCount": 33,"missingAttrs": ["currency"],"variantParent": {"hasParentField": false, "parentAsin": null, "parentAsinValidFormat": false,"hasVariantsField": false, "variantsState": "dataset_missing", "variantCount": 0,"variantAsins": [], "invalidVariantAsinCount": 0, "selfReferentialParent": false,"selfInVariants": false, "hasDuplicateVariants": false, "issue": null},"duplicateSourceRowCount": 1,"evidence": [{ "field": "sourceRow", "value": null, "rule": "source_admitted_partial: source row(s) reported partial:true; any failure warning is retained only with explicit found:true; this result preserves source incompleteness", "sourceRowIndex": 0 },{ "field": "currency", "value": null, "rule": "currency_unknown: a price value is known but its currency was not (checked both price.currency and a top-level currency field), so it must not be assumed or compared cross-currency", "sourceRowIndex": 0 },{ "field": "variants", "value": null, "rule": "variants_dataset_missing: source row has no variants/variations field; sibling-variant existence is unknown, not confirmed absent", "sourceRowIndex": 0 }],"businessPartial": true,"sourceRowIndexes": [0],"partial": true,"sourceCoverage": { "complete": true, "reason": null, "droppedOversized": 0 },"product": "amazon-catalog-variation-quality-auditor","schemaVersion": "1.0","sourceDatasetId": null,"sourceRunId": null,"suppliedSourceRunId": null,"sourceRunIdVerified": null,"processedAt": "2026-09-23T03:37:48.061Z"}
Example 3 — below-price boundary run, exact free diagnostic row (real settled receipt). With maxTotalChargeUsd set to $0.005 (below every tier's $0.0105+ price), the run reads no source at all:
{"product": "amazon-catalog-variation-quality-auditor","schemaVersion": "1.0","status": "budget_stopped","reason": "Run charge limit cannot cover any result; no source was read.","found": false,"processedAt": "2026-09-23T03:38:02.315Z"}
Example 4 — empty inline source, exact free diagnostic row (real settled receipt).
{"product": "amazon-catalog-variation-quality-auditor","schemaVersion": "1.0","status": "no_results","found": false,"reason": "no_usable_rows_in_batch","sourceCoverage": { "complete": true, "reason": null, "droppedOversized": 0 },"processedAt": "2026-09-23T03:38:10.555Z"}
Field dictionary
Result fields: entityId (<ASIN>::<marketScope>); asin; marketScope (verified marketplace host or unknown-market); status (complete | complete_scope_qualified | incomplete_catalog_data | conflicting_duplicate | variant_hierarchy_issue); action (no_action | no_action_scope_qualified | complete_missing_catalog_fields | reconcile_conflicting_duplicate | review_variant_hierarchy); confidence (starts at 1, floored at 0.15 — reduced only by the specific deductions listed under Evidence and boundaries; a missingAttrs entry outside that list, such as stars/reviewsCount/breadCrumbs, still drives status:'incomplete_catalog_data' but does not by itself reduce confidence); priceState/listPriceState (dataset_missing/observed_absent/zero/present/unparseable); price/listPrice ({value, currency, currencySymbol} or null); stockState (dataset_missing/observed_absent/present/unrecognized); inStock (boolean or null); canonicalUrl/canonicalUrlSource (source_verified/generated_us_assumed); marketplaceHost; title/brand (truncated to 500 chars); stars (valid only in 0..5); reviewsCount (valid only as a nonnegative safe integer); breadCrumbs; attributeCount; missingAttrs (array of field names); variantParent (see below); duplicateSourceRowCount; evidence (up to 50 entries); sourceRowIndexes/sourceRowIndexesTruncated (up to 50).
variantParent object: hasParentField/parentAsin/parentAsinValidFormat; hasVariantsField/variantsState (dataset_missing | invalid | confirmed_none | observed); variantCount/variantAsins (up to 50, uppercased, format-validated); invalidVariantAsinCount (malformed entries excluded from variantAsins but disclosed here, never silently dropped); selfReferentialParent (a listing's own parentAsin equals its own ASIN); selfInVariants (informational only — a product commonly lists its own currently-selected ASIN among its variant siblings, never treated as a hierarchy error by itself); hasDuplicateVariants; issue (null, or one of variants_field_malformed | parent_asin_format_invalid | self_referential_parent | variant_asin_format_invalid | duplicate_variant_entries, checked in that order — but self_referential_parent unconditionally overwrites whatever was already detected, so a row that is both malformed AND self-referential reports self_referential_parent, never the earlier-checked issue; among the other four, the first one detected wins).
Free product-specific diagnostic states: invalid_row (unusable row shape); source_row_error (a hard-failure signal — found:false, failed:true, or a status like failed/error/timed-out/aborted — always wins over a contradictory found:true/partial:true claim on the same row); missing_asin (no usable asin/ASIN); invalid_asin_format (present but not exactly 10 alphanumeric characters); insufficient_data_for_audit (an identity with no interpretable price, stock, variant/parent reference, or attributes to audit — a bare title/brand is not an auditable dimension by itself).
Common shared-runtime free Dataset notices (apply identically across the whole value-add product line, not specific to this Actor): invalid_input (a malformed input contract — bad maxResults/maxInputRows, conflicting or invalid dataset/run identifiers, no source provided at all — rejects the run as a diagnostic before any read is attempted, not a crash); source_unavailable (the input was well-formed but the actually-selected dataset/run could not be read — an HTTP error, invalid JSON, or unexpected shape/page size — a FAILED outcome, not a silent empty result); partial_source (the source read stopped early — carries reason: total_byte_cap_reached | pagination_interrupted | invalid_json_page | unexpected_shape | unexpected_page_size | max_input_rows_reached | oversized_rows_dropped); source_rows_dropped (carries count of rows dropped for exceeding the 64 KiB per-row cap); result_dropped_oversized (carries count of business results that individually exceeded 64 KiB after decoration and were dropped rather than delivered); budget_stopped (carries reason; when it occurs mid-run rather than before any read, also carries delivered/totalUseful); max_results_reached (carries reason, delivered, totalUseful); no_results (the run wrote nothing else at all). Every free notice's error/reason field is a short, disclosed code — never a raw exception message or untrusted input value.
Runtime OUTPUT/SUMMARY fields: schemaVersion/product/errorCode (a specific failure code — see FAQ — or null); fatalFailure (true only when outcome:'failed'); outcome (delivered | budget_stopped | max_results_reached | no_results | diagnostic | failed); deliveryUncertain — true under either of two named ledger conditions: FREE_WRITE_FAILED (an unbilled/free write failed and may or may not exist) or DELIVERY_UNKNOWN (a paid row's delivery/charge outcome could not be confirmed); replayBlocked — true on either of two distinct refusal causes: the run's own result-found event counter didn't match its expected record (EXISTING_RESULT), or this run's claim key was already set from a prior attempt and automatic resurrection is unsupported (EXISTING_CLAIM); requested — the count of source rows counted within the read bound, including any later dropped for being oversized; this is never maxResults; usefulResults — every schema-valid business result transform() produced, counted before the delivery loop applies maxResults/budget caps (but after per-row 64 KiB oversized filtering, which is counted separately in failed); delivered/paid — confirmed, actually-written paid rows only; free — every free row actually written, uncapped total; failed — business results dropped for exceeding the 64 KiB per-row cap after decoration; businessPartialResults — counts all partial results transform() actually produced, computed directly on the raw business output before any oversized/delivery filtering, not merely what was ultimately delivered; sourcePartial/sourcePartialReason — true/the disclosed reason when the source read itself stopped early (mirrors the partial_source notice's reason); sourceDroppedOversized — count of source rows dropped for exceeding the 64 KiB per-row read cap; budgetStopped/resultsCapped — true when this run's own delivery loop stopped early on the spend cap or the maxResults cap respectively (mirrors outcome, exposed as discrete booleans for programmatic checks); oversizedDiagnosticsDropped/diagnosticsTruncated (see Evidence and boundaries for the exact 200-row mechanics); partialOverall (true if any of: source partial, oversized drops, diagnostics truncated, budget stopped, results capped, or any business-partial result); budgetMode/maxTotalChargeUsd; replaySafety; businessSummary (this product's own transform() summary object, verbatim — see below); processedAt.
businessSummary (this Actor's own fields): totalSourceRows; resultsCount; diagnosticsCount — business-only diagnostics transform() itself produced, never including the runtime's own partial_source/source_rows_dropped notices layered on top (see Evidence and boundaries); statusBreakdown (counts per status value); observedAt — always null in every canary in this README, because this Actor never reads a source-side capture timestamp field at all; it only ever reflects a caller-supplied context.observedAt, which the shared runtime deliberately never synthesizes from wall-clock/processing time (see Evidence and boundaries). A blank observedAt here is honest, not a bug.
Evidence and boundaries
Currency is never guessed from an ambiguous symbol. Only an explicit 3-letter ISO 4217 code, or an unambiguous symbol (€→EUR, £→GBP), is ever resolved into currency. A bare $ is used by USD, CAD, AUD and others; a bare ¥ is used by JPY and CNY — both are preserved only as currencySymbol and never defaulted to USD/JPY. When a nested price.currency and a top-level currency field disagree, that is disclosed as currency_conflicting_top_level_vs_nested, never silently resolved to either one — even when both would otherwise land on the same fallback value.
Stock is never inferred. stockState is present (with a real inStock boolean) only from a genuine boolean, or an exact recognized status string (instock/outofstock/true/false/yes/no, including the schema.org InStock/OutOfStock URL forms) — matching is exact set membership, never substring ("NotInStock" is never misread via .includes('instock')). A missing or blank field is dataset_missing/observed_absent, never assumed in-stock or out-of-stock.
Variation coverage is never claimed as exhaustive. variantsState: 'dataset_missing' means the source row never captured a variants/variations field at all — sibling-variant existence is genuinely unknown, not confirmed absent. This can never coexist with status: 'complete'; the qualified complete_scope_qualified status exists specifically so a fully-observed-but-variant-blind row is never conflated with a genuinely complete, variant-checked one. A variants array, when present, reflects only what this one source row reported — never a claim of complete catalog variation enumeration, stated explicitly in the variant_enumeration_unverified evidence rule.
Marketplace scope is never inferred from currency. A single ASIN observed on two different verified Amazon marketplace hosts (e.g. .com vs .co.uk) is reported as two separate market-scoped assessments, never forced into one false conflict — and a row with no verified marketplace URL falls into one shared unknown-market scope, genuinely unresolved rather than asserted distinct.
Hard-failure priority. A hard failure signal (found:false, failed:true, or a normalized status like failed/error/timed-out/aborted) always overrides a contradictory found:true/partial:true claim on the same row — a source cannot simultaneously report "this row failed" and "this row is usable." A non-empty error/errors field with no hard-failure signal is retained as a harmless warning ONLY when the same row explicitly carries both found:true and partial:true; in every other case — including a valid row with a bare error/errors value and no found:true/partial:true — it still produces the free source_row_error diagnostic with zero results for that row.
confidence deductions are a fixed, specific list — not "one per missing/conflicting signal." Starting from 1 and floored at 0.15: missingAttrs includes title (−0.15), brand (−0.1), price (−0.15), currency (−0.05), or attributes (−0.15); missingAttrs includes stockStatus (−0.08 when stockState:'unrecognized', else −0.05); variantParent.issue is non-null (−0.1); a conflicting duplicate is detected (−0.2); sourceRowIndexesTruncated (−0.05); the row's variantsState is dataset_missing (−0.05); the source itself admitted partial:true (−0.1). A missingAttrs entry outside this list — stars, reviewsCount, breadCrumbs — still drives status:'incomplete_catalog_data'/missingAttrs disclosure, but by itself deducts nothing from confidence; a row missing only those three fields keeps confidence:1.
No blanket 200-free-row cap, and businessSummary does not count every notice. The initial diagnostics array the shared runtime writes consists of this product's own report.diagnostics plus the runtime's own partial_source/source_rows_dropped notices, combined and then sliced to a 200-row write cap. businessSummary.diagnosticsCount counts business-only diagnostics — uncapped, so it can exceed 200. diagnosticsTruncated covers that combined (business + runtime-notice) list, not the business-only count. Separately, later notices — result_dropped_oversized, budget_stopped, max_results_reached — are written on top of the 200-row cap and are not counted against it. This actor's own mixed-boundary receipt (1000 synthetic-failure rows ahead of 2 real rows, maxResults:1) confirms it exactly: businessSummary.diagnosticsCount: 998 (uncapped business count), 200 source_row_error rows actually written to the free cap, plus 1 additional max_results_reached notice on top — 201 total free rows, diagnosticsTruncated: true, while usefulResults: 2 (both real assessments the batch actually produced, before the maxResults:1 cap dropped one to delivered: 1).
Zero-charge cap normalization. A raw API maxTotalChargeUsd: 0 is normalized per a recorded observation — a positive value below every tier's price (Example 3 above used $0.005) reliably guarantees zero paid results, but the run's effective enforced cap should be inspected after the run starts, not simply trusted from the request; a positive cap that is preserved as requested still enforces spend correctly, it is only the literal 0 value whose normalized behavior must be verified rather than assumed.
Billing sequencing. Dataset-write happens before charge confirmation and is non-transactional. An ambiguous outcome finalizes the run as FAILED with no automatic retry and a possible unconfirmed row. There is no cross-run exactly-once delivery guarantee — a new run is new processing and can bill again.
Exact numeric and length caps, as coded, not paraphrased: MAX_EVIDENCE = 50 entries per result; MAX_ARRAY = 50 (both variantAsins and sourceRowIndexes display caps); MAX_STRING_LEN = 500 characters (title, brand, breadCrumbs, and the invalid_asin_format error preview). Shared runtime source-read bounds: at most 1000 rows, 4 MiB total source JSON, 100 rows per page, 64 KiB per row.
Source capture time is genuinely unknown when absent — never relabeled from another date or from processing time. This Actor does not read or parse any source-side capture/scrape timestamp field at all. businessSummary.observedAt reflects only a caller-supplied context.observedAt; the shared runtime deliberately never substitutes a synthetic "now" for it, so it is honestly blank in every canary in this README rather than fabricated from the moment this run happened to execute. A product-side date shown in evidence (e.g. _exampleProvenance in the input prefill) is retained input-example metadata, not a live re-scrape performed for this listing.
Decision routing
status: 'conflicting_duplicate'→ route to manual reconciliation before ingestion; two source rows for the same ASIN+market disagree on a materially interpreted field.status: 'variant_hierarchy_issue'→ route to catalog-structure review (self-referential parent, malformed/duplicate variant ASINs).status: 'incomplete_catalog_data'→ route to a field-completion queue keyed bymissingAttrs.status: 'complete_scope_qualified'→ treat as usable but explicitly flag that variation coverage for this ASIN was never checked by the source; do not present it internally as "no variants confirmed."- Free diagnostics (
missing_asin,invalid_asin_format,insufficient_data_for_audit) → route to a data-quality backlog for the upstream scraper, not the ingestion queue.
Commercial playbooks
- Pre-ingestion gate. Run every refreshed competitor/supplier batch through this Actor before it reaches your catalog database; block
conflicting_duplicate/variant_hierarchy_issuerows from auto-import. - Currency-hygiene audit. Track how often
missingAttrsincludescurrencyacross a vendor's exports as an objective signal of how reliable that vendor's price data actually is. - Marketplace-expansion tracker. Use
marketScopeto confirm the same catalog is actually represented (and complete) across every Amazon marketplace host you sell in, rather than assuming parity. - Duplicate-listing cleanup. Use
duplicateSourceRowCountandconflicting_duplicatetogether to find and reconcile scraper exports that captured the same listing twice with drifting values.
Integration recipes

This Actor's ID is tXq8nALhVK7jrXa7R. A natural upstream source is Amazon Product Scraper (Actor ID BG3WDrGdteHgZgbPK); E-commerce Scraping Tool (Actor ID 2APbAvDfNDOWXbkWf) works identically for a broader-scope export.
1. Console-native chaining (no code). On your Amazon-scraper run page, open Integrations, add an "Actor/task run" action targeting this Actor, and let the Integrations UI populate the payload with {{resource.defaultDatasetId}}. Map that value explicitly into this Actor's datasetId field — not the generic payload pass-through alone — to request READ access under LIMITED_PERMISSIONS; the caller must already be authorized, and a private source can still deny access.
2. Direct webhook (POST /v2/webhooks), full recipe. Configure a webhook on your upstream scraper Actor for the ACTOR.RUN.SUCCEEDED event, scoped via condition.actorId, targeting this Actor's run-creation endpoint:
{"isApifyIntegration": true,"shouldInterpolateStrings": true,"eventTypes": ["ACTOR.RUN.SUCCEEDED"],"condition": { "actorId": "BG3WDrGdteHgZgbPK" },"requestUrl": "https://api.apify.com/v2/acts/tXq8nALhVK7jrXa7R/runs?maxTotalChargeUsd=0.50","doNotRetry": true,"headersTemplate": "{\"Authorization\": \"Bearer <APIFY_TOKEN>\"}","payloadTemplate": "{\"datasetId\": \"{{resource.defaultDatasetId}}\", \"payload\": {\"resource\": {\"defaultDatasetId\": \"{{resource.defaultDatasetId}}\", \"id\": \"{{resource.id}}\"}}, \"maxResults\": 50}"}
Submit this to POST https://api.apify.com/v2/webhooks with your own Authorization: Bearer <APIFY_TOKEN> header. The <APIFY_TOKEN> placeholder is never a literal token embedded in the template — it is filled in from your own secrets-provisioning store at the moment the webhook is registered, not an implicit platform template variable that resolves automatically. doNotRetry: true avoids a duplicate-charge risk from an automatic retry. datasetId is set explicitly — the resource picker or an authorized Integrations mapping can only request READ access; it never guarantees the platform actually grants it. A positive maxTotalChargeUsd on the request URL caps this chained run's spend; avoid the literal value 0 (see Evidence and boundaries) — a preserved positive value keeps enforcing your intended cap for the life of the run.
3. Generic automation tools (n8n, Zapier, Make, or your own tooling). Use a generic Apify/HTTP node to call run-sync-get-dataset-items on this Actor's ID with a JSON body containing datasetId and your chosen maxInputRows/maxResults — a plain authenticated HTTPS POST, no native Apify-integration support required. The same isApifyIntegration/payloadTemplate shape from recipe 2 applies to any integration definition your own tooling submits.
4. Export / catalog-import mapping. Every run's Dataset supports CSV/Excel/JSON export via the Console or GET /v2/datasets/{datasetId}/items?format=csv. asin, marketScope, status, missingAttrs, and canonicalUrl alone drive a working pre-ingestion gate in a spreadsheet or PIM import.
Operating guide
- Confirm your source batch shape first. Spot-check rows against Input contract — each needs a usable
asin/ASIN, or you'll mostly getmissing_asindiagnostics. - Start with a small
maxResultson a new source to confirm output matches expectations before a full batch. - Set
maxTotalChargeUsdvia the platform run option, and check its effective value after the run starts. For a guaranteed-zero-paid API call, use a small positive value below $0.0105, not literal0— see Evidence and boundaries. - Read the
OUTPUT/SUMMARYrecord, not just the Dataset row count — it reportsdelivered/paid/free/failed, whetherbudgetStopped/resultsCappedended the run early, anddiagnosticsTruncated. - If a run finalizes
FAILED, inspect its Dataset andOUTPUTrecord before retrying — a blind retry can duplicate already-successful paid assessments. - Re-running is always new billing — no cross-run dedup key. Reserve it for a new or updated batch, not a routine safety retry.
FAQ
Does this Actor scrape Amazon itself? No. It only reads an existing product batch you or an upstream Actor already collected — no open-web fetch, no upstream Actor started.
Does a bare $ price get treated as USD? No. A bare $/¥ symbol is preserved as currencySymbol but never resolved to a specific ISO code — currency stays null and missingAttrs includes currency, since $ alone doesn't distinguish USD from CAD/AUD/others.
If a row has no variants field at all, does that mean the product has no variations? No. variantsState: 'dataset_missing' means the source never captured that field — sibling-variant existence is unknown, not confirmed absent. The result status is complete_scope_qualified, never a plain complete claim of full variation coverage.
Does the same ASIN on amazon.com and amazon.co.uk collide into one result? No. marketScope is derived only from a verified marketplace URL, never from currency, so the same ASIN on two real marketplace hosts produces two separate assessments.
What does a FAILED run mean? The runtime can finalize FAILED for several distinct reasons, not one: a pricing-verification failure (PRICING_UNREADABLE/PRICING_UNSUPPORTED/DATASET_PRICE_UNSAFE/RESULT_PRICE_UNSAFE) before any processing starts; a spend/budget-ledger failure (BUDGET_UNREADABLE/COUNTER_UNREADABLE/CLAIM_UNKNOWN/TRANSPORT_UNSAFE); an ambiguous delivery outcome (DELIVERY_UNKNOWN/FREE_WRITE_FAILED, reflected in deliveryUncertain); the product module itself misbehaving (ALGORITHM_FAILED); the selected source being unreadable (SOURCE_UNAVAILABLE); or the final OUTPUT/SUMMARY write itself failing after results were already delivered (FINALIZATION_FAILED). None of these auto-retry — always inspect the Dataset and OUTPUT record first.
Is the "partial-columns" style example a demonstration of missing upstream data? No — see Output example 2. Every field is identical to Example 1's real source snapshot; only the injected partial:true source flag (and its consequence, one added evidence entry and a lower confidence) is synthetic — not a simulation of real column removal.
Why did one run report 201 free rows instead of the usual 200-row diagnostic cap? The initial business-diagnostic list is capped at 200 entries when written, but a later result-size/cap notice like max_results_reached is added on top and not counted against that cap — see Evidence and boundaries for this actor's own confirmed 998/200/201 numbers.
Sources and rights
Public source example: DeJTcFjqeE3iVyUzz — a curated, non-sensitive snapshot of a real upstream Amazon product run, with original values and provenance retained. All output examples and canary numbers in this README come from the product owner's own verification runs — cost to the owner, not customer sales or revenue.
Related tools
Related tools for adjacent e-commerce workflows, plus this Actor's own verified upstream sources.
| Actor | What it does |
|---|---|
| Shopify Catalog & Merchandising Intelligence | Pair it in the e-commerce workflow: qualify Shopify merchant leads with evidence-backed public catalog coverage and observed price positioning. |
| Shopify Store Price & Catalog Change Monitor | Pair it in the e-commerce workflow: monitor store-level price range, catalog size, currency, and Shopify detection over time. |
| Zid Product Export Intelligence | Pair it in the e-commerce workflow: turn merchant-authorized Zid product exports into stable evidence for the same kind of catalog quality review. |
| Amazon Product Scraper | This Actor's own verified primary upstream source — the one used to capture the public example above. |
| E-commerce Scraping Tool | This Actor's own verified secondary upstream source, for a broader-scope multi-marketplace export shape. |