# SaaS Review Competitor Battlecard Miner (`isotonic/saas-review-competitor-battlecard-miner`) Actor

Turn public SaaS reviews into competitor battlecards with recurring complaints, praise themes, switching triggers, and buyer language for sales teams.

- **URL**: https://apify.com/isotonic/saas-review-competitor-battlecard-miner.md
- **Developed by:** [Brian Keefe](https://apify.com/isotonic) (community)
- **Categories:** Business, Automation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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

## SaaS Review Competitor Battlecard Miner

Production-ready Apify Actor for mining public SaaS review pages or supplied HTML into review rows and battlecard/theme rows.

### Public-data-only posture

- Uses public URLs and supplied HTML only.
- No login flows, no private APIs, no anti-bot bypass, no gated scraping.
- If a page returns `403` or is otherwise blocked, the actor emits `warning` records instead of retrying aggressively.

### What it does

- Accepts `startUrls`, `reviewUrls`, and `htmlInputs`.
- Extracts review-like rows from Capterra/GetApp-style public pages with best-effort support.
- Provides best-effort support for public G2 pages when the HTML is accessible.
- Mines battlecard rows from review text using deterministic heuristics.

Theme mining includes signals for:

- pricing and cost
- implementation and setup
- support quality
- integrations
- usability
- performance
- missing reporting, API, mobile, and customization capabilities
- competitor mentions from `competitorKeywords`
- quotable language

### Inputs

See `.actor/input_schema.json` for the Apify input definition.

Main fields:

- `startUrls`: Apify request-list style objects with `url`
- `reviewUrls`: plain string URLs
- `htmlInputs`: raw HTML strings or local file paths
- `maxReviews`: cap the number of kept review rows
- `minRating` / `maxRating`: inclusive filters
- `dateFrom` / `dateTo`: ISO date filters
- `competitorKeywords`: names to flag as competitor mentions

### Outputs

The actor pushes dataset rows with a `type` field:

- `review`: extracted review rows
- `theme`: derived battlecard/theme rows
- `warning`: blocked pages, fetch failures, or pages with no review-like rows

Useful key-value outputs:

- `OUTPUT_SUMMARY`: counts for reviews, themes, warnings, and sources
- `OUTPUT_RECORDS_PREVIEW`: first 25 emitted records

Example `theme` fields:

- `battlecardType`: `objection`, `praised_feature`, `missing_feature`, `competitor_mention`, or `quote`
- `themeKey`
- `quote`
- `rating`
- `date`
- `sourceUrl`
- `evidenceSnippet`

### Local run

Install and test:

```bash
npm install
npm test
````

Run the actor locally against the deterministic fixture:

```bash
mkdir -p storage/key_value_stores/default
cp examples/sample-input.json storage/key_value_stores/default/INPUT.json
CRAWLEE_STORAGE_DIR=./storage npm start
```

If you prefer a direct Node run with raw HTML or fixture paths, the smoke test already exercises the real pipeline without network access.

### Example input

```json
{
  "htmlInputs": ["tests/fixtures/capterra-sample.html"],
  "competitorKeywords": ["HubSpot", "Pipedrive"],
  "maxReviews": 10
}
```

### Warning records

Examples:

- `code: "blocked"` for `403` public-page responses
- `code: "fetch_failed"` for non-403 HTTP failures
- `code: "fetch_error"` for transport/runtime failures
- `code: "no_reviews_found"` when a page is accessible but no review-like rows match the heuristics

### Buyer use cases

- Build competitor battlecards from public review evidence.
- Summarize recurring objections before sales enablement calls.
- Identify praised features to sharpen positioning.
- Flag missing capabilities buyers repeatedly ask for.
- Collect quotable customer language for messaging research.

# Actor input Schema

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

Public review or product pages to fetch and mine.

## `reviewUrls` (type: `array`):

Additional public review pages to fetch directly.

## `htmlInputs` (type: `array`):

Raw HTML strings or local file paths used for deterministic/local runs.

## `maxReviews` (type: `integer`):

Maximum number of review records to keep after filtering.

## `minRating` (type: `number`):

Minimum rating filter, inclusive.

## `maxRating` (type: `number`):

Maximum rating filter, inclusive.

## `dateFrom` (type: `string`):

Keep reviews on or after this ISO date.

## `dateTo` (type: `string`):

Keep reviews on or before this ISO date.

## `competitorKeywords` (type: `array`):

Competitor names or terms to flag in review text.

## Actor input object example

```json
{
  "startUrls": [],
  "reviewUrls": [],
  "htmlInputs": [],
  "maxReviews": 50,
  "competitorKeywords": []
}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("isotonic/saas-review-competitor-battlecard-miner").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("isotonic/saas-review-competitor-battlecard-miner").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{}' |
apify call isotonic/saas-review-competitor-battlecard-miner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=isotonic/saas-review-competitor-battlecard-miner",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "SaaS Review Competitor Battlecard Miner",
        "description": "Turn public SaaS reviews into competitor battlecards with recurring complaints, praise themes, switching triggers, and buyer language for sales teams.",
        "version": "0.1",
        "x-build-id": "70EfSJCcxOWouKhbC"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/isotonic~saas-review-competitor-battlecard-miner/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-isotonic-saas-review-competitor-battlecard-miner",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/isotonic~saas-review-competitor-battlecard-miner/runs": {
            "post": {
                "operationId": "runs-sync-isotonic-saas-review-competitor-battlecard-miner",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/isotonic~saas-review-competitor-battlecard-miner/run-sync": {
            "post": {
                "operationId": "run-sync-isotonic-saas-review-competitor-battlecard-miner",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "startUrls": {
                        "title": "Start URLs",
                        "type": "array",
                        "description": "Public review or product pages to fetch and mine.",
                        "items": {
                            "type": "object",
                            "properties": {
                                "url": {
                                    "type": "string",
                                    "title": "URL",
                                    "description": "Public SaaS review/product page URL to fetch."
                                }
                            },
                            "required": [
                                "url"
                            ]
                        },
                        "default": []
                    },
                    "reviewUrls": {
                        "title": "Review URLs",
                        "type": "array",
                        "description": "Additional public review pages to fetch directly.",
                        "items": {
                            "type": "string"
                        },
                        "default": []
                    },
                    "htmlInputs": {
                        "title": "HTML Inputs",
                        "type": "array",
                        "description": "Raw HTML strings or local file paths used for deterministic/local runs.",
                        "items": {
                            "type": "string"
                        },
                        "default": []
                    },
                    "maxReviews": {
                        "title": "Max Reviews",
                        "minimum": 1,
                        "type": "integer",
                        "description": "Maximum number of review records to keep after filtering.",
                        "default": 50
                    },
                    "minRating": {
                        "title": "Min Rating",
                        "minimum": 0,
                        "maximum": 5,
                        "type": "number",
                        "description": "Minimum rating filter, inclusive."
                    },
                    "maxRating": {
                        "title": "Max Rating",
                        "minimum": 0,
                        "maximum": 5,
                        "type": "number",
                        "description": "Maximum rating filter, inclusive."
                    },
                    "dateFrom": {
                        "title": "Date From",
                        "type": "string",
                        "description": "Keep reviews on or after this ISO date."
                    },
                    "dateTo": {
                        "title": "Date To",
                        "type": "string",
                        "description": "Keep reviews on or before this ISO date."
                    },
                    "competitorKeywords": {
                        "title": "Competitor Keywords",
                        "type": "array",
                        "description": "Competitor names or terms to flag in review text.",
                        "items": {
                            "type": "string"
                        },
                        "default": []
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
