- The input form's description now quotes the post price in force since 26 October. Its opening line still gave the old price for paid plans; it now matches the README and the price table, and no price changed.
- The input form now says what each row status means and what to do next. A handle Threads does not show, a read that did not go through, Threads' 15-post ceiling and each limit you can set are named with the setting to change.
- The run page now says what to check when Threads does not show a handle. It asks you to check that each handle is spelled right and public, instead of repeating the row's status words.
- Prices, charged rows, input fields and output columns are unchanged.
- Clearer run log messages about whether a run can collect. Rows, prices and charges are unchanged.
- A post from a handle's listing now costs $2.50 per 1,000 on Bronze, $2.20 on Silver and $2.00 from Gold. This is the change announced on 12 October, in effect since 26 October 2026 at 19:59 UTC. The Apify free plan stays at $3.00 per 1,000 posts, and a profile row or a post looked up by link keeps its price ($12.00 per 1,000 on the Apify free plan down to $5.00 from Gold). The README, the price table, the input description and the line a run on the Apify free plan writes on its own run page now quote it.
- Internal: the monthly collection limit is now read from a shared setting at run start. Results, rows and prices are unchanged.
- Which rows are charged, the input fields and the output columns are unchanged, and a row we could not deliver is still never charged.
- Rebuilt on the patched Node runtime (no code change).
- An API endpoint request now always gets every row it paid for. A request can be charged for up to three kinds of row — posts, pasted post links and profile rows. When one of those charges cannot be completed after another has gone through, the request now still sends every row whose charge went through, and still charges and sends the other kinds; only the rows whose own charge did not go through are held back. Each held row is named in the answer's notes: it was not sent and was not charged, and asking again for it gets it.
- Nothing changes about runs, prices or charges.
- The actor now also answers as an API endpoint. Send Threads handles or post links in one web request and get the same rows a run returns, in the same response — no run to start, no dataset to read. The address is on the new Endpoints tab. A request takes the same fields as a run and is charged per row returned at the same prices as a run; a request that returns nothing is not charged. Each request reads fresh: unlike a run, it does not skip posts your account already has. Running the actor as before is unchanged — no input field, output column, charged event or price moved.
- 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.
- A Threads tag or topic link is no longer charged as an account. A Threads tag or topic link is refused with an uncharged row instead of being read as an account named "tag" and charged. Nothing changes about the price or the output columns.
- Clearer wording when a run stops at its limit. Rows a run could not finish carried an internal word as their status, and the run page counted them under that same word. Those rows now carry the status "limit_reached" and say "This run reached its limit", with how to fetch the rest in a new run, and the run page counts them as "past this run's limit". Rows held when a run had to pause part-way carried another internal word; they now carry the same status "limit_reached", say "This run reached its limit" and that the remaining items can be fetched in a new run shortly, and the run page counts them as "held for this run". Nothing changes about the price, which rows are charged, the input fields or the other output columns.
- A date this run cannot read now stops the run for $0, instead of running without your date window. Until now, when "Only posts published after" or "Only posts published before" held a date the actor could not read, the run went ahead as if no window were set, so it delivered and charged for posts your date was meant to leave out. A date with no year in it was also misread rather than refused: "Oct 1" was read as October 1, 2001, and a bare number like "7" as a date in 2001. Now such a date stops the run before reading anything, nothing is collected or charged, and an uncharged row for the date and for each handle or post link you named says what to type instead. The same goes for a day that does not exist, like 2026-02-31 (it used to be moved into March), and for a year this actor does not read, like 1/1/2005 or 2150-01-01.
- Every date with a year in it that the last build read is still read the same way. A scheduled run that sends 10/05/2026, "Oct 5, 2026", 2026/10/05, an RFC-2822 date or a JavaScript date string keeps working exactly as before. A date like 10/05/2026 that could also mean 10 May is read month first, as before, and the run adds one uncharged note naming the day it read and the other one, so you can write the year first (2026-10-05) to remove the doubt.
- More of the dates people type are now read. Besides 2026-09-01, an ISO timestamp and windows like "7 days", the two date fields now read "1st October 2026", "25/12/2025", "31.12.2026", "today", "yesterday", "last 30 days", "1 year", "a week ago" and a Unix timestamp. A run whose dates were readable delivers and charges exactly as before. No price, charged event, input field or output column changed.
- A handle or link this run could not use no longer costs you a post. When a line in your list could not be read — not a Threads handle or link — the run told you so on an uncharged row, but that line also took one slot of your "Max rows to return" before any real handle or link got its share, so a run limited to 15 delivered 14 from a profile that had 15, and a list with as many unusable lines as the limit delivered nothing. Such a line now takes no slot: every post your limit allows is delivered from the handles and links the run can read, and the uncharged row naming the line still appears, worded exactly as before. No price, charged event, input field or output column changed.
- A date window this run could not read, or a search term on this actor, no longer costs you a post. When "newerThan" or "olderThan" could not be read, or a search field was set, the run still went ahead as if it were not set and said so on an uncharged row — but that row also took one slot of your "Max rows to return", so a run limited to 15 delivered 14. It now takes no slot: every post your limit allows is delivered, and the row that tells you about the setting still appears, uncharged. A link the run could not use still gets its own uncharged row exactly as before. No price, charged event, input field or output column changed.
- A large run on a paid plan now reads through to the end of its ask, and a run that does stop on this actor's own data limit now says what is true. A paid-plan run that asked for many handles at once, with profile rows switched on, could stop part-way on a fixed per-run data limit of this actor's own, and its rows said the actor had reached a monthly limit that resets at the start of the month. That was wrong: the limit was for that one run. That limit now grows with the size of a paid ask, so such a run reads everything it was asked for. When a run does stop on it, its rows and status line now say to re-run the handles and post links on the uncharged rows for the rest, and that posts you already have are not charged again. Rows that were not delivered are still not charged. No price, charged event, input field or output column changed.
- 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.
Every entry here is one build on the Builds tab, 1:1, newest first.
- The
handles 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 of those profiles, exactly as before. No price, input field, output column or charged event changed.
- 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.
- 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.
- The run log no longer prints internal cost lines, and the row a run writes when this actor's own monthly collection allowance runs out no longer quotes a dollar figure. That row still says why the run stopped and that the allowance resets at the start of each month. No price, input field, output column or charged event changed.
- The whole "Maximum cost per run" you set is now spent on posts. Before this build the run held back a small share of your cost cap — up to a tenth — as a cushion for platform running costs which, on this actor's per-result pricing, your cap does not pay for. That cushion protected nothing and simply left the last few posts unbought: a capped run delivered and charged a little less than you had authorised. The plan now uses the full cap. Prices, charged events, input fields and output columns are unchanged.
- The store page now opens with what this actor is and what a run costs, instead of with the list of columns a row carries. The price sat 614 bytes down the page — below what the store shows above the fold — so someone comparing Threads scrapers had to scroll past a full paragraph of column names before learning that a post row is $1.00 per 1,000 on the Business plan, $3.00 per 1,000 on the Apify free plan, that there is no start fee, and that a handle Threads will not show, a profile that has published nothing and a dead post link each cost $0. Those facts are now the first line, the charging guarantee is the sentence directly under them, and the column list follows. The input form's own description leads with the same two facts, which is what an AI agent shopping through Apify's MCP server reads before it picks an actor. Nothing about what is collected, how it is collected, or what is charged changes.
- The sentence saying what you were NOT charged for now survives the run page's 500-character limit. On every run whose API token could not open your account memory — the shape where the line is longest — the "not charged: N past Threads' own ceiling, N outside your date window, N you already had" sentence was dropped outright, 60 of the 120 endings a run can have, once with 84 characters still going spare, while the 90-character "open an issue" line stayed. Now that sentence shortens to its total with a pointer to the receipt row, which breaks each kind down, and the support ask is what gives way first.
- The listing now says how often a profile’s posts change and what a second run costs. The first screen priced a run and said what is never charged, but not whether running it again next week was worth anything: a post already delivered to your account is skipped, with no row and no charge, so a daily schedule bills for the handles that actually posted rather than for the same rows every morning. That is now on the first screen, with the 90-day memory window named. The "Include profile" switch now names its charge and its never-charged cases after a worked example, and the one-list "Start URLs" door gained an example pair — both inside the space an AI agent’s tool screen reads. Nothing about what is collected or charged changes.
- A run your own "Max run seconds" stopped no longer reads as the 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 a failed read, so the row was offered as temporary and asked you to re-run — into the identical limit, for the identical answer. All three doors — a handle's feed, a pasted post link and the profile row — now say plainly that the run stopped at its own time limit and name it as the setting to raise, and the run no longer waits for, or asks again for, a read it has no time left to use. A read that really was refused still reads and behaves exactly as before, and nothing about what is collected or charged changes.
- A run now stops when your "Max run seconds" says so. This actor reads a handle's posts in a single request, and that request was allowed thirty seconds of its own, counted from the moment it started — never from your limit. A request that hung got its full thirty seconds, the run could ask again up to three times with waits in between, and your run clock was not consulted once: a run could sit well past the limit you typed with nothing new arriving. From this build every request is given only the time your run actually has left, minus what it needs to hand you the rows and write the summary, and it is cut off exactly there; a retry is only bought when the clock can still fit it. Nothing about what you pay moves — the posts already collected are delivered and charged exactly as before, and the ones the clock did not reach still arrive as their own uncharged rows saying your own time limit ended the run.
- And a run moved to another server part-way through now keeps the time it has LEFT of that limit. Apify sometimes moves a run mid-flight; the run picks up where it left off. It was starting your "Max run seconds" again from the moment the new server booted, so a 900-second limit could become 900 seconds more. It now measures from when your run began.
- Nothing else moved: no input field, output column, charged event or price changed.
- A re-started run no longer collects past the number of rows you asked for. Apify sometimes moves a run to another server part-way through; the run picks up where it left off. To know how many rows it had already handed you, it was counting the ones it had CHARGED for — and a row can be delivered without being charged: a post your account already had and asked to see again, or any row once your maximum cost per run is reached. So it read its own tally low and kept collecting past your "Max rows to return" limit. It now counts the rows you actually have. If your run is never moved, nothing changes: this only ever showed up after a restart.
- A run started with a scoped API token now tells you why it charged for posts you already had. The memory that skips what your account already has is a key-value store in your own Apify account, and a token limited under "Restrict what Actors can access" cannot open one — so the check was silently off and every repeat was collected and charged again, while the run looked ordinary. That run now says so on its status line, on one uncharged note row at the top of the dataset, on every row it charged, and in the run summary, and it names the fix: give the token key-value store Read, Write and Create, or set Actor runs to Full access. A run started from the console, or with a full-access token, is unchanged.
- And a token that can READ that memory but not WRITE it now says so too. That one looked like a perfect run: every post you already had was still recognised and skipped, but nothing this run delivered could be written down — so your NEXT run collected and charged all of it again, with nothing anywhere to say why. The run now names it the moment the first write is refused, on the run page, on its own uncharged row in the dataset, in the run summary and in the run log, and tells you the permission to add: key-value store Write (and Create) under Settings → API & Integrations, or Actor runs set to Full access. A run with a full token is unaffected.
- Nothing else moved: no input field, output column, charged event or price changed.
- An answer this actor has already paid for is never bought a second time. When a read was answered in a shape this actor cannot read, the run treated it as a passing hiccup: it asked again, and again, up to nine times for one handle — spending part of its own data budget and up to fourteen seconds of your run clock on a reply that was identical every single time. From this build a reply that cost something is asked for once. Only the refusals that cost nothing — a rate limit, a server error, a dropped connection — are still waited out and retried, exactly as before, so a handle lost to a passing rate limit is still rescued. Nothing about what you are charged moves: only delivered posts are charged, everything collected before a refusal is still delivered, and a handle that did not arrive still carries no result fee.
- A post link you have already collected is no longer charged a second time. The run has always
remembered every post it delivered to your account, and handles already skipped the ones you had.
Pasted post links did not: the same link in tomorrow's run was collected again, delivered again
and charged again, even though nothing about it was new. From this build a pasted link is checked
against your account's memory and against the dataset you name in Skip posts in this dataset,
exactly as a handle is — no row, no result fee. Switch Include posts you already have on and
they come back as before, marked
repeat: true and still uncharged. Re-running the same list of
post links now costs only what is genuinely new.
- A brief "slow down" while reading a handle no longer costs you a whole handle. Threads answers a
handle's posts in a single request, so when that request came back with a rate
limit, a server error or an unreadable reply, every post you asked for from that handle was lost
within a few seconds. From this build the run waits the refusal out, up to twice per handle, and
asks again; a feed that clears delivers the handle's posts as normal, and only a feed that still
refuses afterwards gives up on it. The waiting is bounded by your run clock — no wait is taken
that the remaining time cannot fit — and it stops immediately at your maximum cost per run. A
verdict about the handle itself (no such account, private, no posts) is never waited on, because
waiting cannot change it.
- A handle that really could not be read is unchanged: the row still says so, and nothing that
did not arrive carries a result fee.
First public build.
- A link in your rows never carries a network address. Every media, thumbnail and profile link
this actor delivers goes through our own URL hygiene first, and the address parameter a signed link
can carry is removed however the link is written — HTML-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.
- A Threads handle —
natgeo, @natgeo or a profile link — returns that profile's posts as rows:
post ID, short code, permalink, handle, the stable numeric author ID, the post text, the date it
was published, the media type, the like, reply, repost, quote and reshare counts, the links and
mentions inside the text, an image link, a video link and the moment that link expires.
- Threads shows the last 15 posts of a profile to a logged-out reader and there is no way to page
further. Ask for more and the run delivers 15 and adds one uncharged row per handle saying the
cap is Threads', not ours. Ask for fewer and you get exactly that many.
- A pasted post link is a first-class input.
threads.com/@handle/post/<code> and the
threads.net form both return that one post, with its view count and its top replies — about
twenty, as Threads serves them — inside the same row, with repliesTotal saying how many exist.
includeProfile adds one profile row per handle: follower count, full name, biography, bio links,
verification and the profile picture.
- An account that hides its counts gets
countsHidden: true and explicit nulls, never a 0 that
would read as a real measurement.
- Date filters compare against the post's own publication time, and posts they drop are never
charged.
- No number you type can refuse your run. The accepted ranges — 1 to 2,000 rows, 1 to 1,000 posts
per handle, 30 to 3,600 seconds — are applied inside the actor, not as a validation gate on the
input form. A value outside a range is moved to the nearest one in it and one uncharged row says
what was read and what was used, instead of the run being rejected before it starts.
- Your limit is the limit:
resultsLimit caps each handle and maxItems caps the run, both exact.
limit, count, max_posts and maxPosts are read as the right one of the two, the smallest
wins, and one uncharged note row says which name was read.