# LinkedIn Post API: 100 Posts per Call, Same 5 Credits (https://www.socialcrawl.dev/blog/linkedin-post-api) > 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](https://learn.microsoft.com/en-us/linkedin/shared/authentication/getting-access), [Share on LinkedIn](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/share-on-linkedin), [Posts API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api?view=li-lms-2026-08)). See [LinkedIn API in 2026](/blog/linkedin-data-api-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=` 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. Cream document cards fanning from a small stack into a much larger set, one clay-amber card at the front, the depth a LinkedIn profile posts API returns in one call. ## 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. ```bash 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. ```json { "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. A closed gate with a clay-amber bar blocking a second page of cards, the LinkedIn posts pagination refusal on a single-page feed. ## 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](https://learn.microsoft.com/en-us/linkedin/shared/api-guide/concepts/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](https://docs.stripe.com/api/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](https://github.com/microsoft/api-guidelines/blob/vNext/azure/Guidelines.md)). Silently dropping a paging argument is the anti-pattern. This endpoint now does that: 400, with a reason. ```bash 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. ```bash 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: ```json { "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. ```bash 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. 1. 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. 2. Hit the profile feed at full depth: ```bash 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. 3. If the page is exactly 100, resolve `author.ext.urn` via `GET /v1/linkedin/profile`, then call `GET /v1/linkedin/search/posts?from_member=&sort_by=date_posted` with no `query`. Follow `pagination.next_cursor` as `cursor`, or the documented 1-based `page`. If you are looking for a LinkedIn activity API, the activity id already sits in `post.url` on the profile-feed lane (`urn:li:activity:…`). Search rows use that activity id as `id`. 4. Open the [Explorer](/explorer) to see the envelope before you write the loop, then read the [LinkedIn profile posts docs](/docs/linkedin) for the contract. The [LinkedIn platform page](/platforms/linkedin) lists the rest of this family. How to scrape LinkedIn posts in the legal sense is a different post: [how to scrape LinkedIn](/blog/how-to-scrape-linkedin-2026). ## 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](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api?view=li-lms-2026-08), [Increasing Access](https://learn.microsoft.com/en-us/linkedin/marketing/increasing-access?view=li-lms-2026-08)). The official-API overview lives in [LinkedIn API in 2026](/blog/linkedin-data-api-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](/docs/linkedin); the envelope is easier to read in the [Explorer](/explorer) than in a log.