# Reddit Scraper (`techforce.global/reddit-scraper`) Actor

Scrape Reddit posts, comments, users, no login or API key. Search or paste URLs, filter by keyword/date, export to JSON/CSV/Excel or push to Notion, Slack & more.

- **URL**: https://apify.com/techforce.global/reddit-scraper.md
- **Developed by:** [Techforce Global](https://apify.com/techforce.global) (community)
- **Categories:** Social media, Agents, News
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

### Reddit Scraper

[![Apify Actor](https://img.shields.io/badge/Apify-Actor-orange?logo=apify)](https://apify.com/techforce.global/reddit-scraper)
[![MCP Ready](https://img.shields.io/badge/MCP-Compatible-blue?logo=anthropic)](https://mcp.apify.com?tools=techforce.global/reddit-scraper)
[![Pricing](https://img.shields.io/badge/Pricing-Usage--based-green)](https://apify.com/techforce.global/reddit-scraper)
[![Output](https://img.shields.io/badge/Output-post%20%C2%B7%20comment%20%C2%B7%20user%20%C2%B7%20community-darkgreen)](#-output-contract--data-structure)
[![Public data only](https://img.shields.io/badge/Data-Public%20content%20only-informational)](#-compliance--data-privacy)
[![Target](https://img.shields.io/badge/Target-Reddit.com-red)](https://www.reddit.com)

> **No Reddit app, no OAuth, no API credentials.** Subreddits, posts, search terms, hashtag-style
> queries, user profiles and community lists in — **four record types out in a single dataset**:
> posts, comments, users and communities, each flat, typed and joinable. Built for social
> listening, market research and community analysis, with optional delivery into Notion, Slack,
> Linear, Jira, GitHub or Airtable over MCP.

#### 🔐 Trust & compliance at a glance

| | |
| :--- | :--- |
| **Credentials** | None required. No Reddit account, app registration or OAuth token. Delivery runs through the Apify MCP Proxy, so the Actor never sees your connector tokens. |
| **Data scope** | **Public content only** — public subreddits, posts, comment threads and profiles. No login, no private or quarantined-with-login content, no direct messages. |
| **⚠️ Personal data** | Reddit content **is** personal data. `author`, `authorId`, `username` and free-text `body` identify pseudonymous individuals, and profile rows carry karma and account history. |
| **Reduce your exposure** | `skipComments: true` drops the largest source of user speech; `maxComments: 0` does the same. Both keep post-level metrics intact. |
| **NSFW control** | `includeNSFW` defaults to **`true`** — set it `false` to filter 18+ items out of the dataset. |

Full detail, including GDPR position and Reddit's Terms: **[Compliance & Data Privacy](#-compliance--data-privacy)**.

***

#### Official Reddit Data API vs. Reddit Scraper Actor

Reddit's own API is a real alternative — with a registration and rate-limit story that stops many
projects before they start:

| Feature / Capability | Official Reddit Data API | Reddit Scraper Actor (Apify) |
| :--- | :--- | :--- |
| **Setup** | ❌ Register an app, obtain a client ID/secret, implement OAuth | ✅ Apify account, nothing else |
| **Rate limits** | ⚠️ Per-client request limits you must implement backoff for | ✅ Proxy rotation and retries handled for you |
| **Commercial / high-volume use** | ⚠️ Requires a separate paid agreement with Reddit | ✅ Standard Apify usage billing |
| **Mixed entities in one run** | ❌ Separate endpoints, separate pagination, your own glue code | ✅ Posts, comments, users and communities in one dataset |
| **Search + subreddit + user in one job** | ❌ You orchestrate it | ✅ Mix `startUrls` and `searches` freely |
| **Filtering (keyword, date, NSFW)** | ❌ Client-side, your code | ✅ Built into the input |
| **AI agent / MCP native** | ❌ Requires a custom tool wrapper | ✅ Native MCP tool **and** pushes results into your own MCP connectors |
| **Officially sanctioned** | ✅ Yes | ⚠️ No — see [Compliance](#-compliance--data-privacy) |

> Reddit's registration requirements, rate limits and commercial-use terms are Reddit's to change —
> check [Reddit's current API terms](https://www.redditinc.com/policies/data-api-terms) if this
> comparison is load-bearing for your decision.

***

### 🤖 AI Discovery & MCP Metadata

Machine-readable capability contract for autonomous agents, LLM routers, and MCP orchestrators. Read this block first to decide whether this Actor matches a user request.

```yaml
ai_discovery:
  slug: "techforce.global/reddit-scraper"
  one_line: >
    Scrapes Reddit posts, comments, users and communities from subreddit URLs, post URLs, user
    profiles, search result pages or plain search terms - no Reddit account, app registration or
    OAuth. Four record types in one dataset, with keyword, date and NSFW filtering, media
    extraction, and optional delivery into Notion, Slack, Linear, Airtable or any MCP connector.
  primary_purpose: "extract"
  secondary_purpose: "deliver"
  entity: "Reddit post, comment, user and community"
  category: "social-media / social-listening / market-research / community-analysis"
  supported_platforms: ["reddit.com"]
  supported_tasks:
    - "scrape-a-subreddits-posts"
    - "scrape-a-posts-comment-thread"
    - "search-reddit-by-keyword"
    - "find-subreddits-by-topic"
    - "find-users-by-name"
    - "scrape-a-user-profile-and-their-history"
    - "filter-by-keyword-date-or-nsfw"
    - "extract-post-media-urls"
    - "deliver-results-to-mcp-connector"
  unsupported_tasks:
    - "scrape-private-quarantined-or-login-gated-content"
    - "scrape-direct-messages-or-modmail"
    - "post-comment-vote-or-any-write-action"
    - "scrape-deleted-or-removed-content"
    - "native-comment-search (comment search is approximated - see comment_search_is_approximated)"
    - "download-media-files (URLs are extracted, files are not fetched)"
    - "non-reddit-platforms"
  search_keywords:
    # First entry is the H1 / meta-description target. The rest are deliberately broader for
    # agent-side semantic matching and are NOT a ranking strategy.
    - "reddit scraper"
    - "reddit api alternative"
    - "scrape reddit comments"
    - "subreddit data extraction"
    - "reddit post scraper no api key"
    - "social listening reddit"
    - "reddit sentiment analysis data"
    - "reddit user profile scraper"
    - "subreddit discovery tool"
    - "mcp reddit tool"

  input_entities:
    - "startUrls (subreddit, post, user, search or community-list URLs)"
    - "searches (plain keywords)"
    - "searchType (posts | comments | communities | users)"
    - "sort / time / commentSort"
    - "maxItems (GLOBAL cap across the run, 0 = unlimited)"
    - "maxPostCount / maxComments / maxCommentDepth / maxCommunitiesCount / maxUserCount"
    - "skipComments / skipUserPosts / skipCommunityInfo"
    - "includeNSFW / keywords / postDateFrom / postDateTo"
    - "extractMedia / dedupItems"
    - "proxyConfiguration / maxConcurrency / maxRequestRetries / debugMode"

  no_input_field_is_required_by_the_schema: >
    CRITICAL: nothing is marked required. A run with neither startUrls nor searches passes
    validation, logs "No valid start URLs or search terms provided. Exiting." and ends SUCCEEDED
    with an empty dataset. Supply at least one of startUrls or searches.

  output_is_four_record_types: >
    CRITICAL: one dataset holds four shapes, discriminated by the `type` field - "post",
    "comment", "user" and "community". Always branch on `type` before reading a row. Fields that
    do not apply to a type are absent, not null.

  output_entities_post: ["type", "id", "parsedId", "subreddit", "subredditId", "title", "body",
    "author", "authorId", "url", "link", "domain", "flair", "createdAt", "createdTimestamp",
    "score", "upVotes", "upvoteRatio", "numberOfComments", "numberOfCrossposts", "awardsCount",
    "isSelf", "isVideo", "isOriginalContent", "over18", "spoiler", "locked", "stickied",
    "distinguished", "media"]
  output_entities_comment: ["type", "id", "parsedId", "subreddit", "body", "author", "authorId",
    "url", "parentId", "postId", "createdAt", "createdTimestamp", "score", "upVotes", "depth",
    "isSubmitter", "stickied", "controversiality", "distinguished", "numberOfReplies",
    "postTitle", "postUrl"]
  output_entities_user: ["type", "id", "parsedId", "username", "url", "createdAt",
    "createdTimestamp", "linkKarma", "commentKarma", "totalKarma", "awardeeKarma", "awarderKarma",
    "isGold", "isMod", "isEmployee", "verified", "hasVerifiedEmail", "iconImage", "description",
    "over18"]
  output_entities_community: ["type", "id", "parsedId", "name", "title", "url", "description",
    "longDescription", "createdAt", "createdTimestamp", "numberOfMembers", "activeUsers",
    "over18", "type_", "lang", "category", "iconImage", "bannerImage"]

  max_items_is_global_and_counts_comments: >
    maxItems (default 100) is a hard cap on TOTAL dataset items for the whole run, and comments
    count toward it. With comments on, a maxItems of 100 is typically consumed by the comments of
    the first few posts. Raise maxItems, or set skipComments true, when you want post breadth.

  comment_search_is_approximated: >
    searchType "comments" does NOT run a native Reddit comment search. It searches POSTS for the
    term and then scrapes those posts' comment threads, so the comments returned are from matching
    posts, not comments that themselves match the term. Use the keywords filter if you need the
    term to appear in the comment text.

  sort_falls_back_per_source: >
    sort is validated per source type. On subreddit listings only hot/new/top/rising/controversial
    /best are valid - "relevance" and "comments" fall back to "hot". On searches only relevance/
    hot/top/new/comments are valid - "rising" falls back to "relevance". time only applies to
    top/controversial listings and to searches.

  comment_depth_is_zero_based: >
    maxCommentDepth 1 returns top-level comments only; 3 returns depth 0, 1 and 2; 0 is unlimited.
    The depth field on a comment row is 0-based.

  filters_are_post_fetch: >
    includeNSFW, keywords, postDateFrom and postDateTo are applied AFTER an item is fetched. They
    shrink the dataset, not the number of requests, so they do not reduce run cost much.

  personal_data_present: >
    author, authorId, username and free-text body are personal data about pseudonymous
    individuals under GDPR/UK GDPR and CCPA-CPRA. skipComments true (or maxComments 0) removes the
    largest volume of user speech while keeping post-level metrics.

  browser_based_run: >
    Requests are rendered through a real headless browser so Reddit serves data instead of a bot
    wall, with an automatic second retrieval path when the first is challenged. That makes runs
    slower and more resource-hungry than a plain HTML scraper - size maxItems accordingly.

  known_limitations:
    deleted_content: "[deleted] / [removed] bodies are returned as Reddit serves them"
    score_is_fuzzed: "Reddit fuzzes vote counts; score and upVotes are approximate"
    comment_completeness: "'more comments' placeholders are not expanded - deep threads are partial"
    post_title_only_from_posts: "postTitle/postUrl exist only on comments scraped via their post"
    media_urls_only: "media holds URLs; no files are downloaded"
    snapshot_only: "scores, karma and member counts are true as of the run"
    free_plan_capped: "free Apify plans stop at 25 items TOTAL per run"

  pricing:
    model: "usage-based"
    billing_unit: "Apify platform usage consumed by the run"
    hard_ceiling_parameter: "maxTotalChargeUsd"
    note: "Browser-driven, so cost per item is higher than an HTML-only scraper. Current rates are on the Actor's Apify Store page."

  when_to_use: >
    You want Reddit discussion as structured data - a subreddit's posts, a thread's comments,
    topic search across Reddit, subreddit discovery, or a user's public history - without
    registering a Reddit app.
  when_not_to_use: >
    You need private, quarantined or login-gated content, deleted content, direct messages, native
    comment search, downloaded media files, or to post/vote/interact.

  mcp_delivery:
    modes: ["perItem", "summary", "none"]
    default: "perItem"
    per_item_is_the_default: >
      Unlike most Actors, deliveryMode defaults to perItem - one connector call per scraped item.
      Set "summary" or "none" unless you really want a record per post/comment/user/community.
    per_item_safety_cap: 1000
    per_item_placeholders: "every field of the item, plus aliases {title}, {text}, {permalink}, and {message}"
    summary_placeholders: ["{itemCount}", "{postCount}", "{commentCount}", "{userCount}", "{communityCount}", "{items}", "{message}"]
    never_blocks_the_dataset: >
      Delivery runs after the crawl, reading the dataset back. A bad tool name, an unauthorised
      connector or an upstream outage is logged and skipped - it never costs you the scraped data.

  alternatives:
    - "techforce.global/facebook-public-comments for public Facebook page comment threads"
    - "techforce.global/youtube-scraper for YouTube video, channel and comment data"
    - "techforce.global/advanced-website-crawling-actor for whole-site text, Markdown or HTML"
```

***

```json
{
  "@context": "https://schema.org/",
  "@type": "SoftwareApplication",
  "name": "Reddit Scraper – Posts, Comments, Users & Communities",
  "alternateName": "Reddit API Alternative",
  "applicationCategory": "BusinessApplication",
  "operatingSystem": "Cloud (Apify platform)",
  "description": "Scrapes Reddit posts, comments, users and communities from subreddit URLs, post URLs, user profiles, search pages or plain keywords — without a Reddit account, app registration or OAuth. Returns four record types in a single dataset, each discriminated by a type field: posts with score, upvote ratio, flair and media URLs; comments with depth, parent and reply counts; users with karma breakdowns; and communities with member and active-user counts. Includes keyword, date and NSFW filtering, in-run deduplication, media URL extraction, proxy rotation, and optional delivery into Notion, Slack, Linear, Airtable or any MCP connector.",
  "url": "https://apify.com/techforce.global/reddit-scraper",
  "offers": {
    "@type": "Offer",
    "priceCurrency": "USD",
    "category": "Pay per platform usage",
    "description": "Usage-based: billed for the compute time and resources a run consumes, not per row. Runs are browser-driven, so cost per item is higher than an HTML-only scraper. Current rates are shown on the Actor's Apify Store page."
  }
}
```

***

### What this Actor does

- **Six kinds of input, mixed freely in one run.** Subreddit URLs (with or without a sort tab),
  post permalinks, user profiles, in-subreddit or global search pages, community lists — plus
  plain keywords via `searches`. No Reddit app, no OAuth, no credentials.

- **Four record types in one dataset.** `post`, `comment`, `user` and `community`, each flat and
  discriminated by `type`. Comments carry `postId` and `parentId`, so a thread rebuilds exactly.

- **Whole-entity coverage, not just the headline object.** A subreddit URL also yields the
  community's `about` record; a user profile also yields that user's public history — each
  switchable off with `skipCommunityInfo` / `skipUserPosts`.

- **Real filtering, in the input.** Keyword match, `postDateFrom` / `postDateTo` (absolute dates
  *or* relative strings like `7 days`), and an NSFW switch — applied to every item before it
  reaches your dataset.

- **Media URLs extracted for you.** Thumbnails, preview images, Reddit-hosted video, gallery
  images and the external link a post points at, in one nested `media` object.

- **Deduplicated within the run.** The same post reached from a search *and* from its subreddit is
  written once (`dedupItems`, on by default).

- **Gets past the bot wall.** Requests render through a real headless browser, with an automatic
  second retrieval path when the first is challenged, and a fresh proxy session on rate-limit
  retries.

- **Saves as it goes and stops cleanly.** Items are written as they are parsed and the platform
  `ABORTING` event is handled, so an aborted or capped run keeps everything collected so far.

- **Optionally delivers into your tools.** Notion, Slack, Linear, Jira, GitHub, Airtable or
  anything else you've authorised.

***

### When to use it — and when not to

| Scenario | Fit |
|---|---|
| Every recent post in a subreddit | ✅ Strong — a subreddit URL, or several |
| A specific thread's full comment tree | ✅ Strong — a post permalink, `maxCommentDepth` to taste |
| Topic search across Reddit | ✅ Strong — `searches` + `searchType: "posts"` |
| Finding subreddits about a topic | ✅ Strong — `searchType: "communities"` |
| A user's public posting history | ✅ Strong — a `/user/...` URL |
| Community size and activity metrics | ✅ Strong — `numberOfMembers`, `activeUsers` |
| Social listening / sentiment corpora | ✅ Strong — comment bodies with scores and depth |
| Post-level metrics **without** user speech | ✅ Strong — `skipComments: true` |
| Keyword, date or NSFW filtering | ✅ Strong — built into the input |
| Results in Notion / Slack / Airtable | ✅ Strong — native MCP delivery |
| Comments that themselves match a keyword | ⚠️ `searchType: "comments"` searches **posts**, then scrapes their comments — add a `keywords` filter |
| A complete deep comment tree | ⚠️ "More comments" placeholders aren't expanded — deep threads come back partial |
| Exact vote counts | ⚠️ Reddit fuzzes scores; treat them as approximate |
| **Private, quarantined or login-gated content** | ❌ Public content only |
| **Deleted or removed content** | ❌ Returned only as Reddit serves it (`[deleted]` / `[removed]`) |
| Direct messages or modmail | ❌ Never accessible |
| **Downloading images or video files** | ❌ URLs are extracted; files are not fetched |
| Posting, commenting, voting, moderating | ❌ Read-only |
| Non-Reddit platforms | ❌ Reddit only |

***

### ⚡ Execution Rules (Avoid Common Agent Failures)

1. **Supply at least one of `startUrls` or `searches`.** **Nothing is marked required in the
   schema**, so a run with neither passes validation, logs
   `No valid start URLs or search terms provided. Exiting.` and ends **`SUCCEEDED` with an empty
   dataset**. Never read `itemCount: 0` as a crash.

2. **`maxItems` is a GLOBAL cap and comments count toward it.** Default `100`. With comments on,
   the first two or three posts' threads can consume the whole budget before the fourth post is
   reached. For post *breadth*, either raise `maxItems` or set `skipComments: true`.

3. **Branch on `type` before reading any row.** One dataset, four shapes — `post`, `comment`,
   `user`, `community`. Fields that don't apply to a type are **absent**, not null, so use
   `.get()` / optional access.

4. **`searchType: "comments"` is an approximation.** It searches **posts** for your term and then
   scrapes those posts' comments — the comments themselves are not matched against the term. If
   you need the term to appear in the comment text, add it to the `keywords` filter as well.

5. **`sort` is validated per source and silently falls back.** On subreddit listings, `relevance`
   and `comments` are not valid sorts and become **`hot`**. On searches, `rising` is not valid and
   becomes **`relevance`**. `time` only applies to `top` / `controversial` listings and to
   searches.

6. **`maxCommentDepth` is 0-based in the output.** `1` = top-level only, `3` = depth `0`, `1` and
   `2`, `0` = unlimited.

7. **The per-source caps stack under `maxItems`.** `maxPostCount` (per subreddit / search / user),
   `maxComments` (per post), `maxCommunitiesCount` and `maxUserCount` (per source) all bound their
   own scope — but `maxItems` still ends the run first when it is reached.

8. **Filters run *after* fetching.** `includeNSFW`, `keywords`, `postDateFrom` and `postDateTo`
   shrink the dataset, not the request count — they are not a cost-control lever.

9. **`includeNSFW` defaults to `true`.** Set it `false` if 18+ content must not enter your
   dataset.

10. **A subreddit URL yields a `community` row too**, and a user URL yields the user's history —
    unless you set `skipCommunityInfo` / `skipUserPosts`.

11. **`postTitle` and `postUrl` only appear on comments scraped through their post.** Comments
    collected from a user's history don't carry them — join on `postId` instead.

12. **Keep the proxy enabled.** Default is `{"useApifyProxy": true}`. Reddit rate-limits by IP; on
    larger runs prefer residential. A `429` is retried on a fresh session up to
    `maxRequestRetries` (default `5`).

13. **This Actor drives a real browser.** It is slower and more resource-hungry per item than an
    HTML-only scraper, and `maxConcurrency` (default `10`) ramps up from a low starting point on
    purpose. Lower it if you're being rate-limited.

14. **`deliveryMode` defaults to `perItem`** — one connector call per scraped item, capped at
    **1,000** deliveries with a warning. Set `summary` or `none` unless you want a record each.

15. **Free Apify plans stop at 25 items *total* per run** — not per source.

```yaml
dependencies:
  - rule: "at least one of startUrls / searches must be non-empty"
    on_violation: "run ends SUCCEEDED with an empty dataset and a warning in the log - it does NOT fail validation"
  - rule: "maxItems bounds the whole run, including comments"
    note: "set skipComments true when you want many posts rather than deep threads"
  - rule: "delivery requires mcpConnector AND mcpTool"
    on_violation: "delivery is skipped with a warning; every dataset row is still written"
  - rule: "mcpArguments must reference {message} for the rendered template to appear anywhere"
    on_violation: "message body is empty; the tool is still called"
  - rule: "mcpConnector only resolves when the Actor runs on the Apify platform"
    on_violation: "local runs log 'APIFY_MCP_PROXY_URL is not set' and skip delivery"
authentication:
  actor_input: none
  platform: "Apify API token required for API / SDK / MCP invocation"
  target_site: "no Reddit account, app registration or OAuth token required (public content only)"
```

***

### 📥 Input Contract

`.actor/input_schema.json` is authoritative — if this page disagrees with the schema files,
**the schema files win**.

#### Sources

| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
| `startUrls` | array | see note | — | Subreddit, post, user, search or community-list URLs |
| `searches` | array\<string> | see note | — | Plain keywords; each runs one search |
| `searchType` | string (enum) | ⬜ No | `posts` | `posts` · `comments` · `communities` · `users` |

> **At least one of `startUrls` or `searches` must be non-empty.** Nothing is `required` in the
> schema, so this is enforced at runtime.

**Accepted `startUrls` shapes:**

| Shape | Example | What you get |
|---|---|---|
| Subreddit | `https://www.reddit.com/r/technology/` | `community` row + its posts (+ comments) |
| Subreddit with sort | `https://www.reddit.com/r/technology/top/?t=week` | The sort and time window in the URL win over `sort`/`time` |
| In-subreddit search | `https://www.reddit.com/r/technology/search?q=ai` | Matching posts from that subreddit |
| Post permalink | `https://www.reddit.com/r/x/comments/abc123/slug/` | The post + its comment tree |
| User profile | `https://www.reddit.com/user/spez/` | `user` row + their public history |
| User sub-page | `.../user/spez/submitted` · `/comments` · `/overview` | Just that slice of their history |
| Global search page | `https://www.reddit.com/search?q=bitcoin` | Matching posts |
| Community list | `https://www.reddit.com/subreddits/search?q=ai` | Matching `community` rows |

An unrecognised URL is skipped with `Could not classify URL, skipping: <url>` in the log.

#### Sorting

| Parameter | Type | Default | Valid where |
|---|---|---|---|
| `sort` | string (enum) | `new` | `relevance` · `hot` · `top` · `new` · `rising` · `comments`. **Listings** accept hot/new/top/rising (others → `hot`); **searches** accept relevance/hot/top/new/comments (`rising` → `relevance`) |
| `time` | string (enum) | `all` | `hour` · `day` · `week` · `month` · `year` · `all`. Applies to `top`/`controversial` listings and to searches |
| `commentSort` | string (enum) | `top` | `confidence` (Best) · `top` · `new` · `controversial` · `old` · `qa` |

#### Limits

| Parameter | Type | Default | Scope |
|---|---|---|---|
| `maxItems` | integer | `100` | **Whole run**, all types, comments included. `0` = unlimited |
| `maxPostCount` | integer | `50` | Per subreddit / search / user. `0` = unlimited |
| `maxComments` | integer | `20` | Per post. `0` = skip comments entirely |
| `maxCommentDepth` | integer | `3` | `1` = top-level only, `0` = unlimited (depth is 0-based) |
| `maxCommunitiesCount` | integer | `10` | Per source. `0` = unlimited |
| `maxUserCount` | integer | `10` | Per source. `0` = unlimited |

#### Content toggles and filters

| Parameter | Type | Default | Effect |
|---|---|---|---|
| `skipComments` | boolean | `false` | No comment rows — faster, cheaper, far less personal data |
| `skipUserPosts` | boolean | `false` | For user URLs, return only the profile row |
| `skipCommunityInfo` | boolean | `false` | For subreddit URLs, skip the `community` about-row |
| `includeNSFW` | boolean | **`true`** | `false` drops items flagged `over18` |
| `keywords` | array\<string> | `[]` | Keep only items whose title/body/description contains one of these (case-insensitive) |
| `postDateFrom` | string | `""` | `YYYY-MM-DD`, `DD/MM/YYYY`, an ISO datetime, or relative — `7 days`, `3 months`, `1 year` |
| `postDateTo` | string | `""` | Same formats; a bare date includes the whole day |
| `extractMedia` | boolean | `true` | Populate the `media` object on posts |
| `dedupItems` | boolean | `true` | Drop repeats of the same `type` + `id` within the run |

> Filters are applied **after** an item is fetched — they shrink the dataset, not the request
> count.

#### Runtime

| Parameter | Type | Default | Notes |
|---|---|---|---|
| `proxyConfiguration` | object | `{"useApifyProxy": true}` | **Leave enabled.** Reddit rate-limits by IP; residential is best for larger runs |
| `maxConcurrency` | integer | `10` | `1`–`50`. Browser-driven, so it ramps from a low start. Lower it if rate-limited |
| `maxRequestRetries` | integer | `5` | Retries a blocked or rate-limited request on a fresh session |
| `debugMode` | boolean | `false` | Verbose logging |

#### Delivery (optional)

| Parameter | Type | Default | Description |
|---|---|---|---|
| `mcpConnector` | string | `""` | A connector you've authorised on your Apify account — Notion, Slack, Linear, Jira, GitHub, Airtable, or any MCP-compatible connector. Leave empty for dataset-only runs. |
| `deliveryMode` | string (enum) | **`perItem`** | `perItem` (one call per item, capped at 1,000) · `summary` (one digest call) · `none`. |
| `mcpTool` | string | `""` | Tool to call — `create_page`, `send_message`, `create_issue`. **Required** when a connector is set; run once and the log lists the connector's tools. |
| `mcpArguments` | object | `{}` | Arguments for the tool. String leaves support `{placeholder}` tokens. |
| `mcpMessageTemplate` | string | `""` | Template rendered and exposed to `mcpArguments` as `{message}`. |

**Placeholders by mode:**

| Mode | Placeholders |
|---|---|
| `perItem` | **every field of the item** — `{type}` `{id}` `{author}` `{subreddit}` `{score}` `{url}` `{createdAt}` … — plus the cross-type aliases `{title}`, `{text}`, `{permalink}` and `{message}`. Nested values (like `media`) arrive JSON-encoded; nulls render empty. |
| `summary` | `{itemCount}` `{postCount}` `{commentCount}` `{userCount}` `{communityCount}` `{items}` (a formatted digest, truncated at ~60,000 characters) `{message}` |

> The aliases exist because the four types don't share field names: `{title}` resolves to a post's
> title, a community's name or a username; `{text}` to a comment body, a post body or a
> description. Use them for templates that must work across every type.

***

### 📤 Output Contract & Data Structure

**One dataset, four record types.** Branch on `type` first. Fields that don't apply to a type are
**absent keys**, not nulls.

#### Shared by every type

| Field | Type | Meaning |
|---|---|---|
| `type` | string | `post` · `comment` · `user` · `community` — **read this first** |
| `id` | string | Reddit's base-36 ID for the entity. **The join/dedup key** |
| `parsedId` | string | Reddit's fullname (`t3_…`, `t1_…`, `t2_…`, `t5_…`) |
| `url` | string | Canonical reddit.com URL for the entity |
| `createdAt` | string | **ISO-8601 UTC** |
| `createdTimestamp` | integer | Epoch seconds — use this for arithmetic |

#### `type: "post"`

| Field | Type | Meaning |
|---|---|---|
| `subreddit` / `subredditId` | string | Community the post is in |
| `title` | string | Post title |
| `body` | string | Self-text; `""` for link posts |
| `author` / `authorId` | string | **Personal data.** `[deleted]` when the account is gone |
| `link` / `domain` | string | Where a link post points, and its domain |
| `flair` | string | Link flair text |
| `score` / `upVotes` | integer | **Fuzzed by Reddit** — approximate |
| `upvoteRatio` | number | 0–1 |
| `numberOfComments` | integer | Reddit's count — **not** the number of comment rows scraped |
| `numberOfCrossposts` / `awardsCount` | integer | Crossposts and awards |
| `isSelf` / `isVideo` / `isOriginalContent` | boolean | Post format flags |
| `over18` / `spoiler` / `locked` / `stickied` | boolean | Content and state flags |
| `distinguished` | string | `moderator` / `admin` when applicable |
| `media` | object | `{thumbnail, images[], video, gallery[], externalLink}` — only when `extractMedia` |

#### `type: "comment"`

| Field | Type | Meaning |
|---|---|---|
| `subreddit` | string | Community the comment is in |
| `body` | string | Comment text. **Personal data.** `[deleted]`/`[removed]` as Reddit serves it |
| `author` / `authorId` | string | **Personal data** |
| `parentId` | string | `t1_…` (a comment) or `t3_…` (the post) — rebuilds the tree |
| `postId` | string | `t3_…` fullname of the post — **join key** |
| `depth` | integer | **0-based** nesting level |
| `score` / `upVotes` | integer | Approximate |
| `isSubmitter` | boolean | `true` when the commenter is the post's author |
| `stickied` / `controversiality` / `distinguished` | mixed | Moderation and state signals |
| `numberOfReplies` | integer | Direct replies present in the payload |
| `postTitle` / `postUrl` | string | **Only on comments scraped through their post** — absent for comments from a user's history |

#### `type: "user"`

| Field | Type | Meaning |
|---|---|---|
| `username` | string | **Personal data** — the pseudonymous identity |
| `linkKarma` / `commentKarma` / `totalKarma` | integer | Karma breakdown |
| `awardeeKarma` / `awarderKarma` | integer | Award karma |
| `isGold` / `isMod` / `isEmployee` / `verified` / `hasVerifiedEmail` | boolean | Account flags |
| `iconImage` | string | Avatar URL |
| `description` | string | Profile blurb |
| `over18` | boolean | Profile marked 18+ |

#### `type: "community"`

| Field | Type | Meaning |
|---|---|---|
| `name` | string | Subreddit name without `r/` |
| `title` | string | Display title |
| `description` / `longDescription` | string | Short blurb and full sidebar text |
| `numberOfMembers` | integer | Subscribers |
| `activeUsers` | integer | Users online at capture time |
| `over18` | boolean | 18+ community |
| `type_` | string | `public` / `restricted` / `private` … (**note the trailing underscore**) |
| `lang` / `category` | string | Declared language and advertiser category |
| `iconImage` / `bannerImage` | string | Community imagery |

#### Example output — one of each type

```json
[
  {
    "type": "post",
    "id": "1abcxyz",
    "parsedId": "t3_1abcxyz",
    "subreddit": "technology",
    "title": "Our team open-sourced the ingestion pipeline behind the launch",
    "body": "Writing up what broke and what we'd do differently…",
    "author": "some_builder",
    "authorId": "t2_9q1w2",
    "url": "https://www.reddit.com/r/technology/comments/1abcxyz/our_team_open_sourced/",
    "link": "https://github.com/example/pipeline",
    "domain": "github.com",
    "flair": "Software",
    "createdAt": "2026-08-19T14:03:11+00:00",
    "createdTimestamp": 1787148191,
    "score": 3184,
    "upVotes": 3184,
    "upvoteRatio": 0.94,
    "numberOfComments": 412,
    "isSelf": false,
    "over18": false,
    "locked": false,
    "media": {
      "thumbnail": "https://b.thumbs.redditmedia.com/%E2%80%A6jpg",
      "images": ["https://preview.redd.it/%E2%80%A6png"],
      "video": null,
      "gallery": [],
      "externalLink": "https://github.com/example/pipeline"
    }
  },
  {
    "type": "comment",
    "id": "kx91mno",
    "parsedId": "t1_kx91mno",
    "subreddit": "technology",
    "body": "The backpressure section is the part everyone skips and then regrets.",
    "author": "queue_nerd",
    "authorId": "t2_4d5f6",
    "url": "https://www.reddit.com/r/technology/comments/1abcxyz/%E2%80%A6/kx91mno/",
    "parentId": "t3_1abcxyz",
    "postId": "t3_1abcxyz",
    "createdAt": "2026-08-19T15:22:40+00:00",
    "createdTimestamp": 1787152960,
    "score": 214,
    "depth": 0,
    "isSubmitter": false,
    "numberOfReplies": 3,
    "postTitle": "Our team open-sourced the ingestion pipeline behind the launch",
    "postUrl": "https://www.reddit.com/r/technology/comments/1abcxyz/our_team_open_sourced/"
  },
  {
    "type": "user",
    "id": "9q1w2",
    "parsedId": "t2_9q1w2",
    "username": "some_builder",
    "url": "https://www.reddit.com/user/some_builder",
    "createdAt": "2017-03-04T09:11:02+00:00",
    "createdTimestamp": 1488618662,
    "linkKarma": 12045,
    "commentKarma": 38210,
    "totalKarma": 50255,
    "isMod": false,
    "verified": true,
    "description": "Backend engineer. Opinions my own."
  },
  {
    "type": "community",
    "id": "2qh16",
    "parsedId": "t5_2qh16",
    "name": "technology",
    "title": "Technology",
    "url": "https://www.reddit.com/r/technology/",
    "description": "Subreddit dedicated to the news and discussions about technology.",
    "createdAt": "2008-01-25T05:15:47+00:00",
    "createdTimestamp": 1201238147,
    "numberOfMembers": 18400000,
    "activeUsers": 4123,
    "over18": false,
    "type_": "public",
    "lang": "en"
  }
]
```

> Values illustrate shape and formatting — the posts, people and numbers are invented.

#### What a successful run looks like

- **Status:** `SUCCEEDED`
- **Dataset:** interleaved `post`, `comment`, `user` and `community` rows, in crawl order
- **Log:** `Starting with N seed request(s). Item cap: …`, then a line per listing, post and
  profile, then `Crawl finished.`
- **Zero results is also a success.** No sources supplied, an unclassifiable URL, a private or
  banned subreddit, or a bot-block all return an empty dataset with `SUCCEEDED`. Check the log.
- **Hitting `maxItems` ends the run early and cleanly** — that's the cap doing its job, not a
  failure.

#### Dataset views

| View | Shows |
|---|---|
| **Overview** | Type, title/body, author, subreddit, score, comments, date — everything in one table |
| **Posts** | Title, author, subreddit, score, upvote ratio, comments, flair, links |
| **Comments** | Body, author, subreddit, score, depth, submitter flag, replies, parent post |
| **Communities** | Name, members, active users, description, 18+ flag |
| **Users** | Username, karma breakdown, verified and mod flags |
| **Post Media** | Title, subreddit, self/video flags, the `media` object, link and domain |

***

### ▶️ Invocation & Integration

Authenticate every call with `Authorization: Bearer <APIFY_TOKEN>`.

#### Apify Console

Open the [Actor page](https://apify.com/techforce.global/reddit-scraper) → **Input** tab → paste
subreddit/post/user URLs and/or type search terms → set `maxItems` and the per-source caps →
decide whether you want comments → leave the proxy on → **Start**.

#### Model Context Protocol (MCP)

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com/?tools=techforce.global/reddit-scraper"
```

Then ask your agent for *"what r/technology is saying about this launch"*.

#### Python (apify-client)

```python
import os
from collections import Counter
from apify_client import ApifyClient

client = ApifyClient(os.getenv("APIFY_TOKEN"))

run = client.actor("techforce.global/reddit-scraper").call(run_input={
    "startUrls": [{"url": "https://www.reddit.com/r/technology/top/?t=week"}],
    "maxItems": 500,
    "maxPostCount": 50,
    "maxComments": 20,
    "maxCommentDepth": 3,
    "deliveryMode": "none",
    "proxyConfiguration": {"useApifyProxy": True},
})

rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())
if not rows:
    raise SystemExit("Nothing scraped — check the run log (no sources? blocked?).")

print(Counter(r["type"] for r in rows))   # e.g. Counter({'comment': 420, 'post': 50, 'community': 1})

## ALWAYS branch on type — one dataset, four shapes.
posts    = [r for r in rows if r["type"] == "post"]
comments = [r for r in rows if r["type"] == "comment"]

## Rebuild each thread: comments join to posts on postId -> parsedId.
by_post = {p["parsedId"]: {"post": p, "comments": []} for p in posts}
for c in comments:
    thread = by_post.get(c.get("postId"))
    if thread:
        thread["comments"].append(c)

for thread in sorted(by_post.values(), key=lambda t: t["post"].get("score") or 0, reverse=True)[:5]:
    p = thread["post"]
    # numberOfComments is Reddit's figure; len(...) is what we actually scraped.
    print(f'{p["score"]:>6}  {len(thread["comments"])}/{p.get("numberOfComments")}  {p["title"][:60]}')
```

#### JavaScript / TypeScript (apify-client)

```typescript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('techforce.global/reddit-scraper').call({
  searches: ['vector database'],
  searchType: 'posts',
  sort: 'top',
  time: 'month',
  maxItems: 300,
  skipComments: false,
  maxComments: 25,
  keywords: ['pgvector', 'qdrant', 'weaviate'],   // post-fetch filter
  deliveryMode: 'none',
  proxyConfiguration: { useApifyProxy: true },
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();

// Four record types — split before you touch any field.
const byType = items.reduce<Record<string, any[]>>((acc, i) => {
  (acc[i.type] ??= []).push(i);
  return acc;
}, {});
console.log(Object.entries(byType).map(([t, v]) => `${t}: ${v.length}`).join(', '));

// Top-level comments only (depth is 0-based).
for (const c of (byType.comment ?? []).filter((c) => c.depth === 0).slice(0, 10)) {
  console.log(`[r/${c.subreddit}] u/${c.author} (${c.score}): ${c.body.slice(0, 100)}`);
}
```

#### cURL — synchronous (waits, returns the dataset)

```bash
curl -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \
  -d '{
        "startUrls": [{ "url": "https://www.reddit.com/r/AskReddit/" }],
        "maxItems": 50,
        "skipComments": true,
        "deliveryMode": "none",
        "proxyConfiguration": { "useApifyProxy": true }
      }' \
  'https://api.apify.com/v2/acts/techforce.global~reddit-scraper/run-sync-get-dataset-items'
```

#### cURL — asynchronous with a spend cap (recommended for large runs)

```bash
## 1. Start with a hard spend limit
curl -X POST -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \
  -d '{ "searches": ["bitcoin"], "maxItems": 2000, "maxComments": 50,
        "proxyConfiguration": { "useApifyProxy": true } }' \
  'https://api.apify.com/v2/acts/techforce.global~reddit-scraper/runs?maxTotalChargeUsd=10'

## 2. Poll, then 3. fetch the dataset when SUCCEEDED
curl -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' 'https://api.apify.com/v2/actor-runs/%3CrunId%3E'
curl -H 'Authorization: Bearer <YOUR_APIFY_TOKEN>' \
  'https://api.apify.com/v2/datasets/%3CDATASET_ID%3E/items?clean=true&format=json'
```

#### Posts only, no comments — the cheap, low-risk configuration

```json
{
  "startUrls": [{ "url": "https://www.reddit.com/r/technology/" }],
  "maxItems": 200,
  "maxPostCount": 200,
  "skipComments": true,
  "extractMedia": false,
  "deliveryMode": "none",
  "proxyConfiguration": { "useApifyProxy": true }
}
```

Post-level metrics with no comment rows: far fewer items per post, a much smaller dataset, and
**no user speech in your output**. Start here.

#### One thread, deep — for sentiment analysis

```json
{
  "startUrls": [{ "url": "https://www.reddit.com/r/technology/comments/1abcxyz/some_thread/" }],
  "maxItems": 1000,
  "maxComments": 500,
  "maxCommentDepth": 0,
  "commentSort": "top",
  "deliveryMode": "none",
  "proxyConfiguration": { "useApifyProxy": true }
}
```

`maxCommentDepth: 0` follows every reply level the payload contains. Note that "more comments"
placeholders are not expanded, so very large threads still come back partial.

#### Topic monitoring on a schedule

```json
{
  "searches": ["our product name"],
  "searchType": "posts",
  "sort": "new",
  "postDateFrom": "7 days",
  "maxItems": 300,
  "maxComments": 20,
  "includeNSFW": false,
  "deliveryMode": "none",
  "proxyConfiguration": { "useApifyProxy": true }
}
```

Put a fixed input on a [schedule](https://docs.apify.com/platform/schedules), keep `postDateFrom`
relative so the window moves with each run, and dedupe against your store on `id`.

#### With MCP delivery — a digest into Slack

```json
{
  "searches": ["our product name"],
  "maxItems": 200,
  "proxyConfiguration": { "useApifyProxy": true },
  "mcpConnector": "<your-authorized-slack-connector>",
  "mcpTool": "send_message",
  "deliveryMode": "summary",
  "mcpMessageTemplate": "Reddit: {postCount} posts, {commentCount} comments\n\n{items}",
  "mcpArguments": { "channel": "#social-listening", "text": "{message}" }
}
```

**Use `summary` deliberately** — `perItem` is the default and would post one Slack message per
scraped row.

***

### ⚠️ Autonomous Agent Error Handling & Resolution Matrix

**Read this first:** almost nothing fails the run. Missing sources, an unclassifiable URL, a
blocked request and an empty subreddit all end `SUCCEEDED` while the log carries the reason. An
agent that checks run status alone will read an empty dataset as success-with-data — **check the
row count and the log**.

The `CONDITION` labels are documentation handles for observable states; this Actor writes no error
code into the dataset.

| Condition | What you observe | Cause | Deterministic agent action |
|---|---|---|---|
| `NO_SOURCES_PROVIDED` | `SUCCEEDED`, empty dataset; log: `No valid start URLs or search terms provided. Exiting.` | Both `startUrls` and `searches` empty — **not** caught by validation | **Modify Input:** supply at least one source. |
| `URL_NOT_CLASSIFIED` | That URL contributes nothing; log: `Could not classify URL, skipping: <url>` | Not a recognised Reddit URL shape | **Modify Input:** use one of the shapes in the Input Contract. |
| `BOT_WALL_OR_BLOCKED` | Empty/thin dataset; log: `Could not obtain JSON from … Reddit may be blocking this request` | Proxy disabled, or the IP is blocked | **Modify Input:** enable Apify Proxy, prefer residential, lower `maxConcurrency`. |
| `RATE_LIMITED_BY_REDDIT` | Log: `Rate limited by Reddit (HTTP 429) - retrying with a new session/proxy.` | Too many requests from one IP | **Expected, self-healing.** If it repeats, lower `maxConcurrency` to `2`–`3`. |
| `PRIVATE_OR_BANNED_SUBREDDIT` | No rows for that subreddit | Private, banned or quarantined community | **Do not retry.** Public content only. |
| `MAX_ITEMS_TRUNCATED_RUN` | Fewer posts than expected; comments dominate | `maxItems` is global and comments count toward it | **Modify Input:** raise `maxItems`, or set `skipComments: true`. |
| `TYPE_MISMATCH_IN_CONSUMER` | Consumer crashes reading `title` / `body` | One dataset, four shapes | **Fix consumer:** branch on `type`, use `.get()`. |
| `COMMENT_SEARCH_MISMATCH` | `searchType: "comments"` returned comments that don't mention the term | It searches **posts**, then scrapes their comments | **Modify Input:** add the term to `keywords` as well. |
| `SORT_IGNORED` | Results aren't in the order you asked for | `relevance`/`comments` are search-only (→ `hot`); `rising` is listing-only (→ `relevance`) | **Fix expectation** or pick a sort valid for that source. |
| `TIME_IGNORED` | `time` had no effect | It only applies to `top`/`controversial` listings and searches | **Modify Input:** set `sort: "top"`. |
| `DEPTH_OFF_BY_ONE` | Fewer reply levels than expected | `depth` is 0-based; `maxCommentDepth: 3` yields depths 0–2 | **Fix expectation**, or raise the value / use `0`. |
| `PARTIAL_DEEP_THREAD` | Big thread came back incomplete | "More comments" placeholders are not expanded | **Expected.** Raise `maxComments`; full expansion isn't supported. |
| `FILTERS_DIDNT_CUT_COST` | Small dataset, unchanged run time | Filters are applied after fetching | **Fix expectation:** use the caps to control cost, not filters. |
| `NO_MEDIA_OBJECT` | `media` missing on posts | `extractMedia: false` | **Modify Input:** enable it. |
| `MISSING_POST_TITLE_ON_COMMENT` | `postTitle`/`postUrl` absent | That comment came from a user's history, not from its post | **Fix consumer:** join on `postId` → the post's `parsedId`. |
| `SCORES_DONT_MATCH_REDDIT` | Scores differ slightly from the site | Reddit fuzzes vote counts | **Expected.** Treat scores as approximate. |
| `DUPLICATES_EXPECTED_BUT_ABSENT` | Fewer rows than sources suggest | `dedupItems` is on by default | **Expected.** Set it `false` to keep repeats. |
| `FREE_PLAN_CAPPED` | 25 items; log: `Free plan detected - capping this run to 25 items.` | Free Apify plan — the cap is **total** | **Upgrade the plan.** |
| `CONNECTOR_CALL_STORM` | Hundreds of connector calls | `deliveryMode` defaults to `perItem` (capped at 1,000, with a warning) | **Modify Input:** switch to `summary` or `none`. |
| `CONNECTOR_TOOL_NOT_FOUND` | Log: `Tool 'x' is not available on this connector. Available tools: …` | Wrong `mcpTool` name (empty is logged separately) | **Modify Input:** copy an exact name from that log line. Dataset unaffected. |
| `CONNECTOR_UNREACHABLE` | Log: `Could not reach the connector: …` or `APIFY_MCP_PROXY_URL is not set` | Connector not authorised, or running locally | **Authorise it**, or `apify push` and run on the platform. Data already saved. |
| `AUTH_INVALID` | HTTP `401` | Missing or malformed `APIFY_TOKEN` | **Abort & prompt user** for a valid token. |
| `RATE_LIMITED_APIFY` | HTTP `429` | Apify API rate / concurrency limits — not Reddit | **Retry:** exponential backoff (2s, 4s, 8s). |
| `SYNC_TIMEOUT` | HTTP `408` on the sync endpoint | Run exceeded the synchronous ceiling | **Reconfigure:** async `POST /runs` + polling. |

> **Provenance:** every log line, status and dataset condition above is taken from this Actor's own
> source and verified against it. The three HTTP rows describe the Apify **API transport layer**,
> not this Actor — confirm them against the [Apify API reference](https://docs.apify.com/api/v2)
> before wiring them into automated retry logic.

***

### 🗣️ Natural Language → Actor Mapping

| A user says… | Intent | Constructed Actor input |
|---|---|---|
| "What's on r/technology right now?" | Subreddit listing | `{"startUrls":[{"url":"https://www.reddit.com/r/technology/"}],"sort":"hot"}` |
| "Top posts in r/x this week" | Ranked listing | `{"startUrls":[{"url":"https://www.reddit.com/r/x/top/?t=week"}]}` |
| "Get every comment on this thread" | Thread extraction | `{"startUrls":[{"url":"<permalink>"}],"maxComments":500,"maxCommentDepth":0,"maxItems":1000}` |
| "What is Reddit saying about <topic>?" | Topic search | `{"searches":["<topic>"],"searchType":"posts","sort":"new"}` |
| "Which subreddits discuss <topic>?" | Community discovery | `{"searches":["<topic>"],"searchType":"communities","maxCommunitiesCount":25}` |
| "Find Reddit users called <name>" | User search | `{"searches":["<name>"],"searchType":"users","maxUserCount":25}` |
| "What has u/<name> posted?" | User history | `{"startUrls":[{"url":"https://www.reddit.com/user/%3Cname%3E/"}]}` |
| "Just their profile stats" | Profile only | `{"startUrls":[{"url":"https://www.reddit.com/user/%3Cname%3E/"}],"skipUserPosts":true}` |
| "Mentions of us in the last week" | Monitoring | `{"searches":["<brand>"],"postDateFrom":"7 days","sort":"new"}` |
| "Posts only, no comments" | Cheap/low-risk pull | `{"startUrls":[{"url":"<subreddit>"}],"skipComments":true}` |
| "No adult content please" | Safe corpus | Add `{"includeNSFW":false}` |
| "Only posts mentioning X or Y" | Keyword filter | Add `{"keywords":["X","Y"]}` |
| "How big is r/x?" | Community stats | `{"startUrls":[{"url":"https://www.reddit.com/r/x/"}],"maxPostCount":1}` — read the `community` row |
| "Which of these are Shorts…" *(wrong platform)* | — | ➡️ Not Reddit; see the YouTube Actors |
| "Send new mentions to Slack" | Scrape + deliver | Above, plus `mcpConnector`, `mcpTool`, `deliveryMode: "summary"` |
| "It returned nothing" | Empty result | Check a source was supplied, the URL shape is recognised, and the proxy is on |
| "Post this comment for me" | Write action | ❌ Read-only — out of scope |

**Should NOT route here:** **private, quarantined or login-gated content** · **deleted or removed
content** · **direct messages or modmail** · **posting, voting, or moderating** · **downloading
media files** (URLs only) · **native comment search** (comment search is approximated via posts) ·
non-Reddit platforms — for YouTube use the
[YouTube Scraper](https://apify.com/techforce.global/youtube-scraper), for public Facebook page
comments the [Facebook Public Comment Tracker](https://apify.com/techforce.global/facebook-public-comments).

***

### 🧭 Agent Execution & Routing Logic

```text
Does the user want PUBLIC REDDIT content as data?
├── NO → wrong Actor.
│        YouTube videos/comments   → YouTube Scraper
│        Facebook page comments    → Facebook Public Comment Tracker
│        a whole website's text    → Advanced Website Crawling Actor
│        private/DM/mod content    → not obtainable
│
└── YES
    │
    ├── What do they actually have? (mix freely; at least ONE is mandatory at
    │   runtime even though the schema marks nothing required)
    │     ├── a community      → startUrls: r/<sub>       (+ a community row)
    │     ├── a specific thread→ startUrls: permalink     (most predictable cost)
    │     ├── a person         → startUrls: /user/<name>  (+ their history)
    │     ├── a topic          → searches + searchType "posts"
    │     ├── "which subs?"    → searches + searchType "communities"
    │     └── "which users?"   → searches + searchType "users"
    │
    ├── ALWAYS keep proxyConfiguration enabled (useApifyProxy: true)
    │
    ├── Do they need COMMENTS?
    │     ├── no  → skipComments: true   → fewer items, lower cost, NO user speech
    │     └── yes → maxComments PER POST, maxCommentDepth (0-based; 0 = unlimited)
    │           └── remember: comments count toward the GLOBAL maxItems
    │
    ├── Size the run
    │     ├── maxItems      → GLOBAL cap, comments included (0 = unlimited)
    │     ├── maxPostCount  → per subreddit / search / user
    │     └── big run?      → set maxTotalChargeUsd as well
    │
    ├── Narrow the output (post-fetch — these do NOT cut cost)
    │     ├── keywords      → term must appear in title/body/description
    │     ├── postDateFrom  → "7 days" / "2026-01-01"
    │     └── includeNSFW   → false to drop 18+ items
    │
    ├── What is the actual question?
    │     ├── what's popular  → sort top/hot, read score + upvoteRatio
    │     ├── what's new      → sort new + postDateFrom
    │     ├── how do they feel→ comment bodies, join on postId
    │     ├── where to engage → searchType "communities", read numberOfMembers/activeUsers
    │     └── who is talking  → user rows + karma
    │
    └── Should results land in another app?
          ├── yes → mcpConnector + mcpTool + {message} in mcpArguments
          │           digest → summary   ·   record each → perItem (DEFAULT; capped at 1000)
          └── no  → deliveryMode "none", or leave mcpConnector empty

[Execute Actor]
    ├──► SUCCEEDED with rows → split by `type`, then consume
    ├──► SUCCEEDED, 0 rows   → READ THE LOG: no source / unclassified URL / blocked / private
    └──► exactly 25 rows     → free plan cap, not a data pull
```

***

### 💰 Cost Control & Pricing Transparency

**Billing model: usage-based.** This Actor bills for **platform usage** — the compute time and
resources a run consumes — not per row. Current rates are on the
[Actor's Apify Store page](https://apify.com/techforce.global/reddit-scraper); the Console shows
the live total mid-run.

**It is browser-driven**, which is what gets past Reddit's bot wall — and also what makes each
request heavier than a plain HTML fetch. Size runs accordingly.

| Choice | Effect on cost |
|---|---|
| `maxItems` | **The main lever** — a hard stop on the whole run, comments included. |
| `skipComments` / `maxComments` | **The second lever.** One extra request per post, and comment rows dominate item counts. |
| Number of sources | Each subreddit, search and user is its own paginated crawl. |
| `maxPostCount` | Pagination depth per source — more pages, more requests. |
| `maxCommentDepth` | Deeper trees mean bigger payloads, not more requests. |
| `maxConcurrency` | Faster when Reddit tolerates it; rate-limit retries make it slower. |
| Filters (`keywords`, dates, NSFW) | **No meaningful cost saving** — applied after fetching. |
| Being blocked | Costs run time and returns nothing. **A disabled proxy is the most expensive setting on this page.** |

#### Worked examples — relative run shape

| Scenario | Requests (approx.) | Items | Notes |
| :--- | :--- | :--- | :--- |
| Probe — 1 subreddit, `maxItems: 25`, comments off | 1 listing + 1 about | ≤ 26 | **Baseline.** Confirms proxy and URL shape. |
| Subreddit sweep — 200 posts, comments off | ~3 listing pages | ~201 | Cheapest per item. |
| Same 200 posts with `maxComments: 20` | ~3 listings + 200 post fetches | up to ~4,200 | **~200× the requests** — this is the expensive switch. |
| One thread, `maxComments: 500`, depth 0 | 1 fetch | ≤ 501 | Best value per comment; a single payload. |
| Topic search, 50 posts, comments off | ~1 search page | ~50 | Predictable. |
| Community discovery, 25 subreddits | ~1 search page | ~25 | Very cheap. |
| Brand monitor — search + 20 comments/post, 300 items | search pages + ~15 post fetches | 300 (capped) | `maxItems` stops it precisely. |
| Unbounded — `maxItems: 0` on a busy subreddit | unbounded | unbounded | **Never without `maxTotalChargeUsd`.** |

**Keeping runs predictable:**

1. **First run: one source, `maxItems: 25`, `skipComments: true`.**
2. **Decide the comment question up front** — comments multiply both requests and rows, and they
   are the part that carries personal data.
3. **Prefer permalinks for comment-heavy work** — one post URL is one predictable fetch; a
   subreddit sweep with comments is one fetch per post.
4. **Use the caps, not the filters, to control cost.**
5. **Always set `maxTotalChargeUsd`** when `maxItems` is `0` or in the thousands.
6. **Free Apify plans get 25 items per run** — a connectivity check, not a data pull.

***

### 🛠️ Troubleshooting

Most problems are **missing sources, the proxy, the global `maxItems` cap, or the four-record-type
shape** — not scraper bugs.

| # | Symptom you see | Most likely cause | Fix |
| :-- | :--- | :--- | :--- |
| 1 | `SUCCEEDED` but the dataset is **empty** | Neither `startUrls` nor `searches` supplied — the schema doesn't enforce it | Read the log for `No valid start URLs or search terms provided.` and supply a source. |
| 2 | A URL you pasted produced nothing | It wasn't recognised — log: `Could not classify URL, skipping: …` | Use a documented URL shape (`/r/…`, `/comments/…`, `/user/…`, `/search?q=…`, `/subreddits/search?q=…`). |
| 3 | Thin results, or `Could not obtain JSON from …` in the log | Reddit's bot wall or an IP block | Keep Apify Proxy on, prefer residential, and lower `maxConcurrency` to `2`–`3`. |
| 4 | Log repeats `Rate limited by Reddit (HTTP 429)` | Too many requests from one IP | Self-healing via retries; if persistent, lower `maxConcurrency` and raise `maxRequestRetries`. |
| 5 | Far fewer posts than expected, lots of comments | `maxItems` is **global** and comments count toward it | Raise `maxItems`, or set `skipComments: true` for post breadth. |
| 6 | Consumer crashes on a missing field | Four record types share one dataset | Branch on `type`; use `.get()` — absent ≠ null. |
| 7 | `searchType: "comments"` returned off-topic comments | It searches posts, then scrapes their comments | Add the term to `keywords` too, or accept post-level matching. |
| 8 | Sort seems ignored | `relevance`/`comments` are search-only (→ `hot`); `rising` is listing-only (→ `relevance`) | Pick a sort valid for that source type. |
| 9 | `time` had no effect | It only applies to `top`/`controversial` listings and searches | Set `sort: "top"` alongside it. |
| 10 | Fewer reply levels than expected | `depth` is 0-based — `maxCommentDepth: 3` gives depths 0–2 | Raise it, or use `0` for unlimited. |
| 11 | A big thread came back incomplete | "More comments" placeholders aren't expanded | Raise `maxComments`; full expansion isn't supported. |
| 12 | Filters didn't make the run cheaper | They're applied after fetching | Use `maxItems` / `maxPostCount` / `skipComments` for cost. |
| 13 | NSFW content in the dataset | `includeNSFW` defaults to **`true`** | Set it `false`. |
| 14 | No `media` on posts | `extractMedia: false`, or the post has none | Enable `extractMedia`; text posts legitimately have empty media. |
| 15 | `postTitle` / `postUrl` missing on comments | Those comments came from a user's history | Join on `postId` → the post's `parsedId`. |
| 16 | Scores differ from what the site shows | Reddit fuzzes vote counts | Expected — treat scores as approximate. |
| 17 | `numberOfComments` ≫ the comment rows you got | That's Reddit's count, not the scrape count | Compare deliberately; raise `maxComments` if you need more. |
| 18 | Duplicate posts across sources | `dedupItems` is on by default — if you see repeats, they differ by `id` | Dedupe downstream on `id`, or check `dedupItems`. |
| 19 | Exactly 25 rows came back | Free plan cap — **total**, not per source | Upgrade the plan; the log says `Free plan detected…`. |
| 20 | A private/banned subreddit returned nothing | Public content only | Nothing to fix — no scraper can reach it. |
| 21 | Hundreds of connector calls fired | `deliveryMode` defaults to **`perItem`** | Set `summary` or `none`. Delivery is capped at 1,000 calls with a warning. |
| 22 | Slack / Notion received nothing | Delivery needs **both** `mcpConnector` and `mcpTool` | Set both. Every dataset row is still saved. |
| 23 | Connector message fields are blank | The template used a field that record type lacks | Use the cross-type aliases `{title}` / `{text}` / `{permalink}`, or branch on `{type}`. |
| 24 | Log: *APIFY\_MCP\_PROXY\_URL is not set* | Ran locally; connectors only resolve on the platform | `apify push` and run on the platform. |
| 25 | `HTTP 401` / `403` from the Apify API | Missing, expired or malformed `APIFY_TOKEN` | Regenerate in **Console → Settings → API & Integrations**. |
| 26 | `HTTP 429` / `408` from the Apify API | Apify rate limits / the sync endpoint's time ceiling | Back off (2s → 4s → 8s), or switch to async `POST /runs` + polling. |

#### Diagnostic checklist before opening an issue

1. **Confirm a source is set** — `startUrls` or `searches`.
2. **Open the URL signed-out in a browser.** Private, banned and quarantined communities look
   identical to a scraper.
3. **Confirm Apify Proxy is enabled**, and try residential if you see bot-wall lines.
4. **Baseline run:** one subreddit, `maxItems: 25`, `skipComments: true`, `deliveryMode: "none"`.
5. **Count rows by `type`** before concluding anything is missing.
6. **Turn on `debugMode`** for a verbose log if the above doesn't explain it.

If the issue survives all six, open an **Issues** ticket (or email support) with the **run ID**, the
**input you used**, and what you expected.

***

### ❓ FAQ

**Do I need a Reddit account, app registration or API key?**

No. The Actor reads publicly available Reddit content. You need an **Apify** account and API token; you need no Reddit credentials, client ID or OAuth token.

**Is this the official Reddit API?**

No. This is an independent Actor and is not affiliated with, endorsed by, or sponsored by Reddit, Inc.

**Do I need to configure proxies?**

Apify Proxy is on by default and you should leave it on — Reddit rate-limits by IP and blocks unfamiliar traffic. For larger runs, a residential group is the most reliable choice.

**Can I plug it into Claude, Cursor, or a LangChain agent?**

Yes — it is a native MCP tool: `claude mcp add --transport http apify "https://mcp.apify.com/?tools=techforce.global/reddit-scraper"`

**What can I put in `startUrls`?**

Subreddit URLs (with or without a sort tab and `?t=` window), post permalinks, user profiles and their `/submitted`, `/comments` or `/overview` sub-pages, global or in-subreddit search pages, and community-list URLs. Anything unrecognised is skipped with a log line.

**Can I mix URLs and search terms in one run?**

Yes. `startUrls` and `searches` are processed in the same run, and every item is written to the same dataset.

**Why did I get so few posts and so many comments?**

Because `maxItems` is a **global** cap on the whole run and comments count toward it. With the default of 100 and comments on, the first few threads can consume the entire budget. Raise `maxItems`, or set `skipComments: true` when you want post breadth.

**How do I search comments specifically?**

You can't, natively — and this is the one place the input name is optimistic. `searchType: "comments"` searches **posts** for your term and then scrapes those posts' comment threads, so the comments returned belong to matching posts rather than matching the term themselves. Add the term to the `keywords` filter if it must appear in the comment text.

**Why does my `sort` seem to be ignored?**

Because sorts are validated per source. Subreddit listings accept `hot`, `new`, `top`, `rising` (and `controversial`/`best`) — `relevance` and `comments` silently become `hot`. Searches accept `relevance`, `hot`, `top`, `new`, `comments` — `rising` becomes `relevance`. `time` only applies to `top`/`controversial` listings and to searches.

**How does `maxCommentDepth` work?**

Depth is 0-based: `1` returns top-level comments only, `3` returns depths 0, 1 and 2, and `0` is unlimited. Note that Reddit's "more comments" placeholders are not expanded, so very deep threads still come back partial.

**Why do rows have different fields?**

One dataset holds four record types — `post`, `comment`, `user` and `community` — discriminated by `type`. Fields that don't apply to a type are **absent keys**, not nulls, so branch on `type` and use optional access.

**How do I rebuild a thread from the output?**

Join comments to posts on `postId` → the post's `parsedId` (`t3_…`), then nest comments using `parentId` and order them by `depth`. `postTitle`/`postUrl` are also present on comments scraped through their post.

**Do the scores match what I see on Reddit?**

Approximately. Reddit deliberately fuzzes vote counts, so `score` and `upVotes` are close but not exact, and they are a snapshot as of the run.

**Can I filter by keyword or date?**

Yes — `keywords`, `postDateFrom` and `postDateTo` (absolute dates or relative strings like `7 days`). They're applied after an item is fetched, so they shrink your dataset but don't reduce run cost.

**Does it download images and videos?**

No. The `media` object holds thumbnail, preview image, video, gallery and external-link **URLs**; the files themselves are never fetched.

**Can it get deleted, private or quarantined content?**

No. Deleted content comes back as Reddit serves it (`[deleted]` / `[removed]`), and private, banned or login-gated communities are inaccessible by design.

**Which delivery mode should I use, and does it work locally?**

`summary` for one digest of the whole run; `perItem` — **the default** — sends one connector call per scraped item and is capped at 1,000 with a warning; `none` for dataset-only runs. Delivery only works on the Apify platform; locally you'll see `APIFY_MCP_PROXY_URL is not set` and the dataset saves as normal.

**Does the output contain personal data?**

Yes. `author`, `authorId`, `username` and free-text `body` identify pseudonymous individuals, and user rows include karma and account age. `skipComments: true` removes the largest volume of it while keeping post-level metrics. Read [Compliance & Data Privacy](#-compliance--data-privacy) before storing or reusing it.

**What does it cost, and what do I get on the free plan?**

Usage-based: you pay for the platform usage a run consumes, not per row. Because it drives a real browser, cost per item is higher than an HTML-only scraper — `maxItems` and `skipComments` are your main levers. Free Apify plans return 25 items **total** per run. Pass `maxTotalChargeUsd` for a hard per-run ceiling.

***

### 🔍 Companion machine-readable files

| File | What it defines |
|---|---|
| [`.actor/input_schema.json`](.actor/input_schema.json) | Every input, its type, default, bounds and enums |
| [`.actor/dataset_schema.json`](.actor/dataset_schema.json) | All 59 possible fields across the four record types, and the six Console views |
| [`.actor/output_schema.json`](.actor/output_schema.json) | Where the run's output is exposed |
| [`.actor/actor.json`](.actor/actor.json) | Actor identity and storage wiring |

> **If this page and the schema files disagree, the schema files win.** They are what the platform
> validates against at run time; this page is documentation and can lag a release.

***

### 🔗 Related Actors by Techforce Global

| Actor | Best for |
| :--- | :--- |
| [YouTube Scraper: Videos, Channels & Comments](https://apify.com/techforce.global/youtube-scraper) | The same social-listening question on YouTube — video metadata, channel stats and comment threads |
| [Facebook Public Comment Tracker](https://apify.com/techforce.global/facebook-public-comments) | Comment threads under a public Facebook page's posts |
| [Advanced Website Crawling Actor](https://apify.com/techforce.global/advanced-website-crawling-actor) | Whole-site text, Markdown or HTML for RAG and search |
| [Pinterest Scraper](https://apify.com/techforce.global/pinterest-scraper) | Pins from searches, boards, profiles and pin URLs |
| [YouTube Full Channel Transcripts Scraper](https://apify.com/techforce.global/youtube-full-channel-transcripts-scraper) | Transcripts of every video on a channel, for a text corpus |

**Pipeline patterns:** **brand monitoring** — this Actor on a schedule with `postDateFrom: "7 days"`,
comments on, digest to Slack via MCP · **cross-platform listening** — Reddit + YouTube + Facebook
comment corpora merged on your own topic keys · **community targeting** — `searchType:
"communities"` to size and rank subreddits before you engage · **research corpus** — one thread at
full depth, comment bodies into your LLM for theme extraction.

> Browse all Actors by Techforce Global at **[scraper.techforce.global](https://scraper.techforce.global/)**.

***

### 🔐 Compliance & Data Privacy

**Trademark & affiliation.** This is an independent Actor. It is **not** affiliated with, endorsed
by, or sponsored by Reddit, Inc. Reddit™ is a trademark of Reddit, Inc.; all trademarks are the
property of their respective owners.

- **Public content only.** Everything returned is visible to any signed-out visitor. No private,
  banned, quarantined or login-gated communities, no direct messages, no modmail, no credentials.

- **⚠️ Reddit content is personal data.** `author`, `authorId`, `username` and free-text `body`
  relate to identifiable — if pseudonymous — individuals, and profile rows add karma, account age
  and verification flags. Under **GDPR / UK GDPR** and **CCPA/CPRA**, pseudonymous data is still
  personal data, and the obligations sit with you, not with the Actor.

- **Reddit is unusually sensitive as a source.** People post about health, addiction, sexuality,
  politics, immigration status and employment under a handle they believe is separable from their
  real name. Aggregating a user's history across subreddits — which a `/user/` scrape does by
  design — is exactly the operation that can **de-pseudonymise** someone. Treat it as the
  high-risk processing it is, and don't do it casually.

- **The cheapest compliance measure is a single field.** If your question is about *topics* rather
  than *people*, set `skipComments: true`. You keep post titles, scores, subreddit metrics and
  media, and you shed the bulk of the user speech.

- **Minimise, then aggregate.** Sentiment scores, theme counts and engagement ratios rarely
  require keeping raw `author` / `authorId` columns. Drop them at ingest, keep retention short,
  and restrict access.

- **Special-category content can't be filtered in advance.** You cannot know which threads contain
  it until after retrieval. `includeNSFW: false` removes 18+ flagged items but is not a
  sensitivity filter.

- **Deleted means deleted.** If a user removes a post or comment after your run, your copy no
  longer reflects their choice. Re-scrape or expire your data rather than treating a snapshot as
  permanently valid.

- **Content is the authors' copyrighted expression.** Internal research, analysis and reporting sit
  on far safer ground than republishing scraped posts and comments as your own content. Using
  Reddit content to **train a model** is a legally distinct act with its own contested position —
  take advice rather than relying on "it was public".

- **Reddit's Terms and API terms restrict automated access and commercial reuse.** **This Actor
  does not remove that risk — it is a risk you accept by running it.** The
  [official Data API](https://www.redditinc.com/policies/data-api-terms) is the sanctioned route.

- **Attribution over mirroring.** Every row carries a `url`. Link back to the thread rather than
  presenting Reddit discussion as your own content, and never quote a pseudonymous user in a way
  that ties them to an identity they didn't publish.

***

### 🆘 Support & Custom Pipeline Engineering

Need automated multi-city scheduled feeds, webhook enrichment, or enterprise data warehouse syncs?

- **Email**: bhavin.shah@techforceglobal.com
- **Custom Enterprise Integrations**: [Book a 15-Minute Technical Consultation](https://calendly.com/techforce-global/intro-meeting)
- **Maintained by**: [Techforce Global](https://www.techforceglobal.com) — Specialists in High-Performance Web Scrapers and Agentic Workflows.

***

<sub>Built and maintained by **Techforce Global**. Scrapes public Reddit content only — no logins,
no private or quarantined communities, no direct messages, no write actions, no media files.
Output contains personal data (`author`, `authorId`, `username`, comment `body`); `skipComments:
true` removes most of it. Scores are fuzzed by Reddit and are true only as of the run. You are
responsible for complying with Reddit's Terms of Service and with the data-protection law of your
jurisdiction.</sub>

***

### 🏷️ Structured data for search & AI discovery

`SoftwareApplication` describes the Actor; `FAQPage` wraps the FAQ above so those answers are
eligible for rich results, matching it question-for-question.

```json
{
  "@context": "https://schema.org/",
  "@type": "SoftwareApplication",
  "name": "Reddit Scraper – Posts, Comments, Users & Communities",
  "alternateName": "Reddit API Alternative",
  "applicationCategory": "BusinessApplication",
  "applicationSubCategory": "Web Scraping / Social Listening & Community Analysis",
  "operatingSystem": "Cloud (Apify platform)",
  "description": "Scrapes Reddit posts, comments, users and communities from subreddit URLs, post URLs, user profiles, search pages or plain keywords — without a Reddit account, app registration or OAuth. Returns four record types in a single dataset, each discriminated by a type field: posts with score, upvote ratio, flair and media URLs; comments with depth, parent and reply counts; users with karma breakdowns; and communities with member and active-user counts. Includes keyword, date and NSFW filtering, in-run deduplication, media URL extraction, proxy rotation, and optional delivery into Notion, Slack, Linear, Airtable or any MCP connector.",
  "url": "https://apify.com/techforce.global/reddit-scraper",
  "author": { "@type": "Organization", "name": "Techforce Global", "url": "https://www.techforceglobal.com/" },
  "provider": { "@type": "Organization", "name": "Apify", "url": "https://apify.com/" },
  "offers": {
    "@type": "Offer",
    "priceCurrency": "USD",
    "category": "Pay per platform usage",
    "description": "Usage-based: billed for the compute time and resources a run consumes, not per row. Runs are browser-driven, so cost per item is higher than an HTML-only scraper. Current rates are shown on the Actor's Apify Store page.",
    "url": "https://apify.com/techforce.global/reddit-scraper"
  },
  "featureList": [
    "No Reddit account, app registration or OAuth token required",
    "Subreddit, post, user, search and community-list URLs plus plain keywords in one run",
    "Four record types in one dataset, discriminated by a type field",
    "Posts with score, upvote ratio, flair, crossposts and awards",
    "Comment trees with depth, parentId, postId and reply counts",
    "User profiles with full karma breakdown and account flags",
    "Community records with member and active-user counts",
    "Keyword, absolute-date and relative-date filtering, plus an NSFW switch",
    "Media URL extraction: thumbnails, previews, video, galleries and external links",
    "In-run deduplication on type and id",
    "Global maxItems cap plus per-source and per-post limits",
    "Proxy rotation with automatic retry on rate limits",
    "Optional MCP delivery to Notion, Slack, Linear, Airtable and more",
    "Six Console dataset views covering posts, comments, users, communities and media"
  ],
  "isAccessibleForFree": false,
  "softwareRequirements": "Apify account and API token",
  "keywords": "Reddit scraper, Reddit API alternative, scrape Reddit comments, subreddit data extraction, social listening, sentiment analysis data, Reddit user profile scraper, subreddit discovery, market research, MCP connector"
}
```

```json
{
  "@context": "https://schema.org/",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Do I need a Reddit account, app registration or API key?",
      "acceptedAnswer": { "@type": "Answer", "text": "No. The Actor reads publicly available Reddit content. You need an Apify account and API token; you need no Reddit credentials, client ID or OAuth token." }
    },
    {
      "@type": "Question",
      "name": "Is this the official Reddit API?",
      "acceptedAnswer": { "@type": "Answer", "text": "No. This is an independent Actor and is not affiliated with, endorsed by, or sponsored by Reddit, Inc." }
    },
    {
      "@type": "Question",
      "name": "Do I need to configure proxies?",
      "acceptedAnswer": { "@type": "Answer", "text": "Apify Proxy is on by default and you should leave it on — Reddit rate-limits by IP and blocks unfamiliar traffic. For larger runs, a residential group is the most reliable choice." }
    },
    {
      "@type": "Question",
      "name": "Can I plug it into Claude, Cursor, or a LangChain agent?",
      "acceptedAnswer": { "@type": "Answer", "text": "Yes — it is a native MCP tool: claude mcp add --transport http apify \"https://mcp.apify.com/?tools=techforce.global/reddit-scraper\"" }
    },
    {
      "@type": "Question",
      "name": "What can I put in startUrls?",
      "acceptedAnswer": { "@type": "Answer", "text": "Subreddit URLs (with or without a sort tab and ?t= window), post permalinks, user profiles and their /submitted, /comments or /overview sub-pages, global or in-subreddit search pages, and community-list URLs. Anything unrecognised is skipped with a log line." }
    },
    {
      "@type": "Question",
      "name": "Can I mix URLs and search terms in one run?",
      "acceptedAnswer": { "@type": "Answer", "text": "Yes. startUrls and searches are processed in the same run, and every item is written to the same dataset." }
    },
    {
      "@type": "Question",
      "name": "Why did I get so few posts and so many comments?",
      "acceptedAnswer": { "@type": "Answer", "text": "Because maxItems is a global cap on the whole run and comments count toward it. With the default of 100 and comments on, the first few threads can consume the entire budget. Raise maxItems, or set skipComments: true when you want post breadth." }
    },
    {
      "@type": "Question",
      "name": "How do I search comments specifically?",
      "acceptedAnswer": { "@type": "Answer", "text": "You can't, natively. searchType \"comments\" searches posts for your term and then scrapes those posts' comment threads, so the comments returned belong to matching posts rather than matching the term themselves. Add the term to the keywords filter if it must appear in the comment text." }
    },
    {
      "@type": "Question",
      "name": "Why does my sort seem to be ignored?",
      "acceptedAnswer": { "@type": "Answer", "text": "Sorts are validated per source. Subreddit listings accept hot, new, top, rising (and controversial/best) — relevance and comments silently become hot. Searches accept relevance, hot, top, new, comments — rising becomes relevance. time only applies to top/controversial listings and to searches." }
    },
    {
      "@type": "Question",
      "name": "How does maxCommentDepth work?",
      "acceptedAnswer": { "@type": "Answer", "text": "Depth is 0-based: 1 returns top-level comments only, 3 returns depths 0, 1 and 2, and 0 is unlimited. Reddit's \"more comments\" placeholders are not expanded, so very deep threads still come back partial." }
    },
    {
      "@type": "Question",
      "name": "Why do rows have different fields?",
      "acceptedAnswer": { "@type": "Answer", "text": "One dataset holds four record types — post, comment, user and community — discriminated by type. Fields that don't apply to a type are absent keys, not nulls, so branch on type and use optional access." }
    },
    {
      "@type": "Question",
      "name": "How do I rebuild a thread from the output?",
      "acceptedAnswer": { "@type": "Answer", "text": "Join comments to posts on postId → the post's parsedId (t3_…), then nest comments using parentId and order them by depth. postTitle/postUrl are also present on comments scraped through their post." }
    },
    {
      "@type": "Question",
      "name": "Do the scores match what I see on Reddit?",
      "acceptedAnswer": { "@type": "Answer", "text": "Approximately. Reddit deliberately fuzzes vote counts, so score and upVotes are close but not exact, and they are a snapshot as of the run." }
    },
    {
      "@type": "Question",
      "name": "Can I filter by keyword or date?",
      "acceptedAnswer": { "@type": "Answer", "text": "Yes — keywords, postDateFrom and postDateTo (absolute dates or relative strings like 7 days). They're applied after an item is fetched, so they shrink your dataset but don't reduce run cost." }
    },
    {
      "@type": "Question",
      "name": "Does it download images and videos?",
      "acceptedAnswer": { "@type": "Answer", "text": "No. The media object holds thumbnail, preview image, video, gallery and external-link URLs; the files themselves are never fetched." }
    },
    {
      "@type": "Question",
      "name": "Can it get deleted, private or quarantined content?",
      "acceptedAnswer": { "@type": "Answer", "text": "No. Deleted content comes back as Reddit serves it ([deleted] / [removed]), and private, banned or login-gated communities are inaccessible by design." }
    },
    {
      "@type": "Question",
      "name": "Which delivery mode should I use, and does it work locally?",
      "acceptedAnswer": { "@type": "Answer", "text": "summary for one digest of the whole run; perItem — the default — sends one connector call per scraped item and is capped at 1,000 with a warning; none for dataset-only runs. Delivery only works on the Apify platform; locally you'll see APIFY_MCP_PROXY_URL is not set and the dataset saves as normal." }
    },
    {
      "@type": "Question",
      "name": "Does the output contain personal data?",
      "acceptedAnswer": { "@type": "Answer", "text": "Yes. author, authorId, username and free-text body identify pseudonymous individuals, and user rows include karma and account age. skipComments: true removes the largest volume of it while keeping post-level metrics. Read the Compliance & Data Privacy section before storing or reusing it." }
    },
    {
      "@type": "Question",
      "name": "What does it cost, and what do I get on the free plan?",
      "acceptedAnswer": { "@type": "Answer", "text": "Usage-based: you pay for the platform usage a run consumes, not per row. Because it drives a real browser, cost per item is higher than an HTML-only scraper — maxItems and skipComments are your main levers. Free Apify plans return 25 items total per run. Pass maxTotalChargeUsd for a hard per-run ceiling." }
    }
  ]
}
```

# Actor input Schema

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

Direct Reddit URLs to scrape. Supports subreddits (r/...), posts (/comments/...), user profiles (user/...), search result pages, and community lists.

## `searches` (type: `array`):

Keywords to search on Reddit. Each term runs a search according to the 'Search for' setting below.

## `searchType` (type: `string`):

What each search term should return.

## `sort` (type: `string`):

How to sort posts/search results.

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

Time window used when sorting by 'top' or 'relevance'.

## `commentSort` (type: `string`):

How to sort comments within a post.

## `maxItems` (type: `integer`):

Total maximum number of dataset items to push (0 = unlimited). Hard safety cap across the whole run.

## `maxPostCount` (type: `integer`):

Maximum posts to scrape per subreddit / search / user (0 = unlimited).

## `maxComments` (type: `integer`):

Maximum comments to extract per post (0 = skip comments, only post data).

## `maxCommentDepth` (type: `integer`):

How deep to follow reply threads (1 = top-level only, 0 = unlimited).

## `maxCommunitiesCount` (type: `integer`):

Maximum communities (subreddits) to scrape per source (0 = unlimited).

## `maxUserCount` (type: `integer`):

Maximum users to scrape per source (0 = unlimited).

## `skipComments` (type: `boolean`):

Do not scrape comments for posts (faster and cheaper).

## `skipUserPosts` (type: `boolean`):

When scraping a user, skip their submitted posts/comments and only return the profile.

## `skipCommunityInfo` (type: `boolean`):

When scraping a subreddit, skip the community 'about' metadata item.

## `includeNSFW` (type: `boolean`):

Include content marked NSFW (over\_18). When off, NSFW items are filtered out.

## `keywords` (type: `array`):

Only keep items whose title/body/text contains at least one of these keywords (case-insensitive). Leave empty to keep everything.

## `postDateFrom` (type: `string`):

Only keep items created on/after this date. Accepts YYYY-MM-DD or relative like '7 days', '3 months'.

## `postDateTo` (type: `string`):

Only keep items created on/before this date (YYYY-MM-DD).

## `extractMedia` (type: `boolean`):

Extract image/video/gallery URLs, thumbnails, and external links from posts.

## `dedupItems` (type: `boolean`):

Automatically drop duplicate posts/comments/users/communities within a run.

## `proxyConfiguration` (type: `object`):

Proxy used for requests. Reddit rate-limits by IP, so residential/rotating proxies are strongly recommended for larger runs.

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

Maximum parallel requests. Lower this if you hit rate limits.

## `maxRequestRetries` (type: `integer`):

How many times to retry a failed/blocked request.

## `debugMode` (type: `boolean`):

Enable verbose debug logging.

## `mcpConnector` (type: `string`):

Optionally deliver the scraped Reddit items into a connector you have authorized — Notion, Slack, Linear, Airtable, Jira, GitHub, or any MCP-compatible connector. Leave empty to only save the results to the dataset.

## `deliveryMode` (type: `string`):

How to deliver to the connector: 'perItem' (one call per scraped post/comment/user/community), 'summary' (a single call containing a digest of everything scraped), or 'none' (save to dataset only).

## `mcpTool` (type: `string`):

Name of the tool to call on the connector (e.g. 'create\_page' for Notion, 'send\_message' for Slack, 'create\_issue' for Jira/GitHub). If unsure, run once with a connector selected — the log lists the connector's available tools.

## `mcpArguments` (type: `object`):

Arguments passed to the connector tool. String values support {placeholders}. In 'perItem' mode every field of the item is a placeholder — common ones: {type}, {id}, {title}, {text}, {body}, {author}, {subreddit}, {url}/{permalink}, {score}, {upVotes}, {upvoteRatio}, {numberOfComments}, {createdAt}, {username}, {name}, {numberOfMembers}, plus {message} (the rendered template below). In 'summary' mode: {itemCount}, {postCount}, {commentCount}, {userCount}, {communityCount}, {items} (a formatted digest) and {message}. Example for Slack: {"channel": "#reddit", "text": "{message}"}.

## `mcpMessageTemplate` (type: `string`):

Optional template rendered and exposed as the {message} placeholder in the tool arguments. Per-item example: '\[{subreddit}] {title}\n{url}\nScore {score}, {numberOfComments} comments'. Summary example: 'Scraped {itemCount} Reddit items ({postCount} posts, {commentCount} comments):\n\n{items}'.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.reddit.com/r/AskReddit/"
    }
  ],
  "searches": [
    "bitcoin"
  ],
  "searchType": "posts",
  "sort": "new",
  "time": "all",
  "commentSort": "top",
  "maxItems": 100,
  "maxPostCount": 50,
  "maxComments": 20,
  "maxCommentDepth": 3,
  "maxCommunitiesCount": 10,
  "maxUserCount": 10,
  "skipComments": false,
  "skipUserPosts": false,
  "skipCommunityInfo": false,
  "includeNSFW": true,
  "extractMedia": true,
  "dedupItems": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 10,
  "maxRequestRetries": 5,
  "debugMode": false,
  "deliveryMode": "perItem",
  "mcpTool": "",
  "mcpArguments": {},
  "mcpMessageTemplate": ""
}
```

# Actor output Schema

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

No description

# 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 = {
    "startUrls": [
        {
            "url": "https://www.reddit.com/r/AskReddit/"
        }
    ],
    "searches": [
        "bitcoin"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("techforce.global/reddit-scraper").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 = {
    "startUrls": [{ "url": "https://www.reddit.com/r/AskReddit/" }],
    "searches": ["bitcoin"],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("techforce.global/reddit-scraper").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 '{
  "startUrls": [
    {
      "url": "https://www.reddit.com/r/AskReddit/"
    }
  ],
  "searches": [
    "bitcoin"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call techforce.global/reddit-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/Oaz4C3024G4UcYvEj/builds/n4UEce6qoizf9YVrU/openapi.json
