Pre-Black Friday: 20% off, for as long as you stayEARLYBFon Solo and AgencyEnds Nov 28Start free

How-to guide

How to Scrape LinkedIn Data (Without Running a Scraper)

Three API calls return profile fields, work history and contact data for a LinkedIn URL, a filter or a whole list. No browser, no proxies, no banned account.

Get your free API key →Free to start. No credit card. 1,000 records to spend whenever you like.

The fastest way to get LinkedIn profile data is not to scrape LinkedIn. You ask a database that already holds it. This guide shows three calls: one profile, one filtered list, one CSV.

Most scraping guides rank browser tools, extensions and proxy services. That is the wrong layer for a technical buyer. LinkedIn's user agreement forbids using crawlers, plugins or scripts to scrape or copy profiles from the service (user agreement, October 2026). A scraper also breaks whenever the page markup changes, and the account running it can be restricted.

If you do scrape, build for the failure modes: rotating sessions, rate limits and selector drift. If you only need the fields, use the steps below.

TL;DR

  • You get name, headline, current and past roles, education and (on request) work email and phone for a LinkedIn URL, a filter or a list.
  • It takes about 20 minutes from key to first CSV.
  • It spends about 27 LeadOcean records on the example run. The Free plan has 1,000 records for life, no card.

Prerequisites

  • A free LeadOcean account and an API key from app.leadocean.io, exported as $LEADOCEAN_API_KEY.
  • curl, plus Python 3 with requests for the loop in Step 2.
  • A LinkedIn profile URL to test. This guide uses https://www.linkedin.com/in/jane-doe-example.
bash
export LEADOCEAN_API_KEY="paste-your-key-here"
curl -s https://api.leadocean.io/v1/account -H "x-api-key: $LEADOCEAN_API_KEY"

The account call is free. It confirms the key works and shows what is left.

Step 1: Pull one profile by LinkedIn URL

Send the URL to POST /v1/people/enrich. Enrich takes exactly one of linkedin_url, email, phone or person_id.

bash
curl -s -X POST https://api.leadocean.io/v1/people/enrich \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "content-type: application/json" \
  -d '{"linkedin_url": "https://www.linkedin.com/in/jane-doe-example"}'

A person we hold nothing for is a 404 and costs nothing. A person we return costs one record.

Add "reveal_email": true to get the address back. Do it only for rows you plan to contact.

Since October 2026 the response also carries career history in resume_data: past roles, education, certifications and awards, each when we hold them. Past employers have no company_id yet, so join on company name.

Step 2: Loop over a list of URLs

For a file of profile URLs, call enrich once per line. Stay under your plan's rate limit (5 requests a second on Free) and back off on 429.

python
import os, time, json, requests

KEY = {"x-api-key": os.environ["LEADOCEAN_API_KEY"]}
URL = "https://api.leadocean.io/v1/people/enrich"

with open("urls.txt") as f, open("results.jsonl", "a") as out, open("misses.txt", "a") as miss:
    for line in f:
        u = line.strip()
        if not u:
            continue
        r = requests.post(URL, headers=KEY, json={"linkedin_url": u}, timeout=30)
        if r.status_code == 404:
            miss.write(u + "\n")
        elif r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", 1)))
        elif r.status_code == 402:
            print("out of records"); break
        else:
            r.raise_for_status()
            out.write(json.dumps(r.json()["data"]) + "\n")
        time.sleep(0.25)

The 0.25 second pause holds you near 4 requests a second. The dataset refreshes monthly, so retry misses.txt next month. Misses are free.

Clean the file first with sort -u urls.txt. Each duplicate that returns a person counts as a record. For how well providers resolve LinkedIn URLs, see LinkedIn URL match rates across 8 providers.

Step 3: Search when you have no URLs

Often you do not have a list. You have a target: engineering directors in the US. Size it first, because a count is free.

On 2026-10-21 the free leadocean_count_leads MCP tool returned this. The filters were jobLevel: Director, jobFunction: Engineering and country: US.

json
{
  "total": 64703,
  "totalIsExact": true,
  "totalAll": 129633,
  "distinctCompanies": 21626,
  "credits": 0
}

total counts people with a mailable email, which is the tool's default. Mailable means verified, catch_all_valid or catch_all. totalAll is everyone matching the same filters, 129,633. Enum values are exact strings, so check them with leadocean_list_enum_values before you search.

Now pull a page. The body is flat JSON, filters are arrays and limit runs from 1 to 100.

bash
curl -s -X POST https://api.leadocean.io/v1/people/search \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "content-type: application/json" \
  -d '{"jobLevel": ["Director"], "jobFunction": ["Engineering"], "country": ["US"], "limit": 25}'

Search costs one record per person returned. Pass meta.nextCursor back as cursor for the next page. One search pages to 10,000 rows, then meta.depthCapped is true. Narrow the filters instead of walking the index.

To list the staff of one company, use GET /v1/companies/acme.com/people. It stops at 10,000 rows too.

Step 4: Export the whole list to CSV

When the list is bigger than a few pages, export it. POST /v1/exports takes up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on Free. It is included on Solo ($97 a month), and Agency ($497 a month) allows 50,000 rows a file.

bash
curl -s -X POST https://api.leadocean.io/v1/exports \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "content-type: application/json" \
  -d '{"name": "us-eng-directors", "filters": {"jobLevel": ["Director"], "jobFunction": ["Engineering"], "country": ["US"]}, "limit": 5000, "preset": "search"}'

The body takes the same filters as people search, a row limit and a column preset. The search preset carries no contact points. The default full preset adds emails and phones and needs the enrich scope. Every row costs one record whatever the columns.

The app does the same without code. The Exports page at app.leadocean.io has a filter builder, a column picker, the record price before you start and a CSV download. Poll GET /v1/exports/{id} for progress, then fetch the file from GET /v1/exports/{id}/download. The file stays available for 7 days.

If you work from Claude or another MCP client, the same search and count run as tools on https://api.leadocean.io/mcp. See how to scrape data from any website with no code for the non-LinkedIn side.

Step 5: Decide what to store

Keep the raw data object and flatten later. Store person_id as your key, because it is the stable handle for a later enrich or a company lookup. Store data.meta.fetched_at beside it so you can tell a stale row from a fresh one.

Parse only the fields you use. A typical outbound table needs the full name, current title, company name, email, email_status and the fetched_at date. Skip rows whose email_status is not verified, catch_all_valid or catch_all before you send.

There is no refund for bounced emails, so read email_status first. A count with an email filter is not the same as a list of people you can safely mail, so label each count by what you filtered.

What you get

A successful enrich call returns success, data and meta. This is the response shape from the API reference, with placeholder values:

json
{
  "success": true,
  "data": {
    "profile_data": { "profile_full_name": "Jane Doe", "profile_headline": "VP Engineering at Acme" },
    "contact_data": {
      "has_email": true,
      "email_status": "verified",
      "contact_emails": [{ "email": "jane@acme.com", "type": "work", "status": "verified" }],
      "contact_phones": [{ "phone": "+1 415 555 0133", "type": "mobile" }],
      "contact_current_experiences": [{ "job_title": "VP Engineering", "company_name": "Acme" }]
    },
    "resume_data": { "experiences": [], "educations": [], "certifications": [] },
    "meta": { "fetched_at": "2026-10-01", "schema": "leadocean.person.v1" }
  },
  "meta": { "credits": 1 }
}

Contact points appear only when you send reveal_email. data.meta.fetched_at tells you how old the record is. Compare this with a scrape: you get structured fields and a freshness date, not HTML to parse.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-keyRe-export $LEADOCEAN_API_KEY and check the header name
429Above the plan's rate limit (5 a second on Free, 200 on Agency)Honour Retry-After, or lengthen the sleep
402The plan's records are spentCheck GET /v1/account, then upgrade on /pricing
404We hold nothing for that profileCosts nothing. Log it as a miss and retry next month
Empty resultsA filter value is not an exact enum stringCheck jobLevel, jobFunction and country with leadocean_list_enum_values
Export refusedOver 50,000 rows a request or 500,000 a day, or the plan has no bulk exportSplit the filter across requests, or move to a paid plan

FAQ

Is scraping LinkedIn legal?

This guide does not give legal advice. LinkedIn's user agreement bans scraping its Services with scripts, crawlers or plugins. It also bars copying data obtained through data aggregators without the content owner's consent. Ask counsel before you scrape, and read the terms of any data source you buy from.

Does a 404 cost a record?

No. A person we hold nothing for is a 404 and is free. Only people we return count.

Can I get emails and phones as well?

Yes. Send reveal_email on enrich for the address. Phone numbers come from GET /v2/people/phone, and v2 contact calls cost one record each, including a call that finds nothing.

How fresh is the data?

The dataset refreshes monthly. Each record carries its own fetched_at date, so you can filter out anything older than you accept.

Which tool is better, a scraper or an API?

A scraper wins only for pages no database holds. For profile fields, work history and contacts, an API is faster to build, has no account risk and returns a consistent shape. For a wider comparison of providers, see best B2B databases. More guides are in the LeadOcean blog, and plan limits are on /pricing.

Get LinkedIn profile data from one API call

Free to start. No credit card. 1,000 records to spend whenever you like.

Get your free API key →