You can find phone numbers from LinkedIn in Python by sending public linkedin.com/in/... profile URLs to Datablist's Waterfall Phone Finder API. The API checks eligible phone-data providers in sequence and returns found numbers in international format with their country codes.

This guide uses the Requests library to run one complete Python integration. Then it reuses the same results to prepare a local sales call list that keeps each prospect's name, LinkedIn URL, and account owner.

📌 Summary For Those In a Rush

Use one API to find phone numbers from a list of public LinkedIn profile URLs, then prepare a local sales call list with each prospect's name and account owner. The Waterfall Phone Finder tries eligible providers in sequence and keeps found numbers ready for your team to review.

  • Input: Prospect records with a public LinkedIn profile URL.
  • Output: A local call list with found international phone numbers, number-country codes, names, and account owners.

💡 Prefer a no-code solution? Use Datablist's Waterfall Phone Finder to find phone numbers from LinkedIn profile URLs without writing code.

If you start with names instead of profile URLs, follow the no-code name-to-phone workflow or find LinkedIn profiles from names in Python first.

Set up Python, Requests, and your Datablist API key

For this Python example, I use the requests package. Create a project folder, activate a virtual environment, install Requests, and create the script file:

shell
mkdir waterfall-phone-lookup
cd waterfall-phone-lookup
python -m venv .venv
source .venv/bin/activate
python -m pip install requests
touch find_phone_numbers.py
export DATABLIST_API_KEY="your-api-key"

Create a Personal API Key in your Datablist account and use it for DATABLIST_API_KEY. REST API access is a Growth-plan feature. I cover the credits used by successful lookups later in the guide.

Find phone numbers with one complete Python function

The example uses three editable prospect records. Each record has a stable prospect_id, a name, a public LinkedIn URL, and the account owner responsible for reviewing the result:

  • Florian Poullin at https://www.linkedin.com/in/florianpoullin/
  • Satya Nadella at https://www.linkedin.com/in/satyanadella/
  • Reid Hoffman at https://www.linkedin.com/in/reidhoffman/

Only the stable ID and LinkedIn URL need to go to the API. The name and account owner stay in your local source record. This separation keeps the lookup request small while preserving the business details your team needs after the result is returned.

Paste this complete script into find_phone_numbers.py:

python
import json
import os
import time
import uuid
import requests
def find_phone_numbers(prospects, api_key, settings):
"""Submit one phone lookup task, wait, and return every result."""
tasks_url = "https://api.datablist.com/v1/enrichments/tasks"
headers = {"X-API-Key": api_key}
inputs = [
{
"inputId": prospect["prospect_id"],
"data": {"profileUrl": prospect["linkedin_url"]},
}
for prospect in prospects
]
unique_id = str(uuid.uuid4())
print(f"Task uniqueId: {unique_id}", flush=True)
payload = {
"enrichmentId": "waterfall-phone-finder",
"uniqueId": unique_id,
"settings": settings,
"inputs": inputs,
}
response = requests.post(tasks_url, headers=headers, json=payload)
response.raise_for_status()
task = response.json()
task_id = task["@id"]
task_url = f"{tasks_url}/{task_id}"
while task["status"] not in ("completed", "failed", "stopped"):
time.sleep(3)
response = requests.get(task_url, headers=headers)
response.raise_for_status()
task = response.json()
if task["status"] != "completed":
raise RuntimeError(
f"Task {task_id} {task['status']}: {task.get('error')}"
)
results = []
results_url = f"{task_url}/results"
params = {"limit": 100}
while True:
response = requests.get(results_url, headers=headers, params=params)
response.raise_for_status()
page = response.json()
results.extend(page["results"])
next_cursor = page["nextCursor"]
if next_cursor is None:
break
params["cursor"] = next_cursor
return results
if __name__ == "__main__":
api_key = os.environ.get("DATABLIST_API_KEY")
if not api_key:
raise SystemExit("Set DATABLIST_API_KEY before running this script.")
prospects = [
{
"prospect_id": "prospect-florian-poullin",
"name": "Florian Poullin",
"linkedin_url": "https://www.linkedin.com/in/florianpoullin/",
"account_owner": "Habib",
},
{
"prospect_id": "prospect-satya-nadella",
"name": "Satya Nadella",
"linkedin_url": "https://www.linkedin.com/in/satyanadella/",
"account_owner": "Florian",
},
{
"prospect_id": "prospect-reid-hoffman",
"name": "Reid Hoffman",
"linkedin_url": "https://www.linkedin.com/in/reidhoffman/",
"account_owner": "Habib",
},
]
settings = {"cacheAge": "30"}
phone_results = find_phone_numbers(prospects, api_key, settings)
print(json.dumps(phone_results, indent=2, ensure_ascii=False))

Run the file with:

shell
python find_phone_numbers.py

You can also download Datablist's maintained synchronous Requests example. The script above keeps the same simple order: submit one task, wait for it, and retrieve all result pages.

Submit the phone lookup

The list comprehension turns each source prospect into the API's input shape. Your prospect_id becomes inputId, while linkedin_url becomes data.profileUrl. Use a public person-profile URL with the linkedin.com/in/... path. Sales Navigator URLs are not supported by this enrichment.

The function generates uniqueId and prints it before the POST. The request sends that value with the waterfall-phone-finder enrichment ID, the cache setting, and the inputs. A successful submission returns HTTP 202 Accepted and a task @id while processing continues in the background.

The Waterfall Phone Finder API reference lists the current settings, input fields, outputs, credits, and limits.

For one prospect, the request body follows this shape:

json
{
"enrichmentId": "waterfall-phone-finder",
"uniqueId": "generated-uuid",
"settings": {
"cacheAge": "30"
},
"inputs": [
{
"inputId": "prospect-florian-poullin",
"data": {
"profileUrl": "https://www.linkedin.com/in/florianpoullin/"
}
}
]
}

The public profile URL is the lookup input. inputId is the join key that connects the result to the name and account owner in your source record. Choose an ID that is stable in your own system and unique within the task.

Wait for the background task

The Requests client blocks locally while it checks the task every three seconds. The Datablist task still runs in the background. When its status becomes completed, the function moves to result retrieval. A failed or stopped status raises an error with the task ID so you can inspect it.

Retrieve every result page

The results request asks for up to 100 rows at a time. It keeps fetching pages until nextCursor is null. Every result includes its source inputId, so you can map it back to the correct prospect without relying on response order.

How the managed waterfall finds a phone number

The example sets cacheAge to "30". Datablist first checks for an eligible result no more than 30 days old. When one exists, the API can reuse it. The setting accepts a cache hit but does not guarantee one.

If the profile remains unresolved, the managed waterfall tries eligible phone-data providers in sequence. A provider that returns no usable number lets the record continue. A usable number stops the sequence for that record.

Waterfall phone lookup continuing after no result and stopping when a phone number is found
Waterfall phone lookup continuing after no result and stopping when a phone number is found

Why a waterfall helps across countries and markets

Datablist does not rely on a single phone-data provider. Providers use different datasets, and their coverage varies across countries and markets. When one provider has no phone number, another can find it. Datablist manages these providers through the same API integration.

For a sales team building a call list across several markets, this means fewer prospects are left out after a single provider returns no result. Your Python code submits one task and reads one result format, so you do not need to build and maintain a separate integration for each provider.

Set cacheAge to "0" when your workflow specifically needs to request a fresh lookup. Keep "30" for the example so an eligible cached result can be reused.

Read the complete result

Here is one result for Florian Poullin. Phone numbers are masked below.

json
{
"inputId": "prospect-florian-poullin",
"status": "success",
"outputs": {
"finderProvider": "wizleads",
"phoneNumber": "+33 • •• •• •• ••",
"phoneNumberCountry": "FR"
},
"creditsUsed": "500",
"error": null
}

phoneNumber contains the international number when one is available. The +33 prefix and phoneNumberCountry: "FR" identify a French number.

finderProvider is the finder reported for this result. The row used 500 credits, and error: null means the API returned no row-level error.

Prepare a reviewed sales call list

Once phone_results is available, you can prepare a local list for a sales team to review. Append this block after the result printing in the same file. It reuses the existing results and makes no second API call:

python
results_by_id = {
result["inputId"]: result
for result in phone_results
}
call_list = []
for prospect in prospects:
result = results_by_id.get(prospect["prospect_id"])
if not result or result["status"] != "success":
continue
phone_number = result.get("outputs", {}).get("phoneNumber")
if not phone_number:
continue
call_list_record = prospect.copy()
call_list_record["result"] = result
call_list.append(call_list_record)
print(json.dumps(call_list, indent=2, ensure_ascii=False))

The result index uses inputId as the join key, so each result stays tied to its source record without depending on list position. The API payload contains each prospect's ID and profile URL, while the original prospects list keeps the name and account owner needed for the call list.

For Florian's row, prospect-florian-poullin connects the returned result to the original name, linkedin_url, and account_owner. The team reads the phone and its FR country code from result.outputs. The complete API object, including status, credits, and error, remains attached under result.

The call-list loop follows the order of prospects. It copies each included prospect before attaching result, which leaves the original source list available for corrections or a different follow-up channel.

A no-result, error, or empty-phone row stays in the unchanged prospects list but does not enter call_list. The code prepares a local sales call list for review.

Estimate Waterfall Phone Finder API costs

Each new uncached phone number found by the default managed waterfall uses 500 credits. An accepted cached result uses 0 credits, and a search without a result also uses 0 credits. Read creditsUsed on each returned row for actual spend instead of multiplying every submitted profile by 500.

For planning, use this formula:

new uncached phone numbers found × 500 credits

Forty new numbers use 20,000 credits. One hundred new numbers use 50,000 credits. At the current base USD top-up rate, $20 buys 20,000 credits, so 500 credits are worth $0.50. The Growth subscription that provides REST API access is separate. See Datablist pricing for current plans and credit packages.

For a planning example, suppose 100 profiles produce 20 new numbers, 5 accepted cached numbers, and 75 misses. The billable quantity is the 20 new numbers, so the lookup uses 10,000 credits. The call list can contain all 25 found numbers. Enter 20 in the calculator, rather than all 100 inputs or all 25 found numbers. This example illustrates the billing calculation, not an expected success rate.

Loading pricing…

Fix common errors and recover an existing task

  • DATABLIST_API_KEY is missing: the script stops before making a request. Export the variable and run the file again.
  • The API rejects the key: raise_for_status() reports the HTTP error. Copy the Personal API Key again and confirm that the request sends it in the X-API-Key header.
  • The profile URL is unsupported: replace a blank, malformed, company, or Sales Navigator URL with a public person profile using the linkedin.com/in/... path.
  • Credits are insufficient: check the account balance and available credits before submitting more work.
  • The task is failed or stopped: inspect its error, available results, and credit use before deciding what still needs to run.
  • A row has no result or an error: keep the source prospect for correction or another follow-up channel. The row alone does not explain which provider, geography, or visibility condition caused the outcome.

If the POST response is lost, use the uniqueId printed before submission. List your tasks before submitting again. Replace your-api-key in this request, then find the task whose uniqueId matches the printed value:

shell
curl --get "https://api.datablist.com/v1/enrichments/tasks" \
--header "X-API-Key: your-api-key" \
--data-urlencode "enrichmentId=waterfall-phone-finder" \
--data-urlencode "limit=100"

If the task is not on the first page, follow the task-list response's nextCursor. Copy the matching task's @id, replace TASK_ID below, and check its status or retrieve its existing results:

shell
curl "https://api.datablist.com/v1/enrichments/tasks/TASK_ID" \
--header "X-API-Key: your-api-key"
curl --get "https://api.datablist.com/v1/enrichments/tasks/TASK_ID/results" \
--header "X-API-Key: your-api-key" \
--data-urlencode "limit=100"

The task API documentation explains task states, result pagination, and interruption behavior.

Next steps

You now have one reusable Requests function for public LinkedIn profile URLs and one stable-ID merge that prepares found phone numbers for sales review.

Replace the editable examples with a small set of qualified prospects. Keep their stable IDs and account owners, then review each found number before passing the prepared list to your team's calling process. Start with a small batch so you can inspect the ID mapping and returned fields. If you need non-blocking concurrency, use the maintained async HTTPX example.