{
  "openapi": "3.0.1",
  "info": {
    "title": "Reddit Scraper - Incremental Runs, Posts and Comment Trees",
    "description": "INCREMENTAL runs: only posts since your last run, per subreddit or query - no duplicates, one page instead of twenty. First run seeded from 30 days, never a silent backfill. Comments flatten with depth and parent id for tree rebuild. Author usernames redacted unless opted in.",
    "version": "1.1",
    "x-build-id": "us7Plk3g8zzUuqCBE"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/hridayrungta~reddit-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-hridayrungta-reddit-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/hridayrungta~reddit-scraper/runs": {
      "post": {
        "operationId": "runs-sync-hridayrungta-reddit-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/hridayrungta~reddit-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-hridayrungta-reddit-scraper",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "properties": {
          "subreddits": {
            "title": "Subreddits",
            "type": "array",
            "description": "Communities to read, one per line. Any form works: `smallbusiness`, `r/smallbusiness` or a full `https://www.reddit.com/r/smallbusiness/` link. Public communities only - a private or quarantined community cannot be read without logging in, and this Actor never logs in.",
            "items": {
              "type": "string"
            }
          },
          "searchQueries": {
            "title": "Search queries",
            "type": "array",
            "description": "Phrases to search Reddit for, one per line - e.g. `crm recommendation`, `\"per seat pricing\"`. Reddit's own search syntax works, including quoted phrases. By default each query searches all of Reddit; switch on **Search inside the subreddits above** to narrow it.",
            "items": {
              "type": "string"
            }
          },
          "searchInSubreddits": {
            "title": "Search inside the subreddits above",
            "type": "boolean",
            "description": "OFF by default, so a query searches all of Reddit. Turn it ON and every query is run inside every subreddit you listed - 2 queries x 3 subreddits is 6 separate sources, each with its own high-water mark. Needs at least one subreddit and one query to do anything.",
            "default": false
          },
          "sort": {
            "title": "Sort",
            "enum": [
              "new",
              "hot",
              "top",
              "relevance"
            ],
            "type": "string",
            "description": "The order to read a listing in.\n\n- **New** returns the most recent posts first. It is the only order in which the Actor can stop reading early, so it is both the default and by far the cheapest choice - and it is the order incremental mode requires.\n- **Hot** is Reddit's own front-page ranking.\n- **Top** is the highest-scoring posts inside the **Time range** below.\n- **Relevance** applies to search only; a subreddit listing has no relevance order, so it falls back to Hot.",
            "default": "new"
          },
          "timeRange": {
            "title": "Time range",
            "enum": [
              "hour",
              "day",
              "week",
              "month",
              "year",
              "all"
            ],
            "type": "string",
            "description": "Only used by **Top** listings and by search. Ignored with New and Hot, because neither of those orders takes a period.",
            "default": "week"
          },
          "maxPostsPerSource": {
            "title": "Max posts per source",
            "minimum": 1,
            "maximum": 10000,
            "type": "integer",
            "description": "How many posts to take from each subreddit and each query. Reddit serves 100 posts per request, so 25 is one request and 250 is three. This is your main cost control: you are billed once per post saved.",
            "default": 25
          },
          "minScore": {
            "title": "Minimum score",
            "type": "integer",
            "description": "Drop posts and comments scoring below this. `0` keeps everything that is not net-negative; a negative value keeps downvoted content too. Set it to 10 or 50 on a busy subreddit to skip the noise - a dropped post is never billed.",
            "default": 0
          },
          "includeComments": {
            "title": "Also scrape comments",
            "type": "boolean",
            "description": "OFF by default. Turn it ON and each post's comment tree is fetched as well, and every comment becomes its own row with `body`, `score`, `depth`, `parentId` and `parentType` - so the whole thread rebuilds from a flat table with one self-join.\n\nComments are billed separately as `comment-scraped`, and a comment tree is one extra request per post, so a 25-post run with comments is 26 requests rather than 1. A post Reddit reports as having no comments is skipped without a request.",
            "default": false
          },
          "maxCommentsPerPost": {
            "title": "Max comments per post",
            "minimum": 0,
            "maximum": 5000,
            "type": "integer",
            "description": "Cap on comment rows per post. Only used when **Also scrape comments** is on. The cap is applied while the tree is walked, in Reddit's own order, so a post with 8,000 comments returns the first N rather than building the lot.",
            "default": 50
          },
          "commentDepth": {
            "title": "Max comment depth",
            "minimum": 1,
            "maximum": 50,
            "type": "integer",
            "description": "How deep into a reply chain to go. 0 is top-level comments only, 10 covers almost every real thread. Replies below the cap are reported in the log as \"not expanded\" rather than silently dropped.",
            "default": 10
          },
          "commentConcurrency": {
            "title": "Comment fetches in parallel",
            "minimum": 1,
            "maximum": 20,
            "type": "integer",
            "description": "How many comment trees to fetch at the same time. Only used when **Also scrape comments** is on.\n\nOne comment tree is one unblocking-proxy request and the unblocker takes **40-90 seconds** to answer, so fetching them one at a time is what makes a large comments run slow: 150 posts in sequence is over two hours and can hit your run timeout. At the default of 5 the same 150 posts take **roughly 30-35 minutes**, and at 10 roughly 15-18 minutes.\n\n**Output order does not change.** Each post's tree is buffered and written in listing order, so a parent row always appears before its replies and the dataset rebuilds into the same tree at any setting. Charges do not change either: rows are counted and billed in one sequential pass.\n\nSet it to 1 to fetch strictly one at a time. Raise it above 10 only if you also raise the run's memory, since each tree in flight is held in memory while it is parsed.",
            "default": 5
          },
          "onlyNewSinceLastRun": {
            "title": "Only new posts since my last run",
            "type": "boolean",
            "description": "OFF by default. Turn it ON and the Actor remembers, **per subreddit and per query**, the newest post it has already given you, and every later run returns only what was posted since. Point a schedule at it and you get a clean feed of new posts with no duplicates and no re-billing.\n\nThe marks are kept in a **key-value store on your own Apify account** (see *State store name* below), so they survive across runs, schedules and Actor updates, and 20 tracked sources get 20 independent marks.\n\nIt is also much the cheapest way to run this Actor. Reddit's `new` listing is newest-first, so the moment the Actor reads a post older than your mark it stops reading that source immediately instead of paging the rest. A daily run on a busy subreddit reads **one page instead of twenty**.\n\n**First run:** a source with no mark yet is seeded from *First run reaches back* (30 days by default) - never the whole listing, so a first run cannot surprise you with a bill for a year of posts you did not ask for. Set it to `all` if you do want the backfill.\n\nIncremental mode reads newest-first by necessity, so it overrides **Sort** for the run and says so in the log.",
            "default": false
          },
          "firstRunSince": {
            "title": "First run reaches back",
            "type": "string",
            "description": "Only used by **Only new posts since my last run**, and only for a source the Actor has never seen. Absolute (`2026-01-01`) or relative (`30 days`, `6 months`, `1 year`). Default `30 days`.\n\nSet it to `all` to take everything the listing will give on the first run and set the mark from it - a deliberate backfill. Leaving it at a span is the safe choice: you decide how much history you pay for.",
            "default": "30 days"
          },
          "stateStoreName": {
            "title": "State store name",
            "type": "string",
            "description": "Name of the key-value store on **your** Apify account that holds the high-water marks. Defaults to `reddit-scraper-state`, which is created for you on the first incremental run.\n\nGive different tracking sets different names - e.g. `reddit-state-competitors` and `reddit-state-leads` - and each keeps its own marks, so the same subreddit can be tracked independently in two schedules. Letters, digits and dashes; anything else is folded into those.",
            "default": "reddit-scraper-state"
          },
          "stateNamespace": {
            "title": "State namespace (optional)",
            "type": "string",
            "description": "Extra prefix on every state key. Leave empty and the Actor derives one from your Apify user id, which already keeps your marks separate from everybody else's. Set it only when you want several independent tracking sets inside ONE store - for example one namespace per client or per report."
          },
          "postsNewerThan": {
            "title": "Posts newer than (window START)",
            "type": "string",
            "description": "Keep only posts published at or after this moment. Absolute (`2026-04-01`, or a full ISO timestamp) or relative (`7 days`, `6 months`). Leave empty for no lower bound. On the New sort this also stops the Actor reading past it, so a narrow window is cheap as well as small."
          },
          "postsOlderThan": {
            "title": "Posts older than (window END)",
            "type": "string",
            "description": "Keep only posts published before this moment. Same formats as the window start. Leave empty for no upper bound."
          },
          "personalData": {
            "title": "Include author usernames",
            "type": "boolean",
            "description": "OFF by default, and please leave it off unless you need it. While it is off, the author's username, their Reddit account id and their flair are removed from every post row and every comment row - the fields come back as `null`. The post title, the body, the score, the timestamp, the permalink and the subreddit are all kept, so the dataset stays fully useful for research, monitoring and market analysis without carrying anybody's identity.\n\nTurn it on only if you have a lawful basis for processing the identity of the people who wrote the posts.",
            "default": false
          },
          "maxItems": {
            "title": "Max rows",
            "minimum": 1,
            "maximum": 1000000,
            "type": "integer",
            "description": "Hard stop after this many rows across the whole run, posts and comments together. This is your overall cost cap: you are billed once per row saved.",
            "default": 1000
          },
          "requestTimeoutSecs": {
            "title": "Request timeout (seconds)",
            "minimum": 45,
            "maximum": 300,
            "type": "integer",
            "description": "How long one request may take. **The default of 90 seconds is deliberate and measured** - the unblocking proxy does real work per request and 30 seconds is not enough for it; a shorter value aborts requests that were about to succeed and looks like a network failure. The minimum accepted is 45. Raise it to 120-180 if you see timeouts on a busy day.",
            "default": 90
          },
          "maxRetries": {
            "title": "Max retries per request",
            "minimum": 0,
            "maximum": 10,
            "type": "integer",
            "description": "Retries with exponential backoff for timeouts, 429s and 5xx responses. A 403 is never retried - it means the request never reached Reddit's content, so backing off cannot help.",
            "default": 3
          },
          "proxyConfiguration": {
            "title": "Proxy configuration",
            "type": "object",
            "description": "**Required, and the group matters.** Reddit answers a plain datacenter IP with HTTP 403 and an HTML block page - measured, not assumed - so this Actor defaults to Apify Proxy with the **UNBLOCKER** group, which returns real JSON. Leave it as it is unless you know exactly why you are changing it. Unblocker access is included on every plan including the free one.",
            "default": {
              "useApifyProxy": true,
              "apifyProxyGroups": [
                "UNBLOCKER"
              ]
            }
          },
          "failOnEmpty": {
            "title": "Fail the run if a source returns nothing unexpectedly",
            "type": "boolean",
            "description": "On by default. If a subreddit or query returns no posts at all and nothing explains it - no date window, no score floor, no incremental mark, no cap - the run FAILS and names the source, instead of finishing SUCCEEDED with an empty dataset. A misspelt subreddit name and a missing proxy both look like \"no results\", and neither should pass quietly. Rows already saved are kept either way. Turn this off only if an empty result is acceptable to you.",
            "default": true
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}