# Changelog of Instagram Hashtag Scraper — Posts by Hashtag or Keyword (`steadyfetch/instagram-hashtag-scraper`) Actor

- **URL**: https://apify.com/steadyfetch/instagram-hashtag-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/steadyfetch/instagram-hashtag-scraper.md

## Changelog

### 1.0.37 — 2026-10-03

- **The run page's own line now always fits, even when a run set fields this actor does not have.** When a run sets field names this actor does not read, the run page names them and says to use "Hashtags". With a very long list of such names on an already crowded line, that sentence could push the line past the length the run page shows. It now gives way to a short form that still says which field to use, before anything about what was delivered or charged. The dataset row still lists every name you sent. Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.

### 1.0.36 — 2026-10-01

- **A location typed as a place name no longer buys a lookup when it has no posts left to collect.** "Max posts" is split evenly between your terms, so a run with more terms than posts gives some terms a share of 0, and a run that resumes after a server move can find a term it has already filled. A location typed as a name still had its place looked up in both cases, a paid read whose answer the run could not use. The lookup is now made only for a location that is still owed posts. Which posts are delivered, the rows, the charges, the price, the input fields and the output columns are unchanged.

### 1.0.35 — 2026-10-01

- **A run with several hashtags, keywords or locations now collects them side by side, so far more of a large ask fits inside the run's time limit.** One page of results can take around twenty seconds to come back from the data source, and the run used to read one term after another, so a run on the default 10-minute limit could collect only around 600 posts however many it was asked for. A 5,000-post ask over 18 terms that collected about 630 posts in 10 minutes now collects about 4,600 in the same 10 minutes. The run reads up to ten terms at once. Each term still gets exactly its own share of your limit, a post that two of your terms both reach is still delivered and charged once, and the time limit still ends the collecting, never the delivering: posts already in hand are always written out, and anything the clock cut gets an uncharged row saying so. If your maximum cost per run is lower than your whole ask would cost, the run reads one term at a time as before, so it never pays for a page your cap could not cover. Rows from different terms now arrive interleaved in the dataset; each row still names the hashtag, keyword or location it came from. No price, input field, output column or charged event changed.

### 1.0.34 — 2026-09-29

- **A run on the Apify free plan now says which price it was charged at.** When a run is billed at the Apify free plan's price, the run page now says so in one sentence — $2.40 per 1,000 posts — and names what the same run costs on paid plans: $1.40 on Bronze, $0.90 on Silver and $0.60 from Gold. Runs on paid plans read exactly as before. On a crowded run page this sentence is the first thing to give way, so nothing about what was delivered or charged is ever pushed off it.
- **A run stopped by a data limit of this actor's own now says which one, and what to do.** The row and the run page used to say every such stop was a monthly limit that resets at the start of the month. A per-run limit is now worded as one: a new run on the same terms continues, and posts already delivered to your account are not charged twice. A pause until next month is worded as that. Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.

### 1.0.33 — 2026-09-26

- **A run cut off before it finishes is now accounted for.** The run now notes its own start in its internal record, so a run that ends without finishing — a hard abort, a crash or a timeout — is still accounted for on our side. Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.

### 1.0.32 — 2026-09-24

- **A daily allowance on the Apify free plan.** On the free plan this actor now makes at most 20 pages of results per account in any 24 hours, and accounts on that plan share one daily allowance on this actor. Past either, the run keeps every post it already delivered, adds one uncharged row saying how many posts were not collected and when the allowance resets (UTC), says the same on the run page, and charges nothing past it. Paid Apify plans carry no daily allowance and are unchanged. No price, input field or output column changed.

### 1.0.31 — 2026-09-24

- **The `hashtags` field is no longer marked required in the input form.** An AI agent or API call that leaves it out gets the same sample run a bare Start always gave, and one that sets it gets the posts under its hashtags, exactly as before. No price, input field, output column or charged event changed.

### 1.0.30 — 2026-09-24

- **A run that Apify restarts after it has already delivered keeps its own record of that delivery.** If the restarted part of a run delivers nothing new, the run's internal record now keeps what the earlier part delivered and charged, with a note of the restart, instead of being rewritten as if nothing had been delivered; one line in the run log says so. Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.

### 1.0.29 — 2026-09-22

- **The run log no longer prints an internal usage summary at the end of a run, and a failed internal bookkeeping write no longer prints where it was being saved.** Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.

### 1.0.28 — 2026-09-22

- **The run log no longer prints internal cost lines, and a row stopped by this actor's own monthly collection allowance no longer quotes a dollar figure.** Those numbers described our own costs with the data source, not anything you pay. The row still says the allowance was reached and that it resets at the start of each month; your rows and charges are unchanged.

### 1.0.27 — 2026-09-22

- **When the data source behind this actor goes down, the run now says so and asks you to try again, instead of reporting the hashtag as permanently unavailable.** A source outage answers with a status code that says nothing about the tag you asked for — the kind a service's front door sends while its own servers are unreachable. The run used to treat any answer it did not have on a short list as a final verdict about your hashtag, so during an outage on 22 September a tag that had been served fifteen minutes earlier came back marked as refused for good, with a row telling you not to bother re-running it. It now works the other way round: only the answers the source actually gives ABOUT what you sent — not found, not permitted, malformed — are treated as final, and everything else is read as temporary, retried a few times inside the run, and reported as an uncharged row worth re-running. Nothing you are charged changes, and a hashtag that really does not exist is still the same definitive answer it has always been.

- **A run's saved position now survives many runs of this actor going at once on the same account.** This actor remembers which feeds your account has already read all the way to the end, so a later run of the same term reads one page instead of three. Each run used to write that memory back whole when it finished, so when several runs of this actor ran at the same time on one account — a scheduled batch, for instance — only the last one to finish left a mark and the others were lost, and the saving they had earned was gone by morning. Every run now merges its marks with what is already saved instead of replacing it, keeping the most recent reading for each feed. Measured with fifteen runs going at once: one mark of fifteen survived before, fifteen of fifteen now, and the saved record is in exactly the same format an older build reads. Nothing changes about what is collected, which posts are charged, the price, the input fields or the output columns.

- **An answer the data source has never sent before now counts as an outage rather than as something we paid for.** The run keeps its own record of which of the source's answers cost us money, and until this build that record was a fixed list — so a response nobody had listed, such as a code the source first sent during an outage, was read as one we had bought. Now only the answers the source actually bills count as bought; everything else is booked at nothing and simply tried again. Nothing changes about what is delivered, what you are charged, the price, the input fields or the output columns.

### 1.0.26 — 2026-09-20

- **A run page that names input fields this actor does not have now names at most three of them, so the rest of the line can no longer be cut off.** The platform shows the first 500 characters of a run's status line and drops the rest. That sentence echoed every name you sent, at whatever length you sent it, so one long field name pushed the counts, the stop and the "not charged" statement off the page — a 300-character name measured the line at 503 characters and a 1,000-character one at 1,203. From this build the run page shows the first three names, each shortened if it is very long, and then "and N more". The uncharged row this run writes still names every one of them in full, at any length: nothing truncates a dataset row. No input field, output column, charged event or price changed.

### 1.0.25 — 2026-09-19

- **The monthly allowance behind this actor's data reads has been raised again — a busy month no longer stops delivery part-way through.** Nothing you are charged changes: the allowance is ours, not yours, and a run that ever meets it still stops honestly and charges nothing for what it did not deliver.

### 1.0.24 — 2026-09-19

- **The whole "Maximum cost per run" you set is now spent on hashtag posts; before this build the run held back about a twelfth of it.** Your cap pays for hashtag posts and nothing else — the compute, the proxy and the data behind the run are ours, not yours — but the run was still reserving roughly 8% of your cap against those costs before it planned a single hashtag post. So a cap worth ten hashtag posts bought nine, and the tenth was money you had authorised and could not spend. That reserve is gone. A run at its cap still ends the same honest way it always did: it stops itself, tells you what it cut and why, and charges you for nothing it did not deliver. No price, input field, output column or charged event changed.

### 1.0.23 — 2026-09-19

- **A hashtag whose Instagram record states no post count at all is no longer reported as a hashtag with no posts.** When the first page of a hashtag comes back holding nothing, this actor does not take that as an answer on its own — it reads Instagram's own record for the tag and lets the post count there decide: a record saying zero is the definitive "nothing has ever been published under this tag", and a record with a real count contradicts the blank page, so the page is treated as a read that did not come through and you get an uncharged row worth a re-run. What that check did not allow for was a record carrying no count at all — the field blank rather than zero. Such a record was read as a published zero, so an answer nobody could put a number to became the permanent verdict `no_posts`: the run told you the hashtag was empty, and a re-run for the same tag told you the same thing, over a tag that may well have had posts on it all along. A record that states no count is now treated as what it is, no evidence in either direction, and the blank page takes the honest uncharged `vendor_unavailable` row instead — the read did not come through, nothing was charged, and a re-run is worth it. A record that really does say zero still proves the tag empty exactly as before, a record with a real count still sends a blank grid to the retryable rail, and a tag Instagram has no record of at all is still the definitive `tag_not_found` it has always been. No price, input field, output column or charged event changed.

### 1.0.22 — 2026-09-19

- **A run that your cost cap, your run clock or your result limit stops part-way through the last page of a feed no longer reports that feed as finished — and no longer remembers it as finished.** When one of those limits landed inside the last page Instagram serves for a term, the run's row said Instagram "offered no more" over posts the limit had cut, and since 1.0.21 it also wrote that feed down as read to its end. A later run for the same term would then read one page, find only posts you already had, and tell you it held everything — while the posts past the cut had never been delivered. Such a run now reports the limit that really stopped it, on the same uncharged row every other limited run gets, and remembers nothing about the feed's end; the next run collects the rest as ordinary new posts and is charged for them as usual. A run that genuinely reads a feed to its end is unchanged. No price, input field, output column or charged event changed.

### 1.0.21 — 2026-09-19

- **Ask again for a hashtag, search or place this actor has already read to the end, and the run no longer pays for three pages of posts you already own.** Instagram serves a finite feed for any one term — a tag's whole Top grid, a search, a place's grid — and once a run has read one of those all the way to the end and delivered it to your account, there is nothing further down to go and get. Until now a later run had no way of knowing that: it would read three pages of posts you already hold, find nothing new in them, and stop with an uncharged row — honest, and three paid reads for nothing, on every re-run, however often you asked. Each run now remembers that a feed ended, alongside the posts it delivered, in the same key-value store `ig-hashtag-account` in your own Apify account — kept separately for each term and for each feed of it, so a tag's Top grid never answers a question asked of a different one. A later run for that term reads ONE page of it instead of three, and where that page holds only posts you already have it stops there and tells you so: an uncharged row naming the day that feed was read to its end, how many posts it served then, and that this run read its top page again and found nothing new on it. That one page is the check, not a guess — the Top grid is a ranking, and anything Instagram has promoted into it arrives at the top, where it is collected and charged as an ordinary new post exactly as before, after which the record is re-armed. Nothing about this can cost you more than before: the record only ever removes reads, never adds one. The overrides are unchanged — **Include posts you already have** reads the feed in full whatever is remembered, and a run continuing after Apify moved it to another server always reads from where it left off. If the run cannot open or write that store — a scoped API token without key-value store permission — it behaves exactly as it did before. No price, input field, output column or charged event changed.

### 1.0.20 — 2026-09-19

- **A run Apify moves to another server now finishes the number of posts you asked for, even when the feed hands it the same page twice.** A moved run walks back over the posts it had already delivered so it can find its place again — it never collects or charges any of them a second time. But it was counting each SIGHTING of those posts rather than each post, and the "Top" grid is a ranking rather than a fixed list, so a page it was served twice made the run believe it had already delivered twice as many posts as it really had. It then stopped early, handed back fewer posts than your limit asked for, and — because the same count decided what to say about the shortfall — wrote no row explaining the gap. Each post is now counted once however often the feed offers it, so a moved run finishes the ask and any genuine shortfall is still named on a row of its own. A run that is never moved between servers behaves exactly as it always did. No price, input field, output column or charged event changed.

### 1.0.19 — 2026-09-18

- **A run Apify moves to another server part-way through now reports what the RUN collected, not what the second server did.** Apify occasionally moves a long run to a different machine and restarts it there. The money was always right — every post stays sold once and charged once — but the new machine's counters started at zero, so a run that had already delivered most of a term's posts before the move would hand you a row claiming the whole term went undelivered, a run summary reading zero delivered beside a real charge, and an upstream-read count covering only the second half of its own work. The run now reads what it already sold from its own output before it does anything else, so the numbers are the run's: the shortfall row names only what is still owed, the summary counts every post the run delivered, and a term the first machine had already finished quietly finishes. The walk also no longer gives up while reading back over its own delivered posts — it recognises them as its own, collects nothing twice, charges nothing twice, and carries on to where it had actually got to; where the note the first machine left survives, it resumes there directly and skips that re-reading altogether. A run that is never moved behaves exactly as it always did.
- **A hashtag is only reported as "not found" when Instagram's own record for it says so.** Instagram answers 404 for a hashtag that exists but has nothing on the grid being read, so a 404 there has never been proof that a tag is gone — but on two paths it was reaching you as `tag_not_found`, "the spelling is wrong, or nothing has ever been published under it": when the tag's own record could not be read on that run, and when the 404 arrived further down a chain that had already served posts for the term. Both now ship the honest uncharged `vendor_unavailable` row instead — the read did not come through, nothing was charged, and a re-run is worth it. A record that really says there is no such tag still books `tag_not_found` exactly as before, and a record that states a post count of zero still reads as the definitive "no posts" answer it has always been, whatever the grid answered. Nothing about what is collected or charged changes.
- **A "Recent" hashtag walk no longer buys one more page after Instagram has said there are none left.** That feed publishes an explicit "there is more" flag beside the token for the next page, and this actor was reading only the token — which the feed hands back even on its last page. So the walk paid for one extra read per term to be told nothing, and on a term whose chain had genuinely ended that read could come back as a refusal and end the walk on a worse-sounding row than the truth. The flag is now honoured: the page it says is the last one is the last one, its posts are delivered exactly as before, and a feed that does not send the flag at all is read exactly as it was. Nothing about what is collected or charged changes.

### 1.0.18 — 2026-09-18

- **The monthly allowance behind this actor's data reads has been raised — a busy month no longer stops delivery part-way through.** Nothing you are charged changes: the allowance is ours, not yours, and a run that ever meets it still stops honestly and charges nothing for what it did not deliver.

### 1.0.17 — 2026-09-18

- **When the run page's one-line summary is too long to fit, the sentences about what you were charged are now the last to go, not the first.** Apify cuts that line at 500 characters, so a long run drops clauses until it fits — and the order it dropped them in was wrong: "the posts you already had were skipped, not charged", "none of the undelivered rows was charged" and the note about a repeat check that could not run were being thrown away to make room for a settings note and a request to open a support issue. Worse, the line picked what to drop by counting positions rather than by naming sentences, so which one went depended on which others happened to be present. Measured across 720 shapes a real run can end in, 235 of them lost a sentence about money or lost the one sentence naming the field to fill in, while the line still read as a tidy 500 characters. Every rung now names what it gives up, and everything that costs you nothing to lose goes first — the census of undelivered rows folds to a pointer, then the settings note and the support ask, then the stop's own guidance, which the receipt row carries in full. Only under all of that does the already-had count shorten to its plainest form. It never disappears. Nothing about what is collected or charged changed.

### 1.0.16 — 2026-09-16

- **What a run costs, and what a second run costs, now sit at the top of this page instead of two screens down.** The per-post price and the tier table were only reachable after the output columns, the location section and the uncharged-row list — so a buyer landing here had to read about 6,000 characters before finding out what a post costs. The price is now the fourth line of the page: both ends of the tier ladder, the one charged event, and the fact that a post we could not deliver is never charged, with the full ladder still printed below. Beside it is the line that was missing altogether — how fast a hashtag feed actually moves, and the fact that a later run skips the posts your account already has instead of charging for them again. No input field, output column, charged event or price changed; only where the page says them.

### 1.0.15 — 2026-09-16

- **A search or a tag that answers with nothing is now asked a second time before the run gives up on it.** Instagram's own feed sometimes answers a first page with nothing in it — not because your hashtag or phrase is empty, but because that one read did not come through, which is most likely when several runs start at once. Until this release such a run ended on that first read and handed you a row asking you to try again; starting the run again in the next minute usually met the same answer. Each term's first empty answer is now asked once more, ranking the search itself the second time, and if the second answer carries posts they are simply delivered and you see nothing about any of this. Only when both reads come back holding nothing is the answer written down as an answer — the row then says plainly that the run asked twice and that an immediate re-run will come back the same, instead of asking you to pay for one. A second read that fails, or is refused, proves nothing and leaves the first answer exactly as it was, and a hashtag whose own record says how many posts it has is still judged by that record and not by a repeated read. Nothing about what is collected or charged changes, and there is no result fee for any of these reads.

### 1.0.14 — 2026-09-16

- **A run your own "Max run seconds" stopped no longer reads as the data feed being down.** The last release ended every read at your limit; what it did not do was say what such a read MEANT. A page cut off by your own limit looked exactly like the feed failing, so the row said the source was unavailable and asked you to re-run — and the re-run met the identical limit and gave the identical answer. Those rows now say plainly that the run clock ran out, name it as what stopped the walk, and are not offered as something a re-run fixes; the run also no longer waits for, or asks again for, a page it has no time left to use. A page the feed really did refuse still reads and behaves exactly as before, and nothing about what is collected or charged changes.

### 1.0.13 — 2026-09-15

- **"Max run seconds" now ends the run when it says it will, even when Instagram's data feed stops answering part-way through a read.** Every limit in this actor decided when the next page could be STARTED; nothing bounded how long one page was allowed to TAKE. A read that stalled was still given its own 25-second timer on top of the limit you typed, and the three attempts behind it could add another minute and a half — so a run capped at 30 seconds could still be going long after it, spending the time on an answer that never arrived. Each read is now given the smaller of its own timer and what is actually left of your limit, and a retry the remaining time cannot fit is never started. Your limit is also measured from when the RUN began rather than from the moment Apify last moved it to another server, so a run that gets moved part-way through now gets the rest of the window you bought instead of a fresh full one. Nothing about what is collected or charged moves: a stalled read is still uncharged and still worth a re-run, a run with time to spare still gets its full read timer on every page, and posts already in hand are still written out — a time limit ends the collecting, never the delivering.

### 1.0.12 — 2026-09-14

- **You can now collect everything posted at a PLACE, not only under a hashtag or a phrase.** A new **Locations** field takes an Instagram place as its name ("Eiffel Tower"), as its location ID (`103912118089363`), or as an `/explore/locations/` link — mix them with hashtags and keywords in one run and they share your result limit evenly. Every row carries the location ID it was collected under, and the price does not move: one delivered post is one `hashtag-post` charge exactly as before, with no new charged event and no start fee. A NAME is ambiguous — Instagram ranks two different "Eiffel Tower"s — so a run that resolves one adds an uncharged row naming the place it read, its ID, and the other places ranked under the same name, and an ID you give is never resolved and never ambiguous. A place Instagram has nothing under is an uncharged `tag_not_found` row proved against Instagram's own place record; a place whose grid comes back empty while the place itself exists is an uncharged `vendor_unavailable` row worth a re-run, because a place publishes no post count and this actor will not tell you a place is empty when it cannot know that. Fields named `location`, `place` or `places` are read as **Locations** the same way `tag` and `query` are already read.

### 1.0.11 — 2026-09-14

- **A run that can read your repeat memory but not write to it now says so, instead of letting your next run pay for everything twice.** The previous entry covered a token that cannot OPEN that memory. This is the other half: an API token can be allowed to read key-value stores and not to write them — or to write them and not to create one you do not have yet — and such a run opens your memory, recognises every post you already had correctly, and then remembers nothing it delivered. The run looks perfect and your NEXT run is charged for the whole delivery all over again. The run page now names it the moment the first write is refused, an uncharged row at the top of the dataset carries the cause and the fix in full, the receipt carries `repeatWrite: "denied_scope"`, and the run log keeps Apify's own message and adds the permission to grant: key-value store **Write** (and **Create**), or Actor runs set to **Full access**, under Settings → API & Integrations. A write that fails for an ordinary reason is still just retried bookkeeping and says nothing. Both run-page sentences are also shorter and now read the same on every actor in the portfolio, with the whole fix on the row. No price, no input field, no delivered column and no charged event changed.

- **A run that set a field this actor does not have now reads as one sentence on the run page.** The sentence naming the field you set and the field this actor reads carried a semicolon, which is the character the run page uses to separate one statement from the next — so anything reading that line saw two half-sentences instead of one. Same words, clearer line. Nothing about what is collected or charged changed.

- **A run that cannot open your account's repeat memory now says so instead of quietly charging you twice.** This actor promises you never pay for the same post twice: it remembers what it delivered to your account in a key-value store there, and skips those next time. A run started from the API with a **scoped token** in restricted-access mode cannot open that store unless the token is allowed to — so the check never ran, every post you already had was collected and charged again, and nothing on the run page, the rows or the receipt said a word about it. The run page now names the cause and the fix, an uncharged row at the top of the dataset says the same thing for anything reading rows rather than the page, the receipt carries `repeatCheck: "unavailable_scope"`, and the run log keeps Apify's own message and adds the permission to grant: key-value store **Read, Write and Create**, or Actor runs set to **Full access**, under Settings → API & Integrations. Every other reason the memory can be unreadable reads exactly as it did before. No price, no input field, no delivered column and no charged event changed.

### 1.0.10 — 2026-09-13

- **A target sent under a slightly different field name is now collected instead of ignored.** A run that put its tags in `hashtag`, `tag` or `tags` — or its search phrases in `keyword`, `query`, `queries`, `searchQuery` or `searchQueries` — used to collect nothing: Apify passes a field this actor does not declare straight through, so the run looked like a Start with nothing set and came back with one uncharged row naming a field you had never filled in. Those names are now read as the fields they plainly mean, a value sent under both names is collected once and charged once, and one uncharged row tells you which field to use next time so it stops appearing. A field name that matches nothing still gets an uncharged row, and that row now names the field you set as well as the field this actor reads. No price, no input field, no output column and no charged event changed.

### 1.0.9 — 2026-09-13

- **Your result limit now holds even when Apify moves the run to another server part-way through.** This actor already picks up exactly where it left off after such a move, and it works out how much of your limit is left from the posts it has already delivered — but it was counting only the posts that carried a fee. A post your account already has comes back delivered and uncharged, and so does every row on a run that cannot be billed at all, so those were invisible to the count: the run believed more of your limit was unspent than really was, and could hand you more posts than you asked for. It now counts every one already delivered to you, whether or not it carried a fee. Nothing about what you are charged moves — a repeat is still delivered without a result fee, and only delivered posts are charged.

### 1.0.8 — 2026-09-13

- **The page now shows you a row before you spend anything, and its first line is the listing's own name.** A new **What a row looks like** section carries one real delivered row from a live hashtag run — caption, engagement, owner, media links and the charge record — and the heading at the top of the page is now the store title instead of a different name for the same product. No price, no input, no output column and no charged event changed.

- **A read that will never work is no longer reported as one that might.** Two different answers used to arrive as `vendor_unavailable`, the row that says "this is temporary — please re-run": a term Instagram refuses to look up outright, and an answer that arrives in a shape this actor cannot read yet. Neither changes on a re-run, so that row was selling you a second run for exactly the same answer. Each now has its own uncharged row — `source_refused` and `unsupported_shape` — saying what is actually true, what to check, and that a re-run will not change it. A genuine outage still ships as `vendor_unavailable`, still asks for a re-run, and still carries no result fee. And an unreadable answer on the hashtag lookup itself is no longer reported as "Instagram has no such hashtag" — it was never proof of that. No price, no input and no delivered column changes.

- **A run may now wait longer for a slow source before it gives up.** How long a run can keep waiting is sized against what the posts it is still owed are worth, and this build lets that sizing draw on half of the run's own margin instead of a quarter — so a page that is answering slowly gets about twice as long to come back, and a delivery cut short on a slow page is rarer. Everything that bounds the waiting is unchanged: your run-time limit, your result limit and your cost cap still end it on the spot, no run ever waits less than it did before, and a small ask waits exactly as long as it always has. No input, no output column, no charged event and no price changes.

- **The input schema now speaks to an AI agent as well as to a person.** An agent that finds this actor through Apify's MCP server never opens this page — it reads the input schema. So the schema's own description now opens with the call that works, says when to use Keywords instead, what one post costs and what 30 posts come to, lists what is never charged, says how the result limit is split between terms, and names the run option that caps the bill. The hashtags field leads with the value shape and examples, then what is never charged, then where a profile or reel link belongs, with the console sample instruction last. No price, no charged event, no input field and no delivered column changes.

### 1.0.5 — 2026-09-12

- **The time a run waits out a quiet source is now sized to what you asked for, instead of being the same fourteen seconds for every run.** Since the last build a term that hit a quiet stretch was waited out twice — a short wait, then a longer one — and then given up on, whether you had asked for three posts on a two-minute run or five hundred on an hour-long one. A big ask on a long run now keeps waiting and reading the same page again, each wait longer than the last, for as long as the posts it is still owed are worth the run time it costs. A small ask waits exactly as long as it did before, so nothing gets slower or dearer; no run ever waits less than it used to; and no run waits past your own run-time limit, your result limit or your cost cap, all of which still end the waiting on the spot. Where the source comes back, the posts behind the pause are delivered and charged exactly as they always were. Where it does not, nothing changes: the posts in hand are yours, the rest carries no result fee, and the row still says a re-run is the fix.

### 1.0.4 — 2026-09-12

- **An answer this actor already paid for is never bought twice.** When the source replies in a shape this build cannot read, waiting and asking again cannot change one byte of it — the run did it anyway, buying the same dead page up to nine times and spending up to fourteen seconds of your run clock on it. From this build only the refusals that cost nothing and clear in seconds are waited out: a rate limit, a server error, a dropped connection, exactly as before. Nothing about what you are charged moves.

- **A search or a hashtag that came back blank is no longer reported as having no posts.** A first page holding nothing used to be read as Instagram's own final answer — "that is a definitive answer" — which told you to stop asking on exactly the run where asking again was what would have got you your posts. The two kinds of target are now judged on their own evidence. A keyword search never gets that verdict at all: this surface has no way of saying a phrase has no posts, so a blank search page is always marked temporary and asks you to re-run. A hashtag is judged against its own published post count: where the tag says it has none, the answer stands as the definitive one it is; where it says otherwise, or the check cannot be made, the row is marked temporary. A hashtag that does not exist is unchanged — that is still the spelling answer it always was. Either way there was no result fee.

All notable changes to this actor are documented here. Each entry matches one build on the Builds tab.

### 1.0.3 — 2026-09-12

- **A term no longer ends because Instagram could not be reached for a moment.** When the source went
  quiet part-way through a term, the run used to stop collecting for that term on the spot — about five
  seconds after the first sign of trouble — and report every post it still owed as not delivered. It now
  waits and reads the same page again: a short wait, then a longer one, spent once per term rather than
  on every page, and only while the run still has the clock for it and none of your own limits have been
  reached. Where the source comes back, the posts behind the pause are delivered exactly as they always
  were. Where it does not, nothing changes at all: the posts already collected are yours and charged as
  marked, the rest carries no result fee, and the row still says a re-run is the fix. A hashtag that does
  not exist, one with nothing to show, and a run that has reached a limit are answers, not pauses, and
  are never waited on.
- **A term stopped by one of this actor's own limits is now recorded as that, not as the hashtag
  running out.** Last change the row already said which bound ended a term's walk; the run's own
  summary still filed both of this actor's bounds under "no more posts to give", so the row and the
  summary said opposite things about the same term. They now agree: `no_posts` from here on means
  only that Instagram offered no next page — a definitive answer about the term — while this actor's
  40-page-per-term ceiling and its backstop after three pages of posts you already have are recorded
  separately, as stops rather than as anything missing. Nothing ran out, nothing failed, and none of
  it was charged. The wording on your rows, your inputs, your columns and your prices are unchanged,
  and nothing you have already collected needs re-running.
- **A link in your rows can never carry a network address, whatever form it arrives in.** Every link
  this actor delivers already went through our own URL hygiene before reaching your dataset, and that
  pass had a gap: where an upstream body writes its links with HTML-escaped separators (`&amp;` in
  place of `&`), the address parameter was not recognised by name and could survive into a row. It is
  now removed however the link is written — escaped, plain or legacy separators, upper or lower case,
  and inside a link that wraps another link. Nothing else in a link moves: the expiry, the signature
  and every routing parameter are delivered exactly as they were served, so the links keep working
  exactly as before. No column, no input, no price and no charge changes.
- A run that reaches the maximum cost per run you set no longer fetches one more page of posts it will not deliver. The stop is now asked before each fetch as well as before each row, so with several hashtags a term the run never got to is named without being reached for. Your rows, your charges and your prices are unchanged.
- **A term that came up short now names the limit that stopped it, and never denies a fee for posts it already delivered.** A walk cut by this actor's own page ceiling for one term, or by its backstop after three pages of posts you already have, used to read "the hashtag has no public posts to return right now — that is a definitive answer"; it now says the bound is this actor's own and what gets past it, and only Instagram offering no next page is called definitive. And when a term had delivered posts before its feed stopped answering, the row opened with "there was no result fee" over rows that were charged — it now opens with how many posts were already delivered, and promises no result fee only for the rest. Your rows, your charges and your prices are unchanged.
- **A limit set higher than this actor's own no longer refuses the run.** "Max posts" above 5,000, or a run clock outside 30 seconds to 1 hour, used to be rejected by the platform before the run started — no run, no rows, and nothing to tell you why, which is what an agent calling this actor with another scraper's numbers hit. From this build the run starts, continues at the nearest limit, and leaves one uncharged row naming what you asked for and what bound it. Your prices, your charges and every column are unchanged.

### 1.0.2 — 2026-09-11

A listing-copy build.

- The store page now carries the plain note that this actor is unofficial and not affiliated with, endorsed by, or sponsored by Instagram or Meta Platforms, Inc., the same line the rest of the Instagram actors carry.
- Internal only: our own record of what a licensed data feed costs us was corrected, and a request that feed rejects is no longer retried — nothing about your rows, your charges or your prices changes.

Nothing about the actor itself changed: the same inputs, the same columns, the same events and the same prices. There is no reason to re-run anything.

### 1.0.1 — 2026-09-11

First public release.

- Posts and reels published under any Instagram hashtag, by hashtag or by keyword search.
- **The result limit is exact.** Set 30 and the run delivers 30, or the last row says why it could not. With several hashtags the limit is split evenly between them.
- One charged event, `hashtag-post`, from $0.60 per 1,000 posts, platform usage included. No start fee, and no second charge for "details" — every column is in the row at that one price.
- Honest uncharged rows: a hashtag Instagram does not have, a hashtag with nothing left to give, a read that did not go through, and either of the two limits that can stop a run, each say so in their own words and none of them is billed.
- Repeat memory: posts already delivered to your account are skipped on a later run, not charged again, and their slot goes to the next new post — so a daily schedule on the same hashtags costs only what is new.
- Signed media links are delivered with the requesting network address stripped out, and with `videoUrlExpiresAt` so you know how long each link lasts.
- **`maxTotalChargeUsd` is a hard ceiling on delivery, not only on billing.** When a run reaches the maximum total charge you set, it stops collecting there and the last row says so, naming the posts it did not deliver and confirming they carried no result fee — rather than continuing to collect past the limit you set.
