# API reference — Amazon Bestsellers Scraper + AI: Rising Products Only

Back to the [Actor page](https://apify.com/rich_minds/amazon-product-finder-ai). Reference tables for integrators.

## One dataset item

One real row of the free demo (rules only, `chargedEvent: demo`):

```json
{
  "itemId": "4debd4cce8ac",
  "name": "Silicone Stretch Lids Set of 12 - Reusable Food Covers for Bowls, Cups and Cans",
  "url": "https://www.amazon.com/Silicone-Stretch-Lids/dp/B0DEMO0001",
  "asin": "B0DEMO0001", "rank": 3, "stars": 4.5, "reviewsCount": 412,
  "priceText": "$14.99", "priceValue": 14.99, "currency": "USD", "marketplace": "amazon.com",
  "categoryPath": ["Kitchen & Dining", "Food Storage Covers"],
  "listTypes": ["movers-and-shakers", "bestsellers"], "listRanks": {"movers-and-shakers": 3, "bestsellers": 18},
  "isNew": false, "rankChange": 14, "firstSeenAt": "2026-09-27T08:00:00+00:00",
  "flags": ["rising_rank", "multi_list", "low_barrier"], "ruleScore": 82, "score": 82, "tier": "hot", "label": "hot",
  "verdict": "pursue", "riskFlags": [], "assessmentSource": "rules", "aiModel": null,
  "chargedEvent": "demo", "dedupeKey": "asin:amazon.com:B0DEMO0001"
}
```

`tier` is the hot / warm / cold field; `label` carries the same value for one more minor version (0.1 name).

## The `OUTPUT` record

| Key | Type | Meaning |
|---|---|---|
| `ok` | boolean | `false` when the source was refused or failed and nothing loaded — filter on it |
| `qualified` | integer | products delivered |
| `mode` | string | `demo`, `live`, `list` or `dataset` |
| `stats` | object | the funnel: loaded, merged, skipped per reason |
| `chargedEvents`, `chargeSplit` | object | events charged; how many rows were free, basic, AI |
| `aiCoverage`, `aiError` | object / string | how many rows the AI assessed, and why not the rest |
| `sourceError`, `sourceRefused` | string | why the source delivered nothing (`sourceRefused: "plan"` for a plan refusal) |
| `sourceStatus` | object | `reader` (`own` = the built-in reader), `status`, `partial: true` when a source run stopped early |
| `scheduleTaskUrl`, `reportUrl` | string | the saved Task and the client-ready report page |
| `trend`, `history`, `timing`, `nextSteps` | object / array | run-over-run trend, run series, phase timing, next steps |

## Run outcomes in full

| Outcome | Run status | Dataset | `OUTPUT` | Charged? | What to do |
|---|---|---|---|---|---|
| Free demo (the pre-filled sample) | SUCCEEDED | sample products | `qualified` | no | type a niche and press Start |
| Success | SUCCEEDED | qualified products, best first | funnel, `chargedEvents`, `aiCoverage` | per qualified product | — |
| Nothing matched | SUCCEEDED | empty | `qualified: 0` | not by us | loosen `minScore` / filters |
| Demo text still in place | SUCCEEDED | empty | `inputNeeded` | no | replace the field the status names |
| Invalid input | FAILED | empty | — | no | fix the field the status names |
| Amazon blocked the reader and your plan cannot run Store Actors | SUCCEEDED from the Console / API; FAILED on a scheduled run | empty | `ok: false`, `sourceError`, `sourceRefused: "plan"` | no | paste list-mode rows, or use a plan that runs Store Actors |
| Source failed (nothing loaded) | FAILED | empty | `ok: false`, `sourceError` | no | follow the reason in the status |
| Source partial (a list timed out after scraping) | SUCCEEDED | products from the rows scraped | `sourceStatus` (`partial: true`) | per qualified product | raise `discoveryTimeoutSecs` or scan fewer lists |
| AI unavailable | SUCCEEDED | rule-scored products | `aiError`, `aiCoverage` | free while free products last, then basic | `llmProvider: byok` |

| Input shape | `OUTPUT.mode` | Can it charge? |
|---|---|---|
| `{}` or the untouched form (the sample rows) | `demo` | no |
| the sample rows + your niche, list URLs or goal typed next to them | `live` | yes — per qualified product |
| the same, with `autoSwitchToLive: false` | `demo` | no |
| `sourceMode: "actor"` + `niches` / `categoryUrls` | `live` | yes |
| `sourceMode: "list"` + your `itemsList` rows | `list` | yes, no source run |
| `sourceMode: "dataset"` + `datasetId` | `dataset` | yes, no source run |

## Webhook contract

`webhookBatchSize: 1` sends `{"event": "product.qualified", "product": {…}, "runId": "…"}` per product; a larger size sends `{"event": "products.qualified", "products": [{…}, …], "runId": "…"}` per batch. The 0.1 keys `"product opportunity"` / `"product opportunities"` carry the same value for one more minor version. A 429 / 5xx / network error is retried once after 2 s; a POST that still fails is counted in `OUTPUT.webhook.failed` and never fails the run.

## Every field of a row

| Field | Description |
|---|---|
| `itemId`, `name`, `url` | Identity of the product — stable across runs; `url` is the product page without the `ref=` tail |
| `asin`, `marketplace`, `imageUrl` | Amazon id, domain (`amazon.de`), thumbnail |
| `rank`, `listTypes`, `listRanks`, `listLabel`, `categoryUrl`, `categoryPath` | Best rank, every list the product is on with its rank, the list's title and URL, the category breadcrumb |
| `stars`, `reviewsCount` | Rating and review count — `null` when the list shows none |
| `priceText`, `priceValue`, `currency`, `listPriceText` | Price as shown, parsed (`5,95 €` → 5.95), ISO currency, struck-through price; `priceValue` is `null` when unreadable — never guessed |
| `isNew`, `rankChange`, `firstSeenAt` | From this search's memory: new, previous rank − current rank, first sighting; `null` with the memory off |
| `flags`, `targetFlagsMatched` | Rule-based findings and which of your target flags matched |
| `ruleScore`, `fitScore`, `score`, `label` | Rule score, AI fit score, the final ranking score, hot/warm/cold |
| **Canonical fields** | `url` and `score` are the fields to map; no other field carries their value |
| **Which score to trend on** | `ruleScore` — the same rule scale on every row and every run; `score` is the AI fit where `aiModel` is set (a rate-limited own key assesses only some rows), else the rule score, so sort one run on `score` but threshold and trend across runs on `ruleScore` |
| `qualified`, `verdict`, `assessmentSource` | Always `true`; `pursue` / `watch` / `skip` (AI, else from the score); `llm` or `rules` |
| `demandNote`, `competitionNote`, `angle`, `niche` | AI notes (empty with AI off); `niche` falls back to the last category |
| `riskFlags` | `brand_locked` / `commodity` / `regulated` — title word lists plus the AI's quoted flags |
| `scoreReasons`, `summary`, `recommendedNextStep` | AI assessment (empty when AI is off) |
| `generatedText` | The sourcing line — the AI's when it is on, a rule-based one on hot / warm products when it is off |
| `aiModel`, `chargedEvent`, `billedAs`, `dedupeKey`, `source`, `scrapedAt` | Provenance and billing (`billedAs` = the price tier, also on free rows) |

**Which record to read:** `OUTPUT` is the full run record (`RUN_SUMMARY` is the same record under its older name): the funnel, skip counts per reason, charged events, `aiCoverage`. A source that fails before anything loads **fails** the run with `sourceError`; an unavailable AI says so in the status and `aiError`.

## Flags

| Flag (`targetFlags` value) | Meaning |
|---|---|
| `rising_rank` | on Movers & Shakers, or climbed 10+ ranks since the last run |
| `new_release` | listed in New Releases — a young product already ranking |
| `wished_for` | on Most Wished For — shoppers are saving it for later |
| `multi_list` | appears on 2+ lists — demand shows in several places |
| `low_barrier` | fewer than 500 reviews while ranking — a barrier a new listing can beat |
| `high_barrier` | 5,000+ reviews — a review moat that is slow and costly to match |
| `weak_quality` | rated under 4.0 stars — room for a better product |
| `sweet_price` | priced 15–60 (USD / EUR / GBP style) — the private-label band |
| `missing_price` | no price on the list page, price checks are skipped |
| `brand_locked` | the title names a major brand — hard to enter with a private label |
| `commodity` | commodity wording (cables, packs, batteries, cases) — thin margins |
| `regulated` | regulated wording (supplement, medical, CBD, knives …) — compliance work first |

The three risk flags are matched on whole words of the title from fixed word lists (`Pineapple` is not `Apple`); the AI adds only flags it can quote from the title. Their precision is not measured on a labelled sample yet (dated in the roadmap).

## Niche presets

```json
{"sourceMode": "actor", "categoryUrls": ["https://www.amazon.com/gp/movers-and-shakers/kitchen", "https://www.amazon.com/gp/new-releases/kitchen"],
 "goalDescription": "Kitchen gadgets a new private-label listing can beat: rising, under 1,500 reviews, no big brand.",
 "maxReviews": 1500, "onlyNewOrRising": true}
```

```json
{"sourceMode": "actor", "categoryUrls": ["https://www.amazon.com/gp/new-releases/pet-supplies"],
 "goalDescription": "Young pet products that already rank, with a low review barrier and a price between $10 and $50.",
 "maxReviews": 300, "priceMin": 10, "priceMax": 50, "minStars": 4}
```

```json
{"sourceMode": "actor", "categoryUrls": ["https://www.amazon.com/gp/bestsellers/home-garden", "https://www.amazon.com/gp/most-wished-for/home-garden"],
 "goalDescription": "Home products shoppers wish for and buy, where the review count is under 3,000.",
 "maxReviews": 3000, "excludeBrands": ["amazon basics", "rubbermaid", "oxo"]}
```


## Which call runs what

| Input shape | `OUTPUT.mode` | Can it charge? |
|---|---|---|
| `{}` or the untouched form | `demo` | no |
| sample rows + a niche / list URL typed next to them, or `sourceMode: "actor"` + `niches` / `categoryUrls` | `live` | yes, per qualified product |
| `sourceMode: "list"` / `"dataset"` + your rows | `list` / `dataset` | yes, no source run |

