LinkedIn Post API: 100 Posts per Call, Same 5 Credits
100 posts in one 5-credit LinkedIn post API call. Page and cursor 400 for 0 credits. Member search advances with a real cursor — live JSON, 2026-09-06.
GET /v1/linkedin/profile/posts with limit=100 returned 100 posts for 5 credits on 2026-09-06 — the same meter as limit=20. That is the live LinkedIn post API contract: you ask for depth up front, and the envelope stops pretending there is a second page.
Microsoft's Posts API is a partner write/retrieve surface, not a public member-read API. Self-serve is Sign In plus Share on LinkedIn (w_member_social is write). Retrieve needs r_organization_social (a page you admin) or restricted r_member_social. There is no official GET of a stranger's activity (getting access, Share on LinkedIn, Posts API). See LinkedIn API in 2026.
page and cursor on the profile feed now return HTTP 400 INVALID_REQUEST for 0 credits. The live walk past that 100-item window is GET /v1/linkedin/search/posts?from_member=<urn> with no query. This harvest followed pagination.next_cursor once and got 10 different ids, 0 overlap.
Three live curls plus trimmed JSON against williamhgates, captured 2026-09-06.
How many posts does the LinkedIn profile posts API return in one call?
The LinkedIn posts API (GET /v1/linkedin/profile/posts) is a single-page feed. limit is an integer from 1 to 100. You pick the depth on the first request. A second request does not exist on this lane.
We hit the same public member twice on 2026-09-06, https://www.linkedin.com/in/williamhgates/, once at limit=20 and once at limit=100. Both calls cost 5 credits. Both came back with pagination.has_more=false and pagination.next_cursor=null. dropped was 0 on both.
curl -s -H "x-api-key: $SOCIALCRAWL_API_KEY" -H "Cache-Control: no-cache" \
"https://www.socialcrawl.dev/v1/linkedin/profile/posts?url=https://www.linkedin.com/in/williamhgates/&limit=20"
curl -s -H "x-api-key: $SOCIALCRAWL_API_KEY" -H "Cache-Control: no-cache" \
"https://www.socialcrawl.dev/v1/linkedin/profile/posts?url=https://www.linkedin.com/in/williamhgates/&limit=100"| Call | Items | credits_used | has_more | next_cursor | page_size | Newest published_at | Oldest published_at |
|---|---|---|---|---|---|---|---|
limit=20 | 20 | 5 | false | null | 20 | 2026-09-05T00:00:00.000Z | 2026-08-23T00:00:00.000Z |
limit=100 | 100 | 5 | false | null | 100 | 2026-09-05T00:00:00.000Z | 2026-05-06T00:00:00.000Z |
The 20-id set is a subset of the 100. First id on both calls: 7502080570181586944. Last id on the 100: 7454604928066486273. Asking for 100 did not reshuffle the recent window; it extended it. The newest published_at stayed 2026-09-05T00:00:00.000Z. The oldest moved from 2026-08-23 to 2026-05-06.
published_at on this lane is day-precision. Every timestamp we saw ended in T00:00:00.000Z. Do not treat it as a clock time. If you need to sort inside a day, this field will not help you.
Share counts were present. The first item carried engagement.likes: 1355, engagement.comments: 299, engagement.shares: 69. Views and saves were null.
Same first item on both calls. The envelope below is the limit=20 capture (page_size: 20); the post object matched on the 100.
{
"success": true,
"endpoint": "/v1/linkedin/profile/posts",
"credits_used": 5,
"cached": false,
"pagination": { "next_cursor": null, "has_more": false, "page_size": 20 },
"data": {
"dropped": 0,
"items": [
{
"post": {
"id": "7502080570181586944",
"url": "https://www.linkedin.com/feed/update/urn:li:activity:7502080571335155713",
"published_at": "2026-09-05T00:00:00.000Z",
"author": { "username": "williamhgates", "display_name": "Bill Gates" },
"content": { "text": "Toilets—and the sanitation systems that support them—have saved an incredible number of lives. They’re just one of many examples of how far the world has come. Check your answer: https://b-gat.es/4gHZUt6" },
"engagement": { "views": null, "likes": 1355, "comments": 299, "shares": 69, "saves": null }
}
}
]
}
}has_more=false and next_cursor=null mean this lane is one page. Depth is the limit you send (1–100), not a follow-up request. The SocialCrawl API bills the call, not the row count: 20 items and 100 items both cost 5 credits.
Two honesty checks on that 100: williamhgates returned exactly 100 at limit=100, so the window is truncated at the ceiling. That is not "every member has 100 posts." A quieter member returning fewer than limit is their whole history on this lane. Stop there. Do not treat 100 as a paging constant, and do not treat a full page as proof that a second page exists.
This call does not walk past 100. The LinkedIn posts API on the profile-feed path is a fixed window. The walk past it is member-scoped search.
What happens if you paginate the LinkedIn profile posts API?
The LinkedIn post API returns a named 400, and you keep your credits.
LinkedIn's own REST guide paginates with start (default 0) and count (default 10). Next page is start += N. You're at the end when the response contains fewer elements than count. There is no has_more and no cursor on that page (LinkedIn API pagination). That is honest only if start actually moves the window. A page or cursor that is accepted, billed, and returns the first window again is worse than no pagination: the client cannot tell "end of feed" from "the server ignored me."
Stripe's list methods take limit (1–100) plus mutually exclusive cursors. The body always includes has_more; false means this set is the end of the list (Stripe pagination). Azure REST guidelines require services to validate query parameters and fail with 400 Bad Request plus a diagnostic error if a value is illegal (Azure API guidelines). Silently dropping a paging argument is the anti-pattern. This endpoint now does that: 400, with a reason.
curl -s -H "x-api-key: $SOCIALCRAWL_API_KEY" \
"https://www.socialcrawl.dev/v1/linkedin/profile/posts?url=https://www.linkedin.com/in/williamhgates/&page=2"
curl -s -H "x-api-key: $SOCIALCRAWL_API_KEY" \
"https://www.socialcrawl.dev/v1/linkedin/profile/posts?url=https://www.linkedin.com/in/williamhgates/&cursor=sc.eyJ2IjoyLCJwIjoiY3Vyc29yIiwiYyI6IkFRSFQifQ"| Param | HTTP | error.type | credits_used | latency |
|---|---|---|---|---|
page=2 | 400 | INVALID_REQUEST | 0 | 141ms |
cursor=sc.eyJ… | 400 | INVALID_REQUEST | 0 | 55ms |
The error message is the same sentence both times, with the illegal param swapped. The feed is a single page and does not accept page or cursor. Use limit (1–100) to control how much of that window you get in the one call.
There is no success JSON to paste here. The artifact is the 400. LinkedIn posts pagination on profile/posts fails closed and costs nothing, instead of silently re-billing page 1. If your client retries on 400 without reading the message, you will loop, but you will not pay 5 credits per loop.
Can you search a member's LinkedIn posts without a keyword?
Yes. Pass from_member on its own. Omit query on purpose.
from_member is the bare ACoAA… urn from author.ext.urn on GET /v1/linkedin/profile. This harvest used ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc. A real keyword silently narrows the set. A LinkedIn post search API call scoped to one member has to leave query off for the unfiltered walk. Send sort_by=date_posted. A subject-only call rejects relevance.
This is the live way past the 100-item profile feed. It reads a public search index, not the native feed. Depth is a property of that index and varies by member. Paging harder does not raise it. This harvest followed the cursor once. It does not measure how far the index goes.
The documented page param is 1-based page. This run followed the unified pagination.next_cursor as cursor and it worked. Do not invent a third paging style.
curl -s -H "x-api-key: $SOCIALCRAWL_API_KEY" \
"https://www.socialcrawl.dev/v1/linkedin/search/posts?from_member=ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc&sort_by=date_posted"
curl -s -H "x-api-key: $SOCIALCRAWL_API_KEY" \
"https://www.socialcrawl.dev/v1/linkedin/search/posts?from_member=ACoAAA8BYqEBCGLg_vT_ca6mMEqkpp9nVffJ3hc&sort_by=date_posted&cursor=sc.eyJ2IjoyLCJjIjoiMiIsImUiOiJsaW5rZWRpbi9zZWFyY2gvcG9zdHMiLCJwIjoicGFnZSJ9"| Page | Items | credits_used | has_more | next_cursor (decoded gist) | Overlap |
|---|---|---|---|---|---|
| 1 | 10 | 5 | true | c=2 | 0 |
| 2 (follow cursor) | 10 | 5 | true | c=3 | 0 |
Unique across the two pages: 20. First page-1 id: 7502080571335155713. First page-2 id: 7498545384043036672. has_more stayed true on both. The cursor moved (c=2 → c=3 inside the token). The second page was not a copy of the first.
Trimmed page 1:
{
"success": true,
"endpoint": "/v1/linkedin/search/posts",
"credits_used": 5,
"pagination": {
"next_cursor": "sc.eyJ2IjoyLCJjIjoiMiIsImUiOiJsaW5rZWRpbi9zZWFyY2gvcG9zdHMiLCJwIjoicGFnZSJ9",
"has_more": true,
"page_size": 10
},
"data": {
"dropped": 0,
"items": [
{
"id": "7502080571335155713",
"url": "https://www.linkedin.com/feed/update/urn:li:activity:7502080571335155713",
"title": "Toilets—and the sanitation systems that support them—have saved an incredible number of lives. They’re just one of many examples of how far the world has come. Check your answer: https://b-gat.es/4gHZUt6",
"author": { "name": "Bill Gates", "url": "https://www.linkedin.com/in/williamhgates" },
"activity": { "num_likes": 1357, "num_comments": 299, "num_shares": 69 }
}
]
}
}This lane returns SearchResult, not Post. The unified schema still wraps the page, but the item keys on this run were id, url, title, activity, created_at, author. There is no post.id, no published_at, no content.text.
Ids are activity ids. Page 1's first row is 7502080571335155713. The matching profile-feed row used a sibling id, 7502080570181586944, whose post.url already contains the activity id (urn:li:activity:7502080571335155713). Do not treat the two id columns as the same key. Join on URL, or parse the activity id out of post.url.
created_at is not published_at. The same page-1 first row was 2026-09-05T22:50:19.238Z on a cache-hit check and 2026-09-05T22:52:27.336Z on the recapture two minutes later. Profile/posts had published_at: 2026-09-05T00:00:00.000Z for the matching post. If you persist created_at as a publish time, you will rewrite history on every recrawl.
A LinkedIn member posts API call still has to send a real urn. A public slug or a profile URL is refused before you are charged.
curl -s -H "x-api-key: $SOCIALCRAWL_API_KEY" \
"https://www.socialcrawl.dev/v1/linkedin/search/posts?from_member=williamhgates"
curl -s -H "x-api-key: $SOCIALCRAWL_API_KEY" \
"https://www.socialcrawl.dev/v1/linkedin/search/posts?from_member=https://www.linkedin.com/in/williamhgates/"| Value | HTTP | credits_used | Message names from_member? |
|---|---|---|---|
williamhgates (public slug) | 400 | 0 | yes |
| profile URL | 400 | 0 | yes |
The 400 names the param. from_member must be a LinkedIn member urn — the bare ACoAA… value, with no urn:li: prefix. A public slug is not accepted here; a profile URL is not accepted either. Get the urn from GET /v1/linkedin/profile → author.ext.urn.
Hard ceiling on the claim: this harvest proved two pages, 20 unique search rows, 0 overlap, and a cursor that moved. Product docs say member-scoped search can walk past the 100-item profile feed. This harvest only proved the cursor moves. Two pages of 10 still sit in the recent window. Do not treat 20 as a depth limit either. The index ends when it ends, and that line is different for every member.
How do you pull a member's posts with the LinkedIn post API?
Four steps. Item counts will move with the member's feed. Credit cost (5 / 5 / 0) is the contract to re-check.
-
Get a key. Sign up, copy it once, keep it out of git. Free credits on a new account. Do not paste a demo key into a script.
-
Hit the profile feed at full depth:
export SOCIALCRAWL_API_KEY=sc_live_...
curl -s -H "x-api-key: $SOCIALCRAWL_API_KEY" -H "Cache-Control: no-cache" \
"https://www.socialcrawl.dev/v1/linkedin/profile/posts?url=https://www.linkedin.com/in/williamhgates/&limit=100"That is the whole "get LinkedIn posts by profile" path on this lane. If the page is shorter than limit, you are done.
-
If the page is exactly 100, resolve
author.ext.urnviaGET /v1/linkedin/profile, then callGET /v1/linkedin/search/posts?from_member=<urn>&sort_by=date_postedwith noquery. Followpagination.next_cursorascursor, or the documented 1-basedpage. If you are looking for a LinkedIn activity API, the activity id already sits inpost.urlon the profile-feed lane (urn:li:activity:…). Search rows use that activity id asid. -
Open the Explorer to see the envelope before you write the loop, then read the LinkedIn profile posts docs for the contract. The LinkedIn platform page lists the rest of this family. How to scrape LinkedIn posts in the legal sense is a different post: how to scrape LinkedIn.
Frequently asked questions
Does the official LinkedIn API let you read another member's posts?
No. There is no public GET of a stranger's activity. Self-serve is Sign In with LinkedIn plus Share on LinkedIn, and w_member_social is write. Marketing Posts API retrieve is partner-scoped: r_organization_social for a page you admin, r_member_social restricted and approved-users-only (Posts API, Increasing Access). The official-API overview lives in LinkedIn API in 2026. This post is the public-data read path, not that one.
What is the limit on LinkedIn profile posts per API call?
On the LinkedIn post API, limit is an integer from 1 to 100. This harvest returned 20 items at limit=20 and 100 items at limit=100, both 5 credits, both has_more=false. Ask for the depth you want on the first call.
Can you paginate past 100 LinkedIn posts?
Not on profile/posts. page and cursor return HTTP 400 INVALID_REQUEST for 0 credits. The live walk is member-scoped search/posts with from_member and no query. This harvest proved two pages, 20 unique rows, 0 overlap. That is not a measured depth past 20.
Do I need a keyword to search a member's LinkedIn posts?
No. Pass from_member alone. A real query silently narrows the set. A public slug or a profile URL returns HTTP 400 for 0 credits, and the message names from_member. The value is the bare ACoAA… urn from GET /v1/linkedin/profile → author.ext.urn.
How many credits does GET /v1/linkedin/profile/posts cost?
A LinkedIn post API call costs 5 credits, whether you ask for 20 or 100 (advanced tier). A refused page or cursor costs 0. search/posts is 5 per page of 10. Cache hits are free; this harvest billed every featured success, so there is no cache-hit receipt here to quote.
What happens if the response is shorter than limit?
That is the member's entire history on the profile-feed lane. Do not keep paying. williamhgates returned exactly 100 at limit=100, so that profile is truncated at the ceiling. A quieter member returning fewer than limit is done.
Copy the limit=100 curl first. If the page comes back full, grab author.ext.urn and walk search/posts. The contract is on the LinkedIn docs; the envelope is easier to read in the Explorer than in a log.
Related posts
Best Social Listening Tools: 7 Compared for Growth Teams
Seven of the best social listening tools for growth teams: 2026 list prices, listening-as-add-on called out, and a data API if you already have an LLM stack.
Financial Data API: 1,255 Daily Bars, Both Closes
Financial data API: 1,255 AAPL daily bars with both closes, plus statements, an 81-contract options chain, and ticker news. Live from GET /v1/finance/.
Instagram Email Finder: 1 Credit, Email When Public
9/9 Instagram about lookups returned country (1 credit). Public email on 3/9 — null when unpublished. YouTube about costs 60 credits. Captured 2026-09-05.
