- You can now call this actor as an API endpoint. Send up to five LinkedIn profile or company page links to
https://steadyfetch--linkedin-posts-scraper.apify.actor (GET or POST, the same fields as a run) and get their latest posts back as JSON in the same response — no run to start, no dataset to read. The Endpoints tab lets you try it from the browser, and the README's "Use it as an API endpoint" section lists the settings.
- A request is charged exactly as a run with the same input: per post returned, and nothing for a page with no public posts, a link LinkedIn does not show, or a request whose charge does not go through (its posts are then not sent). The endpoint keeps no account memory, so it returns every post it reads; to skip posts you already have, run the actor as usual.
- Runs are unchanged: the same input, rows, prices and charges.
- 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 page whose posts all fall outside your dates now says so, instead of saying the page has no public posts. Before, when "Posted after" and "Posted before" left nothing on a page, its row read "LinkedIn shows no public posts on it — that is a definitive answer", which pointed you away from a page that does post. That row now has its own status,
skipped_date_window, and says the page has public posts, none of them inside your dates, and that widening "Posted after" or "Posted before" collects them. When some posts were delivered first, the row says the rest fall outside your dates. The status line counts these posts as "outside the dates you set".
- A page held after such a check names the dates too. A page left alone because a recent run found nothing inside your dates now says it had no post inside your dates and that widening them reads it now, instead of pointing at "Include posts you already have".
- The "definitive" sentence stays for a page that truly shows no public posts at all. Nothing is charged for any of these rows, as before, and every delivered post is charged exactly as before.
- A run now stops early when the links it reads keep coming back empty. If the first 20 links a run reads all come back with no post inside your "Posted after" and "Posted before" dates (or with no public posts at all), the run stops there instead of reading the rest of your list. Every link it did not read gets an uncharged
limit_reached row, and the status line says how many links it read, that none had a post inside your dates, and what reaches the rest: wider dates, or a new run with the links it did not read. On a long list where most links come back empty and the rest bring only a few posts each, the run can also stop before the end, with the usual "This run reached its limit" row on each link it did not read.
- Nothing changes for a run where most links bring back posts, for a run that finds posts you already have, or for a run on the Apify free plan, and every delivered post is charged exactly as before. No price, charged event, input field or output column changed.
- "Max posts per page" now gives you exactly the number you set, down to 1. Before, a number under 25 was raised to 25, and a note said asking for fewer would not make the run cheaper. That was wrong, because every post is charged. Set 2 and each profile or company page now delivers, and charges, at most its latest 2 posts. A run that leaves the field empty still takes the usual 50.
- A small number per page now reaches far more of your list. The same maximum cost per run covers many more profiles at 2 posts each than it did at 25. When your maximum cost per run stops a run early, the status line says how many of your links it reached and that raising the maximum or lowering "Max posts per page" reaches the rest, and it names the number per page it used.
- A run that reaches its own limit now says so plainly. That stop has one row status, "limit_reached", in place of the two older words for it, and the row and the status line read "This run reached its limit", with how many posts were delivered and how to fetch the remaining posts in a new run.
- Coming next: from 2026-10-26 a small per-profile check step will be charged once for each profile or company page a run reads, announced separately before it takes effect. No price, charged event or input field changed in this build.
- A scheduled run with "Posted after" set to
today or yesterday no longer re-reads a page that just had nothing new for you. Before, those two were never held, so an hourly schedule read every page every hour. Now a quiet page is left alone for half of what is left of the UTC day with today, and for 12 to 24 hours with yesterday, so a post published after the check is still inside your dates when the next read comes, and your first run after midnight UTC reads every page again. Each page left alone gets the same uncharged skipped.all_repeat row saying when it was checked and when a run reads it again. A page that gave you a new post is read again on your next run, as before.
- "Skip posts in this dataset" now reads every page once, then holds quiet pages like any other run. The first run pointed at a dataset reads every page, as before; your next runs pointed at the same dataset leave a quiet page alone the same way, and pointing at a different dataset reads every page again. No price, charged event, input field or output column changed.
- With "Include posts you already have" on, a scheduled run no longer re-reads a page that just had nothing new for you. When a run with it on reads a profile or company page and finds nothing new for your account (no post inside your dates, or only posts you already have, which it hands back marked as repeats and does not charge), your runs with it on leave that page alone for the next 24 hours under the same "Posted after" and "Posted before" (half the span when "Posted after" is a span shorter than two days, and never when it is
today or yesterday). Each page left alone gets one uncharged skipped.all_repeat row saying when it was checked with the setting on, when a run reads it again, and that the posts you already have are in your earlier runs' datasets. A page that gave you a new post is read again on your next run, as before.
- Each setting keeps its own 24 hours. A page left alone by a run with "Include posts you already have" off is read by your first run with it on, exactly as that row says, and a page left alone by a run with it on does not hold back a run with it off. Pointing "Skip posts in this dataset" at a dataset or changing the dates still reads every page. No price, charged event, input field or output column changed.
- Profile and company links are now read in the forms people copy them in. A page path without the site name (
in/<id> or company/<name>/posts/), a link wrapped in angle brackets, quotes, brackets or a markdown link, a link with a comma or semicolon left on the end, the { "url": … } shape, and the sign-in page LinkedIn shows a logged-out browser in place of a profile or company page are each read as the page they point at. Before, each of these got an uncharged row asking for the link again, and a link with a comma on the end was looked up with the comma in it which finds no page.
- An old
/pub/ link, a Sales Navigator link, a link to one post and a bare name in "LinkedIn page URLs" still get the same uncharged row as before, because none of them says for certain which page is meant. A page read from one of the new forms collects and charges exactly what its plain link does. No price, charged event, input field or output column changed.
- A scheduled run no longer re-reads a page that just had nothing new for you. When a run reads a profile or company page and finds nothing new for your account (no post inside your dates, or only posts you already have), your runs leave that page alone for the next 24 hours under the same "Posted after" and "Posted before" (half the span when "Posted after" is a span shorter than two days, and never when it is
today or yesterday). Each page left alone gets one uncharged skipped.all_repeat row saying when it was checked and when a run reads it again, and nothing is charged for it. A page that gave you a new post is read again on your next run, as before, and turning on "Include posts you already have", pointing "Skip posts in this dataset" at a dataset, or changing the dates reads every page as before.
- A daily schedule is the shape that fits. A company page posts daily to weekly and a person weekly to monthly, so a run a day catches what is new; a run every hour now reads each quiet page about once a day. No price, charged event, input field or output column changed.
- "Posted after" and "Posted before" now read the dates people actually type. Besides
2026-08-01 and the five windows in the picker, a run now reads a span counted back from when it starts — 7 days, 2 weeks, last 30 days, 1 month ago, past year, Last 3 months, or the short forms 7d, 2w, 3m, 1y, 48h — plus today and yesterday (both from midnight UTC), dates like 2026/10/01, 01.10.2026 (day first), 1 Oct 2026 or October 1, 2026, and a Unix timestamp in seconds or milliseconds. Quotes around the value and capital letters no longer matter. A month counts as 30 days and a year as 365, the same as the picker's windows. Every value that worked before still means exactly what it meant.
- A date with slashes or dashes that could be either day or month first is never guessed.
01/10/2026 could be 1 October or January 10, so the run stops before collecting anything, charges nothing, and names both days so you can write it year first. One that reads only one way, like 25/12/2025, is read.
- A date the run still cannot read now gets one uncharged row for each page you named, saying what was typed and which page was not collected, instead of one row for the whole list. As before, nothing is collected or charged on that run.
- No price, charged event, input field or output column changed.
- A re-run no longer reads deep into a feed for posts you already have. LinkedIn lists a page's posts newest first, so once a run reaches posts your account already had, everything after them is older than what you hold. The run used to keep reading further pages to fill the number you asked for, which on a weekly re-run meant reading far into a feed of posts you already had. Now it finishes the page it is on, reads no older page for that link, and closes the rest of your number on one uncharged
skipped.all_repeat row that says how to collect older posts: turn on "Include posts you already have" (those come back marked as repeats, not charged), or set "Posted before". A run pointed at a dataset with "Skip posts in this dataset", the way to pick up where a stopped run left off, still reads past the posts it holds, as before. The description of "Include posts you already have" and the README now say this. Which rows are charged does not change: every delivered post is one charge, and a post you already had is never charged.
- A profile link that carries LinkedIn's member id now works as a profile link. A link like
linkedin.com/in/ACoAA…, which LinkedIn writes into links copied from a feed or a search, is now looked up by that id instead of being read as a profile name.
- On the Apify free plan, a run that stops at its own data budget now says on its status line that a paid Apify plan serves the whole order in one run, and quotes this run's price beside the cheapest paid plan's when the line has room. The line on a paid plan is unchanged.
- No price, charged event or output column changed.
- On the Apify free plan, this actor now has a daily allowance. One account on the free plan reads at most 25 pages of posts in any 24 hours (a page is up to 50 posts from one link, so that covers a run of 25 links at the default 50 posts each), and all accounts on the Apify free plan together share a daily allowance on this actor, which resets at midnight UTC. When a run reaches either, it stops cleanly: the posts it already delivered stay delivered, nothing past that point is read or charged, and every link it did not reach gets an uncharged
skipped.free_plan_allowance row that says which allowance it reached, the time it resets in UTC, and that paid Apify plans carry no daily allowance. The run page says the same. Runs on a paid Apify plan are unchanged: no daily allowance, same rows, same charges. No price, charged event, input field or output column changed.
- A run over many links no longer spends its data budget on pages it does not need. Once a link had given you the posts you asked for from it, the run still went on reading that link's older pages: on a run with more than one link, each link was read to the end of its feed while only its first page or two were used. Those reads came out of the run's own data budget, so a long list stopped early — 500 links at 25 posts each could stop after about 70 of them. The run now moves to the next link as soon as a link has given you its share, so the same budget reaches far more of your list, and on a paid plan an order that size is served whole in one run. Which rows are charged does not change: every delivered post is one charge, and a post that was not delivered is not charged.
- A setting the run adjusted is now a note, not an error. When you ask for more posts per page than one run collects, or use another scraper's name for a field, the run says what it used on an uncharged note row. That note used to arrive as an
input_error row and was counted as if one of your links had been refused, even when every link was served. It is now a note row, and nothing about what was delivered or charged changes.
- On the Apify free plan, a run that stops at its own data budget now says what serves the whole order in one run: a paid Apify plan, where the per-run budget grows with the size of your order. No price, charged event, input field or output column changed.
- The title now names both kinds of page this actor reads: "LinkedIn Profile Posts Scraper & LinkedIn Company Posts Scraper". It has always returned the posts of a company page as well as a person's profile, so a search for "linkedin company posts" now finds the actor that returns them. Nothing changes about what is delivered, which rows are charged, the price, the input fields or the output columns.
- More of the LinkedIn links people paste now work. A school page (
/school/<name>/) is read like a company page, a mobile link (/mwlite/in/<name>) is read as the profile it points to, and a lnkd.in short link is followed to the page it leads to. Tracking parameters and a country subdomain never mattered and still do not.
- A link that still cannot be read says exactly why, and what to paste instead. An old
/pub/ profile link, a Sales Navigator link, and a short link that could not be followed each get their own uncharged row, and every such row now lists the link shapes that do work. The run's status line counts the links you named and says how many could not be read, instead of reading as if you had named none.
- Nothing changes about the price, which rows are charged, the input fields or the output columns.
- A run billed on the Apify free plan now says so on its own status line. When a run is charged at the Apify free plan price, its status line now names that price per 1,000 posts and what the same run costs on Bronze, Silver and Gold, so you can see the difference without leaving the run. A run on a paid plan reads exactly as before. 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.
- A run that stops at its own data budget for the run now tells you to re-run, and a large order is no longer cut short by it. Before this build, every such stop said the run had reached a monthly limit that resets at the start of the month — which was wrong when only this run's own budget had been reached, since a new run can continue straight away. The row and the status line now say which it was: this run's budget (start a new run on the links it did not finish with "Skip posts in this dataset" set to this run's dataset, and nothing already delivered is charged again), or the data feed behind this actor paused until the start of next month. On a paid plan the per-run budget now grows with the size of your order, so a large order is served whole in one run. Nothing changes about the price, which rows are charged, the input fields or the output columns.
- 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 about a twelfth of your cost cap before it started — a cushion for platform running costs which, on this actor's pricing, your cap does not pay for at all: your cap buys posts, and the running costs are ours. So the cushion was simply cap you had asked to spend and could not get. On a run capped at $0.10 with posts at $0.001 the plan asked for 92 posts where the cap paid for 100 — 8 posts of your own cap, unspendable by construction. From this build the plan uses your cap in full, so a run asks for as many posts as your cap can really pay for. Nothing you are charged changes, and nothing can be charged above the cap you typed: every charge is still checked against what is left of your cap at the moment it is posted, and a run that reaches your cap still stops cleanly, ships what it had already fetched, and tells you what is left. No price, input field or output column changes.
- A page the feed answered without saying what the answer was is now asked again, instead of being taken as final. Every page this actor reads carries a status inside the answer itself, and that status is the one that decides: on this feed a target that does not exist, an empty page and a full page all arrive as HTTP 200, so the answer's own word is the only thing worth reading. When that word was present but blank, the run read the blank as a code in its own right — a code it did not recognise — and a code it does not recognise means a deliberate, settled refusal: it stopped on the first read and did not ask again, because asking again after a refusal only buys a second bill. But a blank is not a refusal. It is an answer we could not read, exactly like any other shape this build does not know, and those have always been treated as a passing outage and asked again. They now are here too: where the feed states it did not bill us for that read, the run asks again before giving up, so posts that were sitting behind one unreadable answer are collected instead of missed. If the re-reads fail as well you get the same row you got before, worded the same, and it is still not charged. A page the feed really did refuse, with a status it really sent, still stops on the first read exactly as it did. No price, input field, output column or charged event changed.
- "N posts you already had were skipped, not charged" no longer disappears to make room for the support ask. The run page cuts the status line at 500 characters, and on 12 of the 120 endings a run can have the line dropped that sentence outright — at 405 characters, with 95 still going spare — while the 90-character "open an issue" line stayed. The ladder now shortens the sentence before dropping it, and the support ask is what gives way first; the line also can no longer run past the cut on any shape.
- The per-post price and the re-run rule now sit in the opening list instead of five screens down. A buyer landing on this page had to read past the publish-time section, the output columns and the hidden-counts section — about 5,000 characters — before the price appeared. It is now the fourth bullet: the Gold and entry-plan rates, the fact that one charged unit is one post delivered, and the reminder that the media, the article preview, the reaction breakdown and the author are columns on that one row rather than a second charge. Beside it is the cadence line that was not on the first screen at all — how often a company page and a person actually post, and the fact that a re-run skips the posts your account already had. The full tier table and the honest-rows section are unchanged and still below. No input field, output column, charged event or price changed.
- The row that says your own time limit stopped a page now fires in every case it should, not just the last few seconds of a run. The last release added that row — but it decided 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 feed 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 asks again for a page it has no time left to use. A page the feed really did refuse still reads exactly as before, and nothing about what is collected or charged changes.
- "Max run seconds" is now a limit the run actually keeps. It decided when the next page of posts could be started, and nothing decided how long a page was allowed to take. A page that hung was given its own 30 seconds, then tried twice more — so a run you capped at 180 seconds could still be reading at 275, and a run restarted on another server started your whole time limit again from zero instead of picking up what was left of it. Each read is now sized to the time your run actually has left, a retry that cannot fit in what remains is not attempted, and the limit is measured from when the run started rather than from when the machine it landed on booted. Posts already collected are still written out either way — a time limit ends the collecting, never the delivering.
- And when your own time limit is what stopped a page part-way, the row says so. It used to read as the feed being unavailable, which pointed you at a re-run that would stop in exactly the same place; it now names your "Max run seconds" and counts those posts under it.
- Nothing else moved: no input field, output column, charged event or price changed, and a run with time to spare reads exactly as it did before.
- 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 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 is the one that quietly breaks "a post your account already had is skipped and not charged" while looking 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 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 page that stopped part-way through no longer says "nothing was charged" over posts you were already charged for. When LinkedIn stops answering mid-walk, the row that closes what is missing now opens with how many posts had already been delivered and points at their own rows for what each one cost — the re-run is for the rest, and only the rest is uncharged.
- A walk that runs into this actor's own 200-page limit now says that is what happened. It used to arrive as "LinkedIn shows no more public posts on this page", which is the opposite fact: LinkedIn was still serving. The row names the limit as this actor's own, says it is not a verdict about the page, and names what to change so the pages a run reads can fill the ask.
- And the run's own summary now files that ending separately, instead of under LinkedIn having no more posts. The row said the bound was this actor's own while the summary counted it as the page running out — the row and the summary saying opposite things about the same link. It has its own name on both now,
skipped.page_cap, and it is counted as a stop rather than a miss: nothing ran out, nothing failed, and nothing behind the bound was ever collected or charged. no_data from here on means only what it says — the page is real and LinkedIn shows no public posts on it.
Your inputs, your events, your prices and every column on your rows are unchanged, and nothing you have already collected needs re-running.
First public build.
- Posts from any LinkedIn profile or company page: the text, the real publish timestamp LinkedIn stamps on the post, likes, comments, shares, the per-type reaction breakdown, the images, video, document and article links, and the author.
- Company pages, showcase pages and people's profiles go in one list and are sorted out by their own URL path. A bare public identifier works as well as a full link.
- The publish time is the one LinkedIn itself stamped on the post, to the millisecond — not a reading of a "2 weeks ago" label. The label ships beside it as its own column.
- A repost is one row at one price, marked
isRepost, carrying the original post's id, link, author and publish time underneath it. Turn includeReposts off and reposts are dropped before they take a slot.
- Where LinkedIn hides a reaction, comment, repost or view count on a page, that number is
null and countsHidden names which one is hidden. A hidden count never reads as a zero.
- Limits are hard and they are two different limits:
postsPerTarget is per page you named, maxItems is the whole run. Neither is ever exceeded, and a page holding fewer posts than you asked for says so on a row that costs nothing.
- A date window is applied to the post's own publish timestamp, and the walk stops as soon as a page runs past it — so a narrow window costs less, not more.
- One charged unit, one delivered post: $0.0018 on the Apify free plan, $0.0015 Bronze, $0.0012 Silver, $0.0010 Gold and above. No start fee, and no fee for a page that returned nothing.
- Never charged: a page LinkedIn does not list publicly, a page with no public posts, a link that is not a profile or company page, a link to a single post, a read that failed, and posts cut by your own limit or the run clock. Each of them arrives as a row naming what happened, not as a missing line.
- Re-runs skip the posts your account already had — no row, no charge — so a weekly watch on the same pages pays only for what is new.
- A time limit ends the collecting, never the delivering: posts already in hand are always written out, and the last row reconciles what was delivered, what was charged and what stopped the run.