# Trustpilot Reviews to Complaint Themes (`fractionalhqforyou/trustpilot-review-themes`) Actor

I read a company's Trustpilot reviews and group them into the complaint and praise themes that repeat, each with a count and verbatim quotes tied to the review they came from. Bring your own OpenAI key for the themes, and without one you still get every review.

- **URL**: https://apify.com/fractionalhqforyou/trustpilot-review-themes.md
- **Developed by:** [Jessy Mariau](https://apify.com/fractionalhqforyou) (community)
- **Categories:** AI, E-commerce, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 company analyseds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## Trustpilot Reviews to Complaint Themes

Plenty of Actors will hand you a company's Trustpilot reviews as a spreadsheet. Then you are holding 500 rows and the question you started with: what are these people actually angry about, and how often? This one answers that question. It collects the reviews. Give it your own OpenAI key and it reads them too, then hands back the top complaint themes and the top praise themes. Each theme carries a count and up to three quotes copied word for word from the reviews, tied to review ids so you can check any of them yourself.

I built it for my own pre-sales work. Before I pitch a fix for someone's support queue I want to know what their customers say is broken, in their words, with a number next to it. Ten minutes with a themes row beats an afternoon in a spreadsheet.

### What it does

1. Opens the company's public Trustpilot review pages in a real browser, newest first, and reads the reviews out of the JSON each page ships rather than scraping the HTML. It drives a browser because it has to: Trustpilot puts an AWS WAF JavaScript challenge in front of every page, and a plain HTTP client never gets past it (see Limits).
2. Pushes one dataset row per review with the star rating and the full title and text, both dates, the reviewer's country and verified flag, the company's public reply when there is one, and a link back to the review.
3. With an OpenAI key, sends the reviews to the model you name (gpt-5-mini by default) under a strict JSON schema and asks for 5 to 8 complaint themes and 3 to 5 praise themes. Each theme gets a short label, a count estimate and up to 3 quotes. You also get a one-paragraph readout in plain English.
4. Checks every quote the model returns against the review it claims to come from. A quote that is not a verbatim substring of that review's text or title is dropped, and the row records how many were dropped. The model is told never to invent a quote; this step is what proves it did not.
5. Pushes one summary row per company with `type: "themes"`, and writes the same summary to the `OUTPUT` record.

Run it with no domains and you get a demo dataset: 5 sample reviews and 1 sample themes row, nothing charged, so you can see the shape before pointing it at a real company.

### What you need

- One or more company domains, written exactly as they appear in the Trustpilot URL after `/review/`, one per line. `monzo.com` works, so does `www.asos.com`. Trustpilot keeps its own canonical name for each company and redirects to it, throwing your filters away as it goes, so the Actor resolves that name first and only then asks for filtered pages. You get the star and language filter you asked for, not a first page that is quietly unfiltered.
- An OpenAI API key from platform.openai.com, only if you want the themes step. It is stored as a secret and used for nothing else. The model cost lands on your OpenAI account, not here. For scale: my 20-review test on gpt-5-mini used about 1,600 input tokens and 6,200 output tokens.
- Nothing else. No proxy by default, no Trustpilot login. The browser runs inside the Actor, so there is nothing for you to install.

### How to run it

```json
{
  "domains": "monzo.com\nstarlingbank.com",
  "maxReviewsPerCompany": 200,
  "starsFilter": ["1", "2"],
  "language": "en",
  "openaiApiKey": "sk-your-own-key",
  "themeModel": "gpt-5-mini"
}
```

Pick 1 and 2 stars to feed the themes step complaints only; that is the setting I use most. Leave the star filter empty and the themes row shows the balance between praise and complaints across everything. `maxReviewsPerCompany` goes up to 500, collected 20 per page. `language` takes a Trustpilot language code, or `all`.

Leave out `openaiApiKey` and you still get every review row; the themes row then says the step was skipped and why.

### Output

Review rows, `type: "review"`:

| Field | What it holds |
|---|---|
| company | the domain you gave |
| reviewId | Trustpilot's id for the review |
| rating | 1 to 5 |
| title, text | the review as written |
| publishedDate, experiencedDate | ISO dates |
| language | language code of the review |
| reviewerCountry, reviewerName | as shown on Trustpilot |
| verified, verificationLevel | whether Trustpilot marks it verified, and how (for example `invited`) |
| reply, replyDate | the company's public reply, or null |
| url | link to the review |

Themes row, one per company, `type: "themes"`:

| Field | What it holds |
|---|---|
| company / trustpilotName | the domain you gave and the name Trustpilot resolved it to |
| companyName / trustScore / totalReviewsOnTrustpilot | what Trustpilot shows for the company |
| reviewsAnalysed / ratingBreakdown / filters | how many reviews went into the themes step, split by stars, and the filters used |
| blocked / notFound / fetchStatuses | whether Trustpilot blocked or had no page, plus the HTTP status of every page fetched |
| themesGenerated / model / tokens | whether the themes step ran, with which model, and what it used |
| complaintThemes / praiseThemes | arrays of `{ label, countEstimate, quotes: [{ reviewId, quote, rating }] }` |
| readout | the one-paragraph plain-English summary |
| droppedQuotes | how many model quotes failed the verbatim check and were removed |
| note | anything you should know about this company's run, in a sentence |

The `OUTPUT` record in the key-value store holds every themes row under `companies`, plus a one-line `text` summary you can pipe into Slack or email with an Apify integration.

### Pricing

$0.02 per company analysed. That is the only charge. The reviews themselves cost nothing, however many you pull.

You are charged when a company's themes are actually produced. A company Trustpilot blocked, a run with no OpenAI key, a failed model call and the demo run all produce output and charge you nothing.

The OpenAI cost is separate and sits on your own key.

### Limits

- Trustpilot sits behind AWS WAF. The first version of this Actor used a plain HTTP fetch, and it collected nothing at all: a 403 "Verifying Connection" page from an Apify datacenter IP, the same through residential proxy, and the same from my own home line with a full set of browser headers. Not the IP, then, and not the headers. That interstitial is a JavaScript challenge. It runs a script and earns a token cookie before the real page will load. No HTTP client can do that. So the Actor drives a real Chromium and waits for the challenge to clear, which takes a second or two on the first page of each company and nothing after that, because the token is reused for the rest of the run. Residential proxy is not needed for this and is off by default.
- If the challenge does not clear, the Actor says so rather than guessing. That company gets a themes row with `blocked: true`, the HTTP status of every page it touched in `fetchStatuses`, and a note explaining what happened. A company with no Trustpilot page gets `notFound: true` the same way. You still get a row, and the row still tells the truth.
- Because it runs a browser, this Actor needs 4 GB of memory. That is set as the minimum on the Actor, so you should not have to think about it.
- Themes are model output. Read them against the counts and the quotes. The quotes are verified word for word; the labels and the count estimates are the model's judgement, not a database query. A theme with a count of 1 and a single quote is one reviewer, not a trend.
- One model call per company, with each review text cut at 1,200 characters. 500 reviews fit comfortably in one call on current models.
- Public reviews only. Trustpilot's terms apply to what you do with the data.
- Mixed languages lower the quality of the themes. Filter to one language unless you have a reason not to.

Getting past the bot wall took a rebuild, and I wrote up both that and what the themes will not tell you at https://fractionalhq.uk/use-cases/review-themes

### Built by Fractional HQ

I run Fractional HQ, a one-man studio for design and automation. I do not write code by hand: I architect, review and hold the standard while AI agents do the typing. That method runs my own estate of seven brands, on a Postgres operations system with more than 100 scheduled jobs. I publish n8n templates. I am Zapier Silver certified.

Want the themes wired into a weekly report, or the complaints turned into a fix list for your support team? Get in touch: https://fractionalhq.uk/brief

# Actor input Schema

## `domains` (type: `string`):

One Trustpilot company domain per line, exactly as it appears in the Trustpilot URL after /review/, for example monzo.com or www.asos.com. Leave empty to get a demo dataset that charges nothing.

## `maxReviewsPerCompany` (type: `integer`):

Newest first, 20 per page. Hard cap 500.

## `starsFilter` (type: `array`):

Leave empty for all ratings. Pick 1 and 2 to feed the themes step complaints only.

## `language` (type: `string`):

Trustpilot language code, for example en, fr, de. Use all for every language.

## `openaiApiKey` (type: `string`):

Your own key from platform.openai.com. It pays for the themes step and is stored as a secret. Without it the Actor still collects the reviews and the themes row says the step was skipped.

## `themeModel` (type: `string`):

Any OpenAI model that supports structured JSON output.

## `useApifyProxy` (type: `boolean`):

Off by default, and normally you do not need it: the Actor clears Trustpilot's bot check with the browser it runs, on a plain Apify IP. Switch this on only if a company keeps coming back blocked. It routes the browsing through Apify residential proxy, which is billed to your Apify account on top of this Actor's events.

## Actor input object example

```json
{
  "maxReviewsPerCompany": 100,
  "language": "en",
  "themeModel": "gpt-5-mini",
  "useApifyProxy": false
}
```

# Actor output Schema

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

One row per review (type review) and one summary row per company (type themes) with the complaint and praise themes, counts, verbatim quotes and a plain-English readout.

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

The OUTPUT record: one entry per company with its themes row, plus a one-line text summary.

# 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("fractionalhqforyou/trustpilot-review-themes").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("fractionalhqforyou/trustpilot-review-themes").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 '{}' |
apify call fractionalhqforyou/trustpilot-review-themes --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fractionalhqforyou/trustpilot-review-themes"
        }
    }
}

```

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/4JXcoCU6UkvJKwdBf/builds/kpU93JS9KpkqlD3TK/openapi.json
