{
  "version": 1,
  "slug": "find-linkedin-profiles-python-api",
  "title": "Find LinkedIn Profiles from Names in Python Using an API",
  "excerpt": "Find LinkedIn profiles from names in Python, review candidate URLs and page titles, and prepare a research list for webinar or podcast guests.",
  "cover": {
    "src": "/howto_images/find-linkedin-profiles-python-api/find-linkedin-profiles-python-api-cover.png",
    "optimized": "https://www.datablist.com/_next/image?url=%2Fhowto_images%2Ffind-linkedin-profiles-python-api%2Ffind-linkedin-profiles-python-api-cover.png&w=1200&q=75"
  },
  "url": "https://www.datablist.com/how-to/find-linkedin-profiles-python-api",
  "contentMarkdown": "\nYou can find LinkedIn profiles from names in Python with Datablist's LinkedIn\nProfile Finder API. Give the API a full name and, when you have it, a company or\nschool keyword. It returns a public LinkedIn URL, search-result context, and a\nconfidence signal that helps you decide whether the result belongs to the person\nyou meant.\n\nI will first show you a complete Python integration for four people using the\nRequests library. Then I will reuse the results to prepare a small research list\nfor potential webinar or podcast guests. Stable IDs keep each returned profile\nattached to the right source record, even if the API returns results in a\ndifferent order.\n\n> 📌 **Summary For Those In a Rush**\n>\n> Use Datablist's API to find LinkedIn profiles from names in Python,\n> then turn useful matches into a guest-research list. The example submits one\n> background task, waits for it, and retrieves every result page.\n>\n> - **Input:** A person's full name and an optional company or school keyword.\n> - **Output:** A LinkedIn URL with a search title and confidence signal, plus a\n>   research list mapped back to the original people.\n\n## Set up Datablist API access {#set-up-datablist-api-access}\nCreate a Datablist account, then create a [Personal API\nKey](https://app.datablist.com/account/api). Your key follows this shape:\n\n```text\nauth0|exampleuser:exampleaccount:EXAMPLE_TOKEN_NOT_VALID:1\n```\n\nExport your own key as `DATABLIST_API_KEY`; the script will send it in the\n`X-API-Key` header.\n\nREST API access requires the Growth plan. Profile lookups also use enrichment\ncredits, which I calculate later in the article.\n\nFor this example, I use Python's `requests` package. Create a project folder,\nactivate a virtual environment, install Requests, and create the script file:\n\n```shell\nmkdir linkedin-profile-lookup\ncd linkedin-profile-lookup\npython -m venv .venv\nsource .venv/bin/activate\npython -m pip install requests\ntouch find_linkedin_profiles.py\nexport DATABLIST_API_KEY=\"your-personal-api-key\"\n```\n\n## Run a complete LinkedIn profile lookup in Python {#run-a-complete-linkedin-profile-lookup-in-python}\nThe sample looks up Andrew Ng with DeepLearning.AI, Florian Poullin with\nDatablist, Satya Nadella with Microsoft, and Dharmesh Shah with HubSpot. Each\nAPI input has an `inputId` that will connect the result to the same person later.\n\nPaste this complete script into `find_linkedin_profiles.py`:\n\n```python\nimport json\nimport os\nimport time\nimport uuid\n\nimport requests\n\n\ndef find_linkedin_profiles(inputs, api_key):\n    \"\"\"Submit one profile lookup task, wait, and return every result.\"\"\"\n    tasks_url = \"https://api.datablist.com/v1/enrichments/tasks\"\n    headers = {\"X-API-Key\": api_key}\n\n    request_id = str(uuid.uuid4())\n    print(f\"Request ID: {request_id}\", flush=True)\n\n    payload = {\n        \"enrichmentId\": \"linkedin-profile-lookup\",\n        \"uniqueId\": request_id,\n        \"settings\": {\"searchCountry\": \"US\", \"skipIfNotName\": False},\n        \"inputs\": inputs,\n    }\n    response = requests.post(tasks_url, headers=headers, json=payload)\n    response.raise_for_status()\n    task = response.json()\n    task_id = task[\"@id\"]\n    task_url = f\"{tasks_url}/{task_id}\"\n\n    while task[\"status\"] not in (\"completed\", \"failed\", \"stopped\"):\n        time.sleep(3)\n        response = requests.get(task_url, headers=headers)\n        response.raise_for_status()\n        task = response.json()\n\n    if task[\"status\"] != \"completed\":\n        raise RuntimeError(\n            f\"Task {task_id} {task['status']}: {task.get('error')}\"\n        )\n\n    results = []\n    results_url = f\"{task_url}/results\"\n    params = {\"limit\": 100}\n\n    while True:\n        response = requests.get(results_url, headers=headers, params=params)\n        response.raise_for_status()\n        page = response.json()\n        results.extend(page[\"results\"])\n\n        next_cursor = page[\"nextCursor\"]\n        if next_cursor is None:\n            break\n        params[\"cursor\"] = next_cursor\n\n    return results\n\n\nif __name__ == \"__main__\":\n    api_key = os.environ[\"DATABLIST_API_KEY\"]\n    inputs = [\n        {\n            \"inputId\": \"andrew-ng\",\n            \"data\": {\"fullName\": \"Andrew Ng\", \"keyword\": \"DeepLearning.AI\"},\n        },\n        {\n            \"inputId\": \"florian-poullin\",\n            \"data\": {\"fullName\": \"Florian Poullin\", \"keyword\": \"Datablist\"},\n        },\n        {\n            \"inputId\": \"satya-nadella\",\n            \"data\": {\"fullName\": \"Satya Nadella\", \"keyword\": \"Microsoft\"},\n        },\n        {\n            \"inputId\": \"dharmesh-shah\",\n            \"data\": {\"fullName\": \"Dharmesh Shah\", \"keyword\": \"HubSpot\"},\n        },\n    ]\n\n    lookup_results = find_linkedin_profiles(inputs, api_key)\n    print(json.dumps(lookup_results, indent=2, ensure_ascii=False))\n```\n\nThe task-level `searchCountry` sets the search engine's country to the United\nStates for every search in this task.\n\nThe `skipIfNotName` setting controls what happens to weak name matches. `false`\nis the default and the value used here, so the API can return the URL, title,\nand confidence even when name confidence is `Very Low`. With\n`skipIfNotName: true`, the API discards only `Very Low` name matches. It can\nstill return `Low`, `Medium`, or `High` matches, and it does not change the\nkeyword or country inputs. The API evaluates the name and keyword signals first,\nthen applies this filter. If filtering removes every candidate, the lookup has\nno profile result. The search was still attempted, so it still uses 2.5 credits.\n\nRun the file with:\n\n```shell\npython find_linkedin_profiles.py\n```\n\n### How the lookup finds and selects a profile {#how-the-lookup-finds-and-selects-a-profile}\nThe tool uses Google Search to find public LinkedIn profile candidates. For\nFlorian Poullin, the search is shaped like this:\n\n```text\nFlorian Poullin Datablist site:linkedin.com/in/\n```\n\nThe name is required, while the company or school keyword gives Google and the\nselection step more context. `searchCountry` sets Google's country context for\nevery input in the task.\n\nThe tool then checks the first few Google results. It compares the submitted\nname with each result title and LinkedIn handle, looks for the keyword in the\nURL, title, or summary, and considers the result's Google position. A scoring\nalgorithm combines those signals and selects the strongest remaining result. A\nkeyword match can strengthen a `Low` or `Medium` name match, but it cannot turn\na `Very Low` name match into a stronger one.\n\nThe resulting confidence is the signal used by the `skipIfNotName` setting\ndescribed with the script. After that filter, the strongest remaining profile is\nreturned with its URL, title, and confidence.\n\n### 1. Submit the lookup task {#1-submit-the-lookup-task}\nThe `inputs` list already uses the API contract. `fullName` is required in this\nexample, while `keyword` supplies company context that can help rank search\nresults. Use your own record ID as `inputId`; you will need it to join results\nback to your source data.\n\nYou can omit `keyword` when you only have a name, or send `firstName` and\n`lastName` together instead of `fullName`. When several people share a name, a\nknown company or school usually gives the search a more useful clue. Keep\n`searchCountry` at the task level because it applies to every input in that\ntask. If different groups need different search-country settings, submit them\nas separate tasks. The [LinkedIn Profile Finder API\ndocumentation](https://platform.datablist.com/docs/enrichments/tools/linkedin-profile-lookup)\nhas the current input schema and supported country codes.\n\nHere is the request body produced by the example. The script generates a new\n`uniqueId` for each run:\n\n```json\n{\n  \"enrichmentId\": \"linkedin-profile-lookup\",\n  \"uniqueId\": \"526c13d1-4a02-4093-870b-ccde2da5be55\",\n  \"settings\": {\n    \"searchCountry\": \"US\",\n    \"skipIfNotName\": false\n  },\n  \"inputs\": [\n    {\"inputId\": \"andrew-ng\", \"data\": {\"fullName\": \"Andrew Ng\", \"keyword\": \"DeepLearning.AI\"}},\n    {\"inputId\": \"florian-poullin\", \"data\": {\"fullName\": \"Florian Poullin\", \"keyword\": \"Datablist\"}},\n    {\"inputId\": \"satya-nadella\", \"data\": {\"fullName\": \"Satya Nadella\", \"keyword\": \"Microsoft\"}},\n    {\"inputId\": \"dharmesh-shah\", \"data\": {\"fullName\": \"Dharmesh Shah\", \"keyword\": \"HubSpot\"}}\n  ]\n}\n```\n\nThis call sends that JSON to the task endpoint:\n\n```python\nresponse = requests.post(tasks_url, headers=headers, json=payload)\nresponse.raise_for_status()\ntask = response.json()\ntask_id = task[\"@id\"]\n```\n\nDatablist responds with HTTP `202 Accepted`. The selected fields below show the\ntask ID and queued status returned for this four-person sample:\n\n```json\n{\n  \"@id\": \"d3f6e08e9b9445a29482cebd80091234\",\n  \"enrichmentId\": \"linkedin-profile-lookup\",\n  \"uniqueId\": \"526c13d1-4a02-4093-870b-ccde2da5be55\",\n  \"status\": \"queued\",\n  \"progress\": {\n    \"finishedInputs\": 0,\n    \"totalInputs\": 4\n  }\n}\n```\n\nProcessing continues in the background. The script prints its generated\n`uniqueId` before the POST, so you can find the task if the creation response is\nlost.\n\n### 2. Poll until processing finishes {#2-poll-until-processing-finishes}\nThe function checks the task every three seconds. Although this Python client\nblocks while it waits, the API task itself runs in the background. A completed\ntask is ready for normal result retrieval. If the status is `failed` or\n`stopped`, the function raises an error so you can inspect the task before\ndeciding whether to submit paid work again.\n\n### 3. Retrieve every result page {#3-retrieve-every-result-page}\nThe last loop requests up to 100 results at a time. It fetches the next page\nuntil `nextCursor` is `null`.\n\nEvery result carries the original `inputId`. This matters because results do\nnot need to arrive in the same order as your source list.\n\n## Read the results {#read-the-results}\nHere is the result for Florian Poullin:\n\n```json\n{\n  \"inputId\": \"florian-poullin\",\n  \"status\": \"success\",\n  \"outputs\": {\n    \"profileUrl\": \"https://fr.linkedin.com/in/florianpoullin/fr\",\n    \"linkedInUrlTitle\": \"Florian Poullin - Fondateur chez Datablist | Freelance\",\n    \"profileConfidence\": \"High\"\n  },\n  \"creditsUsed\": \"2.5\",\n  \"error\": null\n}\n```\n\nThe ID connects the result to the original input. The URL, title, and\n`High` confidence support using this link in the small research list. Satya\nNadella's record follows the same shape:\n\n```json\n{\n  \"inputId\": \"satya-nadella\",\n  \"status\": \"success\",\n  \"outputs\": {\n    \"profileUrl\": \"https://www.linkedin.com/in/satyanadella\",\n    \"linkedInUrlTitle\": \"Satya Nadella - Chairman and CEO at Microsoft\",\n    \"profileConfidence\": \"High\"\n  },\n  \"creditsUsed\": \"2.5\",\n  \"error\": null\n}\n```\n\nAndrew Ng also returned `High` confidence, the URL\n`https://www.linkedin.com/in/andrewyng`, and the title `Andrew Ng -\nDeepLearning.AI, AI Fund and AI Aspire`. Dharmesh Shah returned\n`https://www.linkedin.com/in/dharmesh` with the title `Dharmesh Shah - Founder\nand CTO at HubSpot. Helping ...` and `High` confidence.\n\nUse a current company or school as the keyword when you know it. That context\nhelps the search rank useful results, while the returned title, summary, and\nconfidence help you decide whether to use the URL. Public-profile visibility\nand search engine indexing can still affect discovery. LinkedIn explains how\n[public profile visibility and search indexing](https://www.linkedin.com/help/linkedin/answer/a518980)\nwork and how those settings affect what search engines can see.\n\n![Florian Poullin and Satya Nadella return high-confidence LinkedIn profile results](/howto_images/find-linkedin-profiles-python-api/illustrations/guest-profile-lookup-review.png)\n\n## Reuse the results for a guest-research list {#reuse-the-results-for-a-guest-research-list}\nSuppose a content team has a webinar topic, four possible guests, and their\nknown affiliations. The team wants likely profile links to open before deciding\nwho fits the topic.\n\nThe complete script has already fetched `lookup_results`, so this follow-up does\nnot repeat the same API task. Add the code below after the existing\n`print(json.dumps(...))` line. When you replace the sample records with your own\nshortlist, the same function will fetch those records once and the loop will\nprepare the corresponding research list.\n\n```python\nguests = [\n    {\n        \"guest_id\": \"andrew-ng\",\n        \"full_name\": \"Andrew Ng\",\n        \"affiliation\": \"DeepLearning.AI\",\n    },\n    {\n        \"guest_id\": \"florian-poullin\",\n        \"full_name\": \"Florian Poullin\",\n        \"affiliation\": \"Datablist\",\n    },\n    {\n        \"guest_id\": \"satya-nadella\",\n        \"full_name\": \"Satya Nadella\",\n        \"affiliation\": \"Microsoft\",\n    },\n    {\n        \"guest_id\": \"dharmesh-shah\",\n        \"full_name\": \"Dharmesh Shah\",\n        \"affiliation\": \"HubSpot\",\n    },\n]\n\nresults_by_id = {\n    result[\"inputId\"]: result for result in lookup_results\n}\nresearch_list = []\n\nfor guest in guests:\n    prepared_guest = guest.copy()\n    prepared_guest[\"profile_url\"] = None\n    prepared_guest[\"search_title\"] = None\n    prepared_guest[\"profile_confidence\"] = None\n\n    result = results_by_id.get(guest[\"guest_id\"], {})\n    outputs = result.get(\"outputs\", {})\n    title = outputs.get(\"linkedInUrlTitle\", \"\")\n    is_useful_match = (\n        result.get(\"status\") == \"success\"\n        and outputs.get(\"profileConfidence\") == \"High\"\n        and guest[\"full_name\"].casefold() in title.casefold()\n    )\n\n    if is_useful_match:\n        prepared_guest[\"profile_url\"] = outputs.get(\"profileUrl\")\n        prepared_guest[\"search_title\"] = title\n        prepared_guest[\"profile_confidence\"] = outputs.get(\"profileConfidence\")\n\n    research_list.append(prepared_guest)\n\nprint(json.dumps(research_list, indent=2, ensure_ascii=False))\n```\n\nThe dictionary maps every result by `inputId`, so an out-of-order response does\nnot attach one person's URL to another person. The local acceptance rule keeps a\nlink only when the result succeeded, confidence is `High`, and the returned\ntitle contains the submitted full name.\n\nFor Florian, `guest_id: florian-poullin` matches the same `inputId` in the API\nresult. His original name and Datablist affiliation stay the same, while the URL,\ntitle, and confidence are added. The complete output is:\n\n```json\n[\n  {\n    \"guest_id\": \"andrew-ng\",\n    \"full_name\": \"Andrew Ng\",\n    \"affiliation\": \"DeepLearning.AI\",\n    \"profile_url\": \"https://www.linkedin.com/in/andrewyng\",\n    \"search_title\": \"Andrew Ng - DeepLearning.AI, AI Fund and AI Aspire\",\n    \"profile_confidence\": \"High\"\n  },\n  {\n    \"guest_id\": \"florian-poullin\",\n    \"full_name\": \"Florian Poullin\",\n    \"affiliation\": \"Datablist\",\n    \"profile_url\": \"https://fr.linkedin.com/in/florianpoullin/fr\",\n    \"search_title\": \"Florian Poullin - Fondateur chez Datablist | Freelance\",\n    \"profile_confidence\": \"High\"\n  },\n  {\n    \"guest_id\": \"satya-nadella\",\n    \"full_name\": \"Satya Nadella\",\n    \"affiliation\": \"Microsoft\",\n    \"profile_url\": \"https://www.linkedin.com/in/satyanadella\",\n    \"search_title\": \"Satya Nadella - Chairman and CEO at Microsoft\",\n    \"profile_confidence\": \"High\"\n  },\n  {\n    \"guest_id\": \"dharmesh-shah\",\n    \"full_name\": \"Dharmesh Shah\",\n    \"affiliation\": \"HubSpot\",\n    \"profile_url\": \"https://www.linkedin.com/in/dharmesh\",\n    \"search_title\": \"Dharmesh Shah - Founder and CTO at HubSpot. Helping ...\",\n    \"profile_confidence\": \"High\"\n  }\n]\n```\n\nAll four people receive the URL, title, and confidence from the result carrying\ntheir own ID. The merge does not depend on the order of the API response.\n\nYou can download the maintained [synchronous Requests\nexample](https://github.com/datablist/linkedin-profile-finder-api/blob/main/python/sync_requests.py).\nAfter you understand the blocking version, see the [async HTTPX\nalternative](https://github.com/datablist/linkedin-profile-finder-api/blob/main/python/async_httpx.py)\nfor an asynchronous client.\n\n## How much the API lookup costs {#how-much-the-api-lookup-costs}\nEach attempted profile lookup uses 2.5 credits, whether it returns a profile or\n`no_result`. An empty or invalid input that is not searched uses zero credits.\nThe four lookups in this example used 10 credits. At the same rate, 1,000\nattempted lookups use 2,500 credits.\n\n<div format=\"cost-calculator\"\n     data-heading=\"Estimate your API cost\"\n     data-unit-label=\"Attempted lookups\"\n     data-credits-per-unit=\"2.5\"\n     data-lower-rate-label=\"LinkedIn profile lookup\"\n     data-initial-quantity=\"1000\"\n     data-max-quantity=\"100000\">\n<p>Each attempted profile lookup uses 2.5 credits, including a no-match search.</p>\n</div>\n\nAPI access requires the Growth plan, which includes subscription credits.\nOptional top-ups are separate from the subscription. At the current USD base\ntop-up price of $20 for 20,000 credits, one credit costs $0.001, so 2,500\ncredits cost $2.50. Check [Datablist pricing](/pricing) for current plan and\ncredit-package prices, especially if your account uses EUR pricing.\n\nThe [LinkedIn Profile Finder reference](/enrichments/linkedin-finder-from-name)\nlists the enrichment's current inputs, outputs, and credit use.\n\n## Fix common lookup problems {#fix-common-lookup-problems}\n- **`DATABLIST_API_KEY` is missing:** Python raises a `KeyError` before the\n  script makes a request. Export the environment variable and run the file\n  again.\n- **The API rejects the key:** `raise_for_status()` surfaces the HTTP error.\n  Copy your Personal API Key again and make sure you send only the `X-API-Key`\n  credential used in this example.\n- **Credits are insufficient:** check your balance, then use included credits\n  or add a top-up before creating another task.\n- **The task is `failed` or `stopped`:** inspect its error, available results,\n  and credit use before deciding whether another paid lookup is necessary.\n\nIf the connection drops while you submit the task, use the `Request ID` printed\nbefore the POST. List recent profile-lookup tasks and compare that value with\neach task's `uniqueId` before submitting anything again:\n\n```python\nresponse = requests.get(\n    \"https://api.datablist.com/v1/enrichments/tasks\",\n    headers={\"X-API-Key\": api_key},\n    params={\"enrichmentId\": \"linkedin-profile-lookup\", \"limit\": 100},\n)\nresponse.raise_for_status()\n\nrequest_id = \"paste-the-printed-request-id\"\ntasks = response.json()[\"tasks\"]\nmatching_task = None\n\nfor task in tasks:\n    if task[\"uniqueId\"] == request_id:\n        matching_task = task\n        break\n\nif matching_task:\n    print(\"Task ID:\", matching_task[\"@id\"])\n```\n\nTask history can be filtered by `enrichmentId`, but not directly by\n`uniqueId`. If the task is not on the first page, follow the task-list response's\n`nextCursor`. Once you find its `@id`, continue with the status and results URLs\nused by the main function. The [task API\ndocumentation](https://platform.datablist.com/docs/enrichments/tasks) explains\ntask states, result retrieval, and interruption behavior.\n\n## Next steps {#next-steps}\nYou now have one Python function that submits name and company data, waits for\nthe background task, and retrieves every page of LinkedIn profile results. The\nfollow-up maps those results into a local guest-research list without relying on\nresponse order.\n\nReplace the four examples with a small shortlist from your next webinar or\npodcast topic, then open the returned profiles and confirm the people and roles\nbefore using them in your research. If you prefer a spreadsheet-like workflow,\nuse the [no-code LinkedIn profile finder\nguide](/how-to/find-linkedin-profiles-from-name). Once you have reviewed a URL,\nthe separate [LinkedIn profile scraping\nguide](/how-to/scrape-linkedin-profile-details) shows how to retrieve additional\npublic profile details.\n"
}