# LinkedIn Post History API: Full Archive, 100/Page (https://www.socialcrawl.dev/blog/linkedin-profile-posts-archive)
> LinkedIn post history API: 100/page at 5 credits each. Two no-cache calls matched published_at to the millisecond; limit=100 returned 97 posts and a cursor.
`GET /v1/linkedin/profile/posts/archive` is the LinkedIn post history API that walks a public member's posts **100 a page**, with a cursor until nothing older is left. That LinkedIn profile posts archive returns member-authored rows with exact timestamps.
On 2026-09-08 against `williamhgates`, `limit=100` returned **97** posts, **485** credits, `has_more=true`, and a non-null `sc.` cursor. Two no-cache archive calls 74 seconds apart matched `published_at` on all 5 overlapping ids **to the millisecond** (`2026-09-05T19:09:50.123Z`).
Microsoft's Posts API is partner create/retrieve of *your* posts. Self-serve is Sign In plus Share on LinkedIn — `w_member_social` is write, not a public archive read ([getting access](https://learn.microsoft.com/en-us/linkedin/shared/authentication/getting-access), [Posts API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api?view=li-lms-2026-08)). LinkedIn Help will [download your data](https://www.linkedin.com/help/linkedin/answer/a1339364/downloading-your-account-data) and nobody else's. Longer official-API write-up: [LinkedIn API in 2026](/blog/linkedin-data-api-2026).
Start at cheap `GET /v1/linkedin/profile/posts` (up to 100 for a **flat 5 credits**) — [the cheap 100-post LinkedIn profile feed](/blog/linkedin-post-api). Archive is the lane you pay per delivered post when you need the **end of the profile**, **exact times**, or a guaranteed member-authored row.
## How do you retrieve a LinkedIn member's complete post history?
Follow `pagination.next_cursor` on `limit=100` pages until `has_more` is false — that is the complete public history. A LinkedIn post history API call at `limit=5` is the cheap way to see the envelope before you spend 485 credits on a continuable page. Same public member, same day: `https://www.linkedin.com/in/williamhgates/`.
```bash
curl -s -H "x-api-key: $SOCIALCRAWL_API_KEY" -H "Cache-Control: no-cache" \
"https://www.socialcrawl.dev/v1/linkedin/profile/posts/archive?url=https://www.linkedin.com/in/williamhgates/&limit=5"
```
| Call | Items | credits_used | has_more | next_cursor | page_size | Newest `published_at` |
| --- | ---: | ---: | --- | --- | ---: | --- |
| archive `limit=5` | 5 | **25** (5 × 5) | false | null | 5 | `2026-09-05T19:09:50.123Z` |
First id: `7502080571335155713` (activity-shaped). Author `williamhgates` / "Bill Gates". First-item engagement: likes **1741**, comments **437**, shares **86**. `views` and `saves` were **null**. `ext.published_at_epoch` is `1788635390123`. `ext.post_type` is `regular`. Two of the five member posts, trimmed:
```json
{
"success": true,
"platform": "linkedin",
"endpoint": "/v1/linkedin/profile/posts/archive",
"credits_used": 25,
"cached": false,
"pagination": { "next_cursor": null, "has_more": false, "page_size": 5 },
"data": {
"dropped": 0,
"items": [
{
"post": {
"id": "7502080571335155713",
"published_at": "2026-09-05T19:09:50.123Z",
"author": { "username": "williamhgates", "display_name": "Bill Gates" },
"engagement": { "views": null, "likes": 1741, "comments": 437, "shares": 86, "saves": null },
"ext": { "published_at_epoch": 1788635390123, "post_type": "regular" }
}
},
{
"post": {
"id": "7501738402128715777",
"published_at": "2026-09-04T20:30:10.627Z",
"author": { "username": "williamhgates", "display_name": "Bill Gates" },
"engagement": { "views": null, "likes": 6870, "comments": 668, "shares": 595, "saves": null },
"ext": { "published_at_epoch": 1788553810627, "post_type": "regular" }
}
}
]
}
}
```
`request_id`: `req-pq0QiJ0yYh8J765b`. `credits_used=25` is 5 credits **per post delivered**, not per `limit` asked. `has_more=false` / `next_cursor=null` at `limit=5` is expected — only a `limit=100` request is continuable. The objects sit in the unified schema (`dev.socialcrawl.post.v1`); the SocialCrawl API does not hand you a vendor-shaped blob.
Lane choice: cheap `/v1/linkedin/profile/posts` first (flat 5 credits, at most 100, always terminal) → `/v1/linkedin/search/posts?from_member=` for a deeper index → **archive only** when you need the end of the profile, exact times, or a member-authored row. Do not start a full walk here.
Same member, cheap lane, one contrast row from this harvest (`limit=20`): **20** posts, **flat 5 credits**, `has_more=false`, date-only `published_at` (`2026-09-06T00:00:00.000Z`). That is why archive exists.
## How far back does the LinkedIn profile posts archive go?
Ask for 100. This public-member LinkedIn archive API has no fixed depth. A continuable page is `limit=100`; follow `pagination.next_cursor` as `pagination_token` until `has_more` is false. There is no `page=` param on this lane.
```bash
curl -s -H "x-api-key: $SOCIALCRAWL_API_KEY" -H "Cache-Control: no-cache" \
"https://www.socialcrawl.dev/v1/linkedin/profile/posts/archive?url=https://www.linkedin.com/in/williamhgates/&limit=100"
```
| | This harvest (2026-09-08) | 2026-09-07 measured walk (not re-run) |
| --- | --- | --- |
| What we did | one `limit=100` page | complete walk |
| Posts | **97** (asked 100) | **851** posts / **10** pages |
| Credits | **485** (5 × 97) | (not re-measured) |
| `has_more` | **true** | until a page with no cursor |
| Cursor | non-null `sc.` (257 chars) | followed to the end |
| Oldest on this page | `2026-04-27T18:58:34.713Z` | **March 2022** (that member) |
| Wall clock | ~**5.8s** | ~**49s** for 10 pages; ~5s/page |
First id on the page: `7502080571335155713` at `2026-09-05T19:09:50.123Z`. Last id: `7454604928066486273` at `2026-04-27T18:58:34.713Z`. `post_type`: 80 `regular`, 17 `quote`. All 97 author `williamhgates`. Pagination object plus first two items and the last one (`request_id`: `req-2etbGBAiw3xDCQk9`):
```json
{
"success": true,
"endpoint": "/v1/linkedin/profile/posts/archive",
"credits_used": 485,
"cached": false,
"pagination": {
"next_cursor": "sc.eyJ2IjoyLCJjIjoic2MyLmV5SnpJam9pZGlJc0ltTWlPaUprV0VwMVQyMTRjRTl0Um1wa1Iyd3lZVmhTTlU5cVl6Qk9WRkV5VFVSUk5VMXFaM2RPYWxrd1QwUlplVTU2VFhSTlZHTXpUbnBOZUU1cVRYaE9SRmsxVGxFOVBTSjkiLCJlIjoibGlua2VkaW4vcHJvZmlsZS9wb3N0cy9hcmNoaXZlIiwicCI6InBhZ2luYXRpb25fdG9rZW4ifQ",
"has_more": true,
"page_size": 97
},
"data": {
"dropped": 0,
"items": [
{
"post": {
"id": "7502080571335155713",
"published_at": "2026-09-05T19:09:50.123Z",
"author": { "username": "williamhgates" },
"engagement": { "likes": 1741, "comments": 437, "shares": 86 },
"ext": { "published_at_epoch": 1788635390123, "post_type": "regular" }
}
},
{
"post": {
"id": "7501738402128715777",
"published_at": "2026-09-04T20:30:10.627Z",
"author": { "username": "williamhgates" },
"engagement": { "likes": 6870, "comments": 668, "shares": 595 },
"ext": { "published_at_epoch": 1788553810627, "post_type": "regular" }
}
},
{
"post": {
"id": "7454604928066486273",
"published_at": "2026-04-27T18:58:34.713Z",
"author": { "username": "williamhgates" },
"engagement": { "likes": 1421, "comments": 130, "shares": 54 },
"ext": { "published_at_epoch": 1777316314713, "post_type": "quote" }
}
}
]
}
}
```
94 of 97 items omitted. Page 2 was not followed this run, so there is no page-2 envelope to print. The 851 / 10 pages / ~49s figure, and a more prolific member that passed **1,800** posts back to **2017**, are the **2026-09-07 measured walk**. This harvest proved page 1 of a high-volume public profile. It did not prove 851.
The non-obvious contract: a `limit=100` page can return **fewer than 100** rows and still hand you a cursor (97 items, `has_more=true`). Stop on `pagination.has_more`, **not** on `items.length === 100`. A naive loop that treats a short page as the end of the archive would have dropped everything older than `2026-04-27T18:58:34.713Z` on this member and called the job done.
A `limit` below 100 returns that many of the newest posts and no cursor (`limit=5` this harvest: `has_more=false`, `next_cursor=null`). The contract will not pretend a partial page is continuable — that would silently skip the rest of the archive. To walk, pass the previous `pagination.next_cursor` as `pagination_token` on the next `limit=100` request. A page with no cursor is the end of **that member's** public archive. Private / logged-in-only activity is out of scope.
## Why do archive timestamps stay exact across calls?
Because the archive stores the instant, not a relative label. Two no-cache archive calls at `limit=5`, both `X-Cache: MISS`, **74 seconds** apart: **5/5 ids matched to the millisecond.**
Buffer's 2026 analysis of 4.8 million LinkedIn posts puts the problem in one line: LinkedIn Analytics will not show detailed timings after the fact; the best the timestamp will tell you is "3h" or "2d" ([Buffer, 2026](https://buffer.com/resources/best-time-to-post-on-linkedin/)). A time-of-day study needs a clock that does not move when you recrawl.
| id | call 3 | call 4 | match |
| --- | --- | --- | --- |
| `7502080571335155713` | `2026-09-05T19:09:50.123Z` | `2026-09-05T19:09:50.123Z` | yes |
| `7501738402128715777` | `2026-09-04T20:30:10.627Z` | `2026-09-04T20:30:10.627Z` | yes |
| `7501466755261820928` | `2026-09-04T02:30:44.967Z` | `2026-09-04T02:30:44.967Z` | yes |
| `7501456193794633729` | `2026-09-04T01:48:46.917Z` | `2026-09-04T01:48:46.917Z` | yes |
| `7501455180396208128` | `2026-09-04T01:44:45.304Z` | `2026-09-04T01:44:45.304Z` | yes |
Same toilets post, two clocks (cheap lane vs archive, this harvest):
| Lane | id | `published_at` | likes / comments / shares |
| --- | --- | --- | --- |
| `/profile/posts` | `7502080570181586944` | `2026-09-06T00:00:00.000Z` (`precision=day`) | 1741 / 437 / 86 |
| `/profile/posts/archive` | `7502080571335155713` | `2026-09-05T19:09:50.123Z` (`epoch=1788635390123`) | 1741 / 437 / 86 |
The cheap-lane URL for that row is `…/feed/update/urn:li:activity:7502080571335155713` — the archive id sits inside it. **IDs are not the same field across lanes** (ugcPost-shaped vs activity-shaped). Same post, two ids.
The **calendar day disagrees** (6 Sep midnight vs 5 Sep 19:09:50 UTC) because the cheap lane recomputes from a relative label (`ext.published_at_precision` was `day` on 7/20 cheap rows and `week` on 13/20). Archive: 97/97 millisecond instants, **0 midnight**. Midnight is a rounding artifact, not a lifestyle. Search-lane drift was not re-measured on 2026-09-08; the sibling harvest on 2026-09-06 saw `created_at` move from `22:50:19` to `22:52:27` on the same row ([LinkedIn post API](/blog/linkedin-post-api)).
## Does the LinkedIn archive API return share counts?
Yes. If you want a LinkedIn share count API field you can chart, it is `engagement.shares`, sitting next to likes and comments.
This harvest, 2026-09-08:
- Archive `limit=100`: **97/97** numeric `engagement.shares`.
- Archive `limit=5`: 5/5 numeric shares.
- Cheap `/profile/posts` `limit=20`: **20/20** numeric shares.
A 2026-09-07 product note called archive the only LinkedIn lane with share counts. This harvest contradicts that for `/profile/posts`. Live harvest wins. What archive uniquely showed here is exact timestamps, a working cursor, and depth past the cheap window.
`views` and `saves` were **null on every archive row**. Official `r_member_postAnalytics` (impressions plus shares on *your* posts, 3-legged OAuth) is not a public-member read ([Increasing Access](https://learn.microsoft.com/en-us/linkedin/marketing/increasing-access?view=li-lms-2026-08)). `shares: 86` on the toilets post, `shares: 595` on id `7501738402128715777`.
## How many credits does a LinkedIn archive walk cost?
Five credits per post delivered. Not per `limit` asked. Opposite of the cheap lane.
| Call | HTTP | Posts | credits_used | Notes |
| --- | ---: | ---: | ---: | --- |
| archive `limit=5` | 200 | 5 | **25** | 5 × delivered |
| archive `limit=100` | 200 | **97** | **485** | 5 × 97, not 5 × 100 |
| archive `limit=101` | **400** | — | **0** | names the limit |
| fake profile | **200** | 0 | **0** | empty list, not 404 |
| cheap `/profile/posts` `limit=20` | 200 | 20 | **5** (flat) | sibling lane |
`limit=101` (`req-5f79V4LhCyjNSZKX`) is a free named error:
```json
{
"success": false,
"error": {
"type": "INVALID_REQUEST",
"message": "Invalid value for 'limit': '101'. Must be an integer >= 1 and <= 100.",
"status": 400,
"doc_url": "https://www.socialcrawl.dev/docs/errors#invalid-request"
},
"credits_used": 0,
"request_id": "req-5f79V4LhCyjNSZKX"
}
```
A missing profile is not a 404. URL used: `https://www.linkedin.com/in/this-profile-does-not-exist-zzz999/` (`req-UoV4dWgSvJ9sOGDX`):
```json
{
"success": true,
"platform": "linkedin",
"endpoint": "/v1/linkedin/profile/posts/archive",
"data": { "items": [], "total": 0, "dropped": 0 },
"credits_used": 0,
"cached": false,
"pagination": { "next_cursor": null, "has_more": false, "page_size": 0 },
"request_id": "req-UoV4dWgSvJ9sOGDX"
}
```
HTTP **200**, empty list, **0** credits.
Reshares of other people's posts are dropped and not billed. This harvest asked 100, received 97, billed 485 = 5 × 97. `data.dropped` was **0** (that counter is schema-repair loss, not the reshare filter). The 3-post shortfall is consistent with the filter. Those omitted rows were not in the payload to inspect, so this is inferred, not a counted reshare tally.
Cache caveat: this harvest sent `Cache-Control: no-cache` and every paid response was `cached: false`. A normal client without that header can be served a 10-minute cache hit at 0 credits. The cheap lane bills the call, not the row count. Archive bills the opposite: you pay for what came back.
## How do you start using this?
1. Get a [key](/auth/register). New accounts get free credits, no card. Keep it out of git. Export it as `$SOCIALCRAWL_API_KEY`.
2. **Start cheap.** `GET /v1/linkedin/profile/posts?url=…&limit=20` (flat 5 credits) — see the [cheap 100-post lane](/blog/linkedin-post-api). `limit=100` is the same 5 credits. That lane is always one terminal page (`has_more=false`); if you get fewer rows than you asked, that is the whole cheap window. This member filled `limit=20`, and archive page 1 already had 97.
3. If you need the rest of the profile, exact times, or a cursor past that window, switch to archive at `limit=100`. Follow `pagination.next_cursor` as `pagination_token` until `has_more` is false. Expect ~5 credits × posts delivered (this harvest: 97 posts → 485 credits on page 1). One API key, same envelope.
4. Open the [Explorer](/explorer) to see the envelope before writing a single line of the loop. Contract: [LinkedIn docs](/docs/linkedin). Platform family: [/platforms/linkedin](/platforms/linkedin). Public pages; the User Agreement still forbids scraping; this is an API read, not a scrape tutorial — see [how to scrape LinkedIn](/blog/how-to-scrape-linkedin-2026).
Minimal first archive call (expect 5 posts / 25 credits):
```bash
curl -s -H "x-api-key: $SOCIALCRAWL_API_KEY" -H "Cache-Control: no-cache" \
"https://www.socialcrawl.dev/v1/linkedin/profile/posts/archive?url=https://www.linkedin.com/in/williamhgates/&limit=5"
```
The contract is on the [LinkedIn docs](/docs/linkedin); the envelope is easier to read in the [Explorer](/explorer) than in a log. If 100 recent posts is enough, use the cheap lane and keep the 485 credits.
## Frequently asked questions
### Does the official LinkedIn API return a public member's full post history?
No. Self-serve is Sign In with LinkedIn plus Share on LinkedIn, and `w_member_social` is write ([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 retrieve is partner-scoped: `r_organization_social` for a page you admin, `r_member_social` restricted ([Posts API](https://learn.microsoft.com/en-us/linkedin/marketing/community-management/shares/posts-api?view=li-lms-2026-08)). LinkedIn Help will only give *your* data. The official-API overview is [LinkedIn API in 2026](/blog/linkedin-data-api-2026). This endpoint is the public-data archive read.
### What is the difference between `/v1/linkedin/profile/posts` and `/archive`?
Cheap lane: one terminal page, `limit` 1–100, **flat 5 credits**, day/week-precision timestamps. Archive: cursor at `limit=100`, **5 credits per post delivered**, millisecond `published_at` that does not drift (5/5 ids matched this harvest). Share counts were numeric on **both** lanes this harvest, so do not call archive the only shares lane.
### How many LinkedIn posts does the archive return per page?
You may ask for 1–100. A continuable page is `limit=100`. This harvest: `limit=5` → 5 posts, no cursor; `limit=100` → **97** posts, `has_more=true`. Stop on `has_more`, not `length===100`. 851 / 10 pages is the **2026-09-07 measured walk**.
### Can you get LinkedIn share counts from the profile posts archive?
Yes — 97/97 numeric `engagement.shares` on the `limit=100` page. Also present on 20/20 cheap-lane rows this harvest, so do not call archive the only shares lane. `views` / `saves` were null.
### Are reshares of other people's posts included in the archive?
Documented as dropped and not billed. This harvest asked 100, received 97, billed 485. `data.dropped` was 0. Infer the 3-post shortfall; those omitted rows were not in the payload to inspect.
### What happens if the LinkedIn profile is empty, missing, or you set `limit` above 100?
Fake profile: HTTP **200**, empty list, **0** credits (not 404). `limit=101`: HTTP **400** `INVALID_REQUEST`, **0** credits, message names the 1–100 bound.
### Is walking the archive the cheapest way to get 100 recent posts?
**No.** `/profile/posts` is flat **5 credits** for up to 100. Archive page 1 this harvest was **485 credits** for 97 posts. Use archive when you need the end of the profile, exact times, or a cursor.