Reddit Scraper | Threads & Linked Comments
Pricing
from $0.14 / 1,000 results
Reddit Scraper | Threads & Linked Comments
Export public Reddit threads, comments, searches and community data. When Reddit blocks thread data, up to 10 threads a run return their page comments, with shortfalls stated.
Pricing
from $0.14 / 1,000 results
Rating
0.0
(0)
Developer
Eli J
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Notice (updated 2026-10-18): Post search and profile histories now fall back to Reddit's web pages when the JSON listing returns HTTP 403 or 302, as subreddit sources already do. Profiles read the overview, submitted posts and comments pages. The fallback covers community new, profile new with timeRange=all, and search sort=new or relevance with timeRange=all. Other post-search and profile sorts and time ranges keep JSON and report a refusal when blocked. At those search settings, comment search reads the search page directly when it shows comment results; other comment searches take comment IDs from the page and details from JSON. Media search, community/user search and profile metadata keep JSON. The field tables below list the values these HTML routes do not supply. Thread URLs are unchanged.
What you can export
Use this Reddit scraper to export public threads and parent-linked comments, keyword searches, community metadata and media links. Supply supported URLs or source lists to read current snapshots without a Reddit login or API key.
Inspect comment coverage and unresolved branches before treating a thread as complete. If Reddit blocks its normal thread data, the Actor reads the thread's web page instead, unless the initial request redirects to a path with a login segment. Each run allows up to 10 browser reads, and a second read of a page that came back with no post counts as one of them; change-feed runs get none. You get only the comments on that page, and "more replies" are not opened. The larger test pages held about 25 comments where Reddit listed 28 and 74. Snapshot comparisons report new or updated records; missing current IDs never establish deletion. No historical archive, semantic AI or sentiment enrichment is provided.
Try the example input
{"urls": ["https://www.reddit.com/r/jellyfin/comments/1wtpzke/"],"maxItems": 300}
The console prefill uses this exact input. API input {} exits successfully with guidance and zero rows. All source lists may be mixed; records are deduplicated by Reddit fullname across sources. Empty dataTypes or maxItems=0 perform no requests.
Search examples:
{"queries":["proxy"],"searchCommunity":"apify","searchTypes":["posts","comments"],"includeComments":false,"strictSearch":true,"maxPostsPerSource":25,"maxItems":50}
{"subreddits":["apify"],"users":["lukaskrivka"],"dataTypes":["post","comment","community","user"],"postedAfter":"2026-09-01","postedBefore":"2026-10-06","includeComments":false,"maxPostsPerSource":25,"maxItems":100}
Input
| Field | Type | Default | Meaning |
|---|---|---|---|
urls | array | omitted | HTTPS thread, focused-comment, community, user or search URLs. Mixed sources are processed in input order; duplicate records are exported once. |
subreddits | array | omitted | Community names, bare or r/ prefixed. Read about/rules and current listings, selected by dataTypes. |
users | array | omitted | Public usernames, bare or u/ prefixed. Read about, submitted posts and comments; URL lanes can select submitted or comments. History at sort new with timeRange=all can use HTML when JSON is refused; profile metadata keeps JSON. |
queries | array | omitted | Reddit search queries, combined with other sources. No omitted query triggers a scrape. Post and comment searches at sort new or relevance with timeRange=all can use HTML when JSON is refused. HTML field values differ; see the table below. |
searchCommunity | string | omitted | Optional bare community, r/name or HTTPS community URL; applies to queries. |
dataTypes | array | ["post","comment","community","user"] | Selected output families; [] performs no requests. |
searchTypes | array | ["posts"] | Search posts, comments, communities, users or media-bearing posts. [] skips queries. |
sort | string | "new" | Ordering. relevance and comments apply to search; community listings accept new, hot, top, rising and controversial. A community listing sort in the URL path takes precedence. When JSON is refused, HTML covers community new, profile new with timeRange=all, and search new or relevance with timeRange=all. Other post and profile sorts keep JSON and report the refusal. At those search settings, comment search reads the search page directly when it shows comment results; any other comment search takes comment IDs from the page and details from JSON. When Apify fills an omitted sort with the default new, search HTML requests sort=new. When sort reaches the Actor absent or null, search keeps the page's relevance default. |
timeRange | string | "all" | Provider time window for top/search routes; exact UTC dates are filtered separately. |
includeComments | boolean | true | Expand trees for discovered posts when comment rows are selected. False omits thread comment rows; user history and explicit comment searches remain independently selected. |
commentSort | string | "old" | Initial tree and continuations use the same Reddit comment sort; the browser snapshot requests this sort too. |
maxItems | integer | 1000 (code; prefill 300) | Global output row ceiling, default 1000 when omitted/null. 0 performs no requests. Bundled formats count one post bundle as one row. |
maxPostsPerSource | integer | 1000 | Scanned candidate ceiling per listing/search family and sort; 0 drains available cursors. Reddit listings commonly stop around 1000, and filtered yield can be lower. |
maxCommentsPerPost | integer | 50000 | 0 captures no thread comments. Count includes comments before filters; JSON continuations are drained until this ceiling or the request budget. Browser snapshots capture only comments present in the page. |
maxCommentDepth | integer | omitted | Optional export depth: top-level is 0. Omitted/null exports every captured depth; this does not reduce tree retrieval work. |
maxRequestsPerThread | integer | 1000 | Initial tree plus continuation/info reads; stops honestly partial at the ceiling. |
commentContextDepth | integer | 3 | For a focused URL, include target, up to this many ancestors and descendant levels. Missing targets are ERRORS, never replaced with a different comment. |
postedAfter | string | omitted | Inclusive UTC post creation lower bound. YYYY-MM-DD or ISO timestamp. Filters apply only to discovered items; no historical recall promise. |
postedBefore | string | omitted | Inclusive UTC post creation upper bound; date-only includes the whole day. YYYY-MM-DD or ISO timestamp. Filters apply only to discovered items; no historical recall promise. |
commentedAfter | string | omitted | Inclusive UTC comment creation lower bound, independent of the post's date. YYYY-MM-DD or ISO timestamp. Filters apply only to discovered items; no historical recall promise. |
commentedBefore | string | omitted | Inclusive UTC comment creation upper bound; date-only includes the whole day. YYYY-MM-DD or ISO timestamp. Filters apply only to discovered items; no historical recall promise. |
strictSearch | boolean | false | Require every word token of each query in post title/body/content URL or comment body, case insensitive with word boundaries. |
filterKeywords | array | omitted | Optional literal keywords; empty means no keyword filter. |
filterKeywordMode | string | "any" | any/all match title, body and content URL; titleOnly/exactPhrase match literal strings in the title. |
maximizeCoverage | boolean | false | Fan out post/media search across new/relevance/top/comments/hot and deduplicate. This broadens discovery but does not defeat Reddit's search/listing horizon. |
includeNSFW | boolean | true | Request provider NSFW results; false excludes marked NSFW post/comment rows. |
onlyWithFlair | boolean | false | Only export posts with nonempty flair; independent comment filters still apply. |
outputFormat | string | "default" | default exports flat rows. Other formats bundle a post and selected comments with markdown/chunkId or messages; community/user metadata stays flat. |
changeFeed | boolean | false | Compare current selected rows against a compatible full export. Emit new/updated rows only; never infer deletions from an absent or partial result. Browser fallback is disabled in this mode; blocked thread reads go to ERRORS without result charges. |
baselineDatasetId | string | omitted | Required with changeFeed. Readable dataset from this Actor's compatible export; an unreadable/incompatible baseline fails before any target read. |
maxRetriesPerRequest | integer | 3 | Retries for network and gate failures. A supported listing whose JSON returns 403 or 302 goes straight to HTML; an HTML page refused with 403 or 302 stops without identity retries. An initial thread JSON 403 or 302 goes straight to the browser when browser reads are enabled, except in change-feed runs. A login redirect never uses the browser, and an exhausted browser budget sends the thread to ERRORS unread. When a saved anonymous session is reused and a 429 arrives with the rate limit used up and a stated reset time, the run switches to another saved session or waits for the reset within maxRunSeconds. Otherwise it reports the refusal. With session reuse active, any other 429 ends with a refusal, and neither starts a new session. Other retries may keep the current session, use another saved session, or set up a new one. Missing targets and unknown challenges do not retry. |
maxRunSeconds | integer | 900 | Wall budget for target reads, checked before requests; an in-flight request can take its 30-second timeout. |
rotateAfterRequests | integer | 24 | Requests after which a session is retired, when session reuse is not active; 0 disables it. While session reuse is active, sessions are not retired after a request count, but a failed request can still replace one. |
proxyConfiguration | object | omitted | Optional Apify proxy override. Platform default uses RESIDENTIAL (datacenter was blocked in measured qualification); local execution without override uses direct HTTP. |
Dates use creation timestamps, independently for posts and comments. Date-only upper bounds include 23:59:59.999 UTC; timestamps with offsets are converted to UTC. Candidate ceilings count scanned candidates, so narrow filters can return fewer rows. A zero maxPostsPerSource drains available cursors; a zero maxCommentsPerPost captures none. No depth ceiling applies unless maxCommentDepth is supplied.
Output
Flat rows have recordType post, comment, community or user. Each row retains provider fields (except nested replies) plus normalized fields. Fullnames are stable IDs: t3_ for posts, t1_ for comments, t5_ for communities and t2_ for users; parsedId is the bare ID. User dataType is user_profile. created_utc is a UTC ISO string; numeric createdUtc retains Reddit seconds. createdAt, scrapedAt, crawledAt and retrieved_at are UTC ISO strings.
Subreddit posts when JSON is refused
For subreddits entries and community URLs sorted by new, the Actor switches to the server-rendered /r/<name>/new/ page and its load-after partials when the JSON post listing is refused with HTTP 403 or 302. In tests on 2026-10-16 the first page held 3 posts and each partial 24 or 25.
The Actor follows the page's cursor serially and stops at maxPostsPerSource, maxItems, the end of the listing, or the page that crosses postedAfter. It skips posts newer than postedBefore. These dates select parent posts before any requested comment expansion on this route; comment dates still filter the captured comments. A filtered page can yield no rows. This is a current listing, with no historical completeness promise.
Only new has this community HTML route. Refused hot, top, rising and controversial listings keep their selected sort and report the refusal in ERRORS. Community metadata uses about/rules JSON as before: if refused, ERRORS names the community data type and endpoint, and post delivery continues. With includeComments on and the comment data type selected, or with a bundled format, each discovered thread is read through the existing thread path.
HTML listing rows use the same output names, with these values:
| Output fields | HTML listing value |
|---|---|
| IDs, title, URLs, author and community names/IDs, creation time, score, comment count, ratio, flair, domain, totalAwardsReceived | Values from the rendered post attributes and flair. Read times and derived lengths/rates use the same calculations as JSON rows. |
| body, selfText, selftext | Whitespace-normalized rendered text; formatting is not kept. Empty string when the listing shows no body; the full post can still have one. |
| bodyHtml, body_html, html, selftext_html | The body's HTML fragment when present; otherwise null. |
| postType, mediaType/media_type, isSelf, isVideo, isGallery, hasMedia/has_media | Rendered post type and its derived flags; null if the type is absent. postType uses self for a text post. |
| images/imageUrls, videoUrls/videoUrl | Observed primary image URLs plus an image post's content URL; observed video-player source URLs. Empty arrays when none are provided, and videoUrl is null in that case. Avatars are excluded. These are links, not downloaded media. |
| galleryImages, gallery_images, mediaAssets/media_assets, galleryCount/gallery_count | Empty arrays and zero: detailed gallery metadata is unavailable on this route. |
| edited, editedAt, removed_by_category/removedByCategory, removedBy, bannedBy, removalReason, modReasonTitle | Null. This route does not read edit times or the JSON removal category. |
| authorFlair/authorFlairText, authorPremium, over18/isNSFW, distinguished, stickied/isStickied, scoreHidden, gilded, subredditSubscribers, isAd, isSpoiler/spoiler, archived, locked, hidden, pinned, isOriginalContent, isRobotIndexable, numCrossposts, numDuplicates, thumbnail, urlOverriddenByDest, media, secureMedia, mediaMetadata, galleryData | Null: the HTML listing parser does not provide these JSON fields. content_flags is empty: no flags were observed, which does not prove none apply. |
| is_deleted_or_removed | True only when the title is exactly [ Removed by moderator ]; false otherwise. In tests on 2026-10-16 that title also appeared with a deleted author, and a post Reddit had removed showed no marker, so false does not rule out removal. |
JSON-only provider fields are absent from HTML rows unless listed above as null. Use baselines captured by the same route for change feeds; otherwise HTML text and unavailable JSON fields can appear as updates.
Search and profile HTML
When JSON is refused at sort new or relevance with timeRange=all, post search switches to /search/ or /r/<name>/search/. Comment searches at sort new or relevance with timeRange=all read the search page directly when it shows comment results. Those rows carry their own text and parent-post details from the page; no JSON request is made to fill them. Profile history at sort new with timeRange=all switches to /user/<name>/, /submitted/ or /comments/. A refused profile-metadata request is a separate user error; post and comment history is still read. The overview page counts posts and comments together toward maxPostsPerSource.
Paging is serial: each page's next-page link is followed in turn. Search paging over rendered result cards also follows pages that carry only a next link and no rows; an empty profile page is an error. Paging stops at maxPostsPerSource or maxItems. Profile paging also stops at a page boundary once every selected data type on the profile is older than its date filter (postedAfter, commentedAfter). Search filters by date and keeps paging. Completeness is not promised.
Dates filter posts found on search and profile pages before thread expansion. When expansion needs a browser page, it uses the listing's canonical permalink, with no HEAD request first. The pages for community/user search and profile metadata lack the fields a metadata row needs, so those requests keep JSON and report blocked data types in ERRORS.
Profile post rows follow the HTML post table above: rendered text, media links, null metadata and the exact moderator-title marker. Rendered types such as crosspost and multi_media stay as those literal values. Search posts and standalone HTML comments differ from JSON as follows:
| Output fields | Search posts | Search comments | Profile comments |
|---|---|---|---|
| body, selfText/selftext (posts), bodyHtml/body_html/html | Post body is empty text; HTML is null. Search snippets are not used as post bodies. | Rendered comment text and its HTML fragment; whitespace normalized, Markdown formatting lost. | Same rendered-text rule; only the displayed comment body. |
| title, IDs, canonical URLs, createdAt/createdUtc, score/upVotes, numberOfComments/commentsCount/numComments, query/searchTerm | Card title, post identity/permalink, post creation time, score and comment count; query echoed. | Comment identity/permalink, comment creation time/score; query echoed. | Comment identity/permalink, creation time and score. No query. |
| author/username/authorName, authorId/userId/parsedAuthorId, authorFullname | Telemetry profile name/ID when present; otherwise null. authorFullname absent. | Displayed author name; author IDs and authorFullname null. | Author name/ID from the comment controls. |
| communityName/parsedCommunityName, communityId/parsedCommunityId, subredditId/parsedSubredditId/subredditName, category | Telemetry community name and ID; subredditId/parsedSubredditId/subredditName and category absent. | Telemetry community name and ID. | Community name from its link, or u_ |
| postTitle, postUrl, postCommentsCount, postUpVotes, postCreatedAt | Post URL is canonical; comment-only parent fields absent. | Parent title from telemetry, canonical URL, comment count, score and post creation time. | Displayed parent title and canonical URL; postCommentsCount, postUpVotes and postCreatedAt null. |
| parentId/parsedParentId, parentKind/parent_kind, depth, root_comment_id, numberOfReplies | Absent. | Null: parent linkage beyond postId is not supplied. | Null. |
| over18/isNSFW, isSpoiler/spoiler | Post telemetry flags when present. | Parent-post telemetry NSFW flag; spoiler is retained, isSpoiler absent. | over18/isNSFW null; spoiler/isSpoiler absent. |
| edited, editedAt, removed_by_category/removedByCategory, removedBy, bannedBy, removalReason, modReasonTitle | Null. | edited/editedAt and removed_by_category null; other removal fields absent. | edited/editedAt null; removal fields absent. |
| postType, mediaType/media_type, hasMedia/has_media, isSelf, isVideo, isGallery | Null. | Absent. | Absent. |
| contentUrl/link, domain, thumbnail, flair/linkFlairText, upVoteRatio/upvoteRatio, totalAwardsReceived, subredditSubscribers, urlOverriddenByDest, media, secureMedia, mediaMetadata, galleryData | Null. | totalAwardsReceived null; post-only fields absent. | totalAwardsReceived null; post-only fields absent. |
| images/imageUrls, videoUrls/videoUrl, galleryImages/gallery_images, mediaAssets/media_assets, galleryCount/gallery_count | Empty arrays; videoUrl null; gallery counts zero. Thumbnails do not establish original media. | Absent. | Absent. |
| authorFlair/authorFlairText, authorPremium, distinguished, stickied/isStickied, scoreHidden, gilded, isAd, archived, locked, hidden, pinned, isOriginalContent, isRobotIndexable, numCrossposts, numDuplicates | Null; content_flags contains only observed over_18/spoiler flags. | Author flair/premium, distinguished, stickied, scoreHidden, gilded, isSubmitter, collapsed/collapsedReason and controversiality null. | Same as search comments; parent NSFW status is also unknown. |
| is_deleted_or_removed | True only for the exact title [ Removed by moderator ]; false does not rule out removal. | True only for literal [deleted] or [removed] body text; false does not rule out removal. | Same as search comments. |
| Read times, lengths, age/rates and engagement aliases | Same calculations from the card values above. | Normal comment calculations from text, time and score; post engagement aliases absent. | Normal comment calculations from text, time and score; post engagement aliases absent. |
Other JSON-only provider fields are absent. A null or missing value means unknown, not false. Use the same route and filters for change-feed baselines.
A run may reuse an anonymous session saved by an earlier run if that session is under six hours old, which skips session setup. A session carrying Reddit's login cookie is never reused. When a reused session's rate limit is used up and Reddit reports when it resets, the run switches to another saved session or waits for the reset. It waits only when a saved session will be usable after the reset and maxRunSeconds allows. Otherwise it reports the refusal. With session reuse active, any other 429 ends with a refusal, and no 429 starts a new session. If no saved session can be used and none is waiting on a reset, the run sets up its own session as before. It tries once to save that session, after its first successful HTML page with recognized listing content.
With session reuse active, SUMMARY adds jarLeased (boolean), jarSource (pool or bootstrap), and numeric jarParks, jarDeposits, bootstrapRequests and jarPoolErrors. Without session reuse, these keys are absent and session handling is unchanged. Session cookies never appear in results, logs, SUMMARY or ERRORS.
If saved sessions cannot be read, unreadable ones are skipped; when the store cannot be read at all, the run proceeds as if none were saved and saves none of its own. If saving a session fails, the run keeps its current session. Each failure logs one line and adds one to SUMMARY jarPoolErrors; a page already fetched is still used.
{"recordType":"comment","dataType":"comment","id":"t1_example","parsedId":"example","parentId":"t3_post","postId":"t3_post","body":"Public comment text","createdAt":"2026-10-06T12:00:00.000Z","createdUtc":1791288000,"depth":0,"sourceUrl":"https://www.reddit.com/comments/post/","scrapedAt":"2026-10-06T12:01:00.000Z"}
Common post and comment fields include author/username/authorName, authorId/userId/parsedAuthorId, body/bodyHtml/html, authorFlair/authorFlairText, prefixed communityName and parsedCommunityName, communityId, score/upVotes, over18/isNSFW, edited/editedAt, distinguished, stickied, scoreHidden, gilded, totalAwardsReceived, ageHours, bodyLength, wordCount and scorePerHour. Vote and reply counts Reddit does not supply stay null or absent; media lists can be empty with zero gallery counts.
Posts also include title, postUrl, contentUrl/link (the content URL Reddit supplies, not always outbound), selfText, postType, mediaType, hasMedia, imageUrls/images, videoUrls/videoUrl, galleryImages (URLs), gallery_images (caption/dimensions/previews), mediaAssets/media_assets, galleryCount, media/secureMedia/mediaMetadata/galleryData, thumbnail/domain/outboundUrlHost/urlOverriddenByDest, authorFlairText/authorPremium, subredditSubscribers, numberOfComments/commentsCount/numComments, upVoteRatio/upvoteRatio, isSelf/isVideo/isGallery/isAd/isSpoiler/isStickied, archived/locked/hidden/pinned, isOriginalContent/isRobotIndexable, numCrossposts/numDuplicates, removal fields, titleLength, commentsPerHour, engagementTotal and commentToScoreRatio. isHighEngagement is true when score + comments >= 100. The threshold is this Actor's own choice. Snake-case equivalents are also emitted, including canonical_url, old_reddit_url, age_hours, body_length, word_count, score_per_hour, comments_per_hour, engagement_total, comment_to_score_ratio, is_high_engagement, is_deleted_or_removed and content_flags.
Comments include parentId/parsedParentId/parentKind, postId/parsedPostId/postUrl, subredditId/parsedSubredditId/subredditName, authorFullname, commentUpVotes/commentCreatedAt, category, depth and numberOfReplies. Tree comments reconstruct root_comment_id/depth and count direct captured replies; standalone rendered search and history rows have null ancestry and reply fields where unavailable. A JSON request for the parent post supplies postTitle/postCommentsCount/postUpVotes/postCreatedAt; rendered HTML comments use the parent details on the page; see the table above. Collapsed state, controversiality and isSubmitter are retained when supplied. Focused URLs preserve targetCommentId, isTargetComment and commentContextClassification (target or context); an unresolved target goes to ERRORS.
Communities include name/title/description/descriptionHtml/publicDescription/publicDescriptionHtml, headerImage/communityIcon/bannerImage, numberOfMembers/membersCount/onlineUsersCount, over18/nsfw, subredditType/submissionType, restrictions, language, flair/wiki settings, headerTitle/submitText, quarantine, follower/media/poll capabilities, spoiler/original-content settings and suggestedCommentSort. Direct community reads include rules with shortName, description, descriptionHtml, kind, violationReason and priority. Search rows contain only the metadata the search listing returns.
Users include username, postKarma/linkKarma/commentKarma/totalKarma, awardeeKarma/awarderKarma, isGold/isMod/isEmployee/hasVerifiedEmail/verified, iconImg/userIcon/snoovatarImg, acceptFollowers, bio/description, followersCount, bannerImg, isNsfw/over18, previousNames, profileTitle/profileDescription/profileVisibility/hideFromRobots/profileUrl.
Comment coverage and failures
Initial trees are checked against the requested post ID. Explicit morechildren lists are expanded in batches of 100; omitted advertised IDs get /api/info hydration. Empty continuation branches use focused thread reads. COVERAGE in the default key-value store and tree rows' coverage report declared_comments, captured_comments, declared_minus_captured, unresolved_children, unresolved_branches, orphan_comments, cycle_comments, request count and stop_reason. Complete is true only when counts reconcile exactly, continuations are exhausted and parentage is valid. Deleted/moderated/provider-omitted comments can leave a residual even when no continuation remains.
The comment capture limit, request ceiling and target time budget produce honest partial exports. maxItems or charge-limit stop can omit some already captured rows; COVERAGE describes retrieval, not a guarantee that every captured row reached the dataset. Keyword/date/depth/context filters also reduce emitted comments. Depth is reconstructed only when captured parents reach the post. Orphan/cyclic ancestry has null depth/root and cannot pass a finite depth ceiling. Each run stops further target reads after 200 MB of decoded response bodies; an in-flight response can exceed that estimate. This per-run response counter is not metered proxy traffic or an aggregate residential budget.
ERRORS contains unresolved or malformed sources and failed data-type requests; no error is a billable dataset row. A mixed run retains successful rows. A wholly failed attempt panel fails, and its status names any HTTP refusal codes recorded in ERRORS. A valid empty or filtered result succeeds. An HTML profile page with no rows is a failed source. Empty-input guidance applies when no sources are supplied or maxItems=0. A 404, private/forbidden source or unknown challenge format is classified.
Retry exceptions and rate-limit handling for reused sessions are described above. Supported refused JSON listings go straight to HTML; HTML listings and partials refused with 403 or 302 stop without identity retries. Sessions are paced by at least one second between requests. The Actor does not bypass login or make writes to Reddit. SUMMARY holds output count, charge-limit status, requests, decoded response bytes, rotations, source attempts and error count; response bytes are not exact proxy billing bytes.
When a thread's normal data request returns 403 or 302 and browser reads are enabled (not in change-feed runs), the Actor goes straight to a browser read of that thread's public page through a residential proxy. It does not retry with another identity. This also applies when the session setup for that thread request returns 302. If the page comes back with no post on it, the Actor reads that page once more; the re-read counts toward the 10-read and 600-second limits, and a second page with no post is refused as before. It exports the post and comments present on that page, using the same row formats. More-reply controls are reported as unresolved branches and are not expanded. Fields absent from the HTML remain null or absent; a comment without readable body text is omitted and reported as unresolved. The listing HTML routes above remain browserless; requested thread expansion keeps this browser snapshot route.
If the initial thread data request redirects to a path with a login segment, ERRORS records HTTP_302_login_redirect; the thread is not retried and gets no browser read. SUMMARY.browserFallback and ERRORS include redirect_path: that request's Location path, without query or fragment, or null when it had no usable Location or the 302 came from another request. A redirect that yields no parsed snapshot appears in SUMMARY.browserFallback.threads and ERRORS with sourceUrl, reason and redirect_path, without comment counts. Complete redirected threads also get a browser_thread_redirect diagnostic in ERRORS. A browser page that does not identify the requested post is refused.
Change-feed runs do not use the browser snapshot: blocked thread reads go to ERRORS without result charges, because switching between normal data and page text could report unchanged records as updates.
Browser reads are limited to 10 per run, re-reads included, within 600 seconds and the run's time limit. Threads beyond that budget go to ERRORS and are not billed. A re-read never adds a second browser-thread-read event for its thread. Each fetched thread is delivered before the next thread is read. SUMMARY.browserFallback states these bounds and, per parsed thread, declared_comments, captured_comments, delivered_comments, shortfall, unresolved_children and orphan_comments. Any delivery shortfall against Reddit's declared count appears in both SUMMARY and ERRORS, including omissions from output limits or filters. Bundles count their included comments for this comparison while billing one dataset row. The platform's charge limit still stops delivery immediately. BROWSER_FALLBACK_BUDGET records the browser calls and seconds reserved in this run; it is not a charge. SUMMARY's requests, responseBytes and rotations count primary requests only, excluding the browser service's traffic.
In a browser snapshot, comments with no readable text are omitted; unresolved_children lists their bare IDs in both SUMMARY.browserFallback and ERRORS. Readable replies keep their parentId when exported. Replies whose parent was omitted have null depth and are listed by fullname in orphan_comments.
Bundles and change feed
rag-markdown produces a post row with selected comments, markdown and chunkId; jsonl-finetune adds system/user/assistant messages. Community and user metadata stays flat. JSON/JSONL/CSV/XLSX downloads use Apify's dataset export; nested media/comments are best preserved in JSON.
For a change feed, first make a full export, then pass its dataset ID:
The baseline picker requests READ access only to the selected dataset under limited permissions. It never grants dataset writes.
{"urls":["https://www.reddit.com/r/jellyfin/comments/1wtpzke/"],"maxItems":300,"changeFeed":true,"baselineDatasetId":"YOUR_READABLE_DATASET_ID"}
The baseline must use this Actor's compatible rows and the same filters/representation. Unreadable or malformed baselines fail before Reddit reads. New/updated rows carry changeType and changeFingerprint. Read timestamps and derived age/rate values do not cause updates; score/body/metadata changes do. Missing current IDs never imply deletion. Partial exports are unsuitable as complete baselines.
Billing and access
Pay per delivered dataset row, plus the platform's Actor Start event as listed on the current Pricing tab. When browser-thread-read is in your run's active pricing, it adds one event per thread read through the browser that delivers at least one row, even for a partial thread or a bundled output. URL aliases of the same thread do not add another event. Before this event's price takes effect, browser reads incur no browser-thread-read charge; delivered-row and Actor Start charges still apply. Empty, refused, failed and normal data-route threads incur no browser-thread-read event. Errors are stored separately and are not result rows. A binding Apify maximum-charge result stops dataset writes and subsequent fetches immediately. Residential Apify proxy is the platform default. Check the current Pricing tab for your applicable event prices and their effective date.
Reddit's captured robots.txt on 2026-10-06 says User-agent: * / Disallow: /. The held User Agreement limits automated scraping without written consent. Public access does not establish permission or complete historical recall. No login, posting, messages or account changes are supported.
For support, report a reproducible input, run ID and COVERAGE/ERRORS records through the Actor's issues tab.