LinkedIn Search APIPeople · Companies · Jobs · Posts

LinkedIn Search API for People, Companies, Jobs and Posts

Search public LinkedIn profiles, companies, jobs and posts through EnvoAPI GET endpoints. Use one API key to receive structured JSON, without supplying a LinkedIn login or cookie. Choose a route for your application or data pipeline, then send your first request with cURL or Python.

GET/v1/profiles/search?keywords=head+of+salesGet your free API key

Read the API reference100 free credits · No credit card required

EnvoAPI is an independent, unofficial third-party API. It is not affiliated with or endorsed by LinkedIn.

Endpoints

Choose a LinkedIn search endpoint for your data

Choose a route by the records you need and the input you already have. Successful responses from these API endpoints contain data for results and meta for pagination and request cost.

FindInputGET pathResult arrayReference
PeopleKeywords, optional profile filters/v1/profiles/searchdata.profilesPeople search
Companies by keywordKeywords, optional company filters/v1/companies/searchdata.companiesCompany keyword search
Companies by domainWebsite domain/v1/companies/by-domaindata.companiesCompany domain search
JobsKeywords, optional job filters/v1/jobs/searchdata.jobsJob search
Posts by keywordKeywords, optional content filters/v1/search/postsdata.postsPost keyword search
Posts by hashtagHashtag without #/v1/search/posts/by-hashtagdata.postsHashtag search
Mixed resultsKeywords/v1/searchdata.resultsMixed keyword search
Use mixed search to explore results across types. Use a specialized route when you need its people, company, job or post filters.
  1. Create your EnvoAPI account and get an API key.

  2. Store the key as ENVO_API_KEY in your backend environment. Keep it out of browser code and source control.

  3. Send GET requests to https://api.envoapi.com with Authorization: Bearer and Accept: application/json.

For a local Bash session, enter your key at the prompt below. The input stays hidden, and the exported variable works with every example on this page.

Set your backend API keybash
read -r -s -p "EnvoAPI key: " ENVO_API_KEYexport ENVO_API_KEY

The authentication guide covers key handling. For lookups and data beyond search, explore the LinkedIn data API.

Post search

LinkedIn post search API: find posts by keyword or hashtag

Search public posts with a keyword query or a hashtag. These routes read posts; they do not publish content or manage LinkedIn accounts.

SearchInputFilters
Keywordkeywords=Microsoftsort=relevance or recent; postedWithin=day, week or month; contentType
Hashtaghashtag=microsoftUse hashtag and offset; keyword-route filters are not supported

The keyword route accepts contentType values videos, photos, jobs, liveVideos, documents and collaborativeArticles. Omit the parameter to leave content type unfiltered.

Search posts by keyword

GET/v1/search/postsbash
curl --fail-with-body --get 'https://api.envoapi.com/v1/search/posts' \  --header "Authorization: Bearer $ENVO_API_KEY" \  --header 'Accept: application/json' \  --data-urlencode 'keywords=Microsoft' \  --data-urlencode 'offset=0' \  --data-urlencode 'sort=relevance'
As of October 2026, each keyword or hashtag post search costs 1 credit per request or page.
Synthetic keyword response: post text, author and engagement counts are illustrative.
Response200synthetic examplejson
{  "data": {    "posts": [      {        "url": "https://www.linkedin.com/posts/example-systems_example-post-1234567890/",        "text": "An illustrative post mentioning Microsoft.",        "author": {          "kind": "company",          "name": "Example Systems",          "headline": null,          "url": "https://www.linkedin.com/company/example-systems/"        },        "contentType": "text",        "isRepost": false,        "reactionCount": 0,        "commentCount": 0,        "repostCount": 0      }    ]  },  "meta": {    "paging": {      "offset": 0,      "limit": 10,      "returned": 1,      "hasMore": false,      "nextOffset": null    },    "creditCost": 1  }}

Search posts by hashtag

GET/v1/search/posts/by-hashtagbash
curl --fail-with-body --get 'https://api.envoapi.com/v1/search/posts/by-hashtag' \  --header "Authorization: Bearer $ENVO_API_KEY" \  --header 'Accept: application/json' \  --data-urlencode 'hashtag=microsoft' \  --data-urlencode 'offset=0'
Synthetic hashtag response: this route has its own author and paging schema.
Response200synthetic examplejson
{  "data": {    "posts": [      {        "url": "https://www.linkedin.com/posts/example-systems_example-hashtag-post-1234567890/",        "text": "An illustrative post with #microsoft.",        "author": {          "kind": "company",          "name": "Example Systems",          "url": "https://www.linkedin.com/company/example-systems/"        },        "contentType": "text",        "isRepost": false,        "reactionCount": 0,        "commentCount": 0,        "repostCount": 0      }    ]  },  "meta": {    "paging": {      "offset": 0,      "limit": 10,      "returned": 1,      "total": 1,      "hasMore": false,      "nextOffset": null    },    "creditCost": 1  }}
  • Select a post

    Use text, author and url to select a post. The response reports contentType, isRepost and engagement counts. Response content types use labels such as text or video, distinct from the keyword filter values.

  • Schema

    Keyword results include nullable author.headline; hashtag results omit that field and include meta.paging.total. Follow each route's schema when parsing the response.

  • Pagination

    Both routes accept offsets from 0 to 10,000. While hasMore is true, use meta.paging.nextOffset for the next request within that range. See post keyword search and hashtag search for the complete contracts.

Search UI

Build search with filters, typeahead and pagination

Turn an API response into a search experience for sales, recruiting or account research. Start with a keyword, resolve filter IDs, show compact result cards, then enrich the records a user chooses. These examples keep the EnvoAPI key on your backend and separate valid empty results from failed requests.

  1. Build a people search experience

    Show each person's name, headline and available location. Keep the returned LinkedIn URL for a subsequent lookup; hide missing optional fields. For company results, show the name, available logo and summary, then offer a clear action to view or enrich the selected account.

    Node.js backend adapter for people and location search

    Add Express and Zod to your Node.js application. Save this example as server.mjs and run node server.mjs with ENVO_API_KEYset on the server. Route your frontend's /api/search requests to this backend. It validates inputs and the response fields used by the UI, forwards rate limit errors and makes no automatic retries.

    server.mjsjavascript
    import express from "express";import { z } from "zod";const app = express();const key = process.env.ENVO_API_KEY;if (!key) throw new Error("Set ENVO_API_KEY on the server.");const peopleInput = z.object({  q: z.string().trim().min(1).max(200),  location: z.string().trim().min(1).max(200).optional(),  offset: z.coerce.number().int().min(0).max(50).default(0),});const locationInput = z.object({  q: z.string().trim().min(2).max(200),});const peopleResponse = z.object({  data: z.object({    profiles: z.array(z.object({      name: z.string(),      headline: z.string().nullable(),      location: z.string().nullable(),      linkedinUrl: z.string().nullable(),    })),  }),  meta: z.object({    paging: z.object({      hasMore: z.boolean(),      nextOffset: z.number().nullable(),    }),  }),});const locationResponse = z.object({  data: z.object({    locations: z.array(z.object({ id: z.string(), name: z.string() })),  }),});async function callEnvo(path, params, schema, res) {  const response = await fetch("https://api.envoapi.com" + path + "?" + params, {    headers: { Authorization: "Bearer " + key, Accept: "application/json" },    signal: AbortSignal.timeout(15000),  });  if (!response.ok) {    const retryAfter = response.headers.get("Retry-After");    if (retryAfter) res.set("Retry-After", retryAfter);    res.status(response.status).json({ error: "Search request failed." });    return null;  }  return schema.parse(await response.json());}// Mount these routes behind your application's authentication and rate limits.app.get("/api/search/people", async (req, res) => {  const input = peopleInput.safeParse(req.query);  if (!input.success) return res.status(400).json({ error: "Invalid search." });  const { q, location, offset } = input.data;  const params = new URLSearchParams({ keywords: q, offset: String(offset) });  if (location) params.set("location", location);  try {    const payload = await callEnvo("/v1/profiles/search", params, peopleResponse, res);    if (!payload) return;    res.json({ people: payload.data.profiles, pagination: payload.meta.paging });  } catch {    res.status(502).json({ error: "Search is temporarily unavailable." });  }});app.get("/api/search/locations", async (req, res) => {  const input = locationInput.safeParse(req.query);  if (!input.success) return res.status(400).json({ error: "Invalid location query." });  const params = new URLSearchParams({ keywords: input.data.q });  try {    const payload = await callEnvo("/v1/search/locations", params, locationResponse, res);    if (!payload) return;    res.json({ suggestions: payload.data.locations });  } catch {    res.status(502).json({ error: "Location suggestions are unavailable." });  }});app.listen(3001);
  2. Add typeahead and dynamic filters

    Require at least two characters and debounce location suggestions by 300 ms. Cancel pending requests when the input changes, and ignore stale responses. When a user selects a suggestion, store its id as the search filter. Render selected filters as removable chips with a Clear all action; typing a new location clears the previous selection. Submit the full search when the user applies the query and filters.

    Location discovery uses /v1/search/locations with keywords and returns data.locations. Industry discovery uses /v1/search/industries with query and returns data.industries. Pass their resolved IDs into supported search filters.

    Frontend typeahead with debounce and request cancellation

    Connect the render, loading and error callbacks to your UI. Call input(value) on input changes and destroy() when unmounting. Render suggestions as accessible selectable options with keyboard support and use text nodes for API values.

    location-typeahead.tstypescript
    type LocationSuggestion = { id: string; name: string };type TypeaheadUI = {  render: (suggestions: LocationSuggestion[]) => void;  loading: (active: boolean) => void;  error: (message: string) => void;};export function createLocationTypeahead(ui: TypeaheadUI) {  let timer: ReturnType<typeof setTimeout> | undefined;  let active: AbortController | undefined;  function cancel() {    clearTimeout(timer);    active?.abort();    active = undefined;    ui.loading(false);  }  function input(value: string) {    cancel();    ui.render([]);    ui.error("");    const query = value.trim();    if (query.length < 2) return;    timer = setTimeout(async () => {      const controller = new AbortController();      active = controller;      ui.loading(true);      try {        const response = await fetch(          "/api/search/locations?q=" + encodeURIComponent(query),          { signal: controller.signal },        );        if (!response.ok) throw new Error("Location suggestions unavailable.");        const result = await response.json();        if (active === controller) ui.render(result.suggestions);      } catch {        if (active === controller) ui.error("Unable to load suggestions. Try again.");      } finally {        if (active === controller) {          ui.loading(false);          active = undefined;        }      }    }, 300);  }  return { input, destroy: cancel };}
  3. Handle pagination and UI states

    • Initial loading: show progress or result-card skeletons.
    • Loading more: retain existing results and disable duplicate requests.
    • No results: suggest broader keywords or fewer applied filters.
    • Request failed: show a retry action and retain the current results.
    • End of results: remove Load more when there is no valid next offset.

    For people search, use meta.paging.nextOffset while hasMoreis true, within the 0–50 offset range. Keep the submitted query and filters unchanged across pages. A new search resets the list and cancels the previous loader. On HTTP 429, respect the backend's Retry-After header before offering a retry.

    Load more pagination with loading and error callbacks

    Create a loader for the submitted query and selected location ID. Call loadMore() for the first page and subsequent pages. Append returned cards in your UI and hide Load more when pagination reports no further page or its next offset exceeds 50.

    people-pagination.tstypescript
    type PeoplePage = {  people: Array<{    name: string;    headline: string | null;    location: string | null;    linkedinUrl: string | null;  }>;  pagination: { hasMore: boolean; nextOffset: number | null };};// Create a new loader for each submitted query and selected location ID.// Call destroy() when replacing the search or unmounting the view.export function createPeopleLoader(  query: string,  locationId: string | undefined,  ui: {    append: (page: PeoplePage) => void;    loading: (active: boolean) => void;    error: (message: string) => void;  },) {  let offset: number | null = 0;  let active: AbortController | undefined;  let disposed = false;  async function loadMore() {    if (disposed || active || offset === null) return;    const controller = new AbortController();    active = controller;    ui.error("");    ui.loading(true);    const params = new URLSearchParams({ q: query, offset: String(offset) });    if (locationId) params.set("location", locationId);    try {      const response = await fetch("/api/search/people?" + params, {        signal: controller.signal,      });      if (!response.ok) throw new Error("Search request failed.");      const page: PeoplePage = await response.json();      if (disposed) return;      ui.append(page);      const next = page.pagination.nextOffset;      offset = page.pagination.hasMore && next !== null && next > offset && next <= 50        ? next : null;    } catch {      if (!disposed) ui.error("Unable to load results. Try again.");    } finally {      active = undefined;      if (!disposed) ui.loading(false);    }  }  function destroy() {    disposed = true;    active?.abort();    ui.loading(false);  }  return { loadMore, destroy };}
  4. Search, refine and enrich selected records

    A sales user searches for Head of Sales, selects Singapore from location suggestions and compares the returned people cards. Opening a selected card triggers a profile detail lookup. If the workflow starts with target accounts, use company search first, then retrieve company details. Enrich records when a user opens or saves them, and offer downstream actions such as adding a record to a CRM or assigning it to a recruiter.

Credits & limits

Check credit costs and API rate limits

As of October 2026, you can start with 100 one-time free credits without a payment card, or choose Launch at $49 per month. The plans below show credits and account request limits.

PlanPriceCreditsRequests per minute
Free$0100, one time10
Launch$49/month10,000/month60
  • Per request

    Each search request costs 1 credit, including a request for another page. The charge is per request, regardless of how many records appear on that page. Read meta.creditCost for the returned request cost.

  • On Launch

    On Launch, $49 / 10,000 = $0.0049 per credit when you use the full monthly allocation. One search page plus one base detail lookup uses 2 credits: 2 × $0.0049 = $0.0098 in allocated subscription cost.

  • Budget

    For p search pages and n base profile or company detail lookups, budget p + n credits. Add filter-ID and typeahead requests separately, along with optional enrichment costs. This calculation does not set a price for every returned record.

API rate limits are shared across your account's keys. Use X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset response headers to manage capacity. Handle HTTP 429 before scheduling another request.

Check plans and billing and the credit costs table for current terms.

Check what search returns

  • Public, read-only records: search does not provide private profiles, messaging, posting or job publishing.

  • Bounded results: EnvoAPI does not provide bulk dataset downloads or guarantee every matching record.

  • Nullable data: public sources do not expose every field. Handle null values and empty arrays; do not assume email or phone availability.

  • API-controlled page sizes: do not send limit, count, maxPages or fetchAll. Follow returned pagination and the offset or start bounds for each route.

  • Route-specific metadata: search responses contain meta.paging and meta.creditCost. They do not contain meta.sections.

When you move to profile details lookup, inspect its meta.sections for section status. Use that metadata for the lookup response, rather than judging search completeness with it.

Get your free API key and try the route that fits your input.

Access

Understand LinkedIn API access and data responsibilities

As of October 2026, LinkedIn's official APIs use OAuth and product-specific permissions; EnvoAPI uses its own key to read public data independently. Choose access according to the operation you need.

Review our data responsibility and trust policies when designing your downstream workflow.

Start with a search

Your next workflow starts with one request.

Choose your search route, get a key and send the sample request from your backend.

100 free credits · No credit card required