# Reddit Posts & Threads by Date Range | Comments (`danthedataman/reddit-posts-threads-by-date-range`) Actor

2026-10-12 test, 20 matched posts: a 22-day-old archive differed on 9 scores, 5 comment counts. Comments USD 0.00015 each (FREE). Live Reddit posts and threads by UTC date range.

- **URL**: https://apify.com/danthedataman/reddit-posts-threads-by-date-range.md
- **Developed by:** [Eli J](https://apify.com/danthedataman) (community)
- **Categories:** Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

Export Reddit posts and threads from an exact UTC date range, including historical windows beyond Reddit's live listing horizon. Supply subreddit names and two dates to receive current live posts, optionally bundled with their comment trees. A separate frozen ID dataset lets you repeat the same candidate selection later.

### Try a subreddit date range

The form prefill is an example, not an API default or a row-count guarantee.

```json
{"subreddits": ["selfhosted"], "startDate": "2026-09-20T01:27:35Z", "endDate": "2026-09-20", "maxPosts": 1, "maxCommentsPerPost": 0}
```

The form example requests one post and no comments. With `includeRemoved: false`, a removed post is skipped without a result charge. When Reddit blocks direct reads, a skipped historical post uses a browser read and the next available frozen candidate may be attempted within `maxBrowserReads` and the deadline. Direct JSON reads do not replace it. A run can still be empty. If every attempted live read fails and no rows are delivered, the run fails.

Use `mode: "threads"` and increase `maxCommentsPerPost` above zero to include comments. Browser threads contain the comments present on one page, with gaps disclosed in coverage. For a research export, increase `maxPosts`. When Reddit blocks direct reads, `maxPosts` caps delivered posts. A filtered candidate uses a browser read but not a row, and an unreadable read is not replaced. When direct JSON reads work, `maxPosts` caps candidates, so filtered or unreadable candidates leave fewer rows. Dates are inclusive and a plain end date includes its entire UTC day. ISO instants normalize to UTC.

When Reddit blocks direct reads, a run reads at most `maxBrowserReads` historical posts, oldest first. Raising `maxPosts` beyond that bound adds unread IDs. Continue a historical window with a later `startDate`; replaying the same frozen set reads the same oldest IDs again.

Across the October 12, 2026 qualification sample, about 69–73% of capped candidates were delivered with `includeRemoved: false`. Yield depends on each community's removal rate, so a single small window can deliver far fewer. Max-charge discovery uses the conservative 69% share when estimating how many candidates can cover affordable visible rows.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `subreddits` | array | No default; form prefill | Public names such as `selfhosted` or `r/selfhosted`. Names are matched without regard to case, and COVERAGE and the frozen set use lowercase; rows keep Reddit's spelling. When Reddit refuses direct requests, the subreddit page is opened with your spelling, such as `LocalLLaMA`. |
| `startDate` | string | No default; form prefill | Inclusive UTC date or ISO instant. |
| `endDate` | string | No default; form prefill | Inclusive UTC date or ISO instant, at or after startDate. |
| `mode` | string | `posts` | `posts` returns live post rows; `threads` returns one bundled post and comment tree per delivered post. |
| `includeRemoved` | boolean | false | Include live removed/deleted labels. Never restores archive text. |
| `maxPosts` | integer | API omission: 20; form prefill: 1 | Row cap when Reddit blocks direct reads; candidate cap when direct JSON reads work. Archive and replay candidates are oldest first; blocked historical reads stop at maxBrowserReads, leaving the rest unread. Listings arrive newest page first and deliver oldest first within each page. Zero collects nothing. |
| `maxCommentsPerPost` | integer | API omission: 500; form prefill: 0 | Choose threads mode and raise this above zero to include comments, up to 5,000 per bundle. Each delivered comment is billed. Browser snapshots can expose fewer comments; coverage records the gaps. |
| `maxBrowserReads` | integer | 10 | Shared bound of 0–10 browser pages for historical posts and comment threads; one historical page supplies its post and available comments together. Listing pages have a separate run bound of 10. |
| `replayFrozenSetDatasetId` | string | None | READ resource picker for a previous frozen ID dataset. Replays without discovery. Any supplied subreddit/date bounds must match its header. |
| `proxyConfiguration` | object | Apify residential | Optional proxy configuration. Anonymous browserless reads use residential proxies by default. |

Omitted required selection inputs, empty subreddits, and `maxPosts: 0` return guidance with no result rows. Invalid selections or refused communities produce an ERRORS record with no result rows.

### Output

The default dataset contains delivered result rows only. This example illustrates the posts contract; values are illustrative source values.

```json
{
  "id": "t3_aa1",
  "parsedId": "aa1",
  "subreddit": "selfhosted",
  "created_utc": "2026-09-20T00:02:03.000Z",
  "createdUtc": 1789862523,
  "title": "Live source title",
  "selftext": "Live source text",
  "author": "source_author",
  "score": 17,
  "upvote_ratio": 0.91,
  "num_comments": 1,
  "permalink": "/r/selfhosted/comments/aa1/",
  "url": "https://example.com/source",
  "flair": "Source flair",
  "nsfw": false,
  "spoiler": true,
  "removed_by_category": null,
  "isRemovedOrDeleted": false,
  "idSource": ["archive"],
  "scrapedAt": "2026-10-12T00:00:00.000Z"
}
```

Score and counts are Reddit's own integers; `upvote_ratio` is a ratio from zero to one. `createdUtc` is Unix seconds and string timestamps are UTC. Source-absent values are null. `idSource` contains `archive`, `listing`, or both; all delivered content is live Reddit content.

Threads mode adds `comments` and `coverage`. Each comment contains its stable fullname and bare ID, `parentId`, `postId`, depth, UTC creation time, author, body and score. Coverage includes `declared_comments`, `captured_comments`, unresolved children and branches, orphan/cycle checks, the stop reason and `complete`. Complete means that the captured count agrees with Reddit's declared count and the collector has no unresolved continuation or linkage. It is not a promise that Reddit exposes every comment. A cap or source omission is visible in coverage.

### Coverage and frozen ID replay

Run records are separate from result rows:

- `COVERAGE`: one entry per subreddit and UTC day, with archive attempts, discovered counts and live-read counts.
- `SUMMARY`: result/candidate/read counts, the requested `maxPosts`, `effectiveRowCap` (maxPosts lowered when your maximum cost cannot cover it), `removedPosts` and historical/comment `removedBrowserReads`, `recallMode`, gateway reason, degraded days, charge-limit state and browser-fallback usage. `listing` reports the ten-page bound, pages used, browser page reads without and with scripts (`browserReads`) and per-subreddit oldest/newest source timestamps and stop reason. `browserThreadPages` reports the selected read bound, reads used and remaining. `liveRequests.threadPage403Retries` counts the empty-403 retries, how many recovered, and each post ID with its outcome. `coveredRange` reports the oldest/newest read candidate and unread IDs; in threads mode a post read without its requested thread remains unread here. These endpoints are not a claim that every intervening post was read. `estimatedChildCostCeilingUsd` estimates our browser-service cost, which we bear: USD 0.226 for the ten listing pages (at most six of them read with scripts) plus USD 0.03 × maxBrowserReads. `estimatedChildCostUsedUsd` is USD 0.004 per listing page read without scripts, USD 0.035 per listing page read with scripts and USD 0.03 per historical or comment page used. These estimates add no buyer charge and are not a guaranteed cost cap. Hard page/read caps stop further work.
- `frozenSet`: the separate dataset ID, header and candidate/read/emitted counts.
- `ERRORS`: handled input, identity and source failures. Live listing, post-batch and thread read failures carry their `reason`; text that is not a fixed failure token is reduced to its exception class.

Archive discovery requests a candidate pool up to the larger of the effective row cap and maxBrowserReads, so a filtered historical browser candidate can be replaced. Processed listing candidates take priority in the frozen set; the oldest other candidates fill remaining pool capacity. Its candidate count can exceed the row cap. Direct JSON reads still select only the first capped candidates. The dataset contains one header and one record per selected candidate: subreddit, bare ID, creation seconds, ID source and UTC day. Its header records `discoveryEndDay`, `effectiveCandidateCap` (the requested pool size) and `capSource`. Replay selects from that stored set without calling the archive or walking listings. A cap larger than the stored set does not discover additional IDs; the status names the recorded cutoff day. Replay preserves candidate selection; Reddit text, scores, removal state and comments can change. Disappearing or filtered candidates can leave a replay empty, which succeeds with no result-row charges.

Blocked historical replays read the same oldest IDs up to `maxBrowserReads`; replay does not advance through unread IDs. Continue with a later date window to select later IDs. Recent listing rows can be delivered before their frozen set is written; a storage failure or interruption can leave delivered rows without a replayable frozen set.

### Historical recall and interruptions

Historical IDs come from a community archive, whose coverage is not an independent census of every Reddit post. Archive query completion means the day was paged to an exhausted response. It does not establish that no post was missing from the archive.

Day states are `complete`, `archive_incomplete`, `page_budget_exhausted`, `archive_deferred`, `archive_unavailable`, or `not_discovered_cap`. Incomplete pages retain their IDs and attempts; budget/deadline stops keep the partial frozen set. Candidate caps explain the days not fully discovered and do not count as degraded archive recall.

The private archive gateway serializes uncached discovery and limits aggregate archive requests to at most one per second, with a daily ceiling of 5,000 and a per-primary share of at most 1,000. Every attempt, including a retry, counts. Dynamic limits slow all discovery; cache hits on complete days older than 36 hours make no archive requests. Queued work that cannot fit the discovery deadline is deferred. These controls can add waiting time.

When archive discovery is deferred or unreachable, the Actor walks live `/new` listings. Days beyond their roughly 1,000-post horizon become `archive_unavailable`; SUMMARY and the status message disclose `recallMode: listing_horizon`. Partial archive discovery uses `archive_partial`. An archive outage with no reachable listing rows succeeds empty. A login gate ends each affected read as a `login_gate` source error in ERRORS. A run in which every live read failed and nothing was delivered fails. Archive-lag cover also walks recent listings for windows ending within 36 hours of collection; older archive misses inside listing reach are not independently recovered.

### Pricing

This Actor uses pay-per-event pricing. The Pricing tab shows the current price of each event for your discount tier.

- **Result:** one delivered post row in posts mode, or one delivered thread bundle in threads mode.
- **Comment:** one comment delivered inside a thread bundle. Posts mode charges no comment events.
- **Actor start:** charged once per run start, by memory, as shown in the Pricing tab; it applies even when a run delivers no rows.

Discovery records, frozen IDs, unread or filtered posts, COVERAGE, SUMMARY and ERRORS are never charged. Set **Maximum cost per run** to bound a job: discovery and live reads stop when it binds, and in threads mode each thread's comment collection is capped before it is fetched at the number of comments your remaining maximum can pay for. API omission uses 500 comments; the form example uses zero. Raise `maxCommentsPerPost`, up to 5,000, when you need larger threads, and the comment charge grows with the comments delivered. A storage or charging failure ends with a distinct delivery-failure status; inspect ERRORS and any rows already delivered.

### Browser fallback

Browserless anonymous reads are the primary path. A JSON 403 or 302 is not retried with another identity. A historical or comment page that Reddit refuses with an empty HTTP 403 is read once more in a new browser, which uses a new IP address, when a page read and a full call of time remain; that read counts toward `maxBrowserReads`. Classified blocks and refused anonymous bootstrap pages route through our private browser Actor, with no separate service charge. A redirect to a Reddit login page is never sent to the browser: the read ends as a `login_gate` source error. Service calls and waiting time are bounded; a service refusal is disclosed as a source error. SUMMARY counts browser routes (`fallbackTriggers`) and reserved service-call attempts (`fallbackCalls`), including failed calls. The gateway and browser service run under our scoped credentials, and we bear their usage.

Recent ranges use subreddit `/new/` pages and their more-posts partials when JSON is blocked. Each page is first read without page scripts, which costs much less. If a mixed-case subreddit's page comes back without posts and names its case-correct address, that address is read without scripts once; a page that shows no posts that way is read again with scripts before anything from it is used. Each of those extra reads counts toward the ten listing pages. Each page is delivered before the next is requested. Historical IDs beyond listing reach use one HTML thread page each; threads mode reuses that page's available comments. Browser snapshots do not traverse more-reply controls and do not promise a full comment tree. At most ten listing pages and `maxBrowserReads` historical/comment pages run; deadlines, row caps and buyer charge limits can stop earlier. Rows already delivered survive a later refusal or bound. SUMMARY states the range actually read and the selected IDs still unread, so long windows can be partial.

HTML `selftext` is rendered text, and its creation timestamp can include fractional Unix seconds. `upvote_ratio`, flair and link `url` come from page attributes/text when present and remain null otherwise. NSFW, spoiler and JSON removal-category labels remain null. A missing article body gives null selftext. Reddit's `[ Removed by moderator ]` title with no article body is recognized as removed, whatever the author. It returns `isRemovedOrDeleted: true` with a null removal category. With `includeRemoved: false` it is skipped without a result charge, and SUMMARY counts it and its historical/comment browser read. Other posts without an article body have null `isRemovedOrDeleted`; `includeRemoved: false` cannot skip them, so they are delivered and charged. No separate media field or media download is supplied; a media post's source `content-href` is retained as `url`. These nulls and snapshot limits are also described in the output schema.

The current maximum of ten historical/comment reads limits our unbilled service costs. Raising that maximum after per-read pricing becomes effective is a future change; this build keeps the maximum at ten and adds no read charge.

A separate 600-second browser-service waiting budget and the run deadline can stop collection before all page/read allocations are used. Remaining allocations do not guarantee more pages can finish in that run.

### Data sources and responsible use

Historical post IDs come from [Arctic Shift](https://arctic-shift.photon-reddit.com/), the free community Reddit archive maintained by [ArthurHeitmann/arctic_shift](https://github.com/ArthurHeitmann/arctic_shift). Post and comment content is read live from Reddit. Arctic Shift's [README removal-request route](https://github.com/ArthurHeitmann/arctic_shift#readme) explains how to request removal from its archive.

Reddit's robots policy disallows crawling, and its User Agreement prohibits scraping without prior written consent. This is a known limitation. No Arctic Shift terms page or commercial-use statement was located in the reviewed captures; its documentation asks bulk users to use monthly dumps and to respect dynamic limits. This Actor is not affiliated with Reddit or Arctic Shift.

Communities whose purpose is vulnerable people's personal disclosures are refused. The explicit refusal list is: suicidewatch, selfharm, depression, mentalhealth, anxiety, ptsd, cptsd, bipolar, bipolarreddit, schizophrenia, eatingdisorders, anorexianervosa, stopdrinking, addiction, opiatesrecovery, alcoholism, narcoticsanonymous, rape, sexualassault, domesticviolence, abusesurvivors, raisedbynarcissists, cancer, breastcancer, chronicpain, askdocs, legaladvice, legaladviceuk, assistance, homeless. Do not use other communities to target such disclosures. No login, posting or account action is performed.

### Limits and help

This product collects subreddit date windows. Keyword search, user history, community metadata, media downloads, archived content copies and a dump-based independent ID index are outside its scope. Source counts can disagree with available comments and current Reddit state can differ from archived metadata. Preserve SUMMARY, COVERAGE and the frozen dataset when reporting a missing day or thread.

In one measured thread, Reddit declared seven comments and six were readable; coverage reported `complete: false` with no unresolved branch. The cause of such gaps is not established.

#### Run time

On October 12, 2026, three 20-post jobs (one subreddit, one UTC day) on build 0.1.18 took 8.8–12.8 seconds from start to finish, with a median of 11.7 seconds. On the same day and window, an archive-only Actor took 6.3–8.5 seconds. This Actor reads current Reddit content live and runs a separate archive-gateway step, both of which add time. These are measured samples, not a speed claim for the current build.

# Changelog

This Actor's version history is a separate document: https://apify.com/danthedataman/reddit-posts-threads-by-date-range/changelog.md

# Actor input Schema

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

Public subreddit names, with optional r/ prefix. Personal-disclosure communities listed in the README are refused.

## `startDate` (type: `string`):

Inclusive UTC date or ISO instant.

## `endDate` (type: `string`):

Inclusive UTC date or ISO instant. A date includes the entire UTC day.

## `mode` (type: `string`):

Live posts, or one post bundle including its live comment tree and coverage.

## `includeRemoved` (type: `boolean`):

Include the live removal label. Archived text is never returned.

## `maxPosts` (type: `integer`):

When Reddit blocks direct reads, caps delivered posts; otherwise caps candidate IDs. API omission uses 20; the form example uses 1. Blocked historical reads stop at maxBrowserReads. Filtered candidates use reads and the next available frozen candidate may be attempted within that bound and the deadline. Unreadable reads are not replaced. Archive and replay candidates are oldest first; listings arrive page by page. Frozen candidate pools may exceed the row cap. Zero collects nothing.

## `maxCommentsPerPost` (type: `integer`):

Choose mode threads and raise this cap to include comments (up to 5,000 per post). The form example uses 0; API omission uses 500. Browser threads contain only the page snapshot, with gaps disclosed. Each delivered comment is one comment event; your maximum cost per run caps comments before fetching.

## `maxBrowserReads` (type: `integer`):

At most 10 page reads shared by historical posts and comment threads. A historical page supplies its post and available comments together. Zero disables these reads. Listing pages have a separate run bound of 10. SUMMARY reports used and remaining allocations and covered range.

## `replayFrozenSetDatasetId` (type: `string`):

Read a previous frozen set without discovery. Optional supplied subreddit/date bounds must match its header.

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

Apify residential by default. Optionally supply your own proxy configuration.

## Actor input object example

```json
{
  "subreddits": [
    "selfhosted"
  ],
  "startDate": "2026-09-20T01:27:35Z",
  "endDate": "2026-09-20",
  "mode": "posts",
  "includeRemoved": false,
  "maxPosts": 1,
  "maxBrowserReads": 10
}
```

# Actor output Schema

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

Live post rows, optionally with comments and coverage. On browser pages nsfw, spoiler and removed_by_category are null; selftext is null when an article body is absent. isRemovedOrDeleted is true for Reddit's \[ Removed by moderator ] title with no article body, whatever the author; other posts without an article body have null isRemovedOrDeleted. upvote_ratio, flair and link url use page values when present, otherwise null. Media has no separate output field or download. Browser thread comments are a partial page snapshot, disclosed in coverage.

## `errors` (type: `string`):

Handled request failures, when present; never result rows.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

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

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "subreddits": [
        "selfhosted"
    ],
    "startDate": "2026-09-20T01:27:35Z",
    "endDate": "2026-09-20",
    "maxPosts": 1,
    "maxCommentsPerPost": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("danthedataman/reddit-posts-threads-by-date-range").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "subreddits": ["selfhosted"],
    "startDate": "2026-09-20T01:27:35Z",
    "endDate": "2026-09-20",
    "maxPosts": 1,
    "maxCommentsPerPost": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("danthedataman/reddit-posts-threads-by-date-range").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "subreddits": [
    "selfhosted"
  ],
  "startDate": "2026-09-20T01:27:35Z",
  "endDate": "2026-09-20",
  "maxPosts": 1,
  "maxCommentsPerPost": 0
}' |
apify call danthedataman/reddit-posts-threads-by-date-range --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,danthedataman/reddit-posts-threads-by-date-range"
        }
    }
}
```

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/zSTuVSJimGB1nIJsu/builds/KLfHoPVxVv8G2w39e/openapi.json
