# Best Buy Canada Store Stock & Clearance Tracker (`automatic/bestbuy-ca-store-stock`) Actor

Best Buy Canada stock at each store near a postal code: units on hand per store, address, distance, regular and sale price, discount and clearance flags. Search, category, clearance or SKU list; rank by discount, distance or stock; track restocks, sell-outs and markdowns.

- **URL**: https://apify.com/automatic/bestbuy-ca-store-stock.md
- **Developed by:** [Philip Kirkbride](https://apify.com/automatic) (community)
- **Categories:** E-commerce, Business
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 product in a stores

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Best Buy Canada Store Stock & Clearance Tracker

Compare **reported stock at nearby Best Buy Canada stores with national
online prices** — for one product, a list of products, a search, a category
or the clearance section.

Give a Canadian postal code and a radius. The actor finds the Best Buy stores
around you, checks every product at every store, and saves **one row per
product per store**: the store, its address and distance, the units on hand,
the national online regular and current price, the discount, and whether
the item is on clearance. Rows can be ranked by **biggest discount, nearest store or most
units**.

Run it on a schedule in **Changes** mode and it tells you only what moved
since last time: a product **back in stock**, a store that **sold out**, a
stock count that changed, a **price drop**, or a product a store no longer
carries.

### Who it is for

- Deal hunters looking for clearance bargains at stores they can drive to
- Resellers watching stock and markdowns across several stores
- Anyone waiting for a sold-out product to come back at a nearby store
- Price-tracking and stock-alert apps that need store-level Best Buy Canada
  availability as a daily feed
- Market researchers comparing clearance depth by city or province

### Quick start

1. Open the Input tab. The form is prefilled with a Toronto postal code
   (`M5V 3L9`), a 25 km radius, the 10 nearest stores, **Clearance only** and
   12 products.
2. Click **Start**. A typical run finishes in a few minutes, because each
   product is checked at every selected store.
3. Open the Output tab, or export the rows as JSON, CSV or Excel.

A postal code is the only required field; a run without one stops at once
with a message asking for it, and nothing is charged beyond the run start.
To watch a fixed list, put SKUs in **Products** and switch **Mode** to
*Changes since the last run*.

### What you get

One row per product per store:

- **Product** — SKU, name, brand, category, UPC, model number, product link
  and image
- **Store** — store number, name, type (regular store, Best Buy Express, Best
  Buy Mobile, Express kiosk), street address, city, province, postal code,
  latitude and longitude
- **Distance** — straight-line kilometres from your postal code (or from the
  exact point you give)
- **Stock** — units on hand at that store as Best Buy states them, an in-stock
  / out-of-stock flag, whether the store offers pick-up for it, and Best
  Buy's own store and online availability labels word for word, plus the
  number of units available online
- **Prices** — regular price and current price in Canadian dollars
- **Discount** — percent and dollars off, **only when Best Buy itself shows a
  current price below the regular price**
- **Clearance and sale** — Best Buy's clearance and on-sale flags, its offer
  labels (for example *On Clearance* or *40% Off or More*) and the sale end
  date as published
- **Rank** — the row's place in the order you chose
- **Change details** (Changes mode) — what changed, the previous stock and
  prices, the price drop since the last run, and when the product was first
  seen at that store

#### About discounts on clearance items

Best Buy often shows clearance items with the regular price equal to the
current price, so a clearance row can say *On Clearance* with no percentage.
The actor never makes up a discount. `discountPct` and `discountAmount` are
derived comparisons with the current regular reference price, not observed
historical or pre-clearance drops. They do not establish local purchase cost
or resale profit. When you track clearance items in
Changes mode, a markdown since your previous run is reported as
`priceDropFromLastSeen` (dollars) and `priceDropPctFromLastSeen` (percent),
and the discount ranking takes it into account.

### Input

| Field | What it does |
| --- | --- |
| Postal code | Where to look, for example `M5V 3L9` (required) |
| Radius (km) | Keep stores within this distance, 1 to 50 km (25 by default) |
| Maximum stores | How many of the nearest stores to check, up to 30 (10 by default) |
| Latitude / longitude | Optional exact starting point for distances, for example your home |
| Store types | Regular stores by default; add Best Buy Express, Best Buy Mobile or Express kiosks |
| Products | SKUs or product links to check, for example `19860982` |
| Search | Words to search for, for example `oled tv` |
| Category | A Best Buy Canada category link or number |
| Clearance only | Only products Best Buy lists as on clearance |
| Minimum discount | Only products listed at 20%, 30%, 40%, 50% or 60% off or more |
| Maximum products from the search | How many products to take from the search, category or clearance list (20 by default, up to 200) |
| Target distinct in-stock products | Optional `targetInStockProducts`, 1 to 200; find this many different SKUs with a positive local unit count, within the separate scan budget |
| Include out-of-stock stores | Also show stores that list the product with zero units (off by default) |
| Sort by | Biggest discount (default), nearest store, or most units |
| Maximum rows | How many product-store rows to save (200 by default, up to 5,000) |
| Mode | Full results (default) or Changes since the last run |
| State key | In Changes mode, a name for this watch list; keep it the same on every scheduled run |

You can combine them: for example a search for `headphones` with Clearance
only, or a category with a minimum discount, plus a few SKUs you always want
checked.

#### Find a target number of in-stock products

Set `targetInStockProducts` to count **different SKUs with a positive unit
count at a selected local store**. One SKU at three stores counts as one
product. Online units, missing counts and an availability flag without a
positive count do not satisfy the target. Out-of-stock checks still use
part of the scan budget.

For example, `targetInStockProducts: 5` with `maxProducts: 50` looks through
up to 50 different listing products to find five with local stock. It stops
reading the listing when the target is met. `maxProducts` is the discovery
scan budget; `maxItems` still caps product-store rows. Neither limit becomes
a promise of five delivered products. The listing may run out, the time or
spending limit may stop the run, or a row cap may leave fewer distinct
products in the saved results.

SKUs you put in **Products** are still checked first, so this explicit list
can exceed the target. Omit the target to keep the existing full-scan
behavior. In Changes mode, a target stop leaves unvisited products for a
later run; it never reports them as gone. An unchanged positive-stock
product counts toward the observed target even when Changes mode saves no
new row for it. To inspect every tracked product on each bounded run, leave
the target empty.

The **Run summary** separates `productsScanned` (distinct SKUs whose details
were attempted), `inStockProducts` (distinct SKUs with positive local units),
`items` (delivered rows), `deliveredProducts` (all delivered distinct SKUs)
and `deliveredInStockProducts` (delivered SKUs with positive local units).
`targetReached` describes the observed target; `targetDelivered` describes
what was saved. `scanStopReason` says whether the target, discovery budget,
listing end or another limit ended the scan. `deliveryStopReason` names a
row, spending or state-save limit when it cut delivery short. A target stop
is a partial look at the wider product scope, so `scanComplete` is false;
products not reached remain unknown.

#### Example input

```json
{
  "postalCode": "M5V 3L9",
  "radiusKm": 25,
  "maxStores": 10,
  "clearanceOnly": true,
  "searchQuery": "tv",
  "maxProducts": 20,
  "sortBy": "discount"
}
```

#### Example output row

```json
{
  "sku": "19181772",
  "title": "Lenovo Idea Tab Pro 12.7-inch 256GB, Wi-Fi, 8GB RAM, MediaTek Dimensity 8300 - Grey",
  "brand": "LENOVO",
  "url": "https://www.bestbuy.ca/en-ca/product/lenovo-idea-tab-pro-12-7-inch-256gb-wi-fi-8gb-ram-mediatek-dimensity-8300-grey/19181772",
  "image": "https://multimedia.bbycastatic.ca/multimedia/products/1500x1500/191/19181/19181772.jpg",
  "category": "Android Tablets",
  "upc": "198155632846",
  "modelNumber": "ZAE40174CA",
  "storeId": "965",
  "storeName": "Warden",
  "storeType": "regular",
  "address": "50 Ashtonbee Rd., Unit 2",
  "city": "Scarborough",
  "province": "ON",
  "postalCode": "M1L 4R5",
  "latitude": 43.73121,
  "longitude": -79.2863,
  "distanceKm": 12.71,
  "stockQuantity": 21,
  "stockStatus": "in_stock",
  "hasInventory": true,
  "isReservable": true,
  "pickupStatus": "InStock",
  "onlineStatus": "InStock",
  "onlineQuantity": 848,
  "regularPrice": 559.97,
  "salePrice": 559.97,
  "currency": "CAD",
  "priceScope": "national_online",
  "priceStoreId": null,
  "discountPct": null,
  "discountAmount": null,
  "isClearance": true,
  "isOnSale": false,
  "offerLabels": ["On Clearance"],
  "saleEndsAt": "2026-11-06 12:00:00 AM",
  "rank": 8,
  "changeType": null,
  "changedFields": [],
  "previous": null,
  "priceDropFromLastSeen": null,
  "priceDropPctFromLastSeen": null,
  "firstSeenAt": null,
  "lastSeenAt": null,
  "observedAt": "2026-11-04T22:30:00+00:00"
}
```

### Changes mode

Set **Mode** to *Changes since the last run* and give a **State key**. The
first run saves what it finds (every row is `new`). Each later run with the
same key saves only rows where something moved:

- **new** — a product seen at a store for the first time
- **backInStock** — the store had none last time and has units now
- **outOfStock** — the store had units last time and has none now
- **stockChanged** — the unit count changed
- **priceChanged** — the current price, regular price or clearance flag
  changed; a markdown shows in `priceDropFromLastSeen`
- **gone** — the product itself has disappeared from Best Buy and its page
  is gone. If a store simply leaves a product out of its answer, the actor
  treats that as unknown and reports nothing for that store.

`previous` holds the stock and prices from the last run, and `changedFields`
names what moved. A store or product the run could not reach, or a store that
did not list the product, is never reported as gone. If nothing moved, the
run saves no rows. Keep the same postal code, stores and product list for a
state key, and schedule the actor (for example every morning) to get a daily
restock and markdown feed.

A very large watch list may need several runs. Each run saves its completed
results and reports any partial coverage; later runs continue through the
watch list. Keep the same state key and product selection between runs.

### Good to know

- Stock is what Best Buy reports for each store at the moment of the run;
  shelves can change quickly, so call the store before you drive.
- A store availability flag can be present without a published unit count.
  Missing counts stay null and do not satisfy a distinct in-stock product
  target.
- `priceScope` is `national_online`: current and regular-reference prices
  are Best Buy's national online values. `priceStoreId` is null because no
  shop-specific price was observed. The stock belongs to `storeId`; a shop
  may charge a different price. These fields also appear when prices are
  missing, without claiming that a price was observed. A `gone` row has no
  current prices; its `previous` prices are from the last observed run.
- A store that does not carry a product at all gives no row for it; out-of-
  stock stores are left out unless you turn on *Include out-of-stock stores*.
- Stores are searched up to about 50 km from the postal code.
- If a run cannot finish (for example it runs out of time), it saves what it
  completed and says so in its status.

### FAQ

**How fresh is the stock?** Every run reads stock and prices live at run time.
Nothing is cached between runs.

**Which stores are covered?** Best Buy Canada stores within about 50 km of
the postal code you give, up to 30 stores per run. Regular stores by default;
Best Buy Express, Best Buy Mobile and Express kiosks on request.

**Are the prices store prices?** No. Prices are Best Buy's national online
regular and current prices (`priceScope: national_online`); the stock count
is per store. A store may charge a different price at the till.

**Does it cover marketplace sellers?** No. Only products sold by Best Buy
itself, because only those have store stock.

**How do I get daily restock alerts?** Use Changes mode with a fixed State
key and a schedule (for example every morning), then connect the dataset to a
webhook, email or spreadsheet integration.

**What export formats are there?** JSON, CSV, Excel, XML and RSS from the
dataset, like any Apify run.

### Data and privacy

The actor collects public product, price, stock and store information that
Best Buy Canada shows to every visitor. It does not collect personal data.

### Pricing

The run-start event costs **$0.005 per GB of memory**, with a minimum of one
event. The default 512 MB run is one start event, charged even if no rows are
saved. Each delivered product-store row costs **$0.0005**. Changes mode saves
and charges only the changed rows, plus the run start.

Worked costs: the prefilled run, capped at 100 rows, costs at most about
**$0.055**; 1,000 product-store rows cost about **$0.505**. A daily Changes
run that finds 20 changed rows costs about $0.015.

See the Pricing tab for current prices.

# Actor input Schema

## `postalCode` (type: `string`):

Canadian postal code to search around, for example M5V 3L9. Stores are found near this code.

## `radiusKm` (type: `integer`):

Keep stores within this straight-line distance, from 1 to 50 km.

## `maxStores` (type: `integer`):

How many of the nearest stores to check, from 1 to 30.

## `latitude` (type: `number`):

Optional exact starting point for distances, for example your home. Stores are still found near the postal code; distances and the radius are then measured from this point. Give longitude too.

## `longitude` (type: `number`):

Optional exact starting point for distances, used together with latitude.

## `storeTypes` (type: `array`):

Which kinds of Best Buy store to include. Regular Best Buy stores by default; add Best Buy Express and Best Buy Mobile for small items such as accessories and phones.

## `products` (type: `array`):

Best Buy Canada SKUs (for example 19860982) or product page links to check. Every listed product is checked.

## `searchQuery` (type: `string`):

Words to search Best Buy Canada for, for example "oled tv". Combine with Clearance only or a minimum discount to find deals.

## `category` (type: `string`):

Optional Best Buy Canada category link (for example https://www.bestbuy.ca/en-ca/category/laptops-macbooks/20352) or category number.

## `clearanceOnly` (type: `boolean`):

Only products Best Buy lists as On Clearance.

## `minDiscount` (type: `string`):

Only products Best Buy lists at this discount or more.

## `maxProducts` (type: `integer`):

How many products to take from the search, category, clearance or discount listing, from 1 to 200. Listed SKUs are always checked on top.

## `targetInStockProducts` (type: `integer`):

Find this many different SKUs with an observed positive unit count at one or more selected local stores (1 to 200). Stores and online units do not count as separate products. Maximum products remains the separate discovery scan budget; row, time and spending limits may yield fewer. Explicitly listed SKUs are checked first and may exceed the target. Leave empty for the existing full scan.

## `includeOutOfStock` (type: `boolean`):

Also return stores that list the product with zero units. Off by default, so you only see stores that have it.

## `sortBy` (type: `string`):

Biggest discount first, nearest store first, or most units first.

## `maxItems` (type: `integer`):

How many product-store rows to save, from 1 to 5000.

## `mode` (type: `string`):

Full saves every product-store row. Changes saves only what moved since the last run with the same state: new rows, stock changes, back in stock, out of stock, price changes and products no longer listed at a store.

## `stateKey` (type: `string`):

In Changes mode, a name for this watch list's saved state. Use the same key on every scheduled run. Leave empty to derive it from the place and products.

## `proxyConfiguration` (type: `object`):

Connection settings. Apify Proxy is used by default.

## Actor input object example

```json
{
  "postalCode": "M5V 3L9",
  "radiusKm": 25,
  "maxStores": 10,
  "storeTypes": [
    "regular"
  ],
  "clearanceOnly": true,
  "minDiscount": "",
  "maxProducts": 12,
  "includeOutOfStock": false,
  "sortBy": "discount",
  "maxItems": 100,
  "mode": "full",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset rows include priceScope and nullable priceStoreId to distinguish retailer-wide prices from per-store stock; see the linked dataset schema.

## `runSummary` (type: `string`):

Counts scanned SKUs, observed positive-stock SKUs, delivered distinct SKUs and rows separately, including target and delivery limits.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "postalCode": "M5V 3L9",
    "radiusKm": 25,
    "maxStores": 10,
    "clearanceOnly": true,
    "maxProducts": 12,
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("automatic/bestbuy-ca-store-stock").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "postalCode": "M5V 3L9",
    "radiusKm": 25,
    "maxStores": 10,
    "clearanceOnly": True,
    "maxProducts": 12,
    "maxItems": 100,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("automatic/bestbuy-ca-store-stock").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "postalCode": "M5V 3L9",
  "radiusKm": 25,
  "maxStores": 10,
  "clearanceOnly": true,
  "maxProducts": 12,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call automatic/bestbuy-ca-store-stock --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automatic/bestbuy-ca-store-stock"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/MviClitIWckmEl4mK/builds/Tlarmrpq5bH3d5TDt/openapi.json
