LinkedIn Profile Scraper — Public Profile Data by URL
Pricing
from $6.08 / 1,000 profile founds
LinkedIn Profile Scraper — Public Profile Data by URL
LinkedIn profile scraper with no cookies and no account. Give it public LinkedIn profile links and get one flat row each: name, headline, location, current title and company, roles with dates, schools, follower and connection counts, and the photo. Nothing found, nothing charged.
Pricing
from $6.08 / 1,000 profile founds
Rating
0.0
(0)
Developer
Adrian Voss
Maintained by CommunityActor stats
1
Bookmarked
1
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
LinkedIn Profile Lookup: LinkedIn Profile Scraper, No Cookies
Give this actor a list of public LinkedIn profile links and it fetches each one the way a
signed-out browser does, then hands you a flat row per person: name, headline, location, current
job title and company, the current roles the page lists with their start and end years, schools
with years, follower and connection counts, and the profile photo. No login. No cookies. No
li_at session token to paste in, no account of yours at risk, and nothing read from behind
LinkedIn's sign-in wall.
The data comes from the schema.org Person block LinkedIn itself publishes in the page source of
every public profile, so a row is the profile's own structured description of itself rather than a
guess assembled from scraped text. When LinkedIn decides a request should sign in first, the actor
says so in the row's status and charges you nothing.
Who it's for
Recruiters and sourcers who already have a shortlist of profile links and need them as a table instead of forty browser tabs. Sales teams who capture a LinkedIn URL on a form or in a CRM field and want the name, title, company and location filled in behind it before the first email goes out. Founders checking that an inbound lead's title still matches what their CRM said six months ago. And anyone building an agent or a workflow that has a profile link and needs the person behind it in a shape a script can read, without keeping a logged-in LinkedIn session alive somewhere.
If you do not have the profile links yet, this is the wrong tool: it looks up profiles you already name, it does not search LinkedIn for people matching a description.
Why this one
- No cookies, no session token, no account. Every request is the plain public page. You are
never asked to hand over a
li_atcookie, and no LinkedIn account of yours can be restricted because of a run here. - Read from LinkedIn's own structured data. The row is built from the
schema.orgPerson block in the page source, not from CSS selectors chasing a design that changes monthly. Field names stay put, which is what an agent or a Clay column needs. - It tells you when the page was reduced. LinkedIn often answers with a trimmed version of the
profile in which job titles and past employers are replaced by rows of asterisks. Those never
reach your table: the fields come back empty and the row is flagged
masked: true, so you can filter or re-run instead of importing placeholder text into a CRM. See the note under "What you get" for how often that happens, measured rather than guessed. - A block is a labelled miss, not a silent hole. If LinkedIn asks the request to sign in, the
row says
status: BLOCKEDwith a plain explanation and costs nothing. A profile that no longer exists saysNOT_FOUND. A company page pasted by mistake saysBAD_FORMAT. - Never charged for a miss. Billing fires only on a row where a real profile came back.
- Residential proxy by default. LinkedIn's sign-in wall is driven by the reputation of the IP asking, so the actor ships with a residential proxy already selected, and retries a blocked request on a fresh address rather than handing you the failure.
What you get
One row per profile you submit. Every row starts with the same five fields, then adds the profile fields you selected in "Which columns do you want?" (all of them by default).
| Field | Type | Description |
|---|---|---|
query | text | The profile you submitted, echoed back |
found | boolean | true when a public profile was read |
status | text | OK on a hit, or NOT_FOUND, BLOCKED, BAD_FORMAT, REQUEST_FAILED on a miss |
message | text | Plain-English reason for a miss, present only when found is false |
scrapedAt | ISO 8601 datetime | When the page was fetched |
profileUrl | link | Canonical profile link, https://www.linkedin.com/in/<public id> |
publicId | text | The part of the link after /in/, LinkedIn's stable public identifier |
fullName | text | Name as the public page shows it |
headline | text | The one-line headline under the name |
location | text | Location as shown, e.g. Seattle, Washington, United States |
currentTitle | text | Job title of the first current role listed |
currentCompany | text | Employer for that role |
currentCompanyUrl | link | That employer's LinkedIn company page |
experience | array | Current roles the public page lists, each {title, company, companyUrl, location, startDate, endDate} |
education | array | Schools the public page lists, each {school, degree, startDate, endDate} |
followers | number | Exact follower count, taken from the page's structured data rather than the rounded "41M" the top card prints |
connections | text | Connection count as LinkedIn displays it, e.g. 500+, which is why it is text and not a number |
profileImageUrl | image | Profile photo on LinkedIn's media CDN |
source | text | jsonld when the row came from the page's structured-data block, html when it was assembled from the top card instead |
masked | boolean | true when LinkedIn served the reduced page and hid some titles or employers |
Three honest limits on that table, stated here rather than left for you to discover in the data.
education[].degree is null, because the logged-out page does not publish degree or field of study
at all. experience covers the roles the public page presents as current; full career history is
shown only to signed-in members and is not fetched. And there is no skills column, because no
Skills section appeared in any of the fifteen public profile fetches this actor was built and
tested against, so offering one would mean shipping a column that is always empty.
The bigger caveat is masked. LinkedIn decides per request whether to serve the complete profile
page or a reduced one, and across those same fifteen fetches, of thirteen different profiles, on
2026-09-08, the complete page came back four times. That split did not track the route (a plain
home connection got it 2 of 7 times, a residential proxy 1 of 6) and it did not track the profile
either: the same profile served both versions inside fifteen minutes. On a reduced page the name,
location, current employer, follower and connection counts, schools and photo still come through;
the headline and job titles do not, and the row is flagged masked: true so you can see exactly
which rows those are. Plan for headline and currentTitle to be present on a minority of rows,
not on all of them.
Pricing
$8 per 1,000 profiles, plus a $0.00005 start fee. Misses (found:false) are never charged.
A run of 1,000 profiles costs about $8 if every one comes back, less when some are blocked or
gone, and less again on a paid Apify plan. Compare that with a per-seat enrichment platform billing
$0.08 to $0.40 per enriched row on top of a licence, or with LinkedIn profile actors listed at
$100 per 1,000. Rows that come back found: false, for any reason, are free.
Use it from Clay, n8n, Make, or an AI agent
This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.
curl "https://api.apify.com/v2/acts/accountable_eel~linkedin-profile-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \-X POST \-H "Content-Type: application/json" \-d '{"profileUrls":["https://www.linkedin.com/in/williamhgates","satyanadella"]}'
n8n. Add an HTTP Request node: Method POST, URL https://api.apify.com/v2/acts/accountable_eel~linkedin-profile-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>, Body Content Type JSON, JSON Body {"profileUrls":["https://www.linkedin.com/in/williamhgates","satyanadella"]} (swap in an expression from an earlier node for a real value).
Clay. Add an "HTTP API" column: Method POST, URL https://api.apify.com/v2/acts/accountable_eel~linkedin-profile-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>, Body {"profileUrls":["{{profile}}"]}, mapping the row's profile into the profileUrls array.
MCP. In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "LinkedIn Profile Scraper, No Cookies | Apify" — the agent will find and run this actor.
How to use
- In the Apify Console. Open the actor page and click Start — the
profileUrlsfield is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found. - Via the API. Call it directly with a POST request — no Console needed once you have an API token:
curl "https://api.apify.com/v2/acts/accountable_eel~linkedin-profile-lookup/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \-X POST \-H "Content-Type: application/json" \-d '{"profileUrls":["https://www.linkedin.com/in/williamhgates","satyanadella"]}'
- On a schedule. Save this actor as an Apify Task with the input you want, then add a Schedule (hourly, daily, weekly) so it runs on its own — no server of your own required.
- Open the Input tab and paste your profile links into "LinkedIn profiles to look up", one per
line. A full link, a bare
linkedin.com/in/..., or just the identifier after/in/all work. - Leave "Try it first" on for the first run. It stops after 5 profiles so you can see the shape of the output before spending on the whole list.
- Leave the proxy on residential. It is the default, and it is the single biggest factor in how many of your profiles come back rather than blocked.
- Keep "Max concurrency" low, 2 to 3, on a long list. Speed here buys blocks, not throughput.
- Optionally narrow "Which columns do you want?", or use "Only keep rows containing" and "Skip rows containing" to filter by keyword, for example to keep only profiles whose row mentions a city or a job title.
- Turn "Try it first" off and press Start for the full list.
Input
{"profileUrls": ["https://www.linkedin.com/in/williamhgates","satyanadella"]}
One profile per line. Paste the full profile link, or just the part after /in/. Accepted formats: https://www.linkedin.com/in/williamhgates, linkedin.com/in/williamhgates, williamhgates.
Output
One row per profile submitted:
A profile that could not be read comes back as a row with "found": false, a status, and a
message explaining which of the four reasons it was, and is never charged.
Tips
- Run a handful of profiles you know by heart first. It is the fastest way to confirm the fields land where you expect before you point a long list at it.
- Filter on
masked: truerather than trusting an emptycurrentTitle. A masked row is LinkedIn trimming the page for that request, not the member leaving the field blank. If the headline matters to you, treat a masked row as "ask again later", not as "this person has no headline". publicIdis the join key, notfullName. People change their display name and their headline; the identifier after/in/is what stays the same.- A wave of
BLOCKEDrows means the addresses you are running from are being asked to sign in. Lower "Max concurrency", leave residential proxy on, and re-run the blocked rows later. They cost you nothing the first time. - Store
followersas a number andconnectionsas text. LinkedIn caps the connection display at500+, so forcing it into a numeric column silently loses the distinction between 500 and 30,000.
vs. alternatives
| What it costs | What you get | Trade-off | |
|---|---|---|---|
This actor (linkedin-profile-lookup) | $0.008 per profile found at the FREE tier, less on paid tiers, $0.00005 actor start, nothing for a miss | One flat row per public profile: name, location, current company, current roles with dates, schools with dates, follower and connection counts, photo, and the headline and job title on the rows LinkedIn serves in full | Public profile pages only. Current roles, not full career history; no degree, field of study or skills, because the logged-out page does not publish them; and the headline and job title arrive on a minority of rows, flagged by masked. It looks up profiles you name; it does not search for people. |
| HarvestAPI's LinkedIn profile actors | linkedin-profile-search is listed at $100 per 1,000 profiles | A whole family: profile lookup, people search, company and post coverage, and the category's most-used profile scraper at a 4.77 rating | If you need to find people by criteria rather than look up links you already hold, that is a search product and this is not one. |
| Clay | $0.08 to $0.40 per enriched row in credits, on top of a seat | A whole enrichment workspace: waterfalls across dozens of providers, plus the table and the sequencing around it | If you want one place that does everything and you are not counting rows, that is Clay. This is one column, priced per column, callable from Clay via its HTTP step. |
| Doing it yourself | Your time, plus residential proxy spend, plus keeping up with a sign-in wall that moves | The same fields | What this absorbs: the structured-data parsing, the reduced-page detection so asterisks never reach your table, the block-versus-missing-versus-bad-input distinction, and retrying a blocked address instead of failing the row. |
Prices for third-party tools are their published list prices as of September 2026 and are not tracked here. Check the vendor before relying on the comparison.
FAQ
Do I need a LinkedIn account, or to paste in a cookie?
No. That is the point of this actor. It reads the public profile page exactly as a signed-out
browser gets it. You are never asked for credentials, a session cookie or a li_at token, and no
account of yours is put at risk by a run.
Why did a row come back with found: false?
One of four reasons, and status says which. BLOCKED: LinkedIn asked that request to sign in
instead of serving the page. NOT_FOUND: no public profile exists at that address any more, or the
member has turned public visibility off. BAD_FORMAT: the input was not a member profile, most
often a /company/ page pasted by mistake. REQUEST_FAILED: the request did not complete after
retries. None of the four is charged.
What is masked: true?
LinkedIn answers some requests with a reduced version of the profile in which job titles and past
employers are replaced by rows of asterisks. Rather than pass that through, the actor leaves those
fields empty and sets masked: true. Name, location, current employer, follower and connection
counts, schools and photo still come through, so the row is worth having. Expect most rows to be
flagged this way: on the fifteen fetches behind this build, the complete page came back four times.
The decision looks like a per-request one on LinkedIn's side rather than a property of the profile
or of where you run from, so re-running a masked row later is worth a try, and it costs you a
second charge for that profile.
Why is education[].degree null?
Because the logged-out profile page does not publish it. Degree and field of study are shown to
signed-in members only. The field is kept in the row so the output shape never changes between
runs, but it is left null rather than filled from a guess.
Why is there no skills column? Because the logged-out page has no Skills section. None of the fifteen public profile fetches this actor was built against carried one, in either version of the page, so a skills column would be empty on every row. It is better to say that here than to ship the column.
Does it return full career history?
No. The public page shows the roles a member presents as current, and that is what experience
contains. Everything earlier is behind the sign-in wall, and this actor does not go there.
Am I charged for a miss? No. Billing fires only when a real profile was read. Blocked rows, missing profiles and malformed input all produce a row explaining themselves, unless you turn on "Hide rows with no result", and none of them cost anything.
Do I need to configure a proxy? It is already configured. Residential proxy is the shipped default because LinkedIn's sign-in wall keys on the reputation of the address asking. You can change the group in the Advanced section, but lowering it is the most common reason a list comes back mostly blocked.
Is this a live fetch or a stored database? Live. Every run fetches the current page. Nothing is stored between runs, so a member who changed jobs yesterday shows the new one today.
Can an AI agent call this directly? Yes. It is on the Apify MCP server, so an agent in Claude, Cursor or any other MCP client can find and run it by name, and the REST endpoint above works from any script or workflow tool.
Personal data, and your responsibility
This actor reads public LinkedIn profile pages, which are personal data about identifiable people. It reads nothing behind the sign-in wall and it holds nothing between runs: each row is fetched live and written to your own dataset, which only you control.
Whether you may collect, store and use that data is a question about you and your purpose, not about this tool. If you are in the EU or the UK, or your subjects are, the GDPR applies to what you do next: you need a lawful basis, and duties around transparency, retention, and honouring a request to be erased. LinkedIn's own User Agreement also restricts automated collection, and it binds anyone using it under an account. Nothing here is legal advice, and running this actor is not a determination that your use is lawful. That call, and the compliance work behind it, is yours.
Related actors
- LinkedIn Jobs Search Lookup run a public LinkedIn job search by keyword and location and get one row per posting.
- Company Domain Enrichment turn the employer you just found into firmographics: registration, DNS, tech stack, hiring signal.
- ATS Jobs Unified Lookup check what a company is hiring for across the six major applicant tracking systems.