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

How-to guide

How to Get Phone Numbers From LinkedIn Safely (Without an Extension)

Send a profile URL to an API and read the numbers back. Nothing runs in your browser and nothing touches your LinkedIn login.

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

The safe way to get a phone number for a LinkedIn profile is to look it up in a database by profile URL. You never log in to LinkedIn, install anything or scrape a page. This guide shows the three requests that do it, with the records each one spends.

TL;DR

  • Send a linkedin_url to GET /v2/people/phone and read the numbers back, best first. One lookup is one record.
  • Size your list for free first, so you only spend records on people who have a number on file.
  • It takes about 15 minutes and about 6 records on the free plan (1,000 records, no card).

Prerequisites

  • A LeadOcean API key from https://app.leadocean.io/register, exported as $LEADOCEAN_API_KEY. The free plan gets search and enrich scopes.
  • curl and jq. Any HTTP client works.
  • One public LinkedIn profile URL, or a few search filters. Examples below use placeholders such as 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. If it returns your plan, the key works.

Why this avoids the extension risk

Browser extensions run inside your logged-in LinkedIn session. They read pages as you, so the activity sits on your account. An API lookup is different: the request goes from your machine to api.leadocean.io, which answers from its own dataset. Your LinkedIn account is not involved.

One limit applies. The number comes from the dataset, not from the profile page. If the person never shared a number anywhere we hold, the answer is found: false, and the lookup says so.

Step 1: Size the list for free

Count how many people match before you spend a record. A search with count=true and limit=1 costs nothing and returns meta.total with no rows. Send count=true as a query parameter, not in the JSON body.

bash
curl -s -X POST "https://api.leadocean.io/v1/people/search?count=true&limit=1" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "jobLevel": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["US"],
    "hasPhone": true
  }'

hasPhone: true keeps only people with a number on file. Enum values are exact strings. Check them with GET /v1/enums if you get an empty result.

Step 2: Pull a small page of people

Fetch five people so you can test the lookup. Each row returned is one record, so keep limit low while you test.

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": ["VP"],
    "jobFunction": ["Sales & Business Development"],
    "country": ["US"],
    "hasPhone": true,
    "limit": 5
  }'

Each row carries an id at meta.source_ids.person_id. Keep it. You pass it to the phone endpoint in the next step. Page on with cursor from meta.nextCursor.

Step 3: Look up the phone number

Call the phone endpoint with a LinkedIn URL or a person_id. Send one identifier.

bash
curl -s -G "https://api.leadocean.io/v2/people/phone" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  --data-urlencode "linkedin_url=https://www.linkedin.com/in/jane-doe-example"

curl -s -G "https://api.leadocean.io/v2/people/phone" \
  -H "x-api-key: $LEADOCEAN_API_KEY" \
  --data-urlencode "person_id=1234567890"

A v2 phone call costs one record, including a call that finds nothing. That is why Step 1 filters on hasPhone. The v2 response has no meta object. Read your spend from GET /v1/usage.

Step 4: Get the number and the email in one call

If you want both, use enrich. POST /v1/people/enrich takes exactly one of linkedin_url, email, phone or person_id. With reveal_email: true it returns the contact points it holds, at no extra record.

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",
    "reveal_email": true
  }'

Numbers sit in contact_phones[].phone. A person we hold nothing for is a 404 and costs nothing. Enrich also returns career history (past roles, education, certifications) when we hold it.

Step 5: Run it for a whole list

For a few hundred people, loop over the Step 2 ids. This Python loop reads a file of ids, calls the phone endpoint and backs off on a 429.

python
import os, time, requests

H = {"x-api-key": os.environ["LEADOCEAN_API_KEY"]}
URL = "https://api.leadocean.io/v2/people/phone"

def phones(person_id):
    while True:
        r = requests.get(URL, headers=H, params={"person_id": person_id})
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "1")))
            continue
        r.raise_for_status()
        return r.json()["data"]

for pid in open("ids.txt").read().split():
    d = phones(pid)
    print(pid, d["found"], d.get("best"))

The loop stops on a 402 because raise_for_status raises. That is what you want: nothing more is spent once your records run out.

For thousands, use a bulk export instead. POST /v1/exports takes up to 50,000 rows a request and 500,000 a day. Bulk CSV export is not on the free plan. It is included on Solo ($97 a month).

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-sales-vps-with-phone",
    "filters": {
      "jobLevel": ["VP"],
      "jobFunction": ["Sales & Business Development"],
      "country": ["US"],
      "hasPhone": true
    },
    "limit": 500,
    "preset": "full",
    "reveal": "email_phone"
  }'

Phones come back as phone_1_number and phone_2_number in the CSV. Every row costs one record. You can also build the same export in the app at app.leadocean.io under Exports, which shows the record price before you start.

Check the cost before a big run

Records are the only meter on the free plan, so do the sum first. Take the Step 1 total, then take how many you will actually call. Ten thousand lookups is 10,000 records, which is more than the free plan holds. Solo ($97 a month) and Agency ($497 a month) run on a fair-use pace instead of credits, so there is no per-record invoice. Both include phone numbers. See pricing.

A cheap way to test quality: look up 20 people, count how many return found: true, and decide from that.

What you get

Step 1 sizes the list. This is the result of the free leadocean_count_leads MCP tool on 2026-10-21 for the same filters as above.

json
{
  "total": 275259,
  "totalIsExact": true,
  "distinctCompanies": 135386,
  "credits": 0,
  "unsupportedFilters": []
}

That is VPs in sales and business development, in the US, with a phone on file. It is a count of people with a number, not of people you can legally call. Step 3 returns this shape, from the API reference (placeholder values):

json
{
  "success": true,
  "data": {
    "person_id": "1234567890",
    "found": true,
    "phones": [
      { "phone": "+1 415 555 0133", "type": "mobile" },
      { "phone": "+1 415 555 0140", "type": "office" }
    ],
    "best": { "phone": "+1 415 555 0133", "type": "mobile" }
  }
}

The array is ordered best first. The type label is passed through from the source, so treat an unfamiliar value as unknown.

Troubleshooting

ErrorCauseFix
401Missing or wrong x-api-key headerCheck $LEADOCEAN_API_KEY is exported and sent as x-api-key.
429Requests above your plan's rate limit (5 a second on Free, 200 on Agency)Wait for the Retry-After header, then retry. Slow your loop.
402Your 1,000 free records are spentAdd a paid plan. See pricing. Counts stay free.
Empty resultsA filter value is not an exact enum stringList valid values with GET /v1/enums, then copy the string as written.
found: falseNo number held for that personNothing to fix. Filter on hasPhone: true first so you do not pay for misses.
Export refusedOver 50,000 rows a request, 500,000 a day, or on the free planSplit the export, or move to Solo or Agency.

FAQ

Is this really safe for my LinkedIn account?

Your account is not involved. The lookup runs against an API, so there is no session, cookie or page load on LinkedIn. That is the main difference from an extension.

Does LeadOcean scrape the number from the profile?

The number comes from LeadOcean's dataset, which is refreshed monthly. Each record carries its own fetched_at date. If the profile hides a number, we may still hold one from another source, or none.

Can I check the do-not-call status?

Not from this endpoint. v1 returns dnc as null, which means unknown, and v2 does not publish it. Check your own do-not-call lists before dialing, and see how to get phone numbers at scale for cold calling.

What if I only have a name and a company?

Search by domain and title first, then use the person_id from the row. There is no name plus domain lookup. For other ways in, see how to get contact info from LinkedIn without a connection.

How do I hide my own number from LinkedIn?

That is a profile setting, not an API call. Follow how to remove your phone number from LinkedIn.

Get phone numbers for your LinkedIn list without an extension

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

Get your free API key →