# Reddit Search (`fillory/reddit-search`) Actor

- **URL**: https://apify.com/fillory/reddit-search.md
- **Developed by:** [Matt Brown](https://apify.com/fillory) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 posts

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?

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

## Reddit Sub-Scoped Search

**Find the people already asking.** Give this Actor a list of subreddits and a list of phrases, and it
searches *each phrase inside each subreddit* and returns the newest matching posts — the subreddit, the
phrase that found it, the permalink, title, body, author, timestamp and score.

⛑ **That scoping is the point.** Reddit's own search does it — `restrict_sr=1` — and store Actors
generally do not: the widely-used one ignores the query when subreddits are present, so you get a
subreddit's hot posts instead of an answer to your question. This Actor asks Reddit the question you
actually asked.

### Sample output

```json
{
  "subreddit": "propertymanagement",
  "query_that_found_it": "scam listing",
  "title": "Someone copied my rental listing and is collecting deposits",
  "author": "example_user",
  "created": "2026-09-17T14:02:11Z",
  "score": 34,
  "comments": 12,
  "permalink": "https://www.reddit.com/r/propertymanagement/comments/…",
  "body": null
}

`body` is always `null`: Reddit's search results carry the title, the author, the timestamp and the
counters — not the post text. Reading it means opening each permalink, which is a different job.
```

Every pair runs on its own fresh session, so a long list of pairs is a long list of anonymous
visitors rather than one visitor asking sixty questions.

The **last** item of every run is a summary, marked `"__fillory": "run-summary"`, carrying how many pairs
were attempted, how many returned posts, how many were genuinely empty, how many were blocked, and the
error counts by category. ⛑ **It is written even if the run is stopped early** — with the
counts it had reached and `"terminated"` saying why — so a run that ran out of time still tells you
what it found and how far it got. ⛑ **An empty search and a blocked one are different numbers in that item** — a
run that got nothing because it was blocked never reports it as "nothing found".

### Pricing

This Actor is **pay per event**. You are charged for two things and nothing else:

| event | when it is charged | price |
|---|---|---|
| `run-start` | once, when the run begins | **$0.06** |
| `post` | for each post that reaches your dataset | **$5.00 per 1,000** ($0.005 each) |

**A run of 3 subreddits × 2 phrases returning 25 posts each costs $0.06 + 150 × $0.005 = $0.81.**

⛑ **Apify platform usage is on us** — you pay the two events above and nothing else.

⛑ **A post is charged after it is stored, never before.** If a post cannot be stored, it is not charged.

⛑ **Set a maximum total charge on the run and this Actor respects it.** When your budget is reached it
stops asking Reddit for more, finishes the pages already in flight, writes its summary and exits — you
are never billed past the cap, and you keep everything that was collected up to it. The summary item
says `"terminated": "budget"` so you can tell a capped run from a finished one.

### If a run finds nothing

Reddit sometimes has no post matching your phrase, and sometimes serves a challenge page instead of
results. **These are different outcomes and this Actor never reports one as the other.** A run that
returns no posts still returns an item saying which happened:

```json
{ "__fillory": "run-error", "reason": "no_results",
  "message": "The searches ran and this subreddit has no post matching these phrases in the chosen time window. Try a wider time window or a broader phrase.",
  "pairs_attempted": 6, "pairs_blocked": 0, "pairs_empty": 6 }
```

`reason` is one of `no_results`, `blocked`, `partly_blocked`, `budget_exhausted` or `rejected`, and the
message says what to do about it. ⛑ Both this item and the run summary carry a `__fillory` key, so you
can drop them in one filter and keep only the posts.

### Configuration

> ⛔ **The prefill values are the daily auto-test's input.** Apify runs a published Actor once a day
> with the **prefill** values from this schema — never the `default` — and requires the run to succeed
> with a non-empty dataset inside five minutes. Three failures mark the Actor *under maintenance*;
> eighteen mark it *deprecated*.
>
> ⛑ So the prefilled pair is **measured, not chosen**: `r/PropertyManagement` + *"rent out my house"*
> returned posts in every subreddit on both of the days our own production lane has run.
>
> ⛔ And `default` stays `[]` on `subreddits` and `queries` on purpose. A caller who omits them is
> **refused**, not silently handed somebody else's search — and if the test ever read `default`
> instead of `prefill`, every run would fail on an argument error with no line of code being wrong.
>
> ⚠️ This note lives here and in `PRICING-BASIS.md`, not in the schema: Apify **rejects** an unknown
> key, `_comment`-shaped or otherwise (`Property schema._comment_prefill is not allowed`), and the
> build fails.

| field | what it does |
|---|---|
| `subreddits[]` | names, with or without `r/` |
| `queries[]` | each phrase is searched inside each subreddit |
| `time` | `hour` · `day` · `week` (default) · `month` · `year` · `all` |
| `maxPerPair` | one page of results per pair; this bounds the slice taken from it |
| `probe` | diagnostic: fetch one pair and report what the page is, instead of searching |
| `maxConcurrency` | how many pairs run at once, each on its own session (default 2) |
| `probeLabel` | names the HTML the probe saves to the run's key-value store, so two probe runs can be diffed |

`REDDIT_PROXY_URL` overrides the default Apify residential proxy if you bring your own. ⛑ Reddit
serves a challenge page to datacenter addresses, so the default is residential — bringing your own
proxy is the one input that materially changes how many pairs come back `blocked`.

### ⛔ What this Actor will not do

**It is read-only, permanently.** It never signs in, holds no Reddit credential, and has no code path that
posts, comments, votes, messages or edits anything. It reads public search pages and returns what they
contain.

**Use it to find conversations worth joining — and join them as yourself, with disclosure.** Do not use it
to spam subreddits, to build profiles of individual users, or to automate outreach that a human has not
read. Reddit's rules and each subreddit's rules apply to what you do with the results; this Actor's job
ends at handing them to you.

# Actor input Schema

## `subreddits` (type: `array`):

One or more subreddit names, with or without the r/ prefix — for example propertymanagement or r/smallbusiness. Each one is searched for every phrase below.

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

Each phrase is searched INSIDE each subreddit, not across all of Reddit. Quote a phrase to match it as written. Three subreddits and two phrases means six searches.

## `time` (type: `string`):

Reddit's own time filter. Narrower windows return fewer, newer posts — start with a week and widen it if a search comes back empty.

## `maxPerPair` (type: `integer`):

Each search reads one page of Reddit results; this caps how many of those posts are kept. It is also your main cost control — you are charged per post returned.

## `maxConcurrency` (type: `integer`):

Each search runs as its own anonymous visitor. Two at a time is the tested default; higher is faster but Reddit is more likely to answer with a challenge page instead of results.

## `probe` (type: `boolean`):

Runs ONE search and reports what Reddit's page actually was — its size, its title, and whether it was a challenge page — instead of returning posts. Use it only when ordinary runs come back blocked and you want to see why; it does not return post data.

## `probeLabel` (type: `string`):

Names the page snapshot the diagnostic run saves to this run's key-value store, so two diagnostic runs can be compared side by side.

## Actor input object example

```json
{
  "subreddits": [
    "PropertyManagement"
  ],
  "queries": [
    "rent out my house"
  ],
  "time": "week",
  "maxPerPair": 25,
  "maxConcurrency": 2,
  "probe": false,
  "probeLabel": ""
}
```

# Actor output Schema

## `posts` (type: `string`):

Every post found, one row each, in the default dataset. NOTE: the last row of a run is a run summary, and a run that found nothing adds one row explaining why — both carry a `__fillory` key and neither is a post. Filter them out by that key alone (never by a value in it): everything without `__fillory` is a post.

## `runFiles` (type: `string`):

The run's key-value store: `run-summary` (JSON, rewritten as the run goes) and, on a diagnostic run only, the `page-*.html` snapshots of what Reddit actually served.

# 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 = {
    "subreddits": [
        "PropertyManagement"
    ],
    "queries": [
        "rent out my house"
    ],
    "time": "week"
};

// Run the Actor and wait for it to finish
const run = await client.actor("fillory/reddit-search").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 = {
    "subreddits": ["PropertyManagement"],
    "queries": ["rent out my house"],
    "time": "week",
}

# Run the Actor and wait for it to finish
run = client.actor("fillory/reddit-search").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 '{
  "subreddits": [
    "PropertyManagement"
  ],
  "queries": [
    "rent out my house"
  ],
  "time": "week"
}' |
apify call fillory/reddit-search --silent --output-dataset

```

## MCP server setup

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

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/tNVcPUEeEpIm7SXuf/builds/8VTjqmrgOho77gA5X/openapi.json
