# LinkedIn (/docs/linkedin) LinkedIn people, company pages, posts, comments, jobs, groups, and the Ad Library through one API key The widest surface on SocialCrawl: public member profiles broken out into every sub-resource LinkedIn renders, company pages with their posts, staff, hiring volume and workforce breakdowns, the full jobs surface with LinkedIn's own filters, group feeds, and the LinkedIn Ad Library. If you are building B2B prospecting, recruiting intelligence, or competitor tracking, this is where most of that data lives. Base URL: `/v1/linkedin/...` Three different identifiers, and they are not interchangeable. Member and post endpoints take the page `url`. Company endpoints take a numeric `company_id`, which is **not** in the company URL: resolve it once with `/v1/linkedin/company`. Job and group endpoints take `id` and `group_id`, which come from a list call. Passing a `url` to a `company_id` endpoint is a `400` before billing. ## Quickstart ### 1. Fetch a profile ```bash title="cURL" curl "https://www.socialcrawl.dev/v1/linkedin/profile?url=https://www.linkedin.com/in/williamhgates/" \ -H "x-api-key: YOUR_API_KEY" ``` ### 2. Fetch their content ```bash title="cURL" curl "https://www.socialcrawl.dev/v1/linkedin/profile/posts?url=https://www.linkedin.com/in/williamhgates/" \ -H "x-api-key: YOUR_API_KEY" ``` ### 3. Read computed fields When an endpoint supports a computed field and the required source inputs are present, the unified response includes that optional field. Depending on the endpoint, optional fields can include `engagement_rate`, `language`, `content_category`, and `estimated_reach`. See [Computed fields](/docs/computed-fields.md) for formulas, clamping rules, and null semantics. Where an endpoint supports them, list rows also carry judged labels (`computed.labels`, `computed.relevance`) by default at no extra credits; see [Labels](/docs/labels.md). ## People `GET /v1/linkedin/profile` deliberately returns the _condensed_ profile, the same summary LinkedIn shows above the fold. Each section below it is its own call against the same `url`, so you only pay for what you read. | Endpoint | Credits | What it returns | Key parameters | | ---------------------------------------------- | ------- | -------------------------------------------------------------------- | -------------- | | `GET /v1/linkedin/profile` | 5 | Name, headline, location, follower and connection counts, picture | `url` | | `GET /v1/linkedin/profile/stats` | 5 | Just the follower and connection numbers | `url` | | `GET /v1/linkedin/profile/about` | 5 | Join month, plus how recently contact details and photo were touched | `url` | | `GET /v1/linkedin/profile/contact` | 5 | Websites, phone, address, WeChat, and Twitter the member exposed | `url` | | `GET /v1/linkedin/profile/experiences` | 5 | Full role history with dates, locations, and descriptions | `url`, `page` | | `GET /v1/linkedin/profile/educations` | 5 | Schools, degrees, fields of study, years attended | `url`, `page` | | `GET /v1/linkedin/profile/skills` | 5 | Listed skills with the endorsements shown against each | `url`, `page` | | `GET /v1/linkedin/profile/certifications` | 5 | Licences and certifications with issuer and expiry | `url`, `page` | | `GET /v1/linkedin/profile/honors` | 5 | Awards, with issuer, date, and description | `url`, `page` | | `GET /v1/linkedin/profile/publications` | 5 | Articles, papers, and books, with publisher and link | `url`, `page` | | `GET /v1/linkedin/profile/volunteers` | 5 | Unpaid and community roles the paid history does not cover | `url`, `page` | | `GET /v1/linkedin/profile/recommendations` | 5 | Written references, with `type` selecting received or given | `url`, `type` | | `GET /v1/linkedin/profile/interests/companies` | 5 | The companies the member follows | `url`, `page` | | `GET /v1/linkedin/profile/interests/groups` | 5 | The groups the member follows | `url`, `page` | `profile/about` is the fastest way to tell a maintained profile from an abandoned one. The two interest lists are a decent proxy for a member's vendor affinities. ### What a member says and does | Endpoint | Credits | What it returns | Key parameters | | ------------------------------------ | ------- | ----------------------------------------- | --------------------- | | `GET /v1/linkedin/profile/posts` | 5 | What the member wrote | `url`, `urn`, `limit` | | `GET /v1/linkedin/profile/reactions` | 5 | What they engaged with | `url`, `urn` | | `GET /v1/linkedin/profile/comments` | 5 | What they said under other people's posts | `url`, `urn` | | `GET /v1/linkedin/profile/images` | 5 | Their feed narrowed to image posts | `url`, `urn` | | `GET /v1/linkedin/profile/videos` | 5 | Their feed narrowed to video posts | `url`, `urn` | Posts, reactions, and comments are three genuinely different signals about the same person, and worth pulling separately rather than treating the feed as one thing. These five are single-window feeds: LinkedIn serves one page and there is no second one to fetch, so they take no `cursor`. `profile/posts` accepts `limit` (up to 100, the provider's ceiling) at the same credit cost, which is the only depth control this surface has. Every one of them accepts `urn` as an alternative to `url`. If you already hold the member's URN from a previous call (it is on `author.ext.urn`), pass it: the call then skips an internal profile lookup, which is faster and leaves more of the shared rate-limit budget for your other requests. `published_at` on these feeds is derived from LinkedIn's own relative label ("15h", "5d", "2w"), not from a stored timestamp, so it is accurate to the hour, day, week, or month rather than the second. `post.ext.published_at_precision` tells you which, per post. ## Companies Company content is a two-step flow, because the numeric id LinkedIn indexes a company by does not appear anywhere in its public URL. ```bash title="cURL" curl "https://www.socialcrawl.dev/v1/linkedin/company?url=https://www.linkedin.com/company/microsoft/" \ -H "x-api-key: YOUR_API_KEY" curl "https://www.socialcrawl.dev/v1/linkedin/company/posts?company_id=1035" \ -H "x-api-key: YOUR_API_KEY" ``` Hold the id. Every other company endpoint takes the same `company_id`, so you pay for the lookup once and reuse it. | Endpoint | Credits | What it returns | Key parameters | | ------------------------------------------- | ------- | -------------------------------------------------------------------------- | --------------------------------------------- | | `GET /v1/linkedin/company` | 5 | The company page, and the numeric id everything else needs | `url` | | `GET /v1/linkedin/company/posts` | 5 | That company's posts | `company_id`, `page`, `sort_by` | | `GET /v1/linkedin/company/people` | 10 to 50 | The staff directory, without a filter-id round trip; exact counts with `include=profile` | `company_id`, `page`, `include`, `limit` | | `GET /v1/linkedin/company/jobs` | 10 | One employer's open roles, with the full job filter set | `company_id`, `page` | | `GET /v1/linkedin/company/insights` | 5 | Headcount broken down by location, school, job function, skill, and field | `company_id` | | `GET /v1/linkedin/company/job-count` | 5 | One number: currently open roles | `company_id` | | `GET /v1/linkedin/company/affiliated-pages` | 5 | A parent brand mapped to its regional and product pages | `company_id` | | `GET /v1/linkedin/profile/full` | 5 | The company page and its recent posts plus computed analytics, in one call | `url`, `posts` (1-100, default 25), `include` | `company/insights` is a workforce profile without paging through employees, and `company/job-count` is the right shape for tracking hiring volume on a schedule. Each affiliated page has its own id you can feed back into posts and jobs. ## Posts, comments and reactions | Endpoint | Credits | What it returns | Key parameters | | ---------------------------------------- | ------- | --------------------------------------------------------- | --------------------------------------------------------------------- | | `GET /v1/linkedin/post` | 5 | One post | `url` | | `GET /v1/linkedin/post/comments` | 5 | Who replied | `url`, `sort_order`, `post_type`, `page` | | `GET /v1/linkedin/post/comments/replies` | 5 | One comment thread opened up | `url` **and** `comment_id` | | `GET /v1/linkedin/post/reposts` | 5 | Who amplified it | `url`, `cursor` | | `GET /v1/linkedin/post/reactions` | 10 to 50 | Who reacted, with their name, headline, and reaction type; exact counts with `include=profile` | `url`, `type`, `page`, `include`, `limit` | | `GET /v1/linkedin/post/transcript` | 10 | The spoken text of a video post | `url` | | `GET /v1/linkedin/search/posts` | 5 | Public posts and Pulse articles matching a keyword | `query`, `date_posted`, `content_type`, `from_company`, `from_member`, `label`, `relevance`, `judgments` | A post's audience splits three ways and the prices differ, so pick the one that answers your question rather than pulling all three. `date_posted` on `search/posts` accepts `past_24h`, `past_week`, or `past_month`, with underscores rather than hyphens. The endpoint reads public search results rather than LinkedIn's internal index, so treat its coverage as best effort: it is a monitoring tool, not a complete archive. ## Jobs | Endpoint | Credits | What it returns | Key parameters | | ------------------------------- | ------- | ------------------------------------------ | ----------------------------------------------------------------------------------------- | | `GET /v1/linkedin/search/jobs` | 10 | Openings across every employer, as job ids | `query`, `date_posted`, `experience_level`, `job_type`, `remote`, `easy_apply`, `sort_by` | | `GET /v1/linkedin/company/jobs` | 10 | One employer's openings, same filter set | `company_id`, `page` | | `GET /v1/linkedin/job` | 5 | The complete posting text | `id`, `include_skills` | Both searches return job ids rather than descriptions. Pass an id to `job` for the posting itself. ```bash title="cURL" curl "https://www.socialcrawl.dev/v1/linkedin/search/jobs?query=machine%20learning%20engineer&remote=true&date_posted=past_week" \ -H "x-api-key: YOUR_API_KEY" curl "https://www.socialcrawl.dev/v1/linkedin/job?id=$JOB_ID" \ -H "x-api-key: YOUR_API_KEY" ``` ## Groups and ads | Endpoint | Credits | What it returns | Key parameters | | ------------------------------ | ------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------- | | `GET /v1/linkedin/group` | 5 | Description, member count, posting rules, public and active flags | `group_id` | | `GET /v1/linkedin/group/posts` | 5 | The group's discussion feed | `group_id`, `page` | | `GET /v1/linkedin/ads/search` | 5 | The LinkedIn Ad Library, paged on `paginationToken` | `company`, `keyword`, `companyId`, `countries`, `startDate`, `endDate` | | `GET /v1/linkedin/ad` | 5 | One ad in full, including creative images and total impressions | `url` | `group_id` is not in the group URL. You get one from `profile/interests/groups` on a member who belongs to it. `countries` is a comma-separated list, and `ad` takes a result's Ad Library URL. ## Turning names into filter ids LinkedIn's people and job search filter on internal ids, not on text. Three cheap resolvers exist for exactly that. | Endpoint | Credits | What it returns | Key parameters | | ---------------------------------- | ------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `GET /v1/linkedin/search/location` | 1 | A city or country name as a geocode id | `query` | | `GET /v1/linkedin/search/schools` | 1 | A university name as a school id | `query`, `page` | | `GET /v1/linkedin/search/industry` | 1 | An industry name as an industry id | `query` | | `GET /v1/linkedin/search/people` | 10 to 50 | Members matching a name and those filters | `query`, `title`, `current_company`, `past_company`, `school`, `industry`, `geocode_location`, `profile_language`, `include`, `limit` | Resolve once, cache the id, and reuse it across every subsequent search. When you already know the employer, `company/people` lists that company's staff directly and skips the round trip. ## Exact numbers on a people list Three lanes return people rows: `GET /v1/linkedin/search/people`, `GET /v1/linkedin/company/people`, and `GET /v1/linkedin/post/reactions`. Each page is 10 members. On a plain call a row carries the name, headline, location and member URN, but `author.followers` is LinkedIn's rounded display bucket where it appears at all (39,000 for a member on 39,278), flagged `author.ext.followers_approximate`, and `author.following` is null on every row. Add `include=profile` and every row is joined to that member's `/v1/linkedin/profile` lookup inside the same call, so you never loop the profile endpoint yourself: ```bash title="cURL" curl "https://www.socialcrawl.dev/v1/linkedin/search/people?query=Bill%20Gates&include=profile&limit=3" \ -H "x-api-key: YOUR_API_KEY" ``` `author.followers` becomes the member's exact follower count, and `author.ext.followers_approximate` reads `false` on every row the lookup filled, so one field tells you whether a figure is exact on every row instead of only on the rows that arrived rounded. A row the lookup could not fill keeps the flag it arrived with. `author.following` is filled with their connection count, which is what a member's following number means on LinkedIn. `author.location`, `author.joined_at`, and the country, website, cover image, and the creator, influencer, open-to-work, hiring, top-voice and premium flags on `author.ext` land on any row that lacks them. The list stays 10 credits. The join holds 4 credits per row and keeps that only for the rows it filled from a fresh lookup, so a full page of 10 costs at most 50. Rows already in cache are free, rows it could not fill are refunded, and repeating a fully joined page inside the cache window costs nothing at all. `limit` (1 to 10) caps the rows served and the extra credits together, so `limit=3&include=profile` is at most 22 credits; the page cursor still advances past the full page of 10, so the rows past your limit are skipped rather than carried forward. `data.hydration` reports `rows`, `looked_up`, `filled`, `cached`, `unfilled`, `credits_held`, `extra_credits` and `ms`, so you can see what the join did and what it cost. An `include` value other than `profile` is a `400` before anything is billed. No other LinkedIn lane takes the token: the job lists, `profile/images`, `profile/videos` and the comment lanes have no profile join. ## All endpoints ## Platform notes **LinkedIn has no 1-credit content surface.** Every profile, post, and company read is 5 credits, and people search, company staff, company jobs, job search, post reactions, and video transcripts are 10. The three people lanes bill between 10 and 50 when you add `include=profile`. Only the three id resolvers above are 1 credit. Budget accordingly, and prefer the sub-resource that answers your question over pulling the whole profile family. **Sub-resources are per-person calls, not a bundle.** Thirteen profile sub-resources each take the same profile `url`. Pulling a complete dossier on one member is thirteen billed calls, so decide up front which sections your model actually reads. **Most contact fields are empty.** `profile/contact` returns what the member chose to publish, which for the large majority is nothing. No email address is returned by any endpoint on this platform. **Freshness values are relative text.** `profile/about` reports strings such as `Updated over 1 year ago` rather than dates, because that is what LinkedIn renders. Parse accordingly, and do not expect to sort on them precisely. **`post/transcript` is only charged when a transcript comes back.** A post with no transcript, including a post with no video, returns **404** `RESOURCE_NOT_FOUND` with `error.details.reason: "no_captions"`, at no cost. A repost is read from the original post it shares, because LinkedIn does not show a repost's content to logged-out viewers, and a `_warnings` note names the original post's URL. A post that cannot be read logged-out and cannot be resolved returns **404** with `reason: "post_not_public"`, also at no cost. **Member and company URLs are normalised before they are looked up.** `https://www.linkedin.com/in/williamhgates/`, `http://uk.linkedin.com/in/williamhgates`, and the same URL carrying tracking parameters such as `?trk=...` all resolve to one record, so a second read of that member inside the cache window is a free cache hit rather than a repeat charge. **Pagination style varies by endpoint.** Page-paginated endpoints start at `page=1`; cursor-paginated ones return a cursor to pass back. `ads/search` uses `paginationToken` instead of either. ## Labels These lists label their rows by default, at no extra credit. On `/v1/linkedin/search/posts`, each row carries `sponsored`, `intent` and `niche`. `/v1/linkedin/search/posts` also scores each row's relevance to your `query` on `computed.relevance`, without dropping or reordering anything; add `relevance=filter` to drop the rows that are about something else, still free. Send `judgments=off` for the page without them. See [Labels](/docs/labels.md) for the field shapes, rows still `pending`, and the labels you can add with `label=`. ## Next steps - [Pagination](/docs/pagination.md): Cursor and page walks, and what has_more actually means. - [Credits](/docs/credits.md): What each tier costs and when a call is refunded. - [API reference](/docs/api-reference.md): Every parameter and response field, endpoint by endpoint. - [Facebook](/docs/facebook.md): The other full ad library, plus pages, groups, and events. - [Reddit](/docs/reddit.md): Where B2B buyers say what they will not say on LinkedIn.