# Apify Store Trends: demand, weak incumbents \[$0.001/actor] (`perchpermits/apify-store-trends`) Actor

Which Apify Store niches have demand and weak incumbents. One row per actor (30-day users, runs, fail rate, rating, price, idle days, gap signals, change since last snapshot) and one per niche with the leader, its share, and A-G flags. Search a niche in seconds or snapshot the catalog monthly.

- **URL**: https://apify.com/perchpermits/apify-store-trends.md
- **Developed by:** [Perch Data](https://apify.com/perchpermits) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 actor rows

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Apify Store Trends: demand, ratings, failing incumbents

Which Apify Store niches have demand, and which incumbents are weak. One JSON row per store actor and one per niche, for anyone deciding what to build, what to wrap, or which competitor to go after:

- **Demand**: users in the last 7, 30, and 90 days and lifetime, plus public runs in the last 30 days. Apify publishes no revenue, so `users_30d` is the demand proxy throughout.
- **Reliability**: the 30-day fail rate (failed + timed-out over total public runs), the maintenance notice, and days since the actor last ran.
- **Quality**: the store rating and review count, with ratings under three reviews ignored where a judgement is made.
- **Price**: the listed price in one readable label (`$0.0027/Result`, `$0.005/item`, `$30/mo`, `FREE`) and as a number.
- **Gap signals** per actor: `leader_failing`, `unrated`, `under_maintenance`, `idle_14d`, `low_rated`.
- **Target rows** per niche: total demand, actor count, the leader and its share, the leader's rating and fail rate, the user-weighted rating, the best rating anyone holds, how many actors are broken, and the A to G shortlist flags (poorly served, thin market, monopoly, and so on).
- **Change over time**: point every run at the same named dataset and each row carries what it looked like last snapshot: previous users, the change, previous rating and fail rate, and whether the actor is new.

This is the same scan the developer ran to pick their own niches on the store, turned into an actor. Two modes: `search` answers one question in seconds ("who sells Google Trends data and are they any good?"); `full` sweeps the whole catalog for a monthly snapshot.

Source: the public store endpoint `api.apify.com/v2/store`, no key, no login. Nothing is guessed; missing values are `null`.

### Quick start

Everyone selling Google Trends data, with a niche summary row on top:

```json
{ "query": "google trends" }
```

A niche with only serious incumbents, sorted by demand:

```json
{ "query": "bizbuysell", "minUsers30d": 20, "maxActors": 50 }
```

A monthly snapshot of the whole store with deltas against last month (schedule it; about ten minutes a run):

```json
{ "mode": "full", "maxActors": 5000, "minUsers30d": 5, "snapshotDatasetName": "store-snapshots" }
```

### Input

| Field | Type | Notes |
|---|---|---|
| `mode` | `search` | `full` | `search` runs the store's own search for `query`: a few requests, seconds. `full` sweeps the whole catalog across every category and pricing model: tens of thousands of actors, about ten minutes, meant for a scheduled monthly snapshot. Default `search`. |
| `query` | string | Required when `mode` is `search`. Ignored in `full` mode. |
| `minUsers30d` | integer | Drop actors with fewer users than this in the last 30 days. Default 5. |
| `maxActors` | 1 to 5000 | Keep at most this many actors, the most used first. Default 200. Set 5000 for a complete snapshot in `full` mode. |
| `includeTargets` | boolean | Also emit one aggregate row per target bucket, and in `search` mode one for the query itself. Default true. |
| `snapshotDatasetName` | string | Optional named dataset in your account. Every actor row is appended to it; before writing, the newest snapshot already there is read to fill `delta`. Use the same name every run. Not charged again. |
| `requestDelaySeconds` | number | Politeness delay toward the store API. Default 0.2. |

### Output

Rows come in two shapes, told apart by `row_type`. Target rows are pushed first, then actor rows sorted by `users_30d` descending (in `search` mode, actors whose title or name match the query come before the rest). The dataset has an `actors` view and a `targets` view.

#### Actor rows (`row_type: "actor"`)

| Field | Meaning |
|---|---|
| `actor`, `actor_id`, `title`, `url` | `username/name`, the store's internal id, the display title, and the store page. |
| `target` | The niche bucket the actor falls in, matched by regex over title and name (`google trends`, `zillow`, `government/permits`, about 230 buckets). `(unmatched)` when nothing fits. |
| `query_match` | `search` mode only: whether every word of `query` appears in the actor's title or name. The store's search also matches single words and descriptions, so `google trends` returns every Google actor; the ones that are about the query come first and carry `true`. `null` in `full` mode. |
| `categories` | The store categories the author chose, as a list. |
| `users_7d`, `users_30d`, `users_90d`, `users_total` | Distinct users who ran the actor in each window. `users_30d` is the demand proxy. |
| `runs_30d` | Public runs in the last 30 days. |
| `fail_rate_30d` | (failed + timed-out) / total public runs in the last 30 days. `null` when there were no runs. |
| `rating`, `reviews` | Store rating (0 to 5, two decimals; `null` when unrated) and review count. |
| `bookmarks` | Bookmark count. |
| `price`, `price_usd`, `pricing_model` | A readable label (`$0.0027/Result` for pay-per-event at the free tier, `$0.005/item`, `$30/mo`, `FREE`), the same number alone, and the raw model (`PAY_PER_EVENT`, `PRICE_PER_DATASET_ITEM`, `FLAT_PRICE_PER_MONTH`, `FREE`). |
| `notice` | The store's status notice, e.g. `NONE`, `UNDER_MAINTENANCE`. |
| `days_since_last_run` | Days since the actor last started a run. |
| `agentic_payments` | Whether the store lists the actor as enabled for agentic payments. |
| `gap_signals` | Why this incumbent might be beatable, any of: `leader_failing` (fail rate 30%+ with 10+ monthly users), `unrated` (under three reviews with 20+ monthly users), `under_maintenance`, `idle_14d` (no run for two weeks), `low_rated` (under 3.6 with three or more reviews). Empty when nothing stands out. |
| `snapshot_at` | When this run read the store (UTC). Every row in a run shares it; it is the key that groups a snapshot. |
| `delta` | Change since the previous snapshot in `snapshotDatasetName`: `users_30d_prev`, `users_30d_change`, `rating_prev`, `fail_rate_prev`, `is_new` (the actor was not in the previous snapshot). All `null` when no snapshot dataset is given or it is still empty. |

#### Target rows (`row_type: "target"`)

| Field | Meaning |
|---|---|
| `target`, `target_kind` | The bucket name. `site` rows aggregate the actors whose title or name matched that bucket's regex; in `search` mode one `query` row (first in the dataset) aggregates the actors with `query_match: true`. No query row when nothing matched. |
| `hostility` | `high`, `med`, or `low`: a hand-labelled guess at how hard the target site fights scrapers, which sets the on-call load. `med` for query rows. |
| `actors`, `active_actors` | Actors in the bucket (after `minUsers30d`), and how many had at least one user in 30 days. |
| `users_30d`, `users_total` | Summed over the bucket. |
| `leader`, `leader_url`, `leader_share` | The most-used actor, its page, and its share of the bucket's 30-day users. |
| `leader_rating`, `leader_reviews`, `leader_fail_rate`, `leader_price`, `leader_signals` | The leader's rating, review count, 30-day fail rate, price label, and gap signals. |
| `weighted_rating` | User-weighted mean rating over actors with three or more reviews. `null` when none qualify. |
| `best_rated`, `best_rating` | The best-rated actor with five or more reviews, and its rating. |
| `broken_actors` | Actors with 10+ monthly users failing 40%+ of runs. |
| `signals` | The A to G shortlist rules: `A_poorly_served` (150+ monthly users and weighted rating under 4.0, or the leader fails 30%+, or nothing with five reviews rates 4.2+), `B_big_actor_low_rated` (an actor with 500+ lifetime users rated under 3.6), `C_actor_failing_in_use` (an actor with 25+ monthly users and 100+ runs failing 40%+), `D_incumbent_idle` (an actor with 20+ monthly users under maintenance or idle two weeks), `E_thin_market` (40+ monthly users and six or fewer active actors), `F_low_hostility_demand` (low hostility and 60+ monthly users), `G_monopoly` (300+ monthly users and the leader holds 80%+). |
| `snapshot_at` | Same as on actor rows. |
| `delta` | `users_30d_prev`, `users_30d_change`, `actors_prev`, `leader_prev`, `is_new`, computed from the previous snapshot's actor rows in the same bucket. All `null` without a snapshot dataset. |

### Pricing

Pay per event. Free-tier credit covers thousands of rows.

| Event | Price | When |
|---|---|---|
| `actor` | $0.001 | One actor row. |
| `target` | $0.01 | One target row (a niche summary, or the query summary). |

A search for one niche is typically 20 to 60 actor rows and a handful of target rows: a few cents. A full monthly snapshot at `maxActors: 5000` is about $5 plus the target rows.

If `maxTotalChargeUsd` is set, the actor works out up front how many rows fit: target rows first, then as many actor rows as the remaining budget allows. Rows past the cap are dropped, never charged. A run is never charged for rows it did not return.

### Limits and honesty

- Demand is `totalUsers30Days` from the store; Apify publishes no revenue, so this is a count of people, not dollars. Runs and fail rates are the store's public run statistics.
- Target buckets come from a regex over title and name only (descriptions drag generic actors into every bucket they mention), so a few actors land in the wrong bucket; check the actor rows before acting on a target row. Hostility is a hand-labelled guess.
- The store's search matches single words and descriptions, so `google trends` also returns every Google actor. Those rows are kept (they are what a buyer browsing the store sees) but ranked after the ones whose title or name carry the whole query, and flagged with `query_match`. The query target row counts only the matches.
- `full` mode unions the catalog across the global popularity sort, every category, and every pricing model, because the global sort is unstable past about 12,000 rows. Expect tens of thousands of items and about ten minutes; set `maxActors` to what you want kept.
- Deltas compare against the newest snapshot in the named dataset, whatever its age; if you run weekly, deltas are weekly. Rows with the same `snapshot_at` are one snapshot.
- If the store API changes shape (no `items` list, or an item without `stats.totalUsers`) the run fails loudly with `Source drift detected` and nothing is charged, rather than returning empty rows.

### Support

Open an issue on the actor page. Changes at the source are fixed within days.

# Actor input Schema

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

`search` runs the store's own search for `query` (a few requests, seconds). `full` sweeps the whole catalog across every category and pricing model (tens of thousands of actors, about ten minutes) and is meant for a scheduled monthly snapshot.

## `query` (type: `string`):

What to search the store for, e.g. `google trends`, `bizbuysell`, `permits`. Required when mode is `search`; ignored in `full` mode.

## `minUsers30d` (type: `integer`):

Drop actors with fewer users than this in the last 30 days. 0 keeps everything the store returns.

## `maxActors` (type: `integer`):

Keep at most this many actors, the most used first. In `full` mode set it high (5000) for a complete snapshot.

## `includeTargets` (type: `boolean`):

Also emit one aggregate row per target bucket (and one for the search query, over the actors whose title or name match it): users, actor count, leader and its share, rating, fail rate, weighted and best rating, broken actors, and the A to G signals. Charged as `target`.

## `snapshotDatasetName` (type: `string`):

Optional. Name of a dataset in your account. Every actor row is appended to it, and before writing, the newest snapshot already there is read to fill each row's `delta` (previous 30-day users, change, previous rating and fail rate, whether the actor is new). Same name every run, e.g. `store-snapshots`. Not charged again.

## `requestDelaySeconds` (type: `number`):

Politeness delay toward the store API. Lower is faster but risks throttling.

## Actor input object example

```json
{
  "mode": "search",
  "query": "google trends",
  "minUsers30d": 5,
  "maxActors": 200,
  "includeTargets": true,
  "requestDelaySeconds": 0.2
}
```

# Actor output Schema

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

Every actor row (actor, target, users\_7d/30d/90d/total, runs\_30d, fail\_rate\_30d, rating, reviews, price, notice, days\_since\_last\_run, gap\_signals, snapshot\_at, delta) and every target row (target, hostility, actors, users\_30d, leader, leader\_share, leader\_rating, leader\_fail\_rate, weighted\_rating, best\_rating, broken\_actors, signals, delta).

## `resultsCsv` (type: `string`):

The same rows flattened with dot-separated column names (`delta.users_30d_change`).

## `actors` (type: `string`):

Just the `actor` rows, the most used first.

## `targets` (type: `string`):

Just the `target` rows, one per niche, the highest demand first.

# 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 = {
    "query": "google trends"
};

// Run the Actor and wait for it to finish
const run = await client.actor("perchpermits/apify-store-trends").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 = { "query": "google trends" }

# Run the Actor and wait for it to finish
run = client.actor("perchpermits/apify-store-trends").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 '{
  "query": "google trends"
}' |
apify call perchpermits/apify-store-trends --silent --output-dataset

```

## MCP server setup

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

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/cxbrF5ik3wNwt2JWz/builds/USunI17fFG5YO7Lp4/openapi.json
