# Shopify Sales Tracker: Competitor Stock & Units Sold (`rivalstock/competitor-stock-tracker`) Actor

Track competitor sales from exact stock counts: units sold and revenue per product, sell-outs, restocks and price changes, plus an Excel report for the last 7 and 30 days. Shopify, WooCommerce, T-Soft, ikas, IdeaSoft, OpenCart, GoldenPlanet.

- **URL**: https://apify.com/rivalstock/competitor-stock-tracker.md
- **Developed by:** [Rival Stock](https://apify.com/rivalstock) (community)
- **Categories:** E-commerce, Business, Automation
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $2.50 / 1,000 product reads

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Shopify Sales Tracker: Competitor Stock & Units Sold

**See how much your competitors actually sell - as a ready-made Excel report.** Shopify Sales Tracker reads the **real number of units in stock** for every product variant (color, size...) of a competitor's online store - **not just an "in stock / out of stock" flag**. Run it on a schedule and it turns stock drops into **estimated units sold and revenue**, flags **sell-outs, restocks and price changes**, and saves an **Excel competitor report for the last 7 and 30 days** after every run.

It reads **Shopify**, **WooCommerce**, **T-Soft**, **ikas**, **IdeaSoft**, **OpenCart** and **GoldenPlanet** stores; the platform is detected automatically.

### What you get

- **Excel competitor report** after every run - top sellers, best-selling categories, price changes, sell-outs, restocks, new products and daily sales per store, for the last 7 and 30 days. No spreadsheet work.
- **Exact stock per variant** (e.g. `stockQuantity: 42`) wherever the store publishes it
- **Estimated units sold and revenue** since the previous run, row by row in the dataset
- Sell-outs, back-in-stock, restocks (with the number of units added) and price changes
- **Stock corrections filtered out** - placeholder counts such as 999, or a large stock cleared at once, are not counted as sales
- **Exact-stock coverage per store** in every run's status, so the first test tells you whether a store works
- Whole catalogs (thousands of products) or a short list of product pages

### Exact stock counts, not in-stock flags

Most stock monitors only mark a product as *in stock* or *out of stock*. That tells you nothing about sales: a product with 500 units and a product with 1 unit look the same.

| | Typical stock monitor / product scraper | Shopify Sales Tracker |
|---|---|---|
| Stock information | in stock / out of stock | **exact units in stock, per variant** |
| Sales | not possible, or do-it-yourself in a spreadsheet | **units sold and revenue per product, 7 and 30 days** |
| Report | raw rows | **Excel report: top sellers, categories, price moves, sell-outs** |
| Restocks | only when a product comes back from zero | **every restock, with the number of units added** |
| Platforms | usually Shopify only | Shopify, WooCommerce, T-Soft, ikas, IdeaSoft, OpenCart, GoldenPlanet |

### Which stores publish exact stock?

A count can only be read when the store publishes it on its pages or public storefront API. The Actor never guesses: when a count is missing, `stockQuantity` is `null`, and prices, availability, sell-outs and restocks are still tracked.

| Platform | Exact quantity | What we saw when testing real stores |
|---|---|---|
| T-Soft | ✅ every product | 11 of 11 stores |
| ikas | ✅ per variant | 4 of 4 stores |
| WooCommerce | ✅ when the shop manages stock for the product | most stores with stock management on |
| IdeaSoft | ✅ (not for products with selectable options) | 3 of 6 stores |
| OpenCart | ✅ when the theme shows the stock count (not for products with options) | depends on the theme |
| GoldenPlanet (OpenBizBox) | ✅ per variant (not for products with two or more option types) | |
| Shopify | ✅ when the theme or a stock app prints it on the product page | roughly 1 in 4 stores; often only some variants |

**Check a store before tracking it - for free:** the **[Shopify Price & Stock Checker](https://apify.com/rivalstock/store-stock-checker)** samples products across a store's whole catalog in about a minute and tells you whether the store publishes exact stock counts (and so whether its units sold can be tracked), with a ready input for this Actor. Every run of this Actor also shows the share in its status, e.g. `20 products, 64 variants (91% with exact stock)`, and the log warns when a store publishes no counts at all.

### The Excel competitor report

Every run saves `REPORT` (an `.xlsx` file) in the run's key-value store - open it from the run's **Output** tab. It covers all stores of the run:

| Sheet | What it shows |
|---|---|
| **Summary** | Per store: products, exact-stock %, units sold and revenue (since the previous run, 7 days, 30 days), sell-outs, units restocked, price increases / decreases, new products, inventory value |
| **Top sellers** | Products by revenue over 30 days, with units for 7 and 30 days, current price and stock |
| **Categories** | Units and revenue per category - which segments of the competitor grow |
| **Price changes** | Old and new price, change %, date |
| **Sold out** | Variants that sold out and are still out of stock - gaps your offer can fill |
| **Restocks** | Units added - big restocks usually mean a product sells well |
| **New products** | First seen date, price and stock |
| **Daily sales** | Units and revenue per day and store |
| **Stock adjustments** | Moves that were not counted as sales (placeholder counts, stock cleared at once) |

The report uses the history the tracker keeps between runs (up to 90 days), so it fills up as your schedule runs: the first run is a baseline, the second gives the first sales, and after a week the 7-day columns are complete. Turn it off with **Excel competitor report = false**.

### How units sold are estimated

Units sold are calculated by comparing **two runs** of the same tracker:

1. **First run - baseline.** The Actor records the stock of every variant. Rows are marked `changeType: "baseline"` and no sales are reported yet.
2. **Every following run.** Each variant is compared with its previous reading:
   - stock went down → `estimatedUnitsSold` = the drop, `estimatedRevenue` = units × previous price
   - stock went up → `restockedUnits`
   - in stock → out of stock → `sold-out`; out of stock → in stock → `back-in-stock`
   - price changed → `priceChange`
   - a placeholder count (99, 999, 9999...) set or cleared, or at least 90% of a stock of 500+ removed at once → `stock-adjustment`, with no units sold or restocked (the raw `stockChange` is kept)

👉 **Schedule the Actor** (for example once a day) with the same **Tracker name**, and every run gives you the sales since the previous one plus the 7- and 30-day report.

**Good to know**

- Estimated units sold are a **lower bound**: if the store restocked and sold between two runs, only the net change is visible. Shorter intervals between runs give more accurate numbers.
- Stock can also drop for reasons other than a web sale (marketplace or in-store sales, returns to supplier, corrections). The numbers describe stock movement on the store, not audited sales.
- Sales can only be estimated for variants with an exact quantity.

### Where the previous readings are kept

The previous readings and the sales history are stored in **named key-value stores in your own Apify account**: `stock-tracker-<tracker name>` for each tracker (records `snapshot-<store>` and `history-<store>`), and `stock-latest-readings` with the latest reading of every store you have tracked. Nobody else - including the Actor's developer - can see them.

Each run is compared with the previous run of the same tracker. A **new or renamed tracker** has no previous run of its own, so it is compared with the latest reading of the same store from any tracker - you get units sold right away instead of a new baseline (the 7/30-day history starts again for the new name). Store URLs typed with or without `www.` count as the same store. Delete both stores to start over from a baseline.

### Input

| Field | What it does |
|---|---|
| **Competitor stores** | Home page URLs of the stores to track. The whole catalog is read. |
| **Only these products** | Track a fixed list of product pages instead of whole stores - cheaper when only some products compete with yours. |
| **Tracker name** | Runs with the same name are compared with each other and share one sales history. |
| **Output** | All variants, or only the variants that changed since the previous run. |
| **Excel competitor report** | Save the 7/30-day Excel report after each run (default on). |
| **Max products per store** | Stop after N products (0 = whole catalog). Handy for a first test. |
| **Platform** | Automatic detection; set it only if detection fails. |
| **Parallel requests / Delay** | How fast pages are read. Keep it polite. |
| **Proxy** | Datacenter proxy is enough for most stores; use residential only if pages get blocked. |

Example input:

```json
{
    "startUrls": ["https://www.ridgewallet.com", "https://www.cbeaux.com"],
    "trackingId": "my-competitors",
    "outputMode": "all",
    "createReport": true,
    "maxProductsPerStore": 0
}
```

### Output

**Dataset** - one row per variant. Example (values are illustrative):

```json
{
    "storeUrl": "https://www.example-store.com",
    "platform": "shopify",
    "productUrl": "https://www.example-store.com/products/merino-wool-yarn",
    "productTitle": "Merino Wool Yarn 100 g",
    "variantTitle": "Navy Blue",
    "sku": "MW-100-NAVY",
    "price": 129.9,
    "listPrice": 149.9,
    "currency": "USD",
    "available": true,
    "stockQuantity": 42,
    "changeType": "sold",
    "previousStockQuantity": 50,
    "previousPrice": 129.9,
    "hoursSincePrevious": 24.0,
    "stockChange": -8,
    "estimatedUnitsSold": 8,
    "estimatedRevenue": 1039.2,
    "restockedUnits": 0,
    "priceChange": null,
    "scrapedAt": "2026-11-02T06:00:12+00:00"
}
```

**Key-value store** - `REPORT` (the Excel report above) and `RUN_SUMMARY` (JSON): per store, products read, exact-stock %, units sold and revenue since the previous run and for the last 7 and 30 days, sell-outs / restocks / price changes, and any pages that could not be read.

Export the dataset as JSON, CSV or Excel, or connect it to Google Sheets, a webhook or your own system through the Apify API.

### Pricing

You pay **per product read** - all variants of the product (every color and size, each with its exact stock count) are included in that one charge, and the Excel report is included. No monthly fee.

| Apify plan | Price per 1,000 products |
|---|---|
| Free / Starter | $3.00 |
| Scale | $2.70 |
| Business | $2.50 |

Example: tracking a 2,000-product store once a day is about 60,000 products a month - $180 on Starter, $150 on Business. Use **Only these products** or **Max products per store** to track only what matters to you.

### Tips

- Start with **Max products per store = 20** to check what a store exposes (see the exact-stock % in the run status), then remove the limit.
- Use **Output: Only changes** for daily runs - the dataset gets just the movements, and the Excel report still covers the whole period.
- Keep the same **Tracker name** in your schedule so the 7/30-day history keeps growing.

### FAQ

**Can I see how many sales a Shopify store makes?** You can see how many units it sold between two runs, product by product, when the store shows exact stock on its product pages (many Shopify themes and stock apps do; roughly one store in four in our tests). Units sold are estimated from stock drops, so they are a lower bound - see [How units sold are estimated](#how-units-sold-are-estimated).

**Why are some sales missing from the totals?** Moves that look like stock corrections - a placeholder count such as 999 set or cleared, or almost all of a large stock removed at once - are reported as `stock-adjustment` and listed on the report's *Stock adjustments* sheet instead of being counted as sales.

**Does it log in or use private APIs?** No. It reads only what any visitor's browser receives from the store's public pages, sitemaps and public storefront APIs.

**Does it collect personal data?** No. It reads product information only.

**Is it legal?** The Actor reads publicly available product information. You are responsible for how you use it: respect the websites' terms of service and the laws that apply to you.

**A store shows no exact quantities - why?** That store does not publish them (for example a Shopify theme without a stock counter, or WooCommerce products without stock management). You still get prices, availability, sell-outs and restocks. Headless Shopify storefronts (custom front-ends) are not supported yet; the run status names such stores.

**Something is not read correctly?** Open an issue on the Actor's Issues tab with the store URL - layouts change and readers are updated.

# Actor input Schema

## `startUrls` (type: `array`):

Home page URLs of the stores to track. The whole catalog is read. Supported platforms: Shopify, WooCommerce, T-Soft, IdeaSoft, ikas, OpenCart, GoldenPlanet (detected automatically).

## `productUrls` (type: `array`):

Track a fixed list of product pages instead of whole stores. Cheaper when you only care about products that compete with yours. If a store is also listed in Competitor stores, the whole store is read.

## `trackingId` (type: `string`):

Runs with the same tracker name are compared with each other to estimate units sold. Use a different name for each independent watch list. A new or renamed tracker is compared with the latest reading of the same store, so no history is lost. The previous readings are kept in key-value stores in your own account.

## `outputMode` (type: `string`):

All variants, or only variants that changed since the previous run (sold, restocked, sold out, back in stock, price change, new).

## `createReport` (type: `boolean`):

Save a ready-to-read Excel report (REPORT in the key-value store of the run): per store units sold and revenue for the last 7 and 30 days, top sellers, best-selling categories, price changes, sell-outs, restocks and new products. Built from the history the tracker keeps between runs.

## `maxProductsPerStore` (type: `integer`):

Stop after this many products per store. 0 = the whole catalog.

## `platform` (type: `string`):

Leave on automatic unless detection fails for a store.

## `maxConcurrency` (type: `integer`):

How many product pages are fetched at the same time. Keep it low to stay polite to the store.

## `minDelaySecs` (type: `number`):

Minimum pause of each parallel worker between two requests.

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

Datacenter proxy is enough for most stores. Switch to residential only if pages get blocked.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.ridgewallet.com",
    "https://www.cbeaux.com"
  ],
  "productUrls": [
    "https://cbeaux.com/product/badia-dress/"
  ],
  "trackingId": "my-competitors",
  "outputMode": "changes",
  "createReport": true,
  "maxProductsPerStore": 20,
  "platform": "auto",
  "maxConcurrency": 3,
  "minDelaySecs": 1,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `report` (type: `string`):

Ready-to-read workbook: units sold and revenue for the last 7 and 30 days, top sellers, categories, price changes, sell-outs, restocks, new products and daily sales per store.

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

Every variant read in this run: price, availability, exact units in stock, estimated units sold and revenue.

## `summary` (type: `string`):

Per-store totals: products read, variants with exact quantity, estimated units sold and revenue, sell-outs, restocks, price changes, pages that failed.

# 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 = {
    "startUrls": [
        "https://www.ridgewallet.com",
        "https://www.cbeaux.com"
    ],
    "maxProductsPerStore": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("rivalstock/competitor-stock-tracker").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 = {
    "startUrls": [
        "https://www.ridgewallet.com",
        "https://www.cbeaux.com",
    ],
    "maxProductsPerStore": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("rivalstock/competitor-stock-tracker").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 '{
  "startUrls": [
    "https://www.ridgewallet.com",
    "https://www.cbeaux.com"
  ],
  "maxProductsPerStore": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call rivalstock/competitor-stock-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rivalstock/competitor-stock-tracker"
        }
    }
}
```

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/0BJuiLG63MbYjYD2C/builds/ieV8ch1lCsogRahgy/openapi.json
