# G2 Latest Reviews Monitor (`huggable_quote/g2-reviews-monitor`) Actor

Collects the latest G2 product reviews from public RSS feeds. No login, no browser, no residential proxy. Built for incremental monitoring, not full review history.

- **URL**: https://apify.com/huggable\_quote/g2-reviews-monitor.md
- **Developed by:** [OrbitData Labs](https://apify.com/huggable_quote) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 reviews

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

## G2 Latest Reviews Monitor

Collects the **latest reviews** G2 publishes on each product's public RSS feed and
returns them as flat rows, ready for JSON, CSV or Excel.

It is built for monitoring: run it on a schedule, keep an eye on what people are
saying about your product and your competitors', and pull only what is new since
last time.

### How it works

The actor reads the public RSS feed G2 serves for a product:

```
https://www.g2.com/products/{product}/reviews.rss
```

That feed is open. In the checks run for this actor it answered without a login,
without cookies, without a browser and without a proxy — including from a
datacenter IP. Nothing is bypassed and no private page is touched: the actor asks
for the same feed a feed reader would.

### What it is not

This is a **monitor, not an archive**. G2's public feed is a snapshot of recent
reviews with no way to page back through older ones, so:

- the default feed carries roughly the 25 most recent reviews of a product
- asking for more also reads the per-rating feeds and merges them, which brings
  in more recent reviews — but they overlap, so the number of distinct reviews
  you actually get depends on the product
- `maxReviewsPerProduct` accepts up to 125. That is a **ceiling, not a promise**:
  a quiet product may return a handful of reviews, and a busy one will not
  return its older history either
- if you need every review a product has ever received, this actor is the wrong
  tool

### Input

#### One product

```json
{ "products": ["slack"] }
```

A product URL works just as well; only the slug is kept.

```json
{ "products": ["https://www.g2.com/products/slack/reviews"] }
```

#### Several products, filtered and incremental

```json
{
  "products": ["slack", "asana", "https://www.g2.com/products/notion/reviews"],
  "maxReviewsPerProduct": 50,
  "ratings": [1, 1.5, 2],
  "publishedAfter": "2026-08-01T00:00:00Z",
  "knownReviewIds": ["13357798", "13357612"],
  "includeReviewerName": false,
  "maxTotalReviews": 500,
  "requestConcurrency": 3
}
```

#### Input fields

| Field | Type | Default | Description |
|---|---|---|---|
| `products` | array of strings | — (required) | Product slugs or G2 product URLs, 1–100 entries. A product listed twice is collected once. The URL you type is never stored or logged; only the slug is kept. |
| `maxReviewsPerProduct` | integer | `25` | Reviews to keep per product, newest first. 1–125. Up to 25 reads the default feed only; above 25 also reads the per-rating feeds. |
| `ratings` | array of numbers | not set | Keep only these displayed star ratings: `0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5`. Omit to keep every rating. An empty array is rejected. |
| `publishedAfter` | string | not set | ISO 8601 with a time zone, e.g. `2026-08-01T00:00:00Z`. Keeps reviews published **strictly after** this moment. |
| `knownReviewIds` | array of strings | `[]` | Review IDs you already have; matching reviews are skipped and never charged. Up to 10,000 entries. |
| `includeReviewerName` | boolean | `false` | Off by default. Turn on only if you need the public display name and may store it. |
| `maxTotalReviews` | integer | `1000` | Ceiling on stored reviews for the whole run, so a run's price stays predictable. 1–10,000. |
| `requestConcurrency` | integer | `3` | Feed requests in flight at once across the whole run. 1–5. |

Bad input is rejected before the run charges anything or fetches anything.

#### Star ratings are the stars on the review

You pick real star values. G2's own feed filter works in coarser bands, so the
actor uses those bands only to fetch candidates and then **re-checks every review
against the rating it actually displays**. Asking for `5` gives you 5-star
reviews, not 4.5-star ones. A review whose rating cannot be read is left out when
a rating filter is set — it is never rounded or guessed.

#### Incremental runs

Two fields let a scheduled run pick up where the last one stopped:

- `publishedAfter` — strictly greater than. Pass the newest `publishedAt` your
  last run returned and you continue from exactly there, with no repeat of the
  boundary review. A review with no publication date is skipped while this
  filter is set.
- `knownReviewIds` — pass the `reviewId` values you already stored. Send the most
  recent ones; the list is capped at 10,000.

Either one on its own works. Using both is fine.

### Output

One flat row per review in the default dataset. The run summary — per-product
status and the counts from every filtering stage — is stored separately under
the `OUTPUT` key of the key-value store, so the dataset stays pure review rows.

| Field | Type | Description |
|---|---|---|
| `productSlug` | string | Product the review belongs to. |
| `productName` | string | null | Product name from the feed. |
| `productUrl` | string | Canonical G2 reviews page. |
| `reviewId` | string | null | Numeric ID taken from the review URL. Pass these back as `knownReviewIds`. |
| `reviewUrl` | string | Canonical URL of the review. |
| `title` | string | null | Review headline. |
| `rating` | number | null | Stars shown on the review: 0–5 in steps of 0.5. |
| `pros` | string | null | What the reviewer likes best. |
| `cons` | string | null | What the reviewer dislikes. |
| `problemsSolved` | string | null | What problems the product solves for them. |
| `publishedAt` | string | null | ISO 8601 timestamp in UTC. |
| `reviewerRole` | string | null | Public role label, e.g. `G2 User`. |
| `companySize` | string | null | Public band, e.g. `2-10 employees`. |
| `reviewerName` | string | null | Public display name. `null` unless `includeReviewerName` is on. |
| `source` | string | Always `g2-rss`. |

#### About the nulls

A field is `null` when the feed did not publish it. The actor never fills a gap
with a guess: an unreadable date stays `null` rather than becoming the collection
time, a missing rating stays `null` rather than being inferred, and a review ID
appears only when it can be read from the review URL. `reviewerRole` is usually
present; `companySize` is published for most reviews but not all.

Every row is flat, so CSV and Excel exports are plain columns with no nested
objects to unpack.

### Pricing

Pay per event:

| Event | Price |
|---|---|
| `actor-start` | **$0.01** per run |
| `review-saved` | **$0.002** per successfully stored review |

The most a run can cost:

```
$0.01 + (reviews stored x $0.002)
```

| Reviews stored | Maximum cost | Shown as |
|---|---|---|
| 1 | $0.012 | $0.02 |
| 25 | $0.06 | $0.06 |
| 1,000 | $2.01 | $2.01 |

The actor rounds the ceiling it prints up to the whole cent, so the figure you
see is never lower than what you pay. You are billed on the exact arithmetic,
not on the rounded figure. The actor prints its own ceiling at the start of every run,
worked out from `maxReviewsPerProduct`, the number of products and
`maxTotalReviews`, before it fetches anything.

#### What you are not charged for

A review is charged only after the dataset has accepted it. You pay nothing for:

- reviews that arrived from more than one feed (duplicates)
- reviews excluded by your rating filter
- reviews excluded by `publishedAfter`
- reviews excluded by `knownReviewIds`
- products that returned no reviews
- products that could not be found
- reviews that failed to store
- any failed request or failed product

The start event is the exception: it is charged once per run, **including a run
that ends up storing no reviews at all**. Keep `maxTotalReviews` in line with
what you actually want so the ceiling stays where you expect it.

If storing succeeds but the charge does not go through, the reviews stay in your
dataset and the run reports them as uncharged. The reverse — charging for
something that was not stored — cannot happen.

### Scheduling

A monitoring setup usually looks like this:

1. Run once with `publishedAfter` set to where you want to start.
2. Store the newest `publishedAt` you received.
3. Schedule the actor (Apify Console → *Schedules*, e.g. daily at 07:00) with
   that value in `publishedAfter`.
4. After each run, move `publishedAfter` forward to the newest `publishedAt` in
   the results.

If you prefer to track IDs, keep the `reviewId` values instead and pass the most
recent of them as `knownReviewIds`. Either way, reviews you already have are
skipped and not charged.

### Run status

Each product ends in one of these states, reported in the run summary:

| Status | Meaning |
|---|---|
| `succeeded` | Every planned feed answered and reviews were kept. |
| `partial` | Some feeds answered, others failed. What was collected is kept. |
| `no_reviews` | Feeds answered fine, but nothing survived your filters — or the product has no reviews. Not an error. |
| `invalid_product` | G2 has no such product. |
| `blocked` | The request was refused. |
| `rate_limited` | Too many requests; the actor waited and retried. |
| `temporary_error` | A server or network problem, or the product ran out of time. |
| `parse_error` | The feed came back but could not be read as RSS. |

One failing product does not stop the others. The run as a whole succeeds if
every product succeeded or had no reviews, is partial if some worked, and fails
only if every product failed.

### Exporting

Results export straight to JSON, CSV, Excel, XML or HTML table from the Apify
Console, or through the API. Every field is a plain column.

### Limitations

Worth knowing before you rely on it:

- **The feed is a snapshot.** There is no paging back through older reviews.
- **The default feed carries roughly the 25 most recent reviews.** Merging the
  per-rating feeds adds more recent candidates, but they overlap heavily, so the
  number of distinct reviews varies by product.
- **125 per product is a ceiling**, not an amount you should expect.
- **Products are processed one at a time**, in the order you list them, so run
  time grows with the number of products.
- **A restarted run may repeat work.** Within a single process the actor never
  stores or charges for the same review twice, but if the platform restarts the
  run, or a request's outcome is unknown, duplicates are possible. Use
  `knownReviewIds` or `publishedAfter` to control this yourself. Automatic state
  between runs is a candidate for a later version.
- **`knownReviewIds` holds at most 10,000 entries.** Send the most recent ones.
- **The actor depends on G2's public feed.** If its structure changes, parsing
  may need an update; watch for `parse_error` in the run summary.
- **`includeReviewerName` puts personal data in your dataset.** Turning it on
  makes you responsible for handling that data lawfully.
- **You are responsible for your own compliance** with G2's terms and any laws
  that apply to you.

### Privacy

Reviewer display names are left out unless you ask for them. The run summary and
the logs hold counts, HTTP statuses and response hashes only — never review text,
review URLs, review IDs, reviewer names or the product URLs you typed.

### Troubleshooting

**A product returns `invalid_product`.** Check the slug in the G2 URL:
`https://www.g2.com/products/<slug>/reviews`. The actor takes only the slug, so a
typo in it is the usual cause.

**Fewer reviews than `maxReviewsPerProduct`.** Expected. The feed only carries
recent reviews, and the per-rating feeds overlap. Check `collected` and the
per-stage counts in the run summary to see where reviews went.

**Everything filtered out.** Look at `filteredOutByRating`, `filteredOutByDate`
and `filteredOutByKnownId` in the summary. A `publishedAfter` in the future, or a
rating filter no recent review matches, empties the result.

**`rating` is null.** The feed did not show a rating for that review. The actor
leaves it empty rather than guessing, and a rating filter excludes such reviews.

**`blocked` or `rate_limited`.** Lower `requestConcurrency` and try again later.

**Duplicates across runs.** Pass the `reviewId` values you already have as
`knownReviewIds`, or move `publishedAfter` forward.

### Support

Questions and issues: open an issue on the actor's page in Apify Console.

***

This actor is an independent tool. It is not affiliated with, endorsed by or
connected to G2, and it does not use any G2 API.

# Actor input Schema

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

G2 products to monitor. Give a product slug (for example 'slack') or the product reviews URL (for example 'https://www.g2.com/products/slack/reviews'). Only the slug is kept; the URL you type is never stored or logged. A product listed twice is collected once.

## `maxReviewsPerProduct` (type: `integer`):

How many reviews to keep for each product, newest first. The public feed exposes the 25 latest reviews by default; asking for more also reads the per-rating feeds, which together reach about 125 reviews for an active product. It is not the full review history.

## `ratings` (type: `array`):

Keep only reviews showing these star ratings. Values are the stars displayed on the review: 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5 or 5. Leave this out to keep every rating. Every returned review is re-checked against its displayed rating, so a half star is never rounded.

## `publishedAfter` (type: `string`):

Keep only reviews published strictly after this moment. Use ISO 8601 with a time zone, for example '2026-01-01T00:00:00Z' or '2026-01-01T09:00:00+09:00'. A review published at exactly this moment is excluded, so you can pass the newest publishedAt of your last run and continue without duplicates. A review with no publication date is excluded while this filter is set.

## `knownReviewIds` (type: `array`):

Review IDs you already have. Matching reviews are skipped and never charged. Send the most recent IDs only; the list is capped at 10,000 entries.

## `includeReviewerName` (type: `boolean`):

Off by default so no personal data is collected. Turn it on only if you need the public display name G2 shows on the review, and make sure you may store it.

## `maxTotalReviews` (type: `integer`):

Hard ceiling on stored reviews across every product, so the price of a run stays predictable. Products are filled in the order you listed them.

## `requestConcurrency` (type: `integer`):

How many feed requests may run at once across the whole run. Lower it if you want to be gentler on the source.

## Actor input object example

```json
{
  "products": [
    "slack"
  ],
  "maxReviewsPerProduct": 25,
  "publishedAfter": "2026-01-01T00:00:00Z",
  "includeReviewerName": false,
  "maxTotalReviews": 1000,
  "requestConcurrency": 3
}
```

# Actor output Schema

## `reviews` (type: `string`):

One row per review, with the product, rating, published date and the public review answers.

## `runSummary` (type: `string`):

Run status, per-product status and the review counts for every filtering stage. Holds no review text.

# 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 = {
    "products": [
        "slack"
    ],
    "maxReviewsPerProduct": 25,
    "maxTotalReviews": 1000,
    "requestConcurrency": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("huggable_quote/g2-reviews-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 = {
    "products": ["slack"],
    "maxReviewsPerProduct": 25,
    "maxTotalReviews": 1000,
    "requestConcurrency": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("huggable_quote/g2-reviews-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 '{
  "products": [
    "slack"
  ],
  "maxReviewsPerProduct": 25,
  "maxTotalReviews": 1000,
  "requestConcurrency": 3
}' |
apify call huggable_quote/g2-reviews-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,huggable_quote/g2-reviews-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/LjKqJyBeqJqpHyr3f/builds/rpOd8zUtLQFWhmQi7/openapi.json
