{
  "openapi": "3.0.1",
  "info": {
    "title": "Instagram Comments Scraper - Date Filter & New-Comment Monitor",
    "description": "From $0.30 per 1,000. Scrape top-level comments on any public Instagram post or reel: text, GIF, likes, timestamp, commenter username, ID and verified badge. No login or cookies. Comments come back newest-first, so a date filter or the built-in monitor pulls only new ones. Replies not included.",
    "version": "0.2",
    "x-build-id": "BJlBaJes5rHgPqXvJ"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/scrapersdelight~instagram-comments-scraper/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-scrapersdelight-instagram-comments-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/scrapersdelight~instagram-comments-scraper/runs": {
      "post": {
        "operationId": "runs-sync-scrapersdelight-instagram-comments-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/scrapersdelight~instagram-comments-scraper/run-sync": {
      "post": {
        "operationId": "run-sync-scrapersdelight-instagram-comments-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": {
          "startUrls": {
            "title": "Instagram post / reel URLs",
            "type": "array",
            "description": "The posts whose comments you want. Paste any public post, reel or IGTV URL — https://www.instagram.com/p/DZ5T2XPllXv/, https://www.instagram.com/reel/<code>/, https://www.instagram.com/tv/<code>/ — with or without tracking parameters. All three media types were verified live (image, carousel and video/reel posts all resolve). This Actor reads ONE post's comment stream per URL: it does not crawl a profile, a hashtag or an explore feed, and instagram.com/share/ redirect links are rejected rather than guessed at. Leave empty and it runs the documented sample so an empty input still returns rows.",
            "items": {
              "type": "object",
              "required": [
                "url"
              ],
              "properties": {
                "url": {
                  "type": "string",
                  "title": "URL of a web page",
                  "format": "uri"
                }
              }
            }
          },
          "shortcodes": {
            "title": "…or bare shortcodes",
            "type": "array",
            "description": "The same thing without the URL: DZ5T2XPllXv, DWy2NsDD7CE. Handy when your post ids come out of a spreadsheet or another Actor. Mixed with the URL list above; duplicates are collapsed.",
            "items": {
              "type": "string"
            }
          },
          "maxCommentsPerPost": {
            "title": "Max comments per post",
            "minimum": 0,
            "maximum": 500000,
            "type": "integer",
            "description": "Stop after this many delivered comments on each post. 0 = the whole top-level thread. Instagram serves ~13-15 comments per call whatever page size is asked for (measured: first=12, 24, 50 and 100 all returned 14 edges), so a 2,118-comment post is 148 calls / ~106 s and a 397-comment post is 28 calls / ~19 s. Keep a cap on unless you mean to pull an entire thread.",
            "default": 50
          },
          "maxCommentsScannedPerPost": {
            "title": "Max comments scanned per post",
            "minimum": 0,
            "maximum": 500000,
            "type": "integer",
            "description": "The WORK bound, as opposed to the delivery bound above. A filter or a date window drops a comment AFTER it has been read, so 'give me the 10 comments mentioning \"discount\"' can mean reading a 79,000-comment thread to find them. This caps how many comments the Actor will READ per post. 0 = automatic (recommended): no budget at all when nothing can drop a row, and otherwise 100 comments read per row you asked to be delivered — floor 1,000, ceiling 20,000. Measured A/B on one input (a filter matching nothing on a 2,118-comment thread, at maxCommentsPerPost: 10 so the budget is its 1,000 floor, both builds run at the same moment): without the budget 2,119 comments were read over 148 calls in 113 s and 0 delivered; with it, 1,009 read over 72 calls in 38 s and 0 delivered — same result, 2.7x less work. The budget is proportional to the rows you asked for, so it bites hardest where the waste is biggest and not at all on a thread smaller than it: re-measured on that same input at the DEFAULT maxCommentsPerPost: 50 the budget is 5,000, it never trips on that 2,118-comment thread, and the whole thread is read (2,119 comments, 0 rows, $0 charged, scanBudgetReached false — run DFYxmxcpJdiOKsghL, build 0.2.12). Set a number to override; set 500000 for effectively no budget. It is checked at a page boundary, so it can overshoot by one page (~13-15 comments) in your favour. Comments read are NEVER billed — only comments delivered are — and when this stops a post the run says so, names the filter and names this field.",
            "default": 0
          },
          "maxComments": {
            "title": "Max comments in total",
            "minimum": 0,
            "maximum": 500000,
            "type": "integer",
            "description": "Overall ceiling across every post in this run. 0 = no overall ceiling. Useful when you paste 200 URLs and want to bound the bill rather than each post.",
            "default": 0
          },
          "commentsNewerThan": {
            "title": "Only comments newer than",
            "type": "string",
            "description": "THE POINT OF THIS ACTOR. A date (2026-09-01), a timestamp (2026-09-01T12:00:00Z) or a span (\"7 days\", \"48 hours\", \"2 weeks\"). Instagram serves this comment stream strictly newest-first — 0 inversions of created_at across 3,189 comments on 18 posts, measured 2026-09-07 — so the crawl STOPS at the boundary instead of pulling the thread and filtering afterwards. On a 79,000-comment post, \"yesterday\" costs you a handful of calls and a handful of rows, not 79,000 of each. Comments older than the bound are never delivered and never billed."
          },
          "commentsOlderThan": {
            "title": "Only comments older than",
            "type": "string",
            "description": "The other edge of the window, same formats. Comments newer than this sit at the head of the stream and are skipped without being delivered or billed. Use both bounds to pull, say, only the comments posted during a giveaway window."
          },
          "dateBoundGracePages": {
            "title": "Grace pages past the date boundary",
            "minimum": 0,
            "maximum": 10,
            "type": "integer",
            "description": "How many extra pages to read after the stream first crosses the \"newer than\" boundary, before stopping. Reverse-chronological order measured clean (0 inversions in 3,189 comments), so 1 is plenty of insurance; raise it if you would rather pay for a few more calls than risk a straggler. 0 stops immediately at the boundary.",
            "default": 1
          },
          "monitorMode": {
            "title": "Monitor mode — only comments new since the last run",
            "type": "boolean",
            "description": "Schedule this Actor and each run delivers ONLY the comments posted since the previous run. The last-seen comment id per post is kept in a NAMED key-value store ('instagram-comments-monitor-state'), so paging stops exactly at it — no dedupe heuristics, no re-billing of comments you already have. Adds a flat monitor fee per run (see Pricing), charged only if Instagram actually answered.",
            "default": false
          },
          "monitorStateKey": {
            "title": "Watch name",
            "type": "string",
            "description": "Optional label that separates one watch from another on the SAME post — e.g. run a 'support' watch and a 'giveaway' watch over the same URL without either resetting the other. Leave empty for the default watch."
          },
          "monitorSeedComments": {
            "title": "Comments to deliver on the first monitor run",
            "minimum": 0,
            "maximum": 500000,
            "type": "integer",
            "description": "On the very first run for a post there is no last-seen id, so every comment would read as new. This bounds that seed run. 0 = record the baseline and deliver nothing, so your first real delivery is genuinely new comments only.",
            "default": 25
          },
          "alertOnNewComment": {
            "title": "Send alerts for new comments",
            "type": "boolean",
            "description": "In Monitor mode, push each new comment to the webhook / Slack / email sinks below. Alerts are billed only when a delivery actually succeeds, and never fire outside Monitor mode.",
            "default": true
          },
          "webhookUrl": {
            "title": "Webhook URL",
            "type": "string",
            "description": "POSTed one JSON body per new comment: { event: 'new-instagram-comment', comment: {…the full row…}, scrapedAt }."
          },
          "slackWebhookUrl": {
            "title": "Slack incoming-webhook URL",
            "type": "string",
            "description": "A Slack incoming webhook. Each new comment arrives as a card with the commenter, the verified badge, the like count and a deep link to the comment."
          },
          "emailRecipients": {
            "title": "Email recipients",
            "type": "array",
            "description": "One digest email per monitor run listing the new comments, sent through apify/send-mail. Billed as one alert per recipient, and ONLY when that send-mail run reports SUCCEEDED — a refused or failed send is never charged. Note apify/send-mail's own limits: on the Apify free plan it accepts only your own account address, and up to 20 recipients on a paid plan.",
            "items": {
              "type": "string"
            }
          },
          "commentTypes": {
            "title": "Comment types to keep",
            "type": "array",
            "description": "GIF comments come back with text:\"\" and a Giphy payload — 268 of 3,189 comments measured (8.4%), and every single one carried a live GIF URL. Most scrapers in this lane ship them as blank rows; this one types them and fills gifUrl / gifId. 'empty' is the rare row with neither text nor a GIF (6 of 3,189). Rows dropped here are never billed.",
            "items": {
              "type": "string",
              "enum": [
                "text",
                "gif",
                "empty"
              ],
              "enumTitles": [
                "Text comments",
                "GIF comments",
                "Empty (no text, no GIF)"
              ]
            },
            "default": [
              "text",
              "gif",
              "empty"
            ]
          },
          "minLikeCount": {
            "title": "Minimum likes on the comment",
            "minimum": 0,
            "type": "integer",
            "description": "Keep only comments with at least this many likes. 81% of measured comments had zero likes (609 of 3,189 had at least one), so even minLikeCount 1 is a strong signal filter.",
            "default": 0
          },
          "onlyVerifiedCommenters": {
            "title": "Only verified commenters",
            "type": "boolean",
            "description": "Keep only comments from blue-badge accounts. Measured 139 of 3,189 comments (4.4%) — a small, high-signal slice for PR and influencer work.",
            "default": false
          },
          "textIncludes": {
            "title": "Comment text must contain any of",
            "type": "array",
            "description": "Case-insensitive substrings. A comment is kept if it contains ANY of them. Leave empty for no keyword filter.",
            "items": {
              "type": "string"
            }
          },
          "textExcludes": {
            "title": "…and none of",
            "type": "array",
            "description": "Case-insensitive substrings that disqualify a comment. Handy for stripping emoji-spam or a giveaway hashtag.",
            "items": {
              "type": "string"
            }
          },
          "requireEmailInText": {
            "title": "Only comments containing an email address",
            "type": "boolean",
            "description": "Keep only comments where the commenter typed an email address. This reads what is IN the comment — it is not an email lookup or enrichment of any kind.",
            "default": false
          },
          "requireLinkInText": {
            "title": "Only comments containing a link",
            "type": "boolean",
            "description": "Keep only comments containing an http(s):// or www. link.",
            "default": false
          },
          "excludePostOwnerComments": {
            "title": "Drop the post owner's own comments",
            "type": "boolean",
            "description": "Removes the creator's own replies from the thread. Matched on the numeric account id, not the username, so it works even without the post-context fetch.",
            "default": false
          },
          "onlyCommenters": {
            "title": "Only these commenters",
            "type": "array",
            "description": "Usernames to keep (with or without the @). Everything else is dropped before delivery.",
            "items": {
              "type": "string"
            }
          },
          "excludeCommenters": {
            "title": "Exclude these commenters",
            "type": "array",
            "description": "Usernames to drop — your own brand account, known bots, a repeat spammer.",
            "items": {
              "type": "string"
            }
          },
          "includePostContext": {
            "title": "Add post context + the coverage ledger",
            "type": "boolean",
            "description": "One extra fetch of the post's own page (736-947 KB measured, ~1.2 s) per post, which adds the owner's username, Instagram's OWN comment count, the like count, the caption, the posted-at date — and the coverage ledger: what share of the thread this run actually read. Nobody else in this lane tells you that. Billed once per post, only when the post produced rows. In Monitor mode leave it OFF: it re-fetches ~750 KB and a charge per post on every scheduled tick for information that barely changes.",
            "default": true
          },
          "includeRawComment": {
            "title": "Include the raw comment JSON",
            "type": "boolean",
            "description": "Adds a rawComment column holding Instagram's untouched node object, for anyone who wants a field this Actor does not map. Roughly doubles the dataset size.",
            "default": false
          },
          "deduplicateComments": {
            "title": "De-duplicate by comment id",
            "type": "boolean",
            "description": "On by default. 0 duplicate comment ids were measured across 3,189 comments and 228 paginated calls, so this is a belt-and-braces guarantee rather than a fix for a known problem — and de-duplication runs BEFORE billing, so a duplicate could never be charged twice.",
            "default": true
          },
          "resumeMode": {
            "title": "Resumable pagination",
            "type": "boolean",
            "description": "For deep backfills of huge threads. The pagination cursor for each post is stored in a NAMED key-value store ('instagram-comments-resume-state'), so the next run with this switched on continues where the last one stopped instead of paying to re-read the head of the thread. Split a 79,000-comment post over several cheap runs. Ignored when Monitor mode is on — the two pull in opposite directions.",
            "default": false
          },
          "resetResumeState": {
            "title": "Reset the stored cursors",
            "type": "boolean",
            "description": "Clears the saved pagination cursors so the next resumable run starts again from the newest comment.",
            "default": false
          },
          "docIdRecovery": {
            "title": "Recover Instagram's query ids automatically",
            "type": "boolean",
            "description": "This route rides a Meta persisted-query id, and Meta rotates those on deploys. If EVERY post — including this Actor's own known-live sample — comes back empty, the Actor re-mints its session and then sweeps the post page's JavaScript bundles to recover the new ids and caches them, instead of exiting successfully with zero rows. Costs nothing on a normal run. Leave on unless you are debugging.",
            "default": true
          },
          "maxPagesPerPost": {
            "title": "Max pages per post",
            "minimum": 0,
            "maximum": 200000,
            "type": "integer",
            "description": "Hard ceiling on paginated calls per post. 0 = no ceiling. One page is ~13-15 comments regardless of the page size asked for.",
            "default": 0
          },
          "maxSecondsPerPost": {
            "title": "Max seconds per post",
            "minimum": 0,
            "maximum": 86400,
            "type": "integer",
            "description": "Give up on one post after this long and move to the next, so a single enormous thread cannot eat a whole run. 0 = no per-post limit. The reason is recorded as truncationReason on the rows and in the run's POST_STATUS record.",
            "default": 0
          },
          "maxConcurrency": {
            "title": "Posts scraped in parallel",
            "minimum": 1,
            "maximum": 8,
            "type": "integer",
            "description": "Pagination inside ONE post is strictly sequential (each page needs the previous page's cursor), so this parallelises across posts. 3 is a good default; 228 consecutive calls on one minted session measured 100% HTTP 200.",
            "default": 3
          },
          "requestTimeoutSecs": {
            "title": "Request timeout (seconds)",
            "minimum": 5,
            "maximum": 300,
            "type": "integer",
            "description": "Per-request timeout. Comment calls averaged 932 ms across 228 calls through an Apify datacenter proxy (710 ms on the sequential deep-pagination run), so the default is generous.",
            "default": 45
          },
          "maxRequestRetries": {
            "title": "Retries per request",
            "minimum": 0,
            "maximum": 8,
            "type": "integer",
            "description": "Retries on a timeout, a 429 or a 5xx. One transient proxy ECONNRESET was seen in ~350 calls — that is a retry, not a block.",
            "default": 3
          },
          "maxBufferedCommentsPerPost": {
            "title": "Rows buffered per post before streaming",
            "minimum": 500,
            "maximum": 200000,
            "type": "integer",
            "description": "Rows are held until a post's crawl finishes so the completeness columns on every row are final. Past this many they are streamed out instead, and topLevelComplete / truncationReason / postCommentsScanned / topLevelCoveragePct are null on those rows rather than guessed — the run's POST_STATUS key-value record then holds the authoritative ledger. Only bites on threads of tens of thousands of comments.",
            "default": 20000
          },
          "proxyConfiguration": {
            "title": "Proxy",
            "type": "object",
            "description": "Apify Proxy, automatic (datacenter) group. Measured 2026-09-07 on the comment endpoint: datacenter 228/228 HTTP 200 at ~932 ms average, residential US 10/10 at ~1,049 ms. Datacenter is both faster and cheaper here, so it is the default; switch to RESIDENTIAL only if you start seeing 429s.",
            "default": {
              "useApifyProxy": 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
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}