# Staples Canada Store Stock & Clearance Tracker (`automatic/staples-ca-store-stock`) Actor

Staples Canada in-store stock near a postal code: one row per product and store with the live unit count, distance, regular and sale price, discount and clearance flag. Look up SKUs or product links, or list clearance, sale, search or category items; track restocks and price drops.

- **URL**: https://apify.com/automatic/staples-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.75 / 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

## Staples Canada Store Stock & Clearance Tracker

Find out which Staples stores near you have an item on the shelf, how many
units each store has, and how much it is marked down. Give a Canadian postal
code and the products you care about, or let the actor list Staples
clearance, sale, search or category items, and you get **one row per product
and store**: the store's address and distance, the live unit count for
in-store pickup, the regular and current price, the discount and the
clearance flag.

Run it again later in change mode and it tells you only what moved: items
that came back in stock, sold out, changed count or dropped in price.

### Who it is for

- Deal hunters checking Staples clearance before driving to a store
- Resellers watching restocks and markdowns across several stores
- Price-tracking and stock-alert apps that need store-level Staples Canada
  availability on a schedule
- Shoppers comparing stores in a city for one printer, chair or laptop

### Quick start

1. Open the Input tab. The form is prefilled with a downtown Toronto postal
   code (`M5V 3L9`), the 5 nearest stores and 10 listed products.
2. Click **Start**, then open the Output tab or export the rows as JSON, CSV
   or Excel.

An empty input (`{}`) also works: with no products, search, category or
filter, the actor lists Staples clearance items near `M5V 3L9` (20 products,
the 5 nearest stores within 25 km, at most 500 rows). Change the postal code
to search your own area.

### What you get

- **Product** — Staples item number (SKU), name, brand, product link and image
- **Store** — store number, address, city, province, postal code, map
  coordinates, store page, and the distance from your search point in km
- **Stock** — units available for pickup at that store, as Staples states
  it, with `in_stock` or `out_of_stock`
- **Price** — regular price, current price (CAD) and the percent off when
  Staples shows a regular price above the current one
- **Clearance** — whether Staples marks the item as clearance, plus the sale
  and clearance tags Staples attaches to it, word for word
- **Change details** (change mode) — what changed, the earlier unit count or
  price, how much the price fell, and when the pair was first and last seen

### Input

| Field | What it does |
| --- | --- |
| Postal code | Canadian postal code to search around (default `M5V 3L9`, downtown Toronto) |
| Latitude / Longitude | Optional exact point instead of the postal code area |
| Radius (km) | Only stores within this distance, 1 to 250 km (default 25 km) |
| Max stores | How many of the nearest stores to check (default 5, up to 25) |
| Products (SKUs or product links) | Up to 500 Staples item numbers or staples.ca product links to check at every store |
| Search words | Also check products matching these words |
| Category | A staples.ca category link or its last part, for example `pens-6745` |
| Clearance items only | List clearance items (with no products, search or category, the actor lists clearance items) |
| Discounted items only | List only items priced below their regular price |
| Max listed products | How many products to take from the search, category or clearance list (default 20, up to 500) |
| Only stores with stock | Leave out stores with zero units |
| Sort by | Biggest discount, nearest store, or most units first |
| Max rows | Most rows to deliver in one run (default 500, up to 10,000) |
| Mode | Full results, or only changes since the last run |
| State key | Name of the saved state that change mode compares against |

Example: clearance items at the five nearest stores to a Mississauga postal
code, biggest discount first.

```json
{
  "postalCode": "L5B 2C9",
  "clearanceOnly": true,
  "maxProducts": 30,
  "maxStores": 5,
  "sortBy": "discount"
}
```

Example: three items you are watching, every store within 40 km of Ottawa
city centre, only stores that have them.

```json
{
  "postalCode": "K1P 1J1",
  "radiusKm": 40,
  "maxStores": 10,
  "products": [
    "3099949",
    "https://www.staples.ca/products/31307-en-bic-round-stic-ballpoint-stick-pens-10mm-blue-60-pack",
    "24394127"
  ],
  "onlyInStock": true
}
```

### Change mode

Set **Mode** to *Only changes since last run* and pick a **State key**
(for example `toronto-clearance`). The first run delivers every row as `new`.
Each later run with the same key delivers only rows where something moved:

| Change | Meaning |
| --- | --- |
| `new` | First time this product is seen at this store |
| `backInStock` | Was 0 units, now has stock |
| `outOfStock` | Had stock, now 0 units |
| `stockChanged` | The unit count changed |
| `priceChanged` | The current price changed; `priceDropFromLastSeen` says how much it fell |
| `gone` | The item left the same clearance, search or category list this run read all the way to the end |

A row can carry more than one change (for example a restock with a price
drop); `changeTypes` lists them all and `changeType` shows the main one. Only
delivered rows are charged.

A product-store pair missing from an observation stays unknown, rather than
becoming a disappearance alert. A pair that remains unobserved for about six
months is no longer included in future comparisons. `gone` only follows a
complete product list, so a run that stopped
early or a list cut off at your product limit reports no `gone` rows. It must
also be the *same* list: if you reuse a state key but change the search words,
category or filters, the items saved from the old list are left alone rather
than reported as gone.

### Good to know

- Staples reports stock for in-store pickup. Units shown are what Staples says
  is available; shelves can differ, so call the store before a long drive.
- `priceScope` is `retailer_online`: current and regular-reference prices
  come from Staples' retailer-wide online product listing. `priceStoreId` is
  null because no shop-specific price was observed. Stock belongs to
  `storeId`; a shop may charge a different price at the till. The scope is
  also present on stock-only rows with missing prices. A `gone` row retains
  last-observed prices; it does not claim a fresh price for a removed item.
- `discountPct` is a derived comparison with the current regular reference
  price, filled only when it is higher than the current sale price. It is not
  an observed historical or pre-clearance drop, local purchase cost or resale
  profit. In change mode a price drop since
  the last run is shown in `priceDropFromLastSeen` even when Staples shows no
  regular price.
- Distance is a straight line from your exact point, or from the centre of
  the postal code area when you give only a postal code.
- The pickup message is passed on exactly as Staples sends it, which is
  currently in French for every store.
- If full product details could not be read on a run, rows still carry the
  stock, and `productDetails` says how complete the product fields are.
- Store and product data are public business information. The actor reads no
  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.00075**. Change mode saves
and charges only the changed rows, plus the run start.

Worked costs: a run that delivers 100 product-store rows costs about
**$0.08**; 1,000 rows cost about **$0.755**. A daily change-mode run that
finds 20 changes costs about $0.02.

See the Pricing tab for current prices.

### FAQ

**How fresh is the stock?** Each run reads units and prices at run time;
nothing is cached between runs.

**Which stores are covered?** Staples stores in Canada within the radius you
set (up to 250 km), nearest first, up to 25 stores per run.

**How do I get daily restock or clearance alerts?** Use change mode with a
fixed State key, the same location and product list, and a daily schedule.
Then send the dataset to a webhook, e-mail or spreadsheet integration.

**Why is the discount empty?** It is filled only when Staples shows a regular
price above the current one. Change mode still reports a price drop since your
last run in `priceDropFromLastSeen`.

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

### Output example

```json
{
  "productId": "6880465780865",
  "variantId": "40125293822081",
  "sku": "3099949",
  "title": "HP DeskJet 4275e All-in-One Printer with Bonus 3 months Instant Ink",
  "brand": "HP",
  "url": "https://www.staples.ca/products/3099949-en-hp-deskjet-4275e-all-in-one-printer-with-bonus-3-months-instant-ink",
  "image": "https://cdn.shopify.com/s/files/1/0036/4806/1509/files/4608d844b6a72add45285add98eefb837921f2c5_square3099949_1.jpg?v=1776813363",
  "storeId": "26",
  "storeName": "Staples",
  "address": "517 Richmond St. E.",
  "city": "Toronto",
  "province": "ON",
  "postalCode": "M5A 1R4",
  "latitude": 43.654142,
  "longitude": -79.3644767,
  "distanceKm": 3.21,
  "storeUrl": "https://stores.staples.ca/on/toronto/office-supplies-ca-26.html",
  "quantity": 2,
  "stockStatus": "in_stock",
  "pickupMessage": "Prêt en 2 h",
  "regularPrice": 134.99,
  "salePrice": 59.97,
  "currency": "CAD",
  "priceScope": "retailer_online",
  "priceStoreId": null,
  "discountPct": 55.6,
  "clearance": true,
  "saleLabels": [
    "clearance_centre|en_CA",
    "hp_brand_event|en_CA",
    "ink_and_toner_deals_2|en_CA",
    "tech_clearance|en_CA"
  ],
  "productDetails": "full",
  "observedAt": "2026-11-04T22:35:00.184441+00:00",
  "changeType": null,
  "changeTypes": [],
  "previousQuantity": null,
  "previousPrice": null,
  "priceDropFromLastSeen": null,
  "firstSeenAt": null,
  "lastSeenAt": null
}
```

# Actor input Schema

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

Canadian postal code to search around, for example M5V 3L9. Stores are ranked by distance from it. Leave empty only when you give latitude and longitude.

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

Optional. Search around this exact point instead of the postal code area. Give it together with longitude.

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

Optional. Search around this exact point instead of the postal code area. Give it together with latitude.

## `radiusKm` (type: `number`):

Only stores within this distance are checked.

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

How many of the nearest stores inside the radius to check.

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

Staples item numbers (SKU, for example 3099949) or staples.ca product links. Every product listed here is checked at every selected store.

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

Optional. Also check products matching these words, as the staples.ca search finds them.

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

Optional. A staples.ca category link (https://www.staples.ca/collections/...) or its last part, for example pens-6745.

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

List items Staples marks as clearance (combined with the search words or category when given). With no products, search or category, the run lists clearance items.

## `onSaleOnly` (type: `boolean`):

List only items whose price is below their regular price.

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

How many products to take from the search, category or clearance list. Products you list by SKU or link come on top.

## `onlyInStock` (type: `boolean`):

Leave out stores that have zero units. Applies to full results; change tracking always follows every listed store.

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

Order of the rows: biggest discount first, nearest store first, or most units first.

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

Most rows (product-store pairs) to deliver in one run.

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

Full returns every product-store row. Changes returns only what moved since the last run with the same state key: new rows, stock changes, restocks, sell-outs, price changes and rows no longer listed.

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

Name of the saved state used in Changes mode. Runs with the same key compare against each other. Leave empty to derive it from the location and product settings.

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

Connection used for the stock, store and product lookups. The default Apify proxy works.

## Actor input object example

```json
{
  "postalCode": "M5V 3L9",
  "radiusKm": 25,
  "maxStores": 5,
  "maxProducts": 10,
  "onlyInStock": false,
  "sortBy": "discount",
  "maxItems": 500,
  "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.

# 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",
    "maxStores": 5,
    "maxProducts": 10,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("automatic/staples-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",
    "maxStores": 5,
    "maxProducts": 10,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("automatic/staples-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",
  "maxStores": 5,
  "maxProducts": 10,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call automatic/staples-ca-store-stock --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automatic/staples-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/ZASUfTzqlyIYopCgc/builds/WZiSuhePNUF0LQYGT/openapi.json
