# Changelog of Instagram Post Data Scraper — Instagram Reel Data by URL (`steadyfetch/instagram-post-details-scraper`) Actor

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

## Changelog

### 1.0.31 — 2026-10-17

- **A Meta Graph API media id is now named as one, instead of being looked up as a post.** A bare number of 17 or 18 digits that starts with 17 or 18 is the shape of an id from Meta's Graph API, not of an Instagram post id, and looking one up comes back "not found". It is now answered at once with one uncharged row that says what it looks like and asks for the post's own link (instagram.com/p/CODE/ or /reel/CODE/) — if it really is an Instagram post, its link works too. Several of them pasted on one line each get their own row.
- **A number that is not found no longer tells you to open a link in a browser.** When a post asked for by its numeric id is not found, the row and the run page now say the number was not found as an Instagram post id, that ids from other systems (Meta Graph API, ad accounts) look similar, and that the post's own link is the sure way. Rows for links read exactly as before. Nothing changes about the price, which rows are charged, the input fields or the output columns.

### 1.0.30 — 2026-10-16

- **The run summary uses plain names.** The run's summary — the last dataset row and the OUTPUT record — now names the count of reads the run made `reads`. The counts themselves, which rows are delivered, the input fields and every other column are unchanged.

### 1.0.29 — 2026-10-11

- **Clearer wording when a run stops at its limit.** Rows for the links a run could not finish now carry the status "limit_reached" and say "This run reached its limit", with how to fetch the remaining posts in a new run, and the run page counts them as "past this run's limit" instead of printing an internal word. Nothing changes about the price, which rows are charged, the input fields or the other output columns.

### 1.0.28 — 2026-10-08

- **The monthly data allowance behind this actor was raised, so 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.
- **Posts reach your dataset in batches instead of one by one, so a long list spends its time reading posts, not writing them — more of a big list fits the run.** The rows, their order and what you are charged are unchanged, and nothing is charged until its rows are in your dataset.

### 1.0.27 — 2026-10-03

- **A stop now says which limit really ended the run.** When the actor's own limit for a small ask stopped it from reading another post, the run page said "stopped at your maximum cost per run" and the rows for the posts after it said "your maximum cost per run was reached", though your maximum still had room; they now say it was the actor's own limit and that asking for more posts in one run reads further. And a run whose maximum cost per run left no room for even one post now says it stopped before any charge. Nothing changes about the price, which posts are charged, the input fields or the output columns.

### 1.0.26 — 2026-09-27

- **A bare word or a handle in the link list is no longer looked up as a post.** A value with no capital letter and no hyphen — `natgeo`, `@natgeo` or a short number — could be an account's handle, and is now answered with one uncharged row that points to the Instagram Profile Posts Scraper for an account's posts, or to pasting the post's link or numeric media id for one post. Words separated by spaces, like `hello there`, are now kept together as one value and answered with one uncharged row, instead of being split and looked up word by word. Post links, real post codes, numeric media ids and lists of codes separated by commas, new lines or spaces are read exactly as before. Nothing changes about the price, which rows are charged, the input fields or the output columns.

### 1.0.25 — 2026-09-26

- **A post link with its address shortened now works.** A link pasted without its https:// (`instagram.com/reel/<code>/`, as chat apps often leave it), a path with no instagram.com in front of it (`/reel/<code>/`), an `instagr.am` short link, and a bare code copied with a trailing slash are now each read as the post they name: the same post, the same price, delivered and charged once. Until now each came back as an uncharged row, and the link without https:// was wrongly described as longer than any post code. Nothing changes about the price, which rows are charged, the input fields or the output columns.

### 1.0.24 — 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 — $6.00 per 1,000 posts — and names what the same run costs on paid plans: $4.50 on Bronze, $3.60 on Silver and $3.00 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.23 — 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.22 — 2026-09-26

- **A long list of post links 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 links you ask for, so a list of several hundred posts is read in one run. When a run does stop at it, each uncharged row now says that a new run with those links collects them, 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.

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

### 1.0.21 — 2026-09-24

- **A daily allowance on the Apify free plan.** On the free plan this actor now makes at most 30 post lookups 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 looked up 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.20 — 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 read nothing. Nothing changes about the price, the input fields or the output columns.
- **The run page no longer calls a link field "outside this actor's limits" when your links arrived under another field name.** That line is now said only of a cap that really ran at this actor's limit; the uncharged note row naming the field to use is unchanged.

### 1.0.19 — 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.18 — 2026-09-23

- **The Instagram post links 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 often a post's numbers change and how to schedule a rerun:** save your input as a Task and add it to an Apify Schedule; each rerun is a fresh read of every link, charged like any run, 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.17 — 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.16 — 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.15 — 2026-09-22

- **When the data source itself has an outage, the run now says the post 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 post you sent — a bad address, a private account, a post 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 posts 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 post 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.

- **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.14 — 2026-09-20

- **An Instagram address that names no post is now refused before anything is read for it, instead of being turned into somebody else's post.** Instagram writes `instagram.com/reels/audio/<id>/` for its AUDIO page — the reels made with one sound — and `audio` sits in that address exactly where a post's own code sits. The run read it as the code, and because Instagram codes are just numbers written in letters, `audio` is a valid one: it points at a real post belonging to a stranger. So a link naming a SOUND could have come back as a stranger's post, read and charged, under the link you pasted. The same slip let Instagram's own reserved words — `accounts`, `direct`, `s`, `lite` and the rest — stand where an account name goes, so `instagram.com/accounts/reel/<code>/` was read as a real link. Both now get the plain "only links to a single post or reel are read here" row, with nothing read and no result fee. Real links are untouched: `/p/`, `/reel/`, `/reels/`, `/tv/`, the `instagram.com/<account>/reel/<code>/` form, an account genuinely called `audio`, and a code or media ID typed on its own all read exactly as before. No price, input field, column or charged event changed.

### 1.0.13 — 2026-09-19

- **Links passed under `directUrls` — the field name the most-used Instagram scraper on the store uses for its links — are now read as "Instagram post links".** A run that named its posts only that way used to come back saying it did not recognise the field, with nothing read and nothing charged; now the links are read as usual, as a single value or as a list, and the run gets one uncharged note row saying which field they arrived in and which field to use next time. `directUrl` is read the same way. A profile link sent under it is still refused by name — with the sibling actor that collects an account's posts, and the field you actually set — because this actor reads one named post or reel. `postUrls` is still the declared field and nothing about it changed. No price, no charged event, no input field and no output column changes.

### 1.0.12 — 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.11 — 2026-09-19

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

- **The run page's one-line summary no longer runs past the length Apify will show, and the sentence saying nothing undelivered was charged is now the last to go rather than the first.** Apify cuts that line at 500 characters, so a long run drops clauses until it fits — but this actor's line had only three steps and no floor under them, so on a run carrying every cause at once it came out at up to 543 characters and the page cut it off mid-sentence. The order was wrong too: the sentence accounting for what you were NOT charged for, and the one telling you what to do next, were thrown away first while a settings note and a request to open a support issue survived. Measured across 480 shapes a real run can end in, 12 were past the cut and 46 lost one of those two sentences. Everything that costs you nothing to lose now gives way 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 — and only under all of that does the money sentence shorten to its plainest form. It never disappears, and no shape is past the cut. Nothing about what is collected or charged changed.

### 1.0.8 — 2026-09-16

- **A link your own time limit stopped the run from reading no longer reads as the data source being down.** The last release ended every read at your limit; what it did not do was say what such a read MEANT. A read cut off by your own limit looked exactly like the source failing, so the row was offered as temporary and asked you to re-run — into the identical limit, for the identical answer. Those links now get the same row a link stopped by your result limit or your cost cap gets: it says the run clock ran out, nothing is charged, and it is not offered as something a re-run fixes. The run also no longer asks again for a read it has no time left to use. A read the source really did refuse still reads and behaves exactly as before.

### 1.0.7 — 2026-09-15

- **"Max run seconds" now ends the run when you set it to, even when the source stalls.** Until this build your limit only decided whether the run could START another read: a read already under way kept its own 25-second timer, so a source that went quiet near the end of your window carried the run past the limit you typed, and the time the run keeps back to write out your rows and the summary was spent inside that read. Every read is now given the smaller of its own timeout and what your window actually has left, so a stalled source ends the run where your limit says it should and the posts already in hand are still written out. Two things come with it: a link the clock has no time left to read is now turned away before it is read, on the same uncharged "stopped at a limit" row it already got, instead of costing a read that could not finish; and if the platform moves your run to another machine part-way through, the run now finishes inside the window you bought rather than starting your clock again from zero. No price, no charged event, no input field and no output column changes.

- **When a run brings back no posts, the run page now tells you what to do about it.** A run that delivered nothing used to name only the count and the reason code — "Delivered 0 posts of 1 asked for. 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 link Instagram has nothing at tells you to open it in a browser and says a re-run returns the same answer, a private account says this actor reads public posts only, 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 that is not a link to one post — a profile link being the common one — is now counted on that line at all**: it was named on its own row but was missing from the run page's summary entirely, so a run whose only link was a profile address said "Delivered 0 posts of 1 asked for." and nothing else. It now names the count and tells you which field to paste post links into and what one looks like. A run that did deliver posts is unchanged and says nothing extra. No price, no charged event, no input field and no output column changes.

### 1.0.6 — 2026-09-13

- **If you send your post links under a different field name, this actor now reads them instead of telling you it found nothing.** `postUrl`, `url`, `urls`, `link`, `links`, `postLink`, `reelUrl`, `reelUrls`, `shortcode`, `shortcodes`, `startUrl` and `startUrls` are all read as "Instagram post links", as a single value or as a list, and a Start-URLs entry written as `{"url": "…"}` is opened out to the link inside it. The same link sent in both fields is still one read and one charge. The run gets one uncharged note row saying which field the links arrived in and which field to use next time, your run log says the same, and if a link cannot be read the row now names the field you actually set as well as the one this actor reads. `postUrls` is still the declared field and nothing about it changed. No price, no charged event, no input field and no output column changes.

### 1.0.5 — 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 play count, the audio track, the video renditions and the charge record included — 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.

- **Input schema: the description now opens with the call that works, what one post costs, the never-charged list and the cap — the screen an AI agent reads; `postUrls` leads with the value shape and says a link that produces no post is never charged.** Nothing else moved: no price, no charged event, no input field, no output column changed.

- **A code or media id longer than Instagram ever issues is now refused before anything is read.** A post code is at most 16 characters and a media id at most 19 digits; a value past either of those used to be sent to the data feed anyway, which refused it and came back as `source_refused`. The answer was honest but it cost a read to get. From this build the value is turned away on the spot, on an uncharged row that names both limits so you can see which half of the value is wrong. Nothing a real post can be named by changes: every link, code and media id that worked before works exactly as before. 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.

### 1.0.4 — 2026-09-12

- **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 code or media id Instagram refuses outright (it is not one it issues), 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. No price, no input and no delivered column changes.

- **The time a run waits out a quiet source is now sized to what you asked for, instead of being the same five and a half seconds for every run.** A link that hit a quiet stretch was retried twice — a short wait, then a longer one — and then reported as unreadable, 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 asking again, each wait longer than the last, for as long as the posts it is still owed are worth the run time it costs, and that extra patience is a pool the whole run shares so one difficult link cannot use it all. 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. A post that still cannot be read carries no result fee and the row still says a re-run is the fix.

### 1.0.3 — 2026-09-12

- **A post you name twice in one run is read once and charged once — whichever way you name it.** Instagram addresses the same post two ways: by its link (`/p/CODE/`, `/reel/CODE/`) and by its numeric media id. A list that used both for the same post used to read it twice on the licensed data feed and charge you twice, delivering two rows for one post; only a repeat of the *same* form was folded. From this build both forms are recognised as the one post before anything is read: it is delivered once, charged once, and the repeat ships as an uncharged `duplicate` row naming it — exactly what the honest-rows section of this actor's README has always promised. Uncharged rows now also carry both the short code and the media id, so a long list reconciles on either column. No price, no input and no column changes.

### 1.0.2 — 2026-09-12

- **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 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 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. Your prices, your charges and every column are unchanged.

### 1.0.0 — 2026-09-11

First public release.

- Everything one Instagram post or reel exposes, from its link: play count, saves, reshares, likes, comments, video length and codec, all three video renditions, the audio track, co-authors, tagged accounts, location, carousel slides, owner and the caption — one row, one price.
- **Every link shape Instagram serves is read**: `/p/`, `/reel/`, `/reels/`, `/tv/`, the handle-prefixed `/username/reel/`, the bare code and the numeric media id. Tracking parameters are ignored, and a block of links pasted into one box is opened out into one entry per link.
- One charged event, `post-details`, from $3.00 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 post Instagram no longer has, a private or restricted account, a value that is not a link to one post, 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. A link that names a post already read in the same run is delivered once and the repeat is named rather than dropped.
- **`countsHiddenByOwner`** says when an owner has hidden their like and view counts, so a small `likeCount` beside a large `playCount` is explained rather than misread.
- **`maxTotalChargeUsd` is a hard ceiling on reading, not only on billing.** When a run reaches the maximum total charge you set, it stops before reading the next link and the rows that follow name every link it did not reach, confirming they carried no result fee.
- Signed media links are delivered with the requesting network address stripped out, and with `videoUrlExpiresAt` so you know how long each link lasts.
- No repeat memory, deliberately: engagement on a live post moves by the minute, so every run re-reads every link rather than handing back a stale number.
