Every entry here is one build on the Builds tab, 1:1, newest first. Prune convention one.
- A run whose token can read your account memory but not write to it now says so, instead of quietly charging you for the same posts again next time. The memory that stops you paying twice lives in a key-value store in your own Apify account, and a scoped API token can be allowed to READ it and not to write it — or to write it but not to create it the first time. Such a run looked perfect: it skipped the posts you already had, delivered the new ones, and recorded none of them, so your very next run collected and charged for that whole delivery all over again. The run now says it on the run page, in the run log and on one uncharged row in the dataset, each naming exactly what to grant (key-value store Write, and Create, under Settings → API & Integrations) or the alternative of setting Actor runs to Full access. Runs on a full-access token are unchanged. No price, input field, output column or charged event changed.
- A re-started run no longer collects past the number of posts 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 posts it had already handed you, it was counting the ones it had CHARGED for — and a post can be delivered without being charged, on a run that bills nothing or once your maximum cost per run is reached. So it read its own tally low and kept collecting past your "Max posts" 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 limited API token now tells you the repeat check could not run — before it charges you for posts you already had. The memory that stops you paying twice for the same post lives in a key-value store in your own Apify account. A run started through the API with a token whose permissions are restricted cannot open it, so every post you already had was collected and charged again, and nothing said so. That run now says it on the run page, in the run log, and on one uncharged row at the top of the dataset — each naming the permission to grant (key-value store Read, Write and Create, under Settings → API & Integrations) or the alternative, running it from the console. Runs from the console, and API runs on a full-access token, are unchanged: the check runs as before and a post you already have is still never charged twice. No price, input field, output column or charged event changed.
- Nothing else moved: no input field, output column, charged event or price changed.
- A brief hiccup on the page-details call, or on the one call that tells a private group from a quiet one, no longer costs you that answer. The run already waited out a short refusal while collecting posts. Two other reads did not: the page's own details row, and the single lookup that decides whether an empty group is private or simply has no posts. A rate limit or a dropped connection on either one — the kind that clears in a few seconds — used to end that part of the run outright, so the details row went missing with a note asking you to run it again, and a private group was reported as a temporary problem rather than as private. Each of those reads now waits once, briefly, and asks again before giving up. The waiting is bounded and never runs past your "Max run seconds", an answer the data source already gave is never bought twice, and nothing about charging moves: only delivered posts and delivered page-details rows are charged, and anything that still does not arrive carries no result fee, as before.
- An answer this actor has already paid for is never bought a second time. An earlier build taught the run to wait out a brief refusal from the data feed and ask for the same page again. That is right for a rate limit, a server error or a dropped connection — those cost nothing and clear in seconds. It was wrong when the feed had already answered and billed us in a shape this actor cannot read: waiting and asking again cannot change that answer, so the run spent part of its own upstream allowance and up to fourteen seconds of your run clock buying the same dead page three times. From this build only the refusals that cost nothing are waited out. Nothing about what you are charged moves — only delivered posts are charged, the posts collected before a refusal are still delivered, and everything that did not arrive still carries no result fee — but a page or group whose feed has changed shape now finishes sooner instead of spending your run clock on it.
- An aborted or migrated run now leaves its receipt from the first moment of the run, instead of only after start-up finishes. The record that says how a run ended was only put in place once the run had finished starting up — reading your input, opening its stores, working out what a previous attempt had already delivered and charged. A run stopped inside that window ended with nothing written about it at all, which mattered most on a re-started run, where the thing not written about was the earlier attempt's work. It is now in place from the run's first moment. Nothing else moved: no input field, output column, event or price changed, and a run that reaches its work behaves exactly as before.
- A numeric page or group id is no longer answered with something this actor cannot know. An id
that belongs to no page comes back looking exactly like a real page that has simply posted nothing,
so a wrong id used to be reported as "the page is there and has posted nothing" — a confident
answer about a page that may not exist — and a group id that belongs to no group was reported as a
temporary problem you should re-run, which would never have worked. From this build a page reached
by id says plainly that this run cannot tell the two apart and names the one move that settles it
(paste the page's name or link instead), and a group id that is not a group is reported as exactly
that: not found, permanent, no reason to run it again. Neither carries a result fee, as before.
- "Include page details" now works when the target is a numeric page id. It used to come back as
a details row that could not be read on this run — every run, for ever, for that shape of input.
The details row is now collected for an id target the same way it is for a name or a link.
- A brief "slow down" from the data feed no longer costs you the rest of the page. When the feed
behind this actor answered with a rate limit, a server error or an unreadable reply part-way
through a walk, the run used to give up on that page or group within a few seconds: you kept the
posts collected up to that point and the rest of your result limit came back as a walk the feed had
cut short. From this build the run waits the refusal out, up to twice per input, and asks for the
same page again; a feed that clears carries on to your result limit, and only a feed that still
refuses afterwards ends the walk. 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 page itself (not found, private, no posts) is never waited on, because waiting cannot
change it.
- A walk that really was cut short is unchanged: the posts already collected are still delivered,
the row still says so, and nothing that did not arrive carries a result fee.
- 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 walk that runs short says WHOSE bound ended it — in the row AND in the run's own record.
Two of the four endings are bounds of this actor's own, and they are no longer written as a
verdict about your page or group.
page_no_posts / group_no_posts now mean what they say: the
feed ran out, or your date window closed the walk. Our own bounds get their own labels —
skipped.page_cap when the feed was still serving and the run had read the 40 feed pages it
buys for one input, and skipped.all_repeat when two feed pages running added no new post —
so a run that came back short tells you whether to change your input or split it across more runs.
- Any row that follows posts this run already delivered opens with them and says what each was
charged, so the "no result fee" statement only ever covers what did not arrive.
First public build.
- A Facebook page — name, slug or link — returns its posts as rows: post ID, permalink, page name
and id, author, text, post date, post type, one image, and a playable MP4 link with its expiry on
video posts.
- A public group is a first-class input, in the same columns.
facebook.com/groups/<slug>, a
/groups/<id> link or a bare numeric group id all return that group's posts, newest first, with
source: "group".
- Every row carries the reaction total AND the ten-way breakdown (like, love, care, haha, wow, sad,
anger, thankful, pride, confused), the comment count, and the 0–2 top comments the feed serves.
shareCount and videoViewCount are an explicit null on every row — they are not in what a
logged-out reader of these feeds is shown, and this actor does not promise a column it cannot fill.
includePageDetails adds one Page details row per PAGE target — followers, likes, talking-about,
category, intro, website, links, email, phone, address, opening hours, price range, services,
rating percent and count, ad status, verification and Instagram handle. resultsLimit: 0 with the
flag on is a details-only run.
newerThan / olderThan compare against the post's own publish date, on BOTH doors, with no
filter fee. A window this actor cannot read leaves one uncharged row and the run collects as if it
were unset, rather than returning nothing.
- Your limit is the limit:
maxItems is read under limit, count and maxPosts as well, the
smallest wins, and one uncharged note row says which name was read. resultsLimit is exact and per
page or group.
- Newest first is the only order. There is no "top posts" sort and no input that asks for one:
a sort sent under any name comes back as one uncharged note row rather than a silent substitution.
- A reel, watch, share or fb.watch link, a single post permalink and an Ad Library link each come
back as an uncharged row naming the actor that takes it, instead of failing the run.
- A cost cap or a time limit ends the collecting, never the delivering: posts already in hand ship in
full and the last row of the run says what stopped it and what is left.
- Re-runs skip the posts your account already had, so a daily watch on the same pages pays only for
what is new. Turn on Include posts you already have to get them back anyway, marked
repeat: true and still uncharged.
- Never charged: a page or group that does not exist, one Facebook shows to logged-in users or
members only, one with no posts, a read that failed on our side, posts your own limits dropped,
posts this account already had, and starting a run.