# Gumroad Scraper & Products Monitor - New Products, Prices (`neverempty/gumroad-products-monitor`) Actor

For creators tracking their niche on Gumroad: products of any Discover search, category or seller page - name, seller, price and currency, pay-what-you-want, rating average and count. Monitor mode returns only new products, price changes and products whose rating count grew.

- **URL**: https://apify.com/neverempty/gumroad-products-monitor.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.50 / 1,000 product returneds

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

## Gumroad Scraper & Products Monitor - New Products, Prices

Get the **products of any Gumroad Discover search, category or seller page**: **name, seller, price and currency, pay-what-you-want and subscription terms, rating average and rating count**, product type and URL. One row per product.

Turn on **monitor mode** and each run returns **only what changed since the last run: products that are new in the list you watch, price changes, and products whose rating count grew** (the public sign that a product is selling) - each with the previous price and rating count. Unchanged products are not returned and not charged as rows.

Unofficial. Reads only the public lists Gumroad shows to every visitor (the Discover search and the seller's public page), after reading the `robots.txt` of each address. No login, no Gumroad account, no API key. Requests are spaced at least 1.5 seconds apart and go out without a proxy; a refused request or a bot check page is not retried or bypassed.

### What you get

| Column | Example |
|---|---|
| `source`, `searchQuery`, `category`, `seller`, `sortBy`, `position` | search, notion template, null, null, newest, 1 - the list this row was read from and the product's place in it |
| `productId`, `name`, `productUrl` | hpqcg, Headquarters Notion Productivity Template, https://productivesetups.gumroad.com/l/headquarters |
| `sellerName`, `sellerUrl`, `sellerId`, `sellerVerified` | Productive Setups, https://productivesetups.gumroad.com, 3915531815605, false |
| `price`, `currency`, `priceMinor` | 79, AUD, 7900 - the price in the product's own currency, and the same amount in that currency's smallest unit as Gumroad gives it |
| `isFree`, `isPayWhatYouWant` | false, false - with pay-what-you-want, `price` is the minimum ("$5+"); a minimum of 0 is a free product |
| `recurrence` | monthly, quarterly, yearly, or `null` for a one-time purchase |
| `originalPrice` | the price before a discount, when Gumroad lists one; otherwise `null` |
| `ratingAverage`, `ratingCount` | 5, 200 - `ratingAverage` is `null` when there are no ratings yet |
| `productType`, `isSalesLimited`, `quantityRemaining` | digital / course / ebook / membership / bundle ..., false, null |
| `description` | the short description Gumroad shows on the product card (cut off by Gumroad at about 100 characters) |
| `changeType`, `changeTypes`, `previousPrice`, `previousRatingCount`, `ratingCountIncrease`, `previousRecordedAt` | monitor mode only, see below |
| `watchName`, `checkedAt` | the watch name, when this run read the product |

`price` is always in the **product's own currency** (the one the seller set), not converted. For JPY the amount has no decimals. For KRW, VND, IDR, COP and TWD `price` is `null` and only `priceMinor` is given, because the unit of that field was not verified for those currencies. A value Gumroad does not give is `null`, never 0.

**Not included:** sales counts (the lists do not carry them; Gumroad shows a sales count only on the product page of sellers who switch it on), thumbnails, e-mail addresses or any buyer data. Seller information is limited to the public display name and the public page address.

### Input

| Field | What it does |
|---|---|
| `queries` | Words to search on Gumroad Discover, one search per line |
| `categories` | Discover categories as the path in the page address: `design`, `design/icons`, `software-development`, `business-and-money` ... The path is checked against Gumroad's own category list before anything is requested |
| `sellers` | Sellers by name, `name.gumroad.com` or any URL on it. Every product shown on the seller's public page is read |
| `sortBy` | `default`, `newest`, `hot_and_new`, `highest_rated`, `most_reviewed`, `price_asc`, `price_desc`. If empty: Gumroad's default order, or `newest` in monitor mode. Seller pages keep the seller's own order |
| `maxProductsPerSearch` | Products per search, category or seller (1-5000, 100 if empty) |
| `onlyChanges`, `watchName`, `resetMonitoringState` | Monitor mode, see below |

```json
{ "queries": ["notion template"], "sellers": ["productivesetups"], "sortBy": "newest", "maxProductsPerSearch": 50 }
```

With no query, category or seller (and monitor mode off) the run returns the first 36 products of the example search "notion template".

### Monitor mode: what counts as a change

Set `onlyChanges` to true and schedule the run (daily is typical). The Actor remembers, per `watchName` and per search, category or seller, the price and rating count of each product it has seen.

| `changeType` | Meaning |
|---|---|
| `new` | The product was not in the watched list at earlier runs. For a search or category this is the first `maxProductsPerSearch` products in the chosen order - with `newest` (the default in monitor mode) these are newly published products; with another order a product that climbs into the top of the list also counts as new. For a seller it is a product newly shown on the seller's page |
| `price-changed` | `price` differs from the remembered price in the same currency; `previousPrice` holds the old one |
| `ratings-increased` | `ratingCount` is higher than remembered; `previousRatingCount` and `ratingCountIncrease` say by how much |

- **The first run of a watch returns no product rows.** It remembers the list as the starting point and adds one free row that says so.
- A product can carry several changes at once: `changeTypes` lists all of them.
- To keep a product that slides up after another one disappears from being reported as new, monitor mode reads up to 36 products beyond `maxProductsPerSearch` and remembers them silently.
- Products that leave the list are not reported. A rating count that goes down is remembered without a row.
- A change is remembered only after its row was delivered. If the run stops at its maximum charge, the rest is returned by the next run.
- A run with nothing to return adds one free row with `status: "no-change"`.
- Do not run two schedules with the same `watchName` and the same search at the same moment; the remembered state is one record per watch and search.

### Rows that explain instead of products

These rows are free. `status` says what happened and `note` says it in a sentence.

| `status` | When |
|---|---|
| `no-products` | Gumroad answered with an empty list: nothing matches the search, or the seller shows no products |
| `no-such-category` | The category path is not in Gumroad's category list (Gumroad would answer such a request with its whole catalogue, so it is not sent). The note lists the top-level categories |
| `no-such-seller` | The seller address answered 404 |
| `robots-disallowed` | The `robots.txt` of that address disallows the request, so it was not sent |
| `blocked` | Gumroad refused the request (403/429) or showed a bot check page. Not retried, not bypassed |
| `redirected` | The seller address redirects elsewhere (for example to the seller's own domain). Redirects are not followed |
| `unreadable` | The answer could not be read, also after asking once more |
| `baseline-recorded`, `no-change` | Monitor mode: starting point remembered / nothing changed |
| `budget-reached` | The run reached the maximum total charge you set |
| `bad-input` | The input could not be used; nothing was requested |

### Pricing

Pay per event, no start fee, no monthly fee.

- **`product-returned`** - one per product row. In monitor mode only changed products are rows.
- **`search-checked`** - monitor mode only: one for each search, category or seller that was compared and had nothing to return. The first run of a watch (the starting point) is not charged.

Rows that explain why nothing was returned are free. The run never charges more than the maximum total charge you set for it.

### Notes

- The Actor asks Discover the way a visitor who is not logged in does and sends no option that asks for adult (NSFW) products. The lists carry no adult flag, so rows cannot be filtered by it.
- Search results come 36 per request. Requests are spaced 1.5 seconds apart, so 1,000 products take about 45 seconds.
- A seller page shows the products the seller placed in sections of the page; a product the seller keeps off the page is not listed there.
- The rating count is the only public sales signal in these lists. It is not a sales count.

### Related Actors

- [Shopify Products Scraper & Price Monitor](https://apify.com/neverempty/shopify-products-price-monitor) - every product of a Shopify store, with price and stock changes
- [Substack Posts Monitor](https://apify.com/neverempty/substack-posts-monitor) - posts of Substack publications, with new-post alerts

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

## `queries` (type: `array`):

Words to search on Gumroad Discover, one search per line, for example notion template. Each search returns its products in the chosen sort order, up to the maximum below. If queries, categories and sellers are all empty (and monitor mode is off), the first 36 products of one example search are returned.

## `categories` (type: `array`):

Gumroad Discover categories, one per line, written as the path in the address of the category page: design, design/icons, software-development, business-and-money. The Actor checks the path against Gumroad's own category list before asking; an unknown category comes back as a free row that lists the top-level categories.

## `sellers` (type: `array`):

Gumroad sellers, one per line: the name (productivesetups), the address (productivesetups.gumroad.com) or any URL on it. Every product shown on the seller's public page is returned (up to the maximum below). The Actor reads the robots.txt of each seller address first. A name with no seller page comes back as a free row that says so.

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

Order in which Gumroad lists the products of a search or category. If empty: Gumroad's default order, or newest in monitor mode (so that new products are at the top of the list). Seller pages keep the order the seller chose.

## `maxProductsPerSearch` (type: `integer`):

Stop after this many products of each search, category or seller (1-5000). If empty: 100. In monitor mode this is the size of the list that is watched (the first N products in the chosen order).

## `onlyChanges` (type: `boolean`):

Compare each search, category or seller with the last run of the same watch name and return only products that are new in the watched list, whose price changed, or whose number of ratings grew (a sign of sales). Each row carries the previous price and rating count. The first run of a watch only remembers the list as the starting point and returns no product rows. A check that finds nothing to return is charged one search-checked event; unchanged products are not returned and not charged as rows.

## `watchName` (type: `string`):

Monitor mode only: name of the remembered state used to compare runs (letters, digits, dot, dash, underscore; up to 40). Use a different name for each list you track on its own schedule. If empty, the name "default" is used.

## `resetMonitoringState` (type: `boolean`):

Monitor mode only: forget the remembered products of the searches, categories and sellers in this run before it starts, so this run records a new starting point and returns no product rows.

## Actor input object example

```json
{
  "queries": [
    "notion template"
  ],
  "maxProductsPerSearch": 50,
  "onlyChanges": false,
  "resetMonitoringState": false
}
```

# Actor output Schema

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

One row per Gumroad product: the search, category or seller it was read from, position in that list, product ID (permalink), name, seller name and page, price and currency, pay-what-you-want and recurrence, rating average and count, product type and URL. In monitor mode: changeType (new, price-changed, ratings-increased) with the previous price and rating count. A search with no match, an unknown category or seller, a request robots.txt disallows, a refused request, an unusable input, a check with no change or a run that hit its maximum charge comes back as a free row that says why.

# 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 = {
    "queries": [
        "notion template"
    ],
    "maxProductsPerSearch": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/gumroad-products-monitor").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 = {
    "queries": ["notion template"],
    "maxProductsPerSearch": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/gumroad-products-monitor").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 '{
  "queries": [
    "notion template"
  ],
  "maxProductsPerSearch": 50
}' |
apify call neverempty/gumroad-products-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/gumroad-products-monitor"
        }
    }
}
```

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/rq2vlAWrgacHSfhpk/builds/SKrPsOW1Dict4QKrE/openapi.json
