# Changelog of Instagram Followers Scraper & Instagram Following Scraper (`steadyfetch/instagram-followers-scraper`) Actor

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

## Changelog

### 1.0.45 — 2026-10-05

- **A story highlight link no longer collects the wrong account's lists.** A highlight link (instagram.com/stories/highlights/…) carries a number, not the name of the account that posted it, but it was read as the account called "highlights", so the run collected and charged that account's followers. Now it gets one uncharged row saying to paste the handle or profile link of the account you meant. Every handle and profile link is read and charged exactly as before.
- Nothing changes about the price, which rows are charged, the input fields or the output columns.

### 1.0.44 — 2026-10-03

- **Asking again about a list you already hold now reads one page, not three.** When a run reads a list from the top and finds three pages in a row of rows you already have, it now remembers that. The next run of the same list, while the account's published count for it has not risen, stops after the first page if that page also holds nothing new, with the same uncharged row explaining why. If the first page has anything new, it is collected and charged as usual and the run reads on exactly as before. "Include rows you already have" and a pasted `resumeCursor` are not affected.
- **A run moved to another server part-way no longer looks up an account whose lists it had already finished.** The rows already delivered stay delivered and charged once, and the other accounts in the run are collected as usual.
- **If some updates to your account's memory of delivered rows fail to save, the run now says so.** It adds one uncharged row with the number of updates that did not save, because a row delivered in that run could be charged again if a later run meets it. A run where every update saved looks exactly as before.
- Nothing changes about the price, which rows are charged, the input fields or the output columns.

### 1.0.43 — 2026-10-01

- **The input description now says how big one run can be.** One run collects up to "Max results per account" (`resultsLimit`) rows from each account and each list, 100 by default and up to 50,000, within "Max run seconds" (`maxRunSeconds`), 600 by default and up to one hour. Rows the clock does not reach are not charged. By default, running the same input again carries on: rows your account already has from the last 90 days are skipped uncharged and do not count against the limit, so the run reaches further down each list. With "Include rows you already have" on they are delivered again, still uncharged, inside the limit, and the last row's `resumeCursor` starts each list exactly where a run stopped. That memory needs the run to be able to read your account's storage; a restricted API token can block it, and the status line then says so. It also says that on a paid plan, how far a run reads through the licensed data feed grows with the number of rows you ask for. The "Instagram usernames" field names the limit in its first sentence and the default re-run rule right after. Nothing about which rows are collected, delivered or charged changed, and the price, the input fields and the output columns are unchanged.

### 1.0.42 — 2026-09-29

- **A finished run now tells you how to keep a list up to date on a schedule.** When a run delivered rows and ended cleanly, its status line adds one sentence: save the input as a task on an Apify Schedule, daily or weekly, and rows your account already got in the last 90 days are not charged again, so each run pays only for new rows. It only appears when there is room on the line, never on a run a Schedule started, and never when the run could not open your account's memory. The input description an AI agent reads now says the same near its top. No price, charged event, input field or output column changed.

### 1.0.41 — 2026-09-27

- **Our internal run record now tells apart the two points where this actor stops reading a list on its own.** One is the most pages it reads for one list in a single run; the other is a stretch of pages holding only rows your account already has. They were recorded as one fact, so a list you already hold could look to us like a list the run never reached. What you see is unchanged: the same rows, the same uncharged row explaining the stop, the same resume point. Nothing changes about the price, which rows are charged, the input fields or the output columns.

### 1.0.40 — 2026-09-26

- **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.00 per 1,000 rows — and names what the same run costs on paid plans: $1.20 on Bronze, $0.75 on Silver and $0.50 from Gold. Runs on paid plans read exactly as before. Nothing changes about the price, which rows are charged, the input fields or the output columns.

### 1.0.39 — 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.38 — 2026-09-26

- **A deep follower or following list on a paid Apify plan is no longer cut short by this actor's own per-run data budget, and a run that does reach it now says so truthfully.** On paid plans that budget now grows with the number of rows you ask for, so a list of tens of thousands of rows is collected in one run. When a run does stop at it, the uncharged rows and the run page now say that a new run continues, skipping rows you already have, instead of saying the stop lasts until the start of next month. Nothing changes about the price, which rows are charged, the input fields or the output columns.

### 1.0.37 — 2026-09-25

- **The title now names the following list too:** "Instagram Followers Scraper & Instagram Following Scraper". This actor has always returned the accounts a profile follows as well as its followers, so a search for "instagram following" now finds the actor that returns it. Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.

### 1.0.36 — 2026-09-24

- **On the Apify free plan, this actor now has a daily allowance.** An account on the Apify free plan can make up to 80 Instagram reads on this actor in any 24 hours (one read looks up an account or reads one page of 25 rows — about 2,000 follower rows), and accounts on the free plan share one daily allowance on this actor. Past either, the run keeps every row it already delivered, adds one uncharged row saying when the allowance resets (UTC), and says the same on the run page. Paid Apify plans carry no daily allowance, and nothing changes for them: not the price, the input fields or the output columns.

### 1.0.35 — 2026-09-24

- **Settings sent wrapped in an extra "input" object are now read.** A run whose input arrives as `{"input": {…}}` — for example from `run_input={"input": {...}}` in the Python client — now reads those settings as if sent directly and adds one uncharged note row naming them. Before, the run stopped with a guidance row and collected nothing. Nothing changes about the price, the input fields or the output columns.

### 1.0.34 — 2026-09-24

- **"Instagram limits very large accounts" is now said only of a very large account.** A list that ends short of the account's own published count is called Instagram's limit only when the account has 100,000 followers or more; a smaller account whose list ends early gets the plain end-of-list row. Nothing changes about what is delivered or charged.
- **A list that ends before your ask now says so in the run log**, with how many rows it held and how many you asked for — whether the list was read on this run or answered from an earlier run's record.
- **The README's speed line is re-measured on the current build**: about 150 to 190 rows a minute on a typical list, up from the 55 to 105 measured before the account memory was written once per page.
- **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.33 — 2026-09-23

- **Runs on an account that already holds rows start again.** Build 1.0.32 stopped at the very start, before reading anything, on any account whose memory of delivered rows was already in use, and charged nothing. This build fixes that start-up step; what is delivered, what is charged and the price are unchanged from 1.0.32.

### 1.0.32 — 2026-09-23

- **A list you already hold is no longer reported as "no data".** When you ask again for a list this actor has already delivered to your account, and the account's own follower or following count has not grown, the run answers from its record without reading the list again. Until this build that answer counted every row you asked for as "no data", and the run page said Instagram publishes nothing on that list and a re-run would answer the same — said over a list of about 500 rows that was already in your account. Now the rows you already have are reported as rows you already have, skipped and not charged; only the rows past the real end of the list are counted as "no data"; and the run page tells you to turn on "Include rows you already have" to be sent them again. The same holds when a run reads a short list to its end and meets only rows you already hold: it now reports how long the list really is, instead of saying Instagram cut off a very large account. The "publishes nothing" sentence is kept for a list that really is empty. Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.
- **Long runs spend their time collecting again, and the run page says what a long run is doing.** This actor remembers every row it delivers to your account, so a later run never charges you for it twice. It was saving that record after every single row, and each save rewrites the whole record, so on an account that had already collected thousands of rows most of a long run's time went to saving rather than collecting — one seven-list run read about one page of 25 rows every 40 seconds and was stopped by its time limit with most of its ask unread. It now saves once per page, and always before the run's saved resume point moves past that page, so the protection against paying twice is unchanged. While a run works, the run page now says at least every 30 seconds how many rows are in, how many pages have been read, which step the run is on, and which step has taken most of its time. Nothing changes about which rows are charged, the price, the input fields or the output columns.

### 1.0.31 — 2026-09-23

- **The Usernames field is no longer marked required in the input schema, and its help now opens with what to pass and one example.** An AI agent or API call that leaves it out gets the built-in sample rows, exactly as a bare Start always did, and `_demo` stays the sample switch.
- **The top of this page now says how to schedule a rerun:** save your input as a Task and add it to an Apify Schedule; rows your account received in the last 90 days are skipped and not charged, as before. Nothing changes about what is delivered, which rows are charged, the price, the input fields, the output columns or the charged events.

### 1.0.30 — 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.29 — 2026-09-22

- **A run moved to another server part-way through is now tested end to end, and the count it reports and the place it continues from both survive the move.** Since 1.0.13 a moved run has counted the rows its earlier server already delivered and continued from the place that server reached, so a run can no longer report "0 delivered" beside rows it did deliver, or say rows were not delivered when they were. This build adds no new behaviour: it pins that promise with tests that replay the move on the real walk — the run's own record, the shortfall it names, and the page it resumes on. 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 itself has an outage, the run now says the account could not be read this time and asks you to try again, instead of reporting it as permanently unavailable.** Some answers from the source are a real verdict about the account you sent — a bad handle, a private account, an account that no longer exists — and those are final, so the run says so and moves on. Everything else is the source having a bad moment: rate limiting, a server error, a dropped line, or the source's own origin being unreachable. Until this build the run had a fixed list of the moments it recognised as temporary, and anything not on that list was reported as final. On 22 September the source's origin went down for about half an hour and answered with a code that was not on that list, so accounts it had served fifteen minutes earlier came back marked permanently unavailable. That is now the other way round: only the answers that are genuinely a verdict about your account are final, and anything else is treated as temporary, is not charged, and asks you to run it again. Final answers behave exactly as they did. Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.

- **A run's saved position in a list now survives many runs of this actor going at once on the same account.** This actor remembers how far down a list your account has already walked, so a later run asking for more can carry on from there instead of paying for pages you already hold. That note was saved by writing the whole record back at the end of a run — which is fine one run at a time, and wrong when a burst of runs starts together: every one of them read the record as it stood at its own start, so the last one to finish overwrote the rest and only its own list kept a position. Measured here with fifteen runs in flight: fifteen positions were saved and one survived. A run now re-reads the record just before saving and folds its own note into whatever is there, keeping the deeper position for any list two runs both walked, and keeping the fresher answer for a list known to have ended. All fifteen survive. A list you have just proved has moved on still forgets its old position, exactly as before. Nothing changes about what is delivered, which rows 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 started with field names this actor does not have now reads the first three of them on the run page and counts the rest — "and 37 more" — instead of listing every one, so most of those runs keep the names on the line instead of losing them.** Apify cuts a run's status line at 500 characters, and that list was the one part of the line whose length was the caller's rather than ours: forty unknown names composed a sentence of 1,027 characters on their own. Since 1.0.25 the line stayed inside the cut by giving that whole sentence up and pointing at the uncharged row instead — correct, but it meant a caller who sent a dozen names read none of them on the run page. The sentence now carries a ceiling of its own, so on an ordinary run the names ride the line together with what was delivered, what was charged and what to do next. A name longer than 40 characters is shown to its first 39 and marked, and where a sentence still cannot fit, the shorter one takes over exactly as it does today. The uncharged row in your dataset still names every field you sent, in full, as it always did. Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.

### 1.0.25 — 2026-09-20

- **When a run is started with field names this actor does not have, the run page now says so in a sentence of a fixed length instead of listing every one of them and running off the end of the page.** Apify cuts a run's status line at 500 characters and shows "..." for the rest. This actor's line names the fields a caller set that it does not recognise, so the length of that sentence was the CALLER'S rather than ours: a template or an agent that fills a whole generic form sends a dozen unknown names or more, and twelve of them already composed 576 characters — past the cut, with the part the platform threw away being the sentence that says what was and was not charged. Where the names fit, they are still all there, exactly as before. Where they do not, the line now says "This run set fields this actor does not have — it reads "Usernames" (`usernames`), and the uncharged row above names them.", and the uncharged row in the dataset carries every name in full, as it always did. Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.

### 1.0.24 — 2026-09-20

- **When the run clock stops a run part-way through your ask, the run page now does the arithmetic for you — how fast this run was actually collecting, how long the whole ask needs at that speed, and the exact number to put in "Max run seconds", or how many runs to split the ask across when it is bigger than an hour can hold.** Until now a run cut by the clock named the setting to raise and left the number to you, so the only way to find it was to run again and look — and because how much a list serves in ten minutes varies several-fold from one account to the next, running again teaches you almost nothing. The line now reads off this run's own measurement: rows delivered over the time the run actually took. The same numbers ride the last row of the dataset as plain fields — `askedRows`, `deliveredRows`, `elapsedMs`, `rowsPerSecond` and either `suggestedMaxRunSeconds` or `suggestedSplit` — so a script or an agent reads them without parsing a sentence, and where this run saved a point to continue from the line says so, because carrying on from there is cheaper than starting the ask over. The figure is a size and never a promise: the number to type carries headroom above what the run measured, it is never larger than the one hour this actor allows, and where raising the clock could not help — the whole ask is past that hour, or the platform's own run timeout landed first — it says how many runs instead. It appears only where the clock ended a run that was still delivering: never on an ask served in full, never on your own result limit or maximum cost per run, never on this actor's monthly collection allowance, and never on a run that delivered nothing, which measured no rate anybody could honestly extrapolate from. The store page now also says roughly how many rows a minute a typical list collects, so an ask can be sized before it is ever started. No price, input field, charged event or delivered-row column changed.

### 1.0.23 — 2026-09-20

- **An Instagram “Copy link” address of the `instagram.com/s/…` kind is now recognised as a share link, instead of being read as an account called “s”.** Instagram mints share links under two paths — `/share/…` and `/s/…`, the one it uses for stories and highlights — and only the first was recognised. The second was read as a one-character profile name, so a lookup was spent on an account that does not exist and the answer came back “not found” about a link that really did carry a profile behind its redirect. It now gets the same plain sentence the other share links get: open the link, then paste the handle or the profile URL it lands on. Nothing is read and nothing is charged for it. Real accounts are untouched, including one whose name simply starts with s, and `s` typed on its own is still read as the account you named. No price, input field, column or charged event changed.

### 1.0.22 — 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.21 — 2026-09-19

- **The whole "Maximum cost per run" you set is now spent on followers; before this build the run held back about a twelfth of it.** Your cap pays for followers 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 follower. So a cap worth ten followers 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.20 — 2026-09-19

- **An account whose follower count Instagram does not publish is no longer treated as an account with no followers.** Every row this actor collects starts with one lookup of the account itself, and the follower count that lookup returns is not just a column — it is the evidence behind two decisions. It is how a list that came back empty is told apart from a read that did not come through, and it is how a list already read to its end is checked for having grown since. Instagram does not always send that number: it can come back blank. A blank was being read as the number zero, which is the one value that makes both of those decisions fire the wrong way. A first page that arrived empty over an account whose count was blank was recorded as a list that genuinely has nothing in it — a definitive answer, the resume cursor closed, nothing further to ask for — when the honest reading is a read that failed and should be tried again. And an account with a remembered list end was answered straight from that record without buying a single page, because a count of zero can never be higher than the count the list was last read against, so a list that had in fact grown was reported as complete. A count Instagram did not publish is now treated as a count this actor does not know: an empty first page stays a read to retry with the resume point standing, a remembered list end is re-walked rather than re-served, and a cap on a large account is still never claimed without the number to prove it. An account that really does have nobody — Instagram sends a real zero for those, and they are common — behaves exactly as before, and a count sent as text still reads as the number it is. No price, input field, output column or charged event changed.

- **A run that continues a list from where an earlier run stopped no longer over-states how far down the list you have got.** When one of your own limits stops a run in the middle of a page — your result limit filled, your maximum cost per run reached, the run clock out of time — this actor writes down the page it stopped on and the rows it had already delivered from it, so a later run picks that page up again and skips exactly those rows rather than leaving a gap or handing them to you twice. It also remembers that point as the deepest this account has reached on the list, so a later, deeper request can skip straight there instead of paying to read everything above it again. That second record was keeping the page but not the rows, so when a later run jumped back to it and Instagram served that page again, the rows above the stopping point were added to the running depth a second time — they had already been counted when the point was written. A list of 100 rows stopped at 80 came back recorded as 105 rows deep. Nothing was collected twice and nothing was charged twice — the rows were correctly recognised as yours and skipped — but the depth is the number the uncharged summary row reports back to you, and it is the number this actor compares against the account's own follower count to decide whether Instagram cut your list short or the list simply ended. The rows already delivered from a stopping point are now carried with it, so they are skipped without being counted again and the depth is the list you actually hold. A run stopped cleanly at the end of a page was never affected and is unchanged, and a record written by an older release, or one this build cannot read, simply reads as no rows carried — which is exactly how this behaved before. No price, input field, output column or charged event changed.

### 1.0.19 — 2026-09-19

- **A run stopped by one of your own limits no longer reports the list as finished, so a later run goes back for the rest.** When this actor stops part-way through a list — your maximum cost per run reached, the run clock out of time, your result limit full — it says so and publishes a resume cursor to carry on from. But if that stop landed on the last page Instagram had served, the run read its own stopping as the LIST's ending: the uncharged row said Instagram had nothing more to give, no resume cursor was published, and, since the release before this one began remembering lists that have been read to the end, that claim was written into the record kept in your own Apify account. Every later run for that list then answered from the record without buying a page at all — telling you that you hold the whole list, while the rows your own limit had cut were never collected. On a 100-row list with a cost cap worth 80 rows, the last 20 were denied for as long as the re-asks kept that record fresh. A stop of this actor's own is now reported as exactly that, wherever it lands: the uncharged row names the limit that stopped the run and the setting that lifts it, the rows it cut are counted as not delivered and carry no result fee, the resume cursor is published, and nothing is remembered about the list having ended — so the next run, or that cursor pasted back in, collects those rows and charges for them exactly as it always has. Second, and for the same reason: when Apify moves a run onto another server part-way through, the server that took over was deciding "did Instagram cut this list short?" from the rows IT had collected rather than from everything the run had delivered, so a run that delivered a whole list across two servers could report it as limited by Instagram and remember it that way. That judgement now reads everything the run has delivered. No price, input field, output column or charged event changed.

### 1.0.18 — 2026-09-19

- **Ask again for a list this actor has already read to its end, and the run no longer pays to re-read pages you already own.** Instagram only shows part of a very large account's follower list — about a thousand rows — and a short list simply ends. Either way, once a run has read one of those lists 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 rows 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 list ended, alongside the rows it delivered, in the same key-value store in your own Apify account. On a later run for the same list it compares the follower count the account itself publishes with the count that list was last read against, and while that count has not risen — new followers arrive at the top of the list, so a count that has not moved is a top that has not moved — it answers from what it already knows: one uncharged row saying when the list was read to its end, how many rows that was, and both counts, with no page of the list bought at all. The moment the count does rise, the run goes and collects the new rows at the top and charges for them exactly as it always has, and then remembers the list again. Nothing about this can cost you more than before, and the two ways of overriding it are unchanged: **Include rows you already have** reads the whole list again whatever is remembered, and a `resumeCursor` you paste in always wins. 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.17 — 2026-09-19

- **Ask for more rows than you already have and the run now collects them, instead of stopping at the rows you own.** Re-running a handle you have collected before is cheap on purpose: this actor skips the rows already delivered to your account, and when three pages in a row hold nothing new it stops paying to read further, because on a newest-first list that is the honest answer to "what has changed since last time". But it was also the answer to a different question. If you had already taken, say, the first thousand rows of an account and then asked for four thousand, every page the run was allowed to buy was a page you already owned — so it read three of them, delivered nothing, and stopped, however deep you had asked. Each run now remembers, per account and per list, the deepest point it reached, in the same key-value store in your own Apify account that already holds what you have been delivered. When your request goes deeper than what you hold, the run reads at most three pages of the rows you own, then skips past the rest of them to that point and carries on — so the rows below are collected and charged exactly as new rows always are, the stretch you already have is never charged and is not read beyond those three pages, and the same three-page bound applies again underneath. Asking for the same depth as before, or less, is unchanged: that is still "what has changed", and it still ends after three pages with an uncharged row. Two smaller things ride along. If the remembered point has since scrolled out of Instagram's list, the run quietly forgets it and behaves exactly as this actor did before, rather than reporting a problem with the account. And the uncharged row you get when the run does stop now says which of the two happened — that it stopped at rows you already hold (and, where the run has a remembered point for that list, how many rows a later run must ask for to continue below them), or that it did continue below them and found nothing new there either. A list that ended at Instagram's own limit has nothing below it to continue to, so asking it again still ends after three pages, as before. Passing a `resumeCursor` back still wins over all of this: a position you gave is where the run starts. Nothing changes about the price, the input fields, the output columns or the charged events.

### 1.0.16 — 2026-09-19

- **A run Apify moves to another server now reports the right number of rows still owed, and stops paying to re-read a list page that has nothing new on it.** A moved run walks back over the rows it had already delivered to find its place again — never collecting or charging any of them twice. But it counted each SIGHTING of those rows rather than each row, so a list page it was served twice was counted twice: the uncharged row telling you how many rows were not delivered then reported half the true figure, and the run summary was left with rows that nothing accounted for. The same count also told the run that a page repeating rows it had already seen was making progress, so a feed stuck on one page kept buying reads instead of closing. Each row is now counted once however often the list offers it, so the shortfall row states what is really owed and a repeating page ends the walk on the usual honest row. Rows delivered and charges are unchanged, and 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.15 — 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.14 — 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 rows 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 sentence about Instagram's own limit and a request to open a support issue. Measured across 1,440 shapes a real run can end in, 749 of them lost a sentence about money or lost the one sentence telling you what to do next, while the line still read as a tidy 500 characters. Everything that costs you nothing to lose now gives way first — the census of undelivered rows folds to a pointer, then the Instagram-limit sentence (every capped row already says so), the cap note and the support ask, then the stop's own guidance, which the receipt row carries in full — and only under all of that do the money sentences shorten to their plainest form. They never disappear. Nothing about what is collected or charged changed.

### 1.0.13 — 2026-09-18

- **A run Apify moves to another server part-way through now reports the rows it already delivered, and no longer claims a shortfall that did not happen.** Apify sometimes moves a running job onto a different server and starts it again where it left off. Your rows and your charges were always safe through that — nothing already delivered was collected a second time, nothing was charged twice, and a continuation carried on from exactly the right place. What was wrong was what the run then SAID about itself. The server that took over counted from zero, so a chunk of 1,000 rows that had already had 873 delivered came back with an uncharged row reading "1,000 rows were not delivered" sitting directly above those 873 delivered rows, a run summary reading 0 delivered beside 873 charged, and a read count covering only the second half of the run. All three now read the whole run: the summary counts every row the run delivered, a shortfall row is written only for rows that really are still owed and is not written at all when none are, and the run's own read count covers both halves. If you saw that row on a run of yours, the rows above it were delivered and the row was wrong — the charge beside each delivered row was always right.
- **And a moved run now finishes the chunk you asked for, instead of stopping just short of it.** The server that took over restarted at the point your cursor named, met three pages of rows the run had already sold, read that as "nothing new here" and stopped — three pages back from where the run had actually got to. That also left the resume cursor pointing backwards, so the next run paid to read a stretch you already had. A run in that position now walks back over its own delivered rows, with no result fee on a single one of them, picks up at the point it had really reached, and publishes its resume cursor from there. A run that was never moved between servers does exactly what it did before, page for page and row for row. No price, no input field, no output column and no charged event changed.

### 1.0.12 — 2026-09-16

- **The row that says your own time limit stopped a page now fires in every case it should, not just the last seconds of a run.** The last release added that row — but it worked out which case it was by asking whether the run had already reached its own stopping point, and a page is cut off by your limit long before that. In the gap between the two the row still read as the data source being unavailable and still pointed you at a re-run that would stop in exactly the same place. The run now knows, at the read itself, whether its own limit is what ended it, and says so every time; it also no longer waits for, or asks again for, a page it has no time left to use. A page the source really did refuse still reads exactly as before, and nothing about what is collected or charged changes.

### 1.0.11 — 2026-09-15

- **"Max run seconds" now really ends the run — a read that stops answering can no longer carry it a minute past your limit.** Every time check in this actor asked whether there was room to START another read; nothing asked how long one was allowed to RUN. So a page request that hung had only its own 25-second timer to end it, was tried three times with waits in between, and a run could finish about a minute after the clock you set — measured here at 55.6 seconds past a limit that still had 20 seconds on it when the page was bought. Each read is now given the smaller of its own timeout and what your run actually has left, the retries after it are bought only while that time fits too, and the run keeps five seconds back to write out the rows it already has. When your clock is what ended a read, the row says so and points at "Max run seconds" instead of telling you the source was temporarily unavailable and asking you to run it again. **And a run the platform restarts part-way through now continues on the time you bought rather than starting your limit over**: a ten-minute run interrupted at minute seven has three minutes left, not another ten. Nothing about what is collected, what is charged, or what a run with time to spare does has changed — with the clock open, every read still gets its full timeout and delivers exactly as before.

### 1.0.10 — 2026-09-14

- **A run that brings back no rows now tells you what to do about it — and counts the mistake it never showed.** A run that delivered nothing used to name only the count and a reason code — "Delivered 0 rows of 1 asked for across 1 list. Not delivered: 1 not found" — and stop there, with no next step, because the run had ended cleanly and only a run stopped by a limit ever said anything further. Each reason now carries its own plain sentence: a handle Instagram has nothing at tells you to open it in a browser and says a re-run answers the same, a private account says this actor reads public accounts only, an account whose list Instagram publishes nothing for says so, a temporary read says to start the run again, and an answer this actor could not map says it is ours rather than yours and asks you to open an issue. **And a value this actor could not read is now counted on that line at all**: it was named on its own uncharged row but was missing from the run page's summary entirely, so a run whose handles were all unreadable said "Delivered 0 rows of 1 asked for across 1 list." and nothing else. It now names the count and tells you which field to paste handles into and what one looks like. Instagram's own limit on very large accounts keeps the sentence it already had rather than being explained twice, a run that did deliver rows is unchanged and says nothing extra, and nothing about what is collected or charged changed.

- **The sample row now sits at the top of the page, where you can see what you would be paying for.** A real delivered row was seven thousand characters down the page, below the price table, the honest-row list and the section on Instagram’s own cap — so the columns and a row of real output were the last things you reached rather than the first. The column table and the sample row now come straight after the opening block, and the opening block itself carries the price (**$0.50 per 1,000 rows** on Gold and above, $2.00 per 1,000 on the Apify free plan, the same for a followers row and a following row, no start fee) and how often the data changes: follower lists churn daily on an active account, and the rows you already have are skipped rather than charged again, so a schedule pays only for what changed. No price, no input field, no output column and no charged event changed.

### 1.0.9 — 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 row 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 the same row is never charged a second time: it remembers what it delivered to your account in a key-value store there, and skips those next time, which is what makes a schedule on the same handles cost only what changed. 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 row 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.8 — 2026-09-13

- **An account sent under a slightly different field name is now collected instead of ignored.** A run that put its accounts in `username`, `user`, `users`, `handle`, `handles`, `account`, `accounts`, `profile`, `profiles`, `profileUrl`, `profileUrls`, `url`, `urls` or `startUrls` used to collect nothing: Apify passes a field this actor does not declare straight through, so the run looked like a Start with no account set and came back with one uncharged row naming a field you had never filled in. Those names are now read as the field they plainly mean — Apify's own `{"url": …}` Start-URLs shape included — an account 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.7 — 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 rows it has already delivered — but it was counting only the rows that carried a fee. A row 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 rows 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 rows are charged.

### 1.0.6 — 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 run — the account, its flags, which list it came from 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 list Instagram refuses to answer for 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 running it again will not change it. The same goes for the account lookup itself: a reply this build cannot read is no longer reported as a passing outage. A genuine outage still ships as `vendor_unavailable`, still asks for a re-run, and still carries no result fee. 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 rows 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 last row of the run now tells you, in a field of its own, whether running again would get you more.** That row carries a `retryable` field, and until this build it always read `false` — even on a run that had stopped part-way through a list, was still holding the point to continue from, and had rows it had not collected. Worse, the same row opened with the word "Completed" whenever the reading itself had ended tidily, which is not the same thing as your ask being answered. From this build `retryable` is worked out from what the run actually did: rows it did not collect and nothing explains, a live `resumeCursor` on the row, and run time still left. A run that answered your ask in full still reads `Completed` and `retryable: false`, even though it saves a cursor — a cursor on its own has never meant you were owed anything, because almost every list is deeper than the rows you asked for. The row also now publishes `budgetLeftMs`, the run time that was still unspent when it stopped. Nothing about what you are charged 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 what one follower row costs and what 300 rows come to, lists what is never charged, says that the result limit is per account and per list, and names the run option that caps the bill. The handles field leads with the value shape and examples, then what is never charged, then where a hashtag or a single post 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 list 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 rows 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 rows 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 rows behind the pause are delivered and charged exactly as they always were. Where it does not, nothing changes: the rows in hand are yours, the rest carries no result fee, and the resume point still picks up where this run left off.

### 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 list that came back completely empty is no longer reported as Instagram's limit, or as an account with nobody on it.** A first page holding nobody used to be read as a final answer either way — "Instagram limits how much of a very large account's list it will show ... no tool can go past it", or "that is a definitive answer" — even for an account that publishes a follower count of its own. Both told you to stop asking on exactly the run where asking again was what would have got you your rows. Now the account's own published count decides: where it says zero, the answer stands as the definitive one it is; where it says otherwise, or says nothing at all, the row is marked temporary, asks you to re-run, and the place to carry on from is kept. A list that really does serve rows and then stop short of the account's stated total is unchanged — that is still Instagram's cap and still says so. 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 build about what an unfinished list tells you, plus an internal cost fix.

- **A list no longer ends because Instagram could not be reached for a moment.** When the source went
  quiet part-way through a list, the run used to stop collecting it on the spot — about five seconds
  after the first sign of trouble — and report every row 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 list 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 rows behind the pause are delivered exactly as they always were. Where
  it does not, nothing changes at all: the rows already collected are yours and charged as marked, the
  rest carries no result fee, the resume cursor still points at where to continue, and the row still says
  a re-run is the fix. An account that does not exist, a private one, an empty list and a run that has
  reached a limit are answers, not pauses, and are never waited on.

- **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 has reached your maximum cost per run now stops before fetching another page of followers it will not deliver to you. Your rows, your charges and your prices are unchanged — the run stops in the same place and bills exactly the same amount; the read it no longer makes was one we were paying for.

- **A limit set higher than this actor's own no longer refuses the run.** "Max results per account" above 50,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 a caller arriving 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; where Instagram's own limit on very large accounts is what stops a list, that still has its own row. Your prices, your charges and every column are unchanged.

- **A list that stops part way through no longer says "there was no result fee" over rows you were already charged for.** When a feed stops answering — or an account is renamed, removed or turned private — after part of its list has been delivered, the uncharged row now opens with how many rows were delivered and that each of those rows says what it was charged; the no-fee sentence covers only what was never collected. Nothing about your prices or your charges changes: the same rows were billed before and after, the row was describing them wrongly.

- **The two places this actor stops reading on its own now leave a row instead of nothing.** It stops after three pages in a row that held nothing but rows you already have, and it reads at most 2,200 pages for any one list. Either bound used to end the list with no row at all for the rows it did not collect, so a run could deliver fewer rows than you asked for with nothing to say why. Now the shortfall gets one uncharged row that names the bound as this actor's own rather than Instagram's, says the list still has more in it, and points at the lever — "Include rows you already have", or the resume cursor the run already publishes so a second run continues exactly where this one stopped.

- Internal only: our own record of how a run ended now names those two bounds as a deliberate stop of ours, instead of leaving the rows they did not collect unexplained in our telemetry. Nothing in your dataset, your columns, your prices or your charges changes.

### 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.

- The followers list and the following list of any public Instagram account, by handle or profile URL. Pick `followers`, `following` or `both`; every row carries the handle, full name, user id, profile URL, profile picture, the private and verified flags, the account badges, and the position it held in the list.
- **The result limit is exact, per account and per list.** Ask for 500 and the run delivers 500, or a row of its own says why it could not. Three handles in `both` mode at 100 each asks for 600 rows, and all 600 are either delivered or named.
- One charged event, `follower`, from $0.50 per 1,000 rows, platform usage included. It covers both directions at the same price, there is no start fee, and there is no second charge for "details" — every column is in the row at that one price.
- **Instagram's cap on very large accounts is reported, not hidden.** Measured on 2026-09-11, an account with 104,378,996 followers served 49 rows and stopped; an account with 19 followers served all 19. When the cap lands you get a `capped_by_instagram` row naming how many rows were reached against how many the account reports having, and the rows past the cap are not charged.
- Honest uncharged rows for every other way a row can go missing too: an account Instagram does not have, a private account (answered from the account lookup, before a list read is spent), a list that ran out, a read that did not go through, this actor's own monthly collection allowance, a limit or clock on the run, and an input value that could not be used — each says so in its own words and none of them is billed.
- **A run that stops short hands back a resume point.** A follower list is longer than any one run's limit, so the last row of a run that left a list unfinished carries a `resumeCursor`. Pass it back in **Continue a stopped run**, with the same usernames, and the next run starts each list exactly where the previous one stopped: no page is read twice, no row already delivered to you is delivered or charged again, and nothing in between is skipped. A run whose lists all ran out carries no cursor, so a finished walk never invites a re-walk.
- Repeat memory: rows already delivered to your account are skipped on a later run, not charged again, and their slot goes to the next new follower — so a daily or weekly schedule on the same handles costs only what changed. Turn it off with **Include rows you already have**, or carry it across accounts with **Skip rows in this dataset**.
- Pressing Start with nothing filled in returns three built-in sample rows so the column shape is visible before anything is collected: nothing is read from Instagram on that run, and there is no result fee for it.
- Profile picture links are delivered with the requesting network address stripped out.
