Reddit Scraper - Posts, Comments, Users & Keywords, No Login
Pricing
Pay per event
Reddit Scraper - Posts, Comments, Users & Keywords, No Login
Reddit scraper that works without login: posts, comments, full user history and keyword search from subreddit, user, search and domain feeds, plus user and subreddit profiles, extracted indicators (CVE, IP, hash, wallet, domain), related subreddits and a rate-limit report.
Pricing
Pay per event
Rating
0.0
(0)
Developer
daehwan kim
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
10 days ago
Last modified
Share
Give it subreddits, usernames, keywords, domains or post links and get back the posts and comments — plus a profile of each user and subreddit, the indicators inside the text (CVE, IP, hash, wallet, domain, u/ and r/ mentions), keyword result counts, related subreddits, optional rule-based signals (intent, sentiment, your own labels) and a report of what Reddit's limits let the run read. No login and no cookies: it reads Reddit's public feeds. On paid plans you can add your own Reddit API key for scores and comment trees (optional, beta).
| Free plan (preview) | Paid Apify plan (Starter and up) | |
|---|---|---|
| Sources per run (subreddits, users, keywords, domains and post URLs together) | 2 | 500 |
| Post and comment rows per source | the 10 newest | as far as Reddit lists them (1,000 per listing, about 250 per search) — and everything after that with monitoring |
| Text | first 280 characters (textTruncated: true); links from the shown text only | full |
More items, date and start point (maxItemsPerSource above 10, since, after) | locked | yes |
New items only (incremental) | locked | yes |
| Posts and comment stream of a subreddit | one listing per subreddit: its posts (its comments when include leaves posts out), 10 newest | both |
Comments of one post (postUrls) | the post and its 9 newest comments | about 500 per request; a post that reaches that is read again under 5 more sort orders and merged (measured: 497 → 1,700 and 492 → 1,288 comments) |
| Posts of a user whose own address lists nothing | the fact, and how many posts Reddit search lists | those posts, through Reddit search (posts only) |
| Your own Reddit API key (scores, comment counts, comment tree) | locked | yes (optional, beta) |
| User and subreddit profile rows | summary numbers; the top 3 subreddits + how many more | everything: every subreddit with its share, active hours, top authors, domains and terms |
| Indicator rows | 1 of each kind + how many more were found | all |
| Related subreddit rows | 3 + how many more were found | all |
| Keywords | 1 | all |
| Signals (intent, sentiment, sales flags, your own labels — rule-based, no LLM) | on the 10 rows shown; 1 custom label | every row; every custom label |
| AI layer (a language model: analysis, your own questions, translation, summaries) | not available — a free run calls no model | every layer you switch on, up to aiMaxItems posts and comments per run |
Limits report (limits and source rows) | complete | complete |
| Price per event | list price | collection rows and rule-based signals: BRONZE −20%, SILVER −35%, GOLD and up −48%; AI events: the same price on every plan |
How big is the difference? Measured on 2026-10-07: for r/cybersecurity a free run returns 10 posts, a paid run 593 posts for the last 30 days (59×) plus 494 comments per day. For u/kn0thing: 10 items and 3 subreddits against 940 posts, 973 comments and the 286 subreddits he is active in (191×). For the keyword data breach: 10 results against 242 results from 185 subreddits (24×).
A free run never fails because of a limit: it reads the first page of each listing (up to 100 items), shows the 10 newest, tells you in the dataset what stayed locked as counted numbers (1 of 19 shown), and ignores paid-only inputs with a notice instead of an error.
Unofficial. This Actor is not affiliated with, endorsed by, or sponsored by Reddit. It reads public feeds only, without login. You are responsible for how you use the input and the results, for complying with Reddit's terms, and with GDPR, PIPA, CCPA and other applicable laws.
Why use this Reddit scraper?
- It keeps working without an account. Every request goes out on its own proxy IP, because Reddit allows one request per IP per minute on its public feeds. Nothing to log in, nothing to get banned. In the measurement behind this Actor (126 requests, 2026-10-07) 117 of 118 requests to open sources succeeded; the other 8 were two users whose feeds answer 403.
- Empty is explained. Every source gets a
sourcerow: did Reddit answer, how many items were read, how far back, what stopped the run, did a listing reach Reddit's own limit. A hidden profile or a missing subreddit is an answer, not a failed run. - A user as one row.
userProfile: posts and comments counted, first and last activity, every subreddit with its share of the activity, active hours (UTC), most linked domains, most used terms — counted from the items the run read, with the count stated. - A subreddit as one row.
subredditProfile: posts in the last 7 and 30 days, authors, comments per day, top authors, top domains, and the ranked posts when you read a ranked listing. - Keyword answers, including "nothing found". One
keywordSummaryrow per keyword and source: results, last 7 days, first and last, which subreddits, 5 sample posts. Rows carrymatchedKeywordsandmatchedIn(title, body, url);strictMatchdrops search results that only matched somewhere the feed does not show. - Indicators as rows, not as a blob. CVE IDs, IPv4 addresses, MD5/SHA-1/SHA-256 hashes, BTC/ETH/TRON wallet addresses, emails, phone numbers, domains, mentioned users (u/…) and mentioned subreddits (r/…), deduplicated per source with how often and where each was seen.
- Related subreddits with the reason. Where a user is active, which subreddits the items mention, and what Reddit's subreddit search returns for the topic.
- Signals without a model. Switch on
signalsand every post and comment row gets an intent (question, seeking_recommendation, complaint, praise, purchase_intent, comparison, self_promotion, discussion), a sentiment label and score, flags (wantsHelp,mentionsPrice,mentionsCompetitor,hasQuestion) and your own labels — rule-based, no LLM: word lists and patterns counted inside the Actor, with the words that fired each rule insignalEvidence. - AI layer, when you want one. Optional and separate: with
aiset, a language model reads the posts and comments (any language) and adds an analysis, answers to your own questions, an English translation and written summaries whose every statement cites the posts it rests on. Paid plans; see "AI layer" for what is sent where. - Long threads, further than one request. One request returns about 500 comments of a post. A post that reaches that is read again under the other sort orders and merged; every comment is delivered and charged once.
- Honest about the fields. The public feed has no scores, comment counts or comment depth. Those columns are
nulland every row lists them infieldAvailability; with your own Reddit API key (paid plans, beta) they are filled — see "Add your own Reddit API key".
Built for OSINT and threat-intelligence work, brand and leak monitoring, and community research. Runs on the Apify platform with API access, scheduling, integrations and monitoring built in.
How to use it
- Fill in at least one of subreddits (
netsec,r/netsec,netsec+osint), users (spez,u/spez), keywords, domains or post URLs. - Optionally choose the listing (
sort,time), how many items to read per listing, filter keywords and the indicator kinds you care about. - Click Start and read the rows in the Output tab. Filter by
recordType. - For monitoring turn on New items only with a monitor key and put the Actor on a schedule.
Input
| Field | Type | Description |
|---|---|---|
subreddits | array | Name, r/name or link; a+b reads several as one listing |
users | array | Name, u/name or profile link |
keywords | array | Reddit-wide searches; operators subreddit:, title:, author:, url: work |
domains | array | Posts that link to this domain (viaDomain on the rows) |
postUrls | array | One post and the comments its feed carries |
include | array | posts, comments, userProfile, subredditProfile, keywordSummary, indicators, relatedSubreddits (all when omitted) |
sort / time | string | new, hot, top, rising, controversial / hour … all |
maxItemsPerSource | integer | Items to read per listing, newest first, 1–1,000. When omitted: 40 (above 10: paid plans) |
maxCommentsPerPost + deepComments | integer + boolean | Post URLs: items to read from one post, 1–3,000 (default 500 — about what one request carries; raise it to get more); above 500, a post whose first request returned 480 or more comments is read again under other sort orders unless deepComments is false. Each comment is one comment event (paid plans) |
since / after | date / string | Stop at older items / start after an item id (paid plans) |
filterKeywords + onlyMatching | array + boolean | Match the items read; optionally deliver only the matching ones |
strictMatch | boolean | Keyword searches: deliver only results that contain every word |
indicatorKinds | array | cve, ipv4, hash, wallet, email, phone, domain, mentioned_user, mentioned_subreddit (all when omitted) |
incremental + monitorKey | boolean + string | Only items newer than the last run with the same key; any text works as a key and each distinct name keeps its own position (paid plans) |
outputFormat | string | full, minimal or rag |
signals | boolean | Add rule-based signals to every post and comment row (see "Signals") |
competitorNames | array | Names that set flags.mentionsCompetitor when signals are on |
customLabels | array | Your own labels: [{ "label": "pricing", "anyOf": ["too expensive", "price increase*", "$#/mo"] }] — words and phrases, with * and # as wildcards; no regular expressions (free plan: the first label) |
ai | array | AI layers to switch on: analysis, labels, translation, threadSummary, userBrief, subredditDigest, keywordInsight (paid plans; empty = no text is sent to any model) — see "AI layer" |
aiLabels | array | Your own questions for the labels layer: [{ "label": "churn_risk", "question": "Does the author say they will stop using a product?" }], optionally with "options": [...] |
aiMaxItems | integer | The most posts and comments one run sends to the model (1–20,000, default 2,000) |
redditClientId + redditClientSecret + redditUserAgent | secret strings + string | Your own Reddit API app key and User-Agent (paid plans, optional, beta) — see "Add your own Reddit API key" |
apiRequestsPerMinute / apiMaxRequests | integer | Pace (1–100, default 80) and cap for requests sent with your key |
proxyConfiguration | object | Apify Proxy; keep it on |
{"subreddits": ["netsec"],"users": ["spez"],"keywords": ["CVE-2026"],"domains": ["bleepingcomputer.com"],"maxItemsPerSource": 150}
maxItemsPerSource counts the items the run reads from each listing (a subreddit and a user have two: posts and comments); filterKeywords with onlyMatching, strictMatch and since then decide which of those become rows. With include set to profiles or indicators only, the items are read and counted but no post or comment row is delivered or charged.
Output
Every row has a recordType: post, comment, source, userProfile, subredditProfile, keywordSummary, indicator, relatedSubreddit or limits — and threadSummary when that AI layer is on. With signals on, post and comment rows also carry a signals object; with an AI layer on, an ai object. Rows that carry a noticeCode instead are messages, not results (free-plan-preview, free-plan-locked, charge-limit-reached, charge-unavailable, lookup-failed, upstream-blocked, run-summary, invalid-input, no-input, ledger-unreadable, ledger-unwritable, delivery-unconfirmed, restart-unconfirmed) — they are never charged; skip them when parsing. Source rows, the limits row and notices carry the responsibility line in disclaimer.
{"recordType": "post","platform": "reddit","accessLayer": "rss","sourceKind": "subreddit","sourceName": "r/netsec","postId": "1wzpmfz","fullname": "t3_1wzpmfz","postTitle": "One Copilot model refused. Another leaked secrets about half the time.","postUrl": "https://www.reddit.com/r/netsec/comments/1wzpmfz/one_copilot_model_refused_another_leaked_secrets/","subreddit": "netsec","author": "Haunting_Ganache_850","postedAt": "2026-10-07T06:45:54.000Z","text": "This one gets stranger the more you look at it.…","textTruncated": false,"externalUrl": "https://adversa.ai/blog/cryptographic-context-injection-github-copilot/","externalDomain": "adversa.ai","links": ["https://adversa.ai/blog/cryptographic-context-injection-github-copilot/"],"matchedKeywords": [],"matchedIn": [],"rankInFeed": null,"score": null,"commentCount": null,"fieldAvailability": {"accessLayer": "rss","unavailable": ["score", "upvoteRatio", "commentCount", "flair", "isStickied", "isLocked", "over18"],"reason": "Reddit's public feed does not carry these fields; they need Reddit API access with an API key."}}
{"recordType": "userProfile","username": "spez","postCount": 23,"commentCount": 150,"historyComplete": false,"firstActivityAt": "2015-07-11T17:02:59.000Z","lastActivityAt": "2026-08-05T18:31:50.000Z","subredditCount": 15,"subreddits": [{ "subreddit": "RDDT", "posts": 0, "comments": 47, "items": 47, "share": 0.272 }],"activeHoursUtc": [15, 3, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 18, 11, 19, 16, 11, 22, 25, 18],"countBasis": "counted in the 173 items this run read (23 posts, 150 comments)"}
{"recordType": "limits","layers": [{"accessLayer": "rss","requests": 12,"http429": 0,"retriedOnNewIp": 0,"failedRequests": 0,"rateLimit": { "used": 1, "remaining": 0, "resetSeconds": 36, "responsesWithHeaders": 12 },"perIpWindow": { "requestsPerWindow": 1, "secondsUntilReset": 36 }}],"caps": [],"advice": "This run made 12 requests to Reddit's public feeds, each on its own proxy IP; 0 were answered 429 and 0 were retried on a new IP. Reddit's headers allowed 1 request(s) per IP per window (0 left, reset in 36 s), which is why no IP is used twice."}
A run also writes RUN-SUMMARY to its key-value store: rows per kind, per-source numbers, requests and the listings that reached a limit.
You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.
Data table
| Row | Key fields |
|---|---|
post | postId, fullname, postTitle, postUrl, subreddit, author, postedAt, text, textTruncated, markdown, externalUrl, externalDomain, isSelfPost, thumbnailUrl, links, linkCount, mentionedUsers, mentionedSubreddits, feedSort, rankInFeed, viaDomain, matchedKeywords, matchedIn, signals (when switched on); null on the public feed and filled with your own API key: score, upvoteRatio, commentCount, flair, isStickied, isLocked, over18 |
comment | commentId, fullname, commentUrl, postId, postTitle, postUrl, subreddit, author, postedAt, text, textTruncated, markdown, links, linkCount, matchedKeywords, matchedIn, isSubmitter (post URL sources), signals (when switched on); null on the public feed and filled with your own API key: score, parentId, depth |
source | sourceKind, sourceName, status (ok, empty, forbidden, not_found, partial, blocked), statusNote, itemsRead, postsDelivered, commentsDelivered, duplicatesSkipped, newestItemAt, oldestReachedAt, stopReason, capReached, collectionError, feeds (per listing: address, HTTP status, requests, items, stop reason, capLimit, capReached, capAdvice, incrementalMode, incrementalNote, savedPosition, accessLayers, and for a post URL thread: comments received, sort orders used, whether one request came back full), limits; for users profileVisibility (public, hidden-or-empty, unavailable), recoveredVia, recoverablePosts; with a key apiLayer |
userProfile | username, postCount, commentCount, itemsCounted, historyComplete, firstActivityAt, lastActivityAt, subredditCount, subreddits (posts, comments, share), activeHoursUtc, topDomains, topTerms, countBasis, profileVisibility, recoveredVia, recoveredNote, signalSummary (signals on); with your API key karma, postKarma, commentKarma, accountCreatedAt |
subredditProfile | subreddit, postsCounted, commentsCounted, postsLast7Days, postsLast30Days, uniqueAuthors, commentsPerDay, daysCovered, topAuthors, topDomains, topTerms, rankedPosts, countBasis, signalSummary (signals on); with your API key subscriberCount, activeUserCount |
keywordSummary | keyword, keywordOrigin, itemsScanned, matchCount, strictMatchCount, matchesLast7Days, firstMatchAt, lastMatchAt, subredditCount, subredditBreakdown, samplePosts (5 newest), signalSummary (signals on) |
indicator | indicatorKind, value, occurrences, firstSeenAt, lastSeenAt, itemUrls (up to 5), hashAlgorithm (hashes), walletNetwork (wallets) |
relatedSubreddit | relatedSubreddit, relatedUrl, relationKinds (user_active, mentioned, topic_search), relationBasis, occurrences, shareOfActivity, searchRank, relatedTitle, relatedAbout, relatedCreatedAt, itemUrls |
threadSummary (AI layer) | postId, postUrl, postTitle, subreddit, summary, themes, issues, viewsFor, viewsAgainst, solutions, productsMentioned (each a statement { text, evidenceUrls } or a list of them), itemsRead, commentsRead, generatedBy, countBasis |
ai on other rows (AI layer) | post and comment rows: analyzed, language, sentiment, intent, emotion, flags, category, labels, translation, note; userProfile, subredditProfile and keywordSummary rows: summarized, summary and the lists of that summary, itemsRead, note |
limits | layers (per access layer: requests, 429s, retries, Reddit's rate-limit headers; for your API key also the pace, rests and whether it stopped early), caps (listings that reached Reddit's limit, with advice), threads (per post URL: comments received, sort orders used; the post's total comment count is known only with an API key), layerSwitch, unconfirmedCharges (charge calls that failed; their rows were delivered and are not charged again), rowsDeliveryUnconfirmed (charged rows whose write failed and could not be checked), rowsUnconfirmedAfterRestart (rows a restarted run found under way — not charged or written again), ledgerUnwritable, runWideDedupe (whether an item reached through two sources was still handed over once — see "Restarts and repeat charges"), advice |
All rows also carry recordType, platform, accessLayer (rss for the public feed, api for your own key), scrapedAt and sourceUrl. Every number in a profile or summary row is counted from items the run really read; nothing is estimated.
Run summary. At the end of a run the default key-value store holds a RUN-SUMMARY record (JSON, not charged): rows per kind and per source, the period covered, requests per access layer with Reddit's rate-limit values, sources that reached a limit, the signal distribution, the AI layer's counts (calls, tokens, items analysed, why it stopped) and the number of charges per event. It holds counts and dates only — no input values.
Signals (rule-based, no LLM)
With signals: true every post and comment row carries:
"signals": {"evaluated": true,"method": "rules","intent": "seeking_recommendation","sentimentLabel": "negative","sentimentScore": -0.5,"flags": { "wantsHelp": true, "mentionsPrice": true, "mentionsCompetitor": true, "hasQuestion": true },"competitorsMentioned": ["Splunk"],"labels": { "pricing": true, "churn": false },"signalEvidence": [{ "rule": "label:pricing", "matched": "too expensive" },{ "rule": "intent:seeking_recommendation:looking-for", "matched": "looking for an" },{ "rule": "sentiment:negative", "matched": "expensive" }],"note": null}
- How it is computed. Word lists and patterns, counted inside the Actor. No model is called and no text leaves the run.
signalEvidencenames the rule and the words that fired, so you can check every verdict. - Intent is one of
question,seeking_recommendation,complaint,praise,purchase_intent,comparison,self_promotion,discussion. When several fire, the order is self_promotion, purchase_intent, seeking_recommendation, comparison, complaint, praise, question. - Sentiment is
positive,negative,mixed,neutraloruncertain, with a score from −1 to 1 (positive words minus negative words over both; a negation right before a word flips it). - Quoted and negated text. Quoted lines (
>), quoted spans and links are left out of intent and sentiment; "not looking for" does not count as looking for. Your labels and competitor names are searched in the whole text — a mention is a mention. - English rules. The script is checked whatever the length of the text. A text that does not look English — or that has too few letters to tell (emoji, digits or punctuation only) — gets
evaluated: false,intent: "discussion"andsentimentLabel: "uncertain", and nosignals-evaluatedcharge; your labels, competitor names, prices and question marks are still checked and filled in, andnotesays so. The optional AI layer reads any language. - Not evaluated, not charged. Deleted, removed or empty text, a bot's standard message and a text in a language the rules do not cover get
evaluated: falsewithsentimentLabel: "uncertain"and nosignals-evaluatedcharge. - Your labels. Each
anyOfentry is a word or a phrase, matched whole (not inside a longer word) and case-insensitively. Two wildcards:*stands for any letters or digits inside one word (vuln*finds "vulnerability",*warefinds "malware"),#for one or more digits (CVE-#-#finds "CVE-2026-12345"); a phrase such asprice increasehas to stand on consecutive words. Regular expressions are not accepted: an entry written as/…/is refused and reported in aninvalid-inputnotice row, and the label's other entries still apply. Limits: 50 labels (free plan: 1), 20 entries per label, 80 characters per entry; labels read the first 5,000 characters of a text and skip any single word longer than 80 characters (a long link, a hash). The matching work per item is bounded; a set of labels built to be slow leaves the remaining labels of that item unchecked (false), and the row'snotesays how many. - Summaries. Profile and keyword rows carry
signalSummary(counts and shares per intent and sentiment, counts per flag and label) over the rows of that source that carry signals. - These are heuristics. They are right often enough to sort and filter thousands of rows, and wrong often enough that a single verdict should be read with its evidence.
AI layer (optional, paid plans)
AI analysis is a separate, optional layer on top of the rule-based signals. Nothing in it runs unless you put a layer name in ai. You can run the signals, the AI layer, or both: they fill different fields (signals and ai) and are charged separately.
What is sent where. When an AI layer is on, the text of the public posts and comments this run reads (title and body, cut to 2,000 characters per item) is sent to DeepSeek's API — DeepSeek is a company based in China — and the answer comes back to the run. Author names are not sent. Every request of this layer goes to that one address, https://api.deepseek.com — no input changes it. If you do not want text sent to DeepSeek, do not switch an AI layer on: leave ai empty and no text leaves the run (the rule-based signals layer sends nothing anywhere).
Layer in ai | What you get | Where |
|---|---|---|
analysis | sentiment (positive, negative, mixed, neutral), intent (the same eight values as the signals), emotion, flags (wantsHelp, purchaseIntent, mentionsPrice, isVendor, hasQuestion), category, language | ai on post and comment rows |
labels | the answer to each of your own questions in aiLabels — true/false, or one of your options; comes on top of analysis, which it switches on | ai.labels.<label> on post and comment rows |
translation | an English translation of items that are not in English — the first 800 characters of each item's text are translated | ai.translation on post and comment rows |
threadSummary | the answers, experiences or themes that come up most, the points at issue and views for and against (when the post is a debate), recurring solutions, products and companies named | one threadSummary row per post URL |
userBrief | interests, topics, apparent expertise, tone | ai on the userProfile row |
subredditDigest | main themes, recurring complaints, frequent questions | ai on the subredditProfile row |
keywordInsight | the contexts a keyword is used in | ai on keywordSummary rows |
-
What is rule-based and what is a model.
signals,signalEvidenceandsignalSummaryare counted by rules inside the Actor and call no model. Everything underai, and thethreadSummaryrow, is written by a language model. -
Labels on top of the analysis. AI labels come on top of the item analysis:
labelsswitchesanalysison for the same posts and comments, also whenanalysisis not inai, and each item is chargedai-analyzed($0.0005) plusai-label($0.0001) per label — with 3 questions, $0.0008 per item. One request to the model does both; reading the item's text is what the analysis event pays for. Each part is charged only when its own part of the answer passed the checks. A run that pickedlabelswithoutanalysissays so inlimits.ai.note. Withlabelspicked and no usable question inaiLabels, neither runs. -
Any language. The rules behind
signalsare English only; the model reads other languages too. That is one reason to switchanalysison for a non-English subreddit. -
Charged after the answer, and only for what passed. A post or comment is charged its AI event once the model has answered and the answer passed the checks: the expected fields, values from the lists above, and the id of an item that was sent. An answer that is empty, malformed or outside the lists is dropped — nothing is shown and nothing is charged for it, and
ai.notesays why. Rule-basedsignalson the same row are not affected. -
Summaries cite their sources. Every statement of a summary carries
evidenceUrls, and only addresses of posts and comments this run sent to the model are accepted; a statement that cites anything else is dropped, and a summary left without a cited summary sentence is not delivered or charged. A summary is written from at most 100 items (1,000 characters each) and needs at least 3. It holds at most 5 statements in each list and 3 addresses per statement. One summary is at most 2 requests to the model: when the first answer is empty the same question is asked once more, and when it was cut off at the model's output limit a shorter answer is asked for. The second answer is the last — if it is empty, cut off or does not pass the checks, the summary is not delivered or charged. A failed request (an HTTP error, no answer in time) is not asked again. The 2 requests are counted for the whole run, restarts included (see "Restarts" below). The run keeps that count for 2,000 summaries; a summary past that number is not asked for, delivered or charged (summary-uncounted) — only a run with many keyword rows gets there. -
Text is treated as data. Posts are written by strangers, and some contain instructions aimed at language models. The text is passed as data, the model is told not to follow anything inside it, and whatever comes back is read through the fixed lists above — a value outside them never reaches a row.
-
Limits.
aiMaxItems(default 2,000, at most 20,000) caps the posts and comments one run sends.aiLabelstakes up to 10 questions, each cut at 200 characters, with up to 8 options of 40 characters. The run also stops using the AI layer — and goes on without it — after 5 failed model calls in a row, when the model refuses the key or the balance is used up, when the run's own ceiling on model spend is reached, or when your maximum charge per run is reached. Theaientry of thelimitsrow gives the calls, tokens, items sent and analysed, answers dropped, requests that were not made (requestsNotMadeLedgerSave,requestsNotMadeSpendHeld) and the reason it stopped. A row whose summary was not written says why at the end of itsai.note. -
Why an answer was not used.
limits.ai.discardReasons(also inRUN-SUMMARY) counts, per code, how often an answer of the model — or a part of one — was thrown away. Whole answers count per request (an answer asked a second time counts twice),summary-*per summary,item-*per post or comment. Only the code is recorded, never the answer itself. None of these is charged.Code What happened no-answerno answer came back from the model (a timeout or a network error) http-errorthe model's API answered with an HTTP error empty-answerthe model's answer was empty cut-offthe model's answer was cut off at the output limit not-jsonthe model's answer was not the JSON that was asked for key-in-answerthe model's answer held a secret and was thrown away unread summary-missingthe model's answer had no summary sentence summary-uncitedthe model's summary sentence cited no post or comment of this run summary-aloneno statement besides the summary sentence cited a post or comment of this run summary-attempts-usedthe requests this summary may make were already used (a summary is asked at most twice in a run, restarts included) summary-uncountedthe run's record of summary requests was full, so the model was not asked for this summary item-missingthe model's answer had no entry for this item item-id-mismatchan entry of the model's answer had an id that was not asked, or one given twice item-invalid-valuesan entry of the model's answer held values outside the allowed lists -
The ceiling on model spend. It is our cost, not yours — you pay the AI events, never the model's bill. Once the spend known to the run reaches the ceiling, no new request is made. Requests already out are not taken back, and what they really cost can be above the estimate held for them. Far from the ceiling up to 4 requests are out at once; once fewer than 4 large requests fit in what is left of it (about the last $0.08 of the $5), a request goes out only when no other is out. So the spend can end above the ceiling by what one request cost above what was left — the ceiling plus the excess of one request, not of four. Requests that were out together far from the ceiling pass it only if they cost, on average, more than is held for the largest request (about $0.019 each). The first request of a run goes out alone; when an answer costs more than was held for it (
limits.ai.estimateExceeded), requests go out one at a time for the rest of the run. -
Restarts. What the layer sent and spent is saved in the run's ledger before each request, so a run that Apify moves to another server counts on from there:
aiMaxItemsand the spend ceiling hold for the whole run, not per restart. The posts and comments of a request that was out at the moment the run was moved (at most 20 per request) count as sent and are not sent a second time: after the restart their rows go out withai.analyzed: false, a note inai.noteand no AI event charged, andlimits.ai.itemsNotResentcounts them. Items whose answer had already come back but whose rows were not yet handed over are sent again after the restart — you are charged once per item either way. A summary's 2 requests are counted in the ledger before each one goes out, so they hold across a restart: a summary whose first request was out when the run was told to stop, or whose first answer was empty or cut off and whose second request had not gone out, is asked once more (with the shorter question), and one whose second request was already made, or whose answer had come back before the run was told to stop — a summary whose row was not yet handed over, or an answer that was thrown away — is not asked again: it is not delivered and not charged, the row it would ride on says so inai.note, andlimits.ai.discardReasonscounts it assummary-attempts-used. That an answer came back is saved in the ledger before the summary's row goes out; if that one save fails the run goes on, and after a restart that summary may be asked for once more — still at most 2 requests in all. A restarted run that cannot read its ledger ends without collecting (see "Restarts and repeat charges"). A first start that cannot read it at first collects once its store answers again (a row is charged only after it is saved), but calls no model for that run (limits.ai.stopped: "ledger-unreadable"): rows go out withoutaiverdicts and without AI charges. -
No second pass. A post or comment that went out without an AI verdict — a limit was reached, an answer was dropped, a restart — is not analysed in a later run: with
incrementalon, the next run starts after it. -
Same price on every plan. The seven AI events have no plan tier discount: BRONZE, SILVER, GOLD and higher plans pay the list price for them. The discount applies to the collection rows and to rule-based
signals. -
Free plan. A free run calls no model:
aiis ignored and afree-plan-lockedrow says so. -
Not measured yet. How often the model's verdicts agree with a person's has not been measured for this Actor. Use them to sort and filter, and read the text before acting on a single one.
-
Your responsibility. You decide whether this text may be sent to a model provider and what the output is used for; check Reddit's terms on using its content with language models before you do.
Pricing
Pay per event. The compute, proxy and storage a run uses are included in the event prices; Apify platform usage is not billed on top of them.
| Event | Charged when | Price (list) | Paid plan tiers | Not charged |
|---|---|---|---|---|
apify-actor-start | the run starts — once per run (memory is limited to 1 GB) | $0.003 | same on every plan | — |
source-checked | one source's lookup is completed — including the answer that a profile is hidden or a subreddit does not exist | $0.003 | discounted | our errors (timeouts, failed retries, parser errors, an answer that is not a feed) and sources cut short by them; the same source entered twice; sources Reddit refused before anything could be read |
post | one post row is written | $0.002 | discounted | duplicates (a post reached through two sources of one run is delivered once); notice rows |
comment | one comment row is written | $0.002 | discounted | duplicates; notice rows |
user-profiled | one user profile row is written | $0.02 | discounted | profiles cut short by our error; hidden or missing profiles |
subreddit-profiled | one subreddit profile row is written | $0.01 | discounted | profiles cut short by our error |
keyword-checked | one keyword × source result row is written, including zero matches | $0.004 | discounted | sources that failed on our side |
indicator | one indicator row, deduplicated per source, is written | $0.002 | discounted | duplicates; notice rows |
related-subreddit | one related subreddit row is written | $0.002 | discounted | duplicates; notice rows |
signals-evaluated | signals is on and the signals of one post or comment row were evaluated — on top of that row's own event | $0.0005 | discounted | runs with signals off; deleted, removed or empty text; a bot's standard message; items in a language the rules do not cover (English only) or with too few letters to tell; duplicates; notice rows |
ai-analyzed | analysis is on — picked, or switched on by labels — and a model's analysis of one post or comment was written to its ai field — on top of that row's own event | $0.0005 | same on every plan | free plan runs (no model is called); answers that were missing or did not pass the checks; failed model calls; English items (translation); duplicates; notice rows |
ai-label | labels is on and one of your questions was answered for one post or comment — per label and item (all the labels of a row in one charge), on top of that row's own event and of its ai-analyzed | $0.0001 | same on every plan | free plan runs (no model is called); answers that were missing or did not pass the checks; failed model calls; English items (translation); duplicates; notice rows |
translated | translation is on and the English translation of one non-English post or comment was written to its ai.translation (the first 800 characters of its text) | $0.002 | same on every plan | free plan runs (no model is called); answers that were missing or did not pass the checks; failed model calls; English items (translation); duplicates; notice rows |
thread-summarized | threadSummary is on and one threadSummary row is written | $0.02 | same on every plan | free plan runs (no model is called); answers that were missing or did not pass the checks; failed model calls; English items (translation); duplicates; notice rows |
user-briefed | userBrief is on and a brief was written to a user profile row — on top of that row's own event | $0.03 | same on every plan | free plan runs (no model is called); answers that were missing or did not pass the checks; failed model calls; English items (translation); duplicates; notice rows |
subreddit-digested | subredditDigest is on and a digest was written to a subreddit profile row — on top of that row's own event | $0.05 | same on every plan | free plan runs (no model is called); answers that were missing or did not pass the checks; failed model calls; English items (translation); duplicates; notice rows |
keyword-insight | keywordInsight is on and an insight was written to a keyword result row — on top of that row's own event | $0.02 | same on every plan | free plan runs (no model is called); answers that were missing or did not pass the checks; failed model calls; English items (translation); duplicates; notice rows |
apify-actor-start: Charged once when a run starts. This Actor's memory is limited to 1 GB, so that is one charge per run (the platform adds one more charge for every 1 GB of memory above 1 GB). It costs $0.003. The start fee is the same on every plan tier.
Paid plan tiers pay less per event for collection rows and rule-based signals (source-checked to signals-evaluated in the table): BRONZE −20%, SILVER −35%, GOLD and up −48%. The seven AI events cost the same price on every plan, and so does the start fee: an AI event pays for a call to a language model, and that does not get cheaper with your plan.
Example: r/netsec with 40 posts + 40 comments + 48 indicators + 24 related subreddits + 1 subreddit profile + 1 source check + start (once) = $0.32 at list price; with signals on, the 80 post and comment rows add up to $0.04; with ai: ["analysis"], the same 80 rows add another $0.04 and a subredditDigest $0.05; with ai: ["labels"] and 3 questions in aiLabels, the same 80 rows add 80 × ($0.0005 + 3 × $0.0001) = $0.064 — the analysis and the three answers of each row. The AI amounts are the same on every plan; the tier discount applies to the other amounts. Rows read with your own Reddit API key cost the same as rows read from the public feed. Use include to leave out the row kinds you do not need, and set a maximum charge per run in Run options to cap spending; when the cap is reached the run stops normally and says so in a charge-limit-reached row.
Restarts and repeat charges. When Apify moves a run to another server, or you abort a run and resurrect it, the run gets a signal, saves its own ledger and continues from it: rows already delivered are neither written nor charged again, and finished sources are not read again. A run that dies without such a signal (for example killed at its timeout) and is then resurrected continues from its ledger as well: a row is charged only after the ledger holding it was saved, so no row is charged a second time. A row that was being handed over at the very moment the run died (charged or not, written or not — the run cannot tell) is neither charged nor written again; rowsUnconfirmedAfterRestart in the limits row and a restart-unconfirmed notice give the count. If the ledger cannot be saved (3 tries), the run charges no further row or AI event and says so in a ledger-unwritable notice. If a run that starts again cannot read its ledger (three reads in a row fail, or the record is broken), it cannot tell what it already delivered, so it delivers nothing more and charges no row and no AI event more: it makes no request to Reddit and calls no model, writes one ledger-unreadable notice row (not charged) that says how many rows were in the dataset, and ends as a normal run. The run's start fee ($0.003) is charged as for every run — "nothing more" is about rows and AI events, not about the run as a whole. The rows delivered before the restart stay valid. Run it again with the same input to get the rest; with incremental on, the next run continues after the last saved position, without it the next run starts from the beginning and charges what it delivers. A run is taken as started again when its dataset already holds a row and its ledger cannot be read or is not there — or when the dataset cannot be counted; a first start has an empty dataset and collects as usual (a store that never answers lets it charge nothing: see ledger-unwritable above). The ledger that could not be read is not written over — every save reads it again first, and once that works the old record and the new one are saved together, so the next restart skips the rows of both. If the dataset refuses a row that was already charged, the run reads the end of the dataset back and writes the row once more only when it is not there; if that fails too, the row is missing from the dataset, is not charged again, and the count is reported in the source row's collectionError (or in a run-summary row). When the dataset cannot be read back, the row is not written a second time (to avoid a duplicate) and a delivery-unconfirmed notice gives the count. From that row on the run charges no further row or AI event: sources it had not read yet are not requested. If the platform's charge call itself fails, that row is still delivered and the call is not made again, so a row is never charged twice because of it; the number of such calls is unconfirmedCharges in the limits row. When the call fails 3 times in a row, or 10 times in one run, the run stops charging and requesting, and a charge-unavailable notice says how many charges could not be confirmed and how many sources were not requested. In incremental runs the saved position is written one write at a time; a write that fails is repeated with the next one, on the next save signal and once more when the run ends, and only if every one of those fails does the next run return (and charge) those items again. A post or comment reached through two sources of one run is delivered and charged once. For the first 400,000 items of a run that also holds across a restart; past that number the run only remembers the items (it no longer saves them), so it still holds inside the run, but after a restart an item first delivered past that number and reached again through another source may be delivered and charged by both — the limits row says so (runWideDedupe.savedForRestart: false). Past 1,900,000 items the run cannot remember more, and such an item may be delivered once per source (runWideDedupe.active: false).
Interrupted monitoring runs. With incremental: true, items are handed over oldest first, and the run saves the last item it delivered when it gets a migration or abort signal and with every periodic state save of the platform. The next scheduled run starts after that item, so it does not charge those items again even when the interrupted run is never resurrected. Two things remain. A run that dies without a signal leaves the items it delivered after its last periodic save to be delivered and charged again by the next run. And the indicator, related-subreddit, profile and keyword rows of a source are written after its items, so for a source that was interrupted they come only if that run is resurrected; a later run does not produce them for items an earlier run delivered.
Limits Reddit sets, and how the run reports them
Measured on 2026-10-07 on Reddit's public feeds (no login). The limits row and every source row report the values of your own run.
| Limit | Value | How it was established |
|---|---|---|
| Requests per IP | 1 per window of about a minute (x-ratelimit-used: 1, x-ratelimit-remaining: 0.0, x-ratelimit-reset: <seconds>) | read from Reddit's response headers on every answer; the same IP answered 200 again after the window |
| Items per listing | the newest 1,000 | r/cybersecurity new: 999 posts in 10 pages; r/sysadmin comments: 984, which was 13.5 hours |
| Results per search | about 250 | 5 keywords: 224 to 249 results |
| Comments of one post per request | about 500 (limit=500) | 4 posts: 497, 492, 305 and 14 comments. Other sort orders return other comments: 6 sort orders merged gave 1,700 and 1,288 comments for the two large posts; after= does not page a post's comments (0 new) |
| Posts of a user whose address lists nothing | through Reddit search (author:name), posts only | 5 users: one address answered 200 with nothing and search listed 17 posts; for an open profile search listed 38 posts against 23 at the address; a user whose address answers 403 had 0 in search |
| Fields in the feed | id, title, body, author, time, subreddit, link, outside link | no score, upvote ratio, comment count, comment depth or flair |
What the Actor does about them: every request uses a new proxy session, at most 4 requests run at the same time, and requests to one source are at least 1 s apart. Only 429, 5xx and dropped connections are retried (after 1, 2 and 4 s, each time on a new IP; a Retry-After that asks for longer is honoured up to 30 s). A 403 or 404 is the source's answer and is not retried. When 3 sources in a row cannot be read — or answer 403 before the run has read anything at all — the run stops asking, says so in an upstream-blocked row and ends normally; nothing is charged for what was not read. For a post URL the source row and the limits row say how many comments came, under which sort orders, and whether one request came back full; the public feed does not say how many comments the post has in total (the API key layer does). When a listing reaches the 1,000-item limit the source row sets capReached and capAdvice tells you, from the time span your run actually covered, how often to schedule a monitoring run so that nothing is missed.
Add your own Reddit API key (optional, paid plans, beta)
The public feed has no scores. If you need them, register an app at reddit.com/prefs/apps and give the run its client ID, client secret and a User-Agent. The run then reads through Reddit's API with your key and switches back to the public feed whenever the key cannot be used.
- What you get in addition:
score,upvoteRatio,commentCount,flair,isStickied,isLocked,over18on posts;score,parentId,depthon comments (a post URL returns the comment tree of one request, flattened with depth);karma,postKarma,commentKarma,accountCreatedAton user profiles;subscriberCounton subreddit profiles; the post's own comment count in thelimitsrow. Rows read this way sayaccessLayer: "api". - No login as you. The Actor uses app-only access (
grant_type=client_credentials). It takes no Reddit password, no refresh token and no cookie, so there is no session of yours to log out. It only reads: the single POST it sends is the token request. - Your User-Agent, not ours. Reddit asks API clients for
<platform>:<app ID>:<version> (by /u/<your username>), for exampleweb:com.example.myapp:v1.0 (by /u/yourname). Without it — or with one not in this form — the key layer stays off and thelimitsrow says why. The Actor never makes one up. - Your key is kept out of the output. The client ID and secret are secret inputs (stored encrypted by Apify). They and the access token are not written to the log, the dataset, the run's key-value store or any error message.
- Same price. Rows read with your key are charged with the same events at the same prices. An item is delivered once, whichever layer read it.
- Free plan: the key inputs are ignored and a
free-plan-lockednotice says so; nothing is sent with your key.
How not to get your Reddit key throttled. Limits as Reddit publishes them (documents read on 2026-10-07):
| Limit | Value | Source |
|---|---|---|
| Requests per key | 100 queries per minute per OAuth client id, averaged over a window of 10 minutes | Reddit Data API Wiki |
| What Reddit reports on every answer | X-Ratelimit-Used, X-Ratelimit-Remaining, X-Ratelimit-Reset | same document |
| Authentication | OAuth is required; a unique User-Agent in the form above | same document |
| App-only access | client_credentials at https://www.reddit.com/api/v1/access_token, requests to https://oauth.reddit.com, a token lasts 1 hour | OAuth2 wiki, "Application Only OAuth" |
What the Actor does with your key: one request at a time, at apiRequestsPerMinute (default 80, that is 80% of the limit; lower it when the key is used elsewhere too). It reads the three headers on every answer and, when less than 10% of the window is left, rests until Reddit's reset. On a 429 it waits what Retry-After asks (60 s at most) and tries once; a second 429 in a row stops the key layer for the rest of the run. On a 401 or 403 it checks once with a new token; if that is refused too, the key layer stops (this also happens when one of your sources is private — the rest of the run then reads the public feed). apiMaxRequests caps how many requests a run may send with the key, the token request included. A stopped key layer is never started again in that run: the run goes on over the public feed from the same item, and the source and limits rows say where and why it switched (apiLayer.switchedToRss, layerSwitch, stopReason). The limits row reports the requests sent with your key, Reddit's last header values, the pace, the rests and the share of the 10-minute budget used.
Beta — what is and is not verified. This layer was built from the two Reddit documents above. Its tests run against synthetic API answers (made up in the documented shape), because we have no Reddit API app of our own. A check with a live key has not been done yet (status 2026-10-07: not verified; planned before the layer leaves beta). If your key returns something the Actor does not understand, the run stops using it, continues over the public feed and says so in the limits row — please tell us in the Issues tab.
What it does not do
- No scores, upvote ratios, comment counts or comment depth without your own API key. Reddit's public feed does not carry them. The columns are present and
null, andfieldAvailabilityon each row says so. With your own Reddit API key (paid plans, beta) they are filled. - No login of any kind. It never asks for a Reddit password or session cookie, and the optional API key is app-only, so there is no account to get logged out or banned — and nothing that needs an account can be read: private subreddits, age-restricted content, the comments of a hidden profile.
- No complete history of a busy listing in one run. Reddit lists the newest 1,000 items; use
incrementalon a schedule for the rest. - No guaranteed complete comment tree of a post. One request returns about 500 comments; reading under 6 sort orders reaches more (1,700 and 1,288 in two large posts) but not provably all, and the public feed has no reply structure. With your API key the comments of one request come with depth and parent; replies Reddit leaves out of that answer are counted, not fetched.
- No comments of a hidden profile. When a user's own address lists nothing, Reddit search still lists their posts; it does not list their comments.
- No user search. Reddit's public feeds do not offer one; give usernames.
- No language model unless you switch one on. Signals are rule-based (word lists and patterns, English) and come with their evidence; they are not a model's reading of the text. The AI layer is off by default, and its accuracy has not been measured yet: treat a verdict as a model's opinion, not as a fact.
- No media downloads. Image and video addresses are delivered as links.
Tips
- Start with the default (
maxItemsPerSource: 40) to see the shape of the data, then raise it. - For monitoring, schedule the Actor with
incremental: trueand onemonitorKeyper schedule. The first run takes a baseline (the newest items); every later run returns only what is newer, oldest first. When more new items are waiting thanmaxItemsPerSource, the oldest are delivered and the rest come with the next run — nothing is skipped. A run cut short by an error or by your charge limit saves the last item it delivered and the next run continues from there. - A busy subreddit's comments pass 1,000 within hours. Read the
capAdviceof the first run and schedule accordingly. include: ["userProfile", "relatedSubreddits"]profiles a list of usernames without paying for every post and comment row.mentioned_userindicator values are plain usernames — you can pass them back intousers, or to a username-lookup Actor in the Apify Store, as a next step.- A
forbiddenornot_foundsource is a fact about that source, not a failed run. - A post URL returns up to 500 items by default (one request, each comment one
commentevent). For a long thread setmaxCommentsPerPostto 3000: only above 500 is the post read again under the other sort orders. - Lead and support triage:
signals: truewithcustomLabelsandcompetitorNames, then filter the dataset onsignals.intentandsignals.flags.
FAQ, disclaimers, and support
Is this legal? The Actor reads feeds that Reddit serves publicly to anyone without an account. Whether a particular use of that data is lawful depends on what you collect and what you do with it. You are responsible for how you use the results. Do not use this tool for harassment, stalking, or any unlawful purpose.
Reddit's terms. Your use of the data is subject to Reddit's own rules: the User Agreement and, where you build on the data, the Developer Terms and Data API Terms. Among other things they expect you to respect deletions: when a post, a comment or an account is deleted on Reddit, delete it from your copies too. The Developer Terms say "as soon as possible"; Reddit's Data API Wiki strongly recommends routinely deleting stored user data and content within 48 hours (both read on 2026-10-07). Read the current texts — they change, and they are the authority, not this summary.
Privacy & data responsibility. You are the data controller for every lookup you run with this Actor; ntriqpro is a data processor acting solely on your instructions. You are responsible for having a lawful basis and a legitimate, purpose-limited reason for each lookup, and for complying with GDPR, PIPA, CCPA, and other applicable privacy laws. We do not store your results — output is written only to your own run's dataset. With incremental, the last item id per listing is kept in a key-value store in your own account.
Why did a source return nothing? Look at its source row: forbidden means Reddit answered 403 (for a user: the profile is hidden, suspended or does not exist — the feed does not say which); not_found means there is nothing at that address; empty means the feed is open and lists no items. For a user, profileVisibility is hidden-or-empty when the user's own addresses answer 200 with nothing — the profile is hidden or has no public activity, the feed does not say which — and unavailable when they answer 403. In the first case a paid run reads the user's posts through Reddit search (recoveredVia: "search"); a free run says how many it found (recoverablePosts).
Trademarks. Reddit is a trademark of its owner. This Actor is an independent tool and uses the name only to describe what it reads.
Found a bug or want a feature? Use the Issues tab.
Used this Actor? A sentence in the Reviews tab about what you used it for helps the next person decide.