Introducing Supportive - Profiles in your Gmail Learn more

Ultimate Guide to the LinkedIn API: Person Profile Enrichment API, with Python Examples
profile api

Ultimate Guide to the LinkedIn API: Person Profile Enrichment API, with Python Examples

curl -G \
  "https://nubela.co/api/v1/employee/profile" \
  --data-urlencode "first_name=Patrick" \
  --data-urlencode "last_name=Collison" \
  --data-urlencode "employer_website=https://stripe.com" \
  -H "Authorization: Bearer demo-api-key"

If you are building against the LinkedIn API for person profile enrichment, start there.

That demo call hits NinjaPear's Person Profile endpoint through the Employee API. It returns enriched profile data using public web sources and AI extraction, not LinkedIn scraping.

Now the important part.

If you are searching for a LinkedIn People Profile API, there are really two questions you need answered.

First, what can the official LinkedIn API actually do?

Second, when does it stop being the right tool for the job?

I have unusually strong opinions on this because I earned them the expensive way. I used to be the CEO of Proxycurl. We grew it to ~10M ARR and, for a time, it was probably the largest LinkedIn scraping service in the market. I spent years inside the machinery of LinkedIn profile data, official and unofficial. So this guide is not theory. It is the version I wish more developers got on day one.

What the LinkedIn API actually does

LinkedIn shut down broad public API access years ago. If you want meaningful access today, you are dealing with approvals, OAuth scopes, product restrictions, and business qualification.

That does not mean the LinkedIn API is bad. It means it is opinionated. It is designed around LinkedIn's platform interests, not around every enrichment workflow a sales, recruiting, or data product team might want.

At a high level, the official LinkedIn API is still good at a few specific things:

  • Sign In with LinkedIn
  • member-authorized profile access
  • posting and social actions
  • organization workflows
  • approved partner use cases

If your product needs native LinkedIn functionality, use LinkedIn.

Examples:

  • users sign in with LinkedIn
  • users post to LinkedIn from your app
  • your workflow depends on authenticated member actions
  • you are already in an approved LinkedIn program

That is the cleanest use case for the official API. No argument from me.

Where person profile enrichment gets tricky

Most people searching for a LinkedIn API People Profile API are not actually trying to build a LinkedIn app.

They are trying to:

  • enrich a lead in a CRM
  • find the right buyer at a company
  • turn a work email into a usable person profile
  • turn a title plus company into an actual human being
  • pull professional identity data into an internal workflow

That is a different problem.

When I was running Proxycurl, this was the mistake I saw over and over. Teams said they needed LinkedIn data. What they usually needed was accurate structured professional identity data for person profile enrichment. Those are not the same thing.

The official LinkedIn API can still help, but only within the constraints LinkedIn allows.

What most developers discover

The first trap is simple.

A lot of developers assume Sign In with LinkedIn returns a detailed member profile. Usually it does not. The basic member-authorized flow typically gets you a small set of fields such as:

  • first name
  • last name
  • email address
  • profile photo

That is fine for auth. It is weak for enrichment.

And there is a more annoying catch.

Sign In with LinkedIn also does not give you the signed-in user's LinkedIn profile URL, and it does not give you the person's full profile object in the way most developers expect. If your plan is, "I will let the user sign in, grab their LinkedIn profile URL, then use that as the key for person profile enrichment," you are already at a dead end.

This is the part a lot of guides hand-wave away. I will not. For person profile enrichment, auth data is not profile data.

If you need work history, education, social handles, current company context, or a way to resolve a person from role + company, the official LinkedIn API is usually not the easy path people hope it is. It is especially not a good fit if your real requirement is person profile enrichment across prospects or candidates you do not control.

LinkedIn API methods

For profile access, there are really two lanes.

Method 1: 3-legged OAuth

This is the normal member-authorized flow. A user grants your app permission, and you fetch the profile data available to that permission set.

This is useful when a user is inside your app and intentionally connects their own LinkedIn account.

It is not useful if you want to enrich arbitrary prospects at scale.

Method 2: approved profile access

This is the path people usually mean when they talk about the real LinkedIn People/Profile API.

The catch is the catch: approvals, business qualification, product restrictions, and commercial friction.

If you are in recruiting tech, sales tech, or anything adjacent to where LinkedIn has its own product interests, life tends to get more complicated.

Python example: Sign In with LinkedIn

I am keeping this part because the OAuth mechanics are still useful.

import requests
import secrets

LINKEDIN_CLIENT_ID = "your_client_id"
LINKEDIN_CLIENT_SECRET = "your_client_secret"
LINKEDIN_REDIRECT_URI = "https://yourapp.com/callback"


def generate_authorization_url():
    auth_url = "https://www.linkedin.com/oauth/v2/authorization"
    return requests.Request(
        "GET",
        auth_url,
        params={
            "response_type": "code",
            "client_id": LINKEDIN_CLIENT_ID,
            "redirect_uri": LINKEDIN_REDIRECT_URI,
            "state": secrets.token_hex(8).upper(),
            "scope": " ".join(["r_liteprofile", "r_emailaddress"]),
        },
    ).prepare().url


def get_access_token(authorization_code):
    token_url = "https://www.linkedin.com/oauth/v2/accessToken"
    response = requests.post(
        token_url,
        data={
            "grant_type": "authorization_code",
            "code": authorization_code,
            "redirect_uri": LINKEDIN_REDIRECT_URI,
            "client_id": LINKEDIN_CLIENT_ID,
            "client_secret": LINKEDIN_CLIENT_SECRET,
        },
    )
    response.raise_for_status()
    return response.json()["access_token"]


def get_profile(access_token):
    profile_url = "https://api.linkedin.com/v2/me"
    response = requests.get(
        profile_url,
        headers={"Authorization": f"Bearer {access_token}"},
    )
    response.raise_for_status()
    return response.json()

This works for authentication.

It is not what most GTM, recruiting, or enrichment products actually need.

If you are hoping this flow gets you a signed-in member's LinkedIn profile URL plus a rich person record you can use for downstream enrichment, it does not. That is why so many teams burn a week here and then quietly change direction.

Python example: approved LinkedIn profile access

If you are on an approved path that allows broader profile retrieval, the flow looks more like this:

import requests

LINKEDIN_CLIENT_ID = "your_client_id"
LINKEDIN_CLIENT_SECRET = "your_client_secret"


def get_access_token():
    token_url = "https://www.linkedin.com/oauth/v2/accessToken"
    response = requests.post(
        token_url,
        data={
            "grant_type": "client_credentials",
            "client_id": LINKEDIN_CLIENT_ID,
            "client_secret": LINKEDIN_CLIENT_SECRET,
        },
    )
    response.raise_for_status()
    return response.json()["access_token"]


def get_profile(access_token, profile_id):
    profile_url = f"https://api.linkedin.com/v2/people/{profile_id}"
    response = requests.get(
        profile_url,
        headers={
            "Authorization": f"Bearer {access_token}",
            "X-RestLi-Protocol-Version": "2.0.0",
        },
    )
    response.raise_for_status()
    return response.json()

Two caveats.

First, this is conceptually useful, but actual access depends on LinkedIn approvals and product terms.

Second, LinkedIn can be the right platform API and still be the wrong person profile enrichment API for your use case.

What the official LinkedIn API is good at

I want to be precise here, because a lot of content on this topic gets lazy.

The official LinkedIn API is good when all three of these are true:

  1. you need official platform support
  2. the member is knowingly participating in the workflow
  3. the workflow is native to LinkedIn itself

That includes:

  • sign-in
  • content publishing
  • organization-level actions
  • approved partner features

If that is your world, do not fight it. Use LinkedIn.

The problems start when developers try to stretch that model into broad outbound enrichment, prospecting, recruiting discovery, or CRM backfill for people who have never touched their product.

That is where people start looking for a person profile enrichment API, even if they use LinkedIn-flavored language to describe it.

Start with NinjaPear's Person Profile Endpoint

Now that we have covered the official LinkedIn API properly, here is the practical alternative for the enrichment use case.

Run this:

curl -G \
  "https://nubela.co/api/v1/employee/profile" \
  --data-urlencode "first_name=Patrick" \
  --data-urlencode "last_name=Collison" \
  --data-urlencode "employer_website=https://stripe.com" \
  -H "Authorization: Bearer demo-api-key"

That call hits NinjaPear's Person Profile endpoint through the Employee API.

It returns enriched profile data using public web sources and AI extraction, not LinkedIn scraping.

That distinction matters.

It means you can get the shape of data most people want from a LinkedIn People Profile API, or more specifically a person profile enrichment API, without inheriting the scraping mess that usually comes with it.

Why I am confident saying this

This is where my opinion gets less academic.

I used to run Proxycurl, the company that became one of the best-known LinkedIn scraping businesses in the market. We scaled it to ~10M ARR. For a while, if you were buying LinkedIn profile data from a third party, there was a decent chance you were buying from someone whose architecture rhymed with ours.

So when I say LinkedIn profile data is not just a coding problem, I mean that literally.

It is:

  • an access problem
  • a scaling problem
  • a freshness problem
  • a legal problem
  • and eventually a business model problem

That is exactly why NinjaPear went in a different direction.

Why scraping LinkedIn gets ugly

This is where I have earned the right to be blunt.

When I hear companies say, "we scrape public LinkedIn profiles at scale," I immediately split them into two buckets:

  1. they are serving stale database results
  2. they are not only scraping public profiles

That sounds harsh. It is still usually true.

Open an incognito window and visit a public LinkedIn profile. Look at what is actually visible. Recent experience is often censored. Important fields are truncated. Public profile pages are not what they used to be.

So when a vendor says they can always return rich current profile data from public LinkedIn pages only, one of two things is happening:

  • they are reading from an old warehouse of previously captured profiles
  • they are using login-bound access patterns and calling it public

I am not guessing from the outside. I lived inside this machine for years.

r/coldemail u/evilprince2009 · ▲ 1
LinkedIn can detect automated activity & patterns. Scrapping higher volume will definitely catch their eyes - no matter what tool you use.

The hard part is not writing a scraper. A good engineer can do that over a weekend.

The hard parts are:

  • keeping it alive for years
  • keeping quality high under rate pressure
  • handling bans, throttles, retries, and account churn
  • handling legal and operational risk
  • doing all of this at millions of profiles per month

That is why I no longer think "just scrape LinkedIn" is serious advice for most companies.

Why NinjaPear is different

NinjaPear's Employee API does not scrape LinkedIn and will not scrape LinkedIn.

Instead, NinjaPear aggregates professional identity signals from public web sources, then uses AI extraction and entity resolution to turn messy evidence into a clean profile object.

That means you can get the output shape you wanted from a LinkedIn People Profile API without taking on LinkedIn's platform constraints or scraping bottlenecks.

In some cases, you can get more useful workflow data because the source base is broader than LinkedIn alone.

That is the important distinction. NinjaPear is not a LinkedIn clone. It is a person profile enrichment system that happens to solve the same business problem most people think they need LinkedIn for.

NinjaPear Person Profile inputs

This is the practical part.

With NinjaPear, you do not need to start from a LinkedIn URL. In many workflows, that is not even the best input.

You can resolve a person profile in three useful ways:

  1. work email
  2. name + company
  3. role + company

That maps much better to how real teams work.

Work email

This is the cleanest input when you have it.

curl -G \
  "https://nubela.co/api/v1/employee/profile" \
  --data-urlencode "[email protected]" \
  -H "Authorization: Bearer YOUR_API_KEY"

Why it works well:

  • work email is a strong identifier
  • ambiguity is low
  • it fits CRM enrichment and inbound lead flows

Name + company

This is the standard path when you know the person and want a structured profile.

curl -G \
  "https://nubela.co/api/v1/employee/profile" \
  --data-urlencode "name=Patrick Collison" \
  --data-urlencode "employer_website=stripe.com" \
  -H "Authorization: Bearer YOUR_API_KEY"

Good for:

  • CRM backfills
  • prospecting pipelines
  • candidate enrichment
  • spreadsheet workflows

If you prefer explicit fields, the same lookup pattern also works well as first_name + last_name + employer_website, which is why I used that format in the demo call at the top.

Role + company

This is the input I like most.

You know the title. You know the company. You do not know the person yet.

curl -G \
  "https://nubela.co/api/v1/employee/profile" \
  --data-urlencode "role=cto" \
  --data-urlencode "employer_website=delve.co" \
  -H "Authorization: Bearer YOUR_API_KEY"

This is a much better starting point for outbound than poking around the LinkedIn UI and hoping the workflow holds together.

Python example: NinjaPear Person Profile

Here is the Python version using work_email.

import requests

API_KEY = "your_api_key"
API_URL = "https://nubela.co/api/v1/employee/profile"


def get_profile_by_work_email(work_email: str):
    response = requests.get(
        API_URL,
        headers={"Authorization": f"Bearer {API_KEY}"},
        params={"work_email": work_email},
        timeout=60,
    )
    response.raise_for_status()
    return response.json()


if __name__ == "__main__":
    profile = get_profile_by_work_email("[email protected]")
    print(profile)

Here is name + company.

import requests

API_KEY = "your_api_key"
API_URL = "https://nubela.co/api/v1/employee/profile"


def get_profile_by_name_company(name: str, employer_website: str):
    response = requests.get(
        API_URL,
        headers={"Authorization": f"Bearer {API_KEY}"},
        params={
            "name": name,
            "employer_website": employer_website,
        },
        timeout=60,
    )
    response.raise_for_status()
    return response.json()

Here is the explicit first_name + last_name + employer_website version that matches the demo.

import requests

API_KEY = "your_api_key"
API_URL = "https://nubela.co/api/v1/employee/profile"


def get_profile_by_name_parts(first_name: str, last_name: str, employer_website: str):
    response = requests.get(
        API_URL,
        headers={"Authorization": f"Bearer {API_KEY}"},
        params={
            "first_name": first_name,
            "last_name": last_name,
            "employer_website": employer_website,
        },
        timeout=60,
    )
    response.raise_for_status()
    return response.json()


if __name__ == "__main__":
    profile = get_profile_by_name_parts("Patrick", "Collison", "https://stripe.com")
    print(profile)

And role + company.

import requests

API_KEY = "your_api_key"
API_URL = "https://nubela.co/api/v1/employee/profile"


def get_profile_by_role_company(role: str, employer_website: str):
    response = requests.get(
        API_URL,
        headers={"Authorization": f"Bearer {API_KEY}"},
        params={
            "role": role,
            "employer_website": employer_website,
        },
        timeout=60,
    )
    response.raise_for_status()
    return response.json()

Short. Boring. Good.

That is how an enrichment API should feel.

LinkedIn API vs person profile enrichment API

If we compare the LinkedIn API and NinjaPear specifically for person profile enrichment, my view is straightforward.

Factor Official LinkedIn API NinjaPear Person Profile Scraper vendors Winner
Getting started ⭐⭐☆☆☆ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐☆ NinjaPear
Legal clarity ⭐⭐⭐⭐☆ ⭐⭐⭐⭐⭐ ⭐⭐☆☆☆ NinjaPear
Cost for most teams ⭐⭐☆☆☆ ⭐⭐⭐⭐⭐ ⭐⭐⭐☆☆ NinjaPear
Native LinkedIn actions ⭐⭐⭐⭐⭐ ⭐☆☆☆☆ ⭐☆☆☆☆ LinkedIn API
Profile enrichment usefulness ⭐⭐☆☆☆ ⭐⭐⭐⭐⭐ ⭐⭐⭐☆☆ NinjaPear
Scaling reliability ⭐⭐⭐⭐☆ ⭐⭐⭐⭐⭐ ⭐⭐☆☆☆ NinjaPear
Overall score 3.00/5 4.33/5 2.83/5 NinjaPear

Faster to start

NinjaPear's homepage is explicit: 3-day free trial, 10 credits included, no credit card required.

That beats waiting on partner approvals.

This matters more than people like to admit.

If you are not scraping LinkedIn, you are not building your product on top of LinkedIn scraping risk. That is not only a legal simplification. It is an operational simplification.

No account farms. No anti-detection stack. No proxy bill that slowly turns into a second cloud bill.

Better workflow fit

LinkedIn thinks in LinkedIn-shaped primitives. NinjaPear thinks in workflow-shaped primitives.

Examples:

  • enrich this work email
  • find the CTO at this company
  • get a full company profile
  • find a verified work email
  • find peer contacts across a competitive set

These are business actions, not platform actions.

Useful adjacent endpoints

This is where shallow comparisons usually fail.

It is not enough to ask, "can I get a profile?" You should ask whether you can finish the workflow.

Useful adjacent NinjaPear primitives include:

  • Work email finder
  • Company profile
  • Employee count
  • Competitor API
  • Customer API
  • Supportive for Gmail

That ecosystem matters because person profile enrichment is rarely a one-endpoint problem.

r/coldemail u/brooklyn_babyx · ▲ 1
yea that’s the problem w apollo tbh even if you use the “verified” filter, you still end up w a bunch of unverifieds. they don’t verify in real time, so by the time you export, a lot of emails are already invalid. plus, most other scrapers pull from Apollo too, so you get a ton of duplicates across tools...

That quote is about email data, but the pattern is universal: stale warehouse data always looks great in a deck and worse in production.

The scaling truth

This is the least sexy part of the article and probably the most useful.

At small scale, almost anything works.

At 200 profiles, your scraper works.
At 2,000, it still sort of works.
At 200,000, you start learning religion.
At millions per month, every lie in your architecture gets exposed.

When we were running Proxycurl, the biggest hidden cost was not the code. It was uncertainty.

You do not know when the source changes. You do not know which access pattern dies next. You do not know when throughput assumptions collapse. You do not know whether your current quality depends on something brittle and semi-private.

That is why I care so much that NinjaPear does not scrape LinkedIn.

The absence of that dependency is not just a compliance talking point. It is a scaling advantage.

It means the business is not forced to keep fighting the same source war forever.

Practical decision framework

Use this if you want the simple version.

Pick the LinkedIn API if

  • your product lives inside LinkedIn workflows
  • you need authenticated member actions
  • you already have, or can realistically get, the required approvals
  • official platform support matters more than enrichment breadth

Pick NinjaPear if

  • you need person profiles, not LinkedIn buttons
  • you care about work_email, name + company, or role + company lookups
  • you want to get started immediately
  • you want lower legal exposure
  • you want adjacent enrichment like work email lookup and company data
  • you need a person profile enrichment API rather than a social platform API

Avoid scraper-first architecture if

  • your product depends on predictable throughput
  • your customers expect stable SLAs
  • you are building for the long haul
  • you do not want your data pipeline to become a permanent cat-and-mouse job

My honest conclusion

If you came here searching for the LinkedIn API People Profile API, the most useful answer I can give you is this:

The official LinkedIn API is real, and it has valid use cases. But for most person profile enrichment workflows, it is the wrong hill to die on.

I used to be much more willing to brute-force this class of problem. That was my mistake. I spent years too close to the machinery of LinkedIn scraping, and the lesson I took away was not, "get better proxies." Instead, I learned to stop depending on this source unless the business absolutely requires it.

That is exactly why NinjaPear went the direction it did.

You can get person-profile-shaped data through the Employee API, resolve people by work email, name + company, first_name + last_name + company, or role + company, and build the rest of the workflow with adjacent enrichment primitives. No LinkedIn scraping. No pretending public profile pages still contain everything. No bullshit.

If you truly need LinkedIn-native actions, use LinkedIn.

If you want a person profile enrichment API with Python examples and a sane integration path, start with NinjaPear instead. Run the demo call at the top. That will tell you more in 30 seconds than another week of reading LinkedIn partner documentation.

Steven Goh | CEO
World's laziest CEO. CEO of NinjaPear. Ex-Founder of Proxycurl (10+M), Steven founded 5 other startups: Gom VPN, Kloudsec, SilvrBullet, NuMoney, and SharedHere.

Featured Articles

Here's what we've been up to recently.

I dismissed someone, and it was not because of COVID19

The cadence of delivery. Last month, I dismissed the employment of a software developer who oversold himself during the interview phase. He turned out to be on the lowest rung of the software engineers in my company. Not being good enough is not a reason to be dismissed. But not

sharedhere

I got blocked from posting on Facebook

I tried sharing some news on Facebook today, and I got blocked from posting in other groups. I had figured that I needed a better growth engine instead of over-sharing on Facebook, so I spent the morning planning the new growth engine. Growth Hacking I term what I do in