{
  "version": 1,
  "slug": "find-linkedin-company-pages-python-api",
  "title": "How to Find LinkedIn Company Pages from Websites in Python Using an API",
  "excerpt": "Find LinkedIn company pages from websites with Python, review match confidence, and prepare a company-research list for manual account research.",
  "cover": {
    "src": "/howto_images/find-linkedin-company-pages-python-api/find-linkedin-company-pages-python-api-cover.png",
    "optimized": "https://www.datablist.com/_next/image?url=%2Fhowto_images%2Ffind-linkedin-company-pages-python-api%2Ffind-linkedin-company-pages-python-api-cover.png&w=1200&q=75"
  },
  "url": "https://www.datablist.com/how-to/find-linkedin-company-pages-python-api",
  "contentMarkdown": "\nUse Datablist's [Company to LinkedIn API](/apis/company-to-linkedin) to find a LinkedIn company page from a website in Python. You can send a bare domain or a full website URL, wait for the matching task, and receive the company name, website, LinkedIn company URL, and a confidence signal.\n\nIn this guide, I use Python and the Requests library to match five company websites. The list includes familiar software companies plus VAUDE, an outdoor-equipment company, and Drooms, a virtual-data-room business. I then turn the results into two local queues: matches ready for company research and matches that need a manual check.\n\n> 🔑 **Summary For Those In a Rush**\n>\n> Send the websites from your account list to Datablist's Company to LinkedIn API. The API returns LinkedIn company-page URLs and confidence signals that help you decide which relationships to use and which to review.\n>\n> - **Input:** bare domains and full company website URLs.\n> - **Output:** company names, matched websites, LinkedIn company-page URLs, and `high` or `low` confidence, grouped into `Ready for research` and `Needs review` queues.\n\n> 💡 **Prefer a no-code solution?** Use Datablist's [LinkedIn Company Pages & Domain Lookup](/enrichments/linkedin-company-pages-domain-lookup-bulk) to match company websites and LinkedIn pages without writing code.\n\n## Set up Python and your Datablist API key {#set-up-python-and-your-datablist-api-key}\nFor this example, I use the `requests` package. The API itself works over HTTP, so you can adapt the same request sequence to another language later.\n\nYou need a Datablist account with REST API access, which is available on the Growth plan. Successful lookups also consume enrichment credits. I cover the exact credit calculation later in the guide.\n\nCreate a working directory and a file named `find_linkedin_company_pages.py`, then create a virtual environment and install Requests:\n\n```shell\nmkdir company-linkedin-matcher\ncd company-linkedin-matcher\npython -m venv .venv\nsource .venv/bin/activate\npython -m pip install requests\ntouch find_linkedin_company_pages.py\n```\n\nCreate a Personal API Key from your [Datablist account](https://app.datablist.com/account/api), then export it as an environment variable:\n\n```shell\nexport DATABLIST_API_KEY=\"your-api-key\"\n```\n\nThe script reads this value and sends it in the `X-API-Key` header.\n\n## Find LinkedIn company pages with one Python function {#find-linkedin-company-pages-with-one-python-function}\nThe example uses these five source accounts:\n\n- `account-datablist`: `datablist.com`\n- `account-posthog`: `posthog.com`\n- `account-n8n`: `https://n8n.io`\n- `account-vaude`: `vaude.com`\n- `account-drooms`: `https://www.drooms.com`\n\nEach `account_id` becomes the API `inputId`. This matters because results do not need to arrive in the same order as the inputs. A stable ID lets you join each result to the right source account.\n\nPaste the following complete script into `find_linkedin_company_pages.py`:\n\n```python\nimport json\nimport os\nimport time\nimport uuid\n\nimport requests\n\n\ndef match_companies(accounts, api_key, matching_direction):\n    \"\"\"Submit one matching task, wait for it, and return every result.\"\"\"\n    tasks_url = \"https://api.datablist.com/v1/enrichments/tasks\"\n    headers = {\"X-API-Key\": api_key}\n    inputs = [\n        {\n            \"inputId\": account[\"account_id\"],\n            \"data\": {\"sourceUrl\": account[\"source_url\"]},\n        }\n        for account in accounts\n    ]\n\n    unique_id = str(uuid.uuid4())\n    print(f\"Task uniqueId: {unique_id}\", flush=True)\n    payload = {\n        \"enrichmentId\": \"company-to-linkedin\",\n        \"uniqueId\": unique_id,\n        \"settings\": {\"matchingDirection\": matching_direction},\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    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        next_cursor = page[\"nextCursor\"]\n        if next_cursor is None:\n            break\n        params[\"cursor\"] = next_cursor\n\n    return results\n\n\naccounts = [\n    {\n        \"account_id\": \"account-datablist\",\n        \"company_name\": \"Datablist\",\n        \"source_url\": \"datablist.com\",\n    },\n    {\n        \"account_id\": \"account-posthog\",\n        \"company_name\": \"PostHog\",\n        \"source_url\": \"posthog.com\",\n    },\n    {\n        \"account_id\": \"account-n8n\",\n        \"company_name\": \"n8n\",\n        \"source_url\": \"https://n8n.io\",\n    },\n    {\n        \"account_id\": \"account-vaude\",\n        \"company_name\": \"VAUDE\",\n        \"source_url\": \"vaude.com\",\n    },\n    {\n        \"account_id\": \"account-drooms\",\n        \"company_name\": \"Drooms\",\n        \"source_url\": \"https://www.drooms.com\",\n    },\n]\n\napi_key = os.environ.get(\"DATABLIST_API_KEY\")\nif not api_key:\n    raise RuntimeError(\"Set DATABLIST_API_KEY before running this script.\")\n\nmatch_results = match_companies(\n    accounts, api_key, matching_direction=\"domain_to_linkedin\"\n)\nprint(json.dumps(match_results, indent=2))\n```\n\nRun it with:\n\n```shell\npython find_linkedin_company_pages.py\n```\n\nThe function makes three types of calls in order.\n\nFirst, the `POST` sends the five websites with `matchingDirection` set to `domain_to_linkedin`. The API responds with `202 Accepted` and a task ID because matching runs as a background task. The Requests script still feels sequential because it waits before moving to the next step.\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\nSecond, the status request checks that task until it becomes `completed`, `failed`, or `stopped`. The three-second pause prevents the script from sending status requests continuously.\n\nThird, the results request reads pages of up to 100 results. If `nextCursor` contains a value, the loop sends it as `cursor` on the next request. It stops only when `nextCursor` is `null`.\n\n## Read the matches and decide what needs review {#read-the-matches-and-decide-what-needs-review}\nHere is the complete result list for the five inputs:\n\n```json\n[\n  {\n    \"inputId\": \"account-datablist\",\n    \"status\": \"success\",\n    \"outputs\": {\n      \"companyWebsite\": \"datablist.com\",\n      \"confidence\": \"high\",\n      \"companyLinkedInUrl\": \"https://www.linkedin.com/company/datablist\",\n      \"companyName\": \"datablist\"\n    },\n    \"creditsUsed\": \"1.5\",\n    \"error\": null\n  },\n  {\n    \"inputId\": \"account-posthog\",\n    \"status\": \"success\",\n    \"outputs\": {\n      \"companyWebsite\": \"posthog.com\",\n      \"confidence\": \"high\",\n      \"companyLinkedInUrl\": \"https://www.linkedin.com/company/posthog\",\n      \"companyName\": \"PostHog\"\n    },\n    \"creditsUsed\": \"1.5\",\n    \"error\": null\n  },\n  {\n    \"inputId\": \"account-n8n\",\n    \"status\": \"success\",\n    \"outputs\": {\n      \"companyWebsite\": \"n8n.io\",\n      \"confidence\": \"high\",\n      \"companyLinkedInUrl\": \"https://www.linkedin.com/company/n8n\",\n      \"companyName\": \"n8n\"\n    },\n    \"creditsUsed\": \"1.5\",\n    \"error\": null\n  },\n  {\n    \"inputId\": \"account-vaude\",\n    \"status\": \"success\",\n    \"outputs\": {\n      \"companyWebsite\": \"vaude.com\",\n      \"confidence\": \"high\",\n      \"companyLinkedInUrl\": \"https://www.linkedin.com/company/vaude\",\n      \"companyName\": \"vaude\"\n    },\n    \"creditsUsed\": \"1.5\",\n    \"error\": null\n  },\n  {\n    \"inputId\": \"account-drooms\",\n    \"status\": \"success\",\n    \"outputs\": {\n      \"companyWebsite\": \"drooms.com\",\n      \"confidence\": \"low\",\n      \"companyLinkedInUrl\": \"https://www.linkedin.com/company/drooms-software\",\n      \"companyName\": \"Drooms\"\n    },\n    \"creditsUsed\": \"1.5\",\n    \"error\": null\n  }\n]\n```\n\nRead `status` and `confidence` separately. The API returns a result object for every input, including unsuccessful inputs. A `success` status means it found a company-page candidate. The `outputs.confidence` value tells you whether the website match was validated. The `inputId` connects the object to your source account, while `creditsUsed` records the charge for that row and `error` carries a structured failure when one occurs.\n\nKeep the full `outputs` object in your application even if the first screen only needs the LinkedIn URL and confidence. `companyName` and `companyWebsite` make a manual review faster, especially when the returned company name differs in capitalization or the input was a full URL. They also give you useful context without making another API call.\n\nThe matching process starts by normalizing the submitted website and checking company datasets for a known website-to-LinkedIn relationship. It then compares the input domain with the website published on the candidate LinkedIn company page. A registered-domain match produces `high` confidence.\n\nWhen the datasets do not provide a LinkedIn URL, the lookup can run a focused Google search for company-page candidates. A candidate that cannot be validated against a published website can return with `low` confidence. A known website mismatch is not accepted.\n\nTreat confidence as a review instruction. VAUDE returned `https://www.linkedin.com/company/vaude` with `high` confidence, so it can enter the research queue. Drooms returned `https://www.linkedin.com/company/drooms-software` with `low` confidence. Open the Drooms website and LinkedIn page before relying on that relationship.\n\nDatablist, PostHog, and n8n also returned high-confidence matches. This API finds the relationship between the website and company page. If you need company size, industry, or other page data after reviewing the URL, use the [LinkedIn company scraper API guide](/how-to/scrape-linkedin-companies-python-api) as the next step.\n\n## Prepare an account-research queue from the results {#prepare-an-account-research-queue-from-the-results}\nNow reuse `match_results` to prepare a local queue for manual company research. This block does not call the API again. It indexes results by `inputId`, then loops over the original account list so every source account stays visible even if a future result is missing.\n\n```python\nresults_by_id = {result[\"inputId\"]: result for result in match_results}\nready_for_research = []\nneeds_review = []\n\nfor account in accounts:\n    result = results_by_id.get(account[\"account_id\"])\n    if result is not None:\n        outputs = result.get(\"outputs\", {})\n    else:\n        outputs = {}\n\n    research_record = {\n        **account,\n        \"companyLinkedInUrl\": outputs.get(\"companyLinkedInUrl\"),\n        \"confidence\": outputs.get(\"confidence\"),\n        \"api_result\": result,\n    }\n\n    is_ready = (\n        result is not None\n        and result[\"status\"] == \"success\"\n        and outputs.get(\"companyLinkedInUrl\")\n        and outputs.get(\"confidence\") == \"high\"\n    )\n    if is_ready:\n        ready_for_research.append(research_record)\n    else:\n        needs_review.append(research_record)\n\nprint(\"Ready for research:\")\nprint(json.dumps(ready_for_research, indent=2))\nprint(\"Needs review:\")\nprint(json.dumps(needs_review, indent=2))\n```\n\nThe result dictionary makes the join independent of response order. `account-vaude` connects the original `vaude.com` record to the captured VAUDE page and enters `ready_for_research`. `account-drooms` keeps its full input URL and full API result, but enters `needs_review` because confidence is low even though the result status is `success`.\n\nThe same logic keeps a missing, `no_result`, or error row in the review queue. The two printed lists give the team a clear starting point for manual company research.\n\n![VAUDE routed to Ready for research after a high-confidence LinkedIn company-page match and Drooms routed to Needs review after a low-confidence match](/howto_images/find-linkedin-company-pages-python-api/illustrations/website-to-linkedin-match-review.png)\n\nYou can also download the maintained [synchronous Requests example](https://github.com/datablist/company-to-linkedin-api/blob/main/python/sync_requests.py). The repository includes an [asynchronous HTTPX example](https://github.com/datablist/company-to-linkedin-api/blob/main/python/async_httpx.py) for applications that need non-blocking HTTP, but the blocking version above is easier to follow for a first integration.\n\n## Reverse the lookup from a LinkedIn company page to a website {#reverse-the-lookup-from-a-linkedin-company-page-to-a-website}\nThe same function accepts LinkedIn company pages when you change the matching direction. This can help when your source list has company-page URLs and you need websites before a compatible company or work-email enrichment.\n\n```python\nlinkedin_accounts = [\n    {\n        \"account_id\": \"account-datablist\",\n        \"company_name\": \"Datablist\",\n        \"source_url\": \"https://www.linkedin.com/company/datablist\",\n    }\n]\n\nwebsite_results = match_companies(\n    linkedin_accounts,\n    api_key,\n    matching_direction=\"linkedin_to_domain\",\n)\n```\n\nA standard LinkedIn company-page lookup uses the regular matching rate. Numeric company IDs and Sales Navigator company URLs can use 10 credits because they follow a different reverse-lookup path. For a no-code version of this direction, see [how to get website URLs from LinkedIn company pages](/how-to/linkedin-company-pages-to-websites).\n\n## Estimate website-to-LinkedIn lookup credits {#estimate-website-to-linkedin-lookup-credits}\nA successful regular website-to-LinkedIn lookup uses 1.5 credits. The five displayed matches therefore used 7.5 credits. At the same rate, 1,000 successful matches use 1,500 credits. Invalid inputs and rows with no result can use zero credits, so check each result's `creditsUsed` value for actual usage.\n\nREST API access is part of the Growth plan, and the monthly subscription is separate from top-up credits. A $20 top-up gives you 20,000 credits, so one credit costs $0.001. At 1.5 credits per successful match, 1,000 matches use 1,500 credits worth $1.50. See [Datablist pricing](/pricing) for current plans and packages.\n\n<div format=\"cost-calculator\"\n     data-heading=\"Estimate your API cost\"\n     data-unit-label=\"Successful lookups\"\n     data-credits-per-unit=\"1.5\"\n     data-lower-rate-label=\"Website-to-LinkedIn match\"\n     data-initial-quantity=\"1000\"\n     data-max-quantity=\"100000\">\n<p>Each successful regular website-to-LinkedIn lookup uses 1.5 credits.</p>\n</div>\n\n## Fix common input, task, and result problems {#fix-common-input-task-and-result-problems}\n- **`DATABLIST_API_KEY` is missing:** the script stops before sending a request. Export the environment variable and run the command again.\n- **The API rejects the key:** verify that you copied a Personal API Key and that the request sends it only in `X-API-Key`.\n- **The input is unsupported:** use a company website or domain for `domain_to_linkedin`. For the reverse direction, use a supported company-page URL rather than a personal profile or blank value.\n- **Credits are insufficient:** check your balance, then use included credits or add a top-up before creating another task.\n- **The task is `failed` or `stopped`:** inspect the task status, error, existing results, and credit usage before deciding whether to submit again.\n- **A result is low confidence, `no_result`, or error:** keep the source website and full result in `needs_review`. Open the candidate page when one exists, and do not guess why a missing result occurred.\n\nIf the connection drops during task creation, use the `uniqueId` printed before the `POST`. List your tasks and look for that value before creating another task:\n\n```shell\ncurl --get \"https://api.datablist.com/v1/enrichments/tasks\" \\\n  --header \"X-API-Key: $DATABLIST_API_KEY\" \\\n  --data-urlencode \"enrichmentId=company-to-linkedin\" \\\n  --data-urlencode \"limit=100\"\n```\n\nThe task list cannot be filtered by `uniqueId`, so inspect each returned task and follow `nextCursor` if needed. Once you find the matching value, use its `@id` with the same status and results URLs as the script.\n\nOne task can contain up to 10,000 inputs. For the full request, task-retention, and pagination limits, use the [Company to LinkedIn API documentation](https://platform.datablist.com/docs/enrichments/tools/company-to-linkedin).\n\n## Next steps {#next-steps}\nYou now have one reusable Python Requests function that turns company websites into LinkedIn company-page candidates. The local queue keeps high-confidence matches ready for research and leaves uncertain or unsuccessful records visible for review.\n\nReplace the five examples with a small account list, review every low-confidence candidate, then use accepted URLs for manual company research or as inputs to the [LinkedIn company scraper API](/how-to/scrape-linkedin-companies-python-api). If your source list contains company names instead of websites, start with the [company website finder API guide](/how-to/find-company-websites-python-api)."
}