{
  "version": 1,
  "slug": "find-company-websites-python-api",
  "title": "How to Find Company Websites from Company Names in Python Using an API",
  "excerpt": "Find company websites with Python, preserve URLs you already have, and match asynchronous API results back to the right company records.",
  "cover": {
    "src": "/howto_images/find-company-websites-python-api/find-company-websites-python-api-cover.png",
    "optimized": "https://www.datablist.com/_next/image?url=%2Fhowto_images%2Ffind-company-websites-python-api%2Ffind-company-websites-python-api-cover.png&w=1200&q=75"
  },
  "url": "https://www.datablist.com/how-to/find-company-websites-python-api",
  "contentMarkdown": "\nTo find a company website from a company name in Python, send the name and any\navailable country context to Datablist's Company Name to Domain API. The API\nprocesses the lookup in the background, then returns the domain and full website\nURL.\n\nI will first show you a simple Python API request. Then I will reuse the same\nfunction to fill only the missing websites in a list of company records while\npreserving the URLs already present.\n\nIf you prefer a spreadsheet-like interface, follow the [bulk company name to\nwebsite guide](/how-to/find-company-websites-from-names).\n\n> 💡 **Summary For Those In a Rush**\n>\n> Build one Python function that finds websites from company names, then use it\n> to complete records with blank websites without replacing known URLs.\n>\n> - **Input:** company names with optional country context.\n> - **Output:** domain, full website URL, confidence, result status, and credits used.\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 to make synchronous HTTP calls.\nInstall it in a virtual environment:\n\n```shell\npython3 -m venv .venv\nsource .venv/bin/activate\npython -m pip install requests\n```\n\nYou also need a Datablist Growth plan because REST API access is a Growth\nfeature. Create a Personal API Key from your [Datablist API account\npage](https://app.datablist.com/account/api), then put it in an environment\nvariable:\n\n```shell\nexport DATABLIST_API_KEY=\"auth0|exampleuser:exampleaccount:EXAMPLE_TOKEN_NOT_VALID:1\"\n```\n\nReplace the sample value above with the key generated on your account page.\nCompany website lookups consume credits. I explain the 1-credit and 2.5-credit\npaths before scaling the script, and current plan values are on the [Datablist\npricing page](/pricing).\n\n## Run one company website lookup in Python {#run-one-company-website-lookup-in-python}\nCreate a file named `find_company_websites.py`. The script submits the names,\nwaits for the task, and fetches the results in one function. Each input uses\nyour stable record ID as `inputId`, so you can match every result to the right\nrecord even if the response order changes.\n\n```python\nimport json\nimport os\nimport time\nimport uuid\n\nimport requests\n\n\nAPI_BASE_URL = \"https://api.datablist.com/v1\"\n\n\ndef find_domains(companies, api_key):\n    headers = {\n        \"Accept\": \"application/json\",\n        \"Content-Type\": \"application/json\",\n        \"X-API-Key\": api_key,\n    }\n    inputs = [\n        {\n            \"inputId\": company[\"id\"],\n            \"data\": {\n                \"companyName\": company[\"company_name\"],\n                \"targetCountry\": company.get(\"country\", \"\"),\n            },\n        }\n        for company in companies\n    ]\n    unique_id = f\"python-domain-lookup-{uuid.uuid4().hex}\"\n    payload = {\n        \"enrichmentId\": \"company-name-to-domain\",\n        \"uniqueId\": unique_id,\n        \"settings\": {\n            \"workflow\": \"dataset_google\",\n            \"countryOverwrite\": True,\n            \"acceptNonRoot\": True,\n        },\n        \"inputs\": inputs,\n    }\n\n    print(f\"Task uniqueId: {unique_id}\")\n    response = requests.post(\n        f\"{API_BASE_URL}/enrichments/tasks\",\n        headers=headers,\n        json=payload,\n    )\n    response.raise_for_status()\n    task_id = response.json()[\"@id\"]\n\n    while True:\n        response = requests.get(\n            f\"{API_BASE_URL}/enrichments/tasks/{task_id}\",\n            headers=headers,\n        )\n        response.raise_for_status()\n        task = response.json()\n        if task[\"status\"] in {\"completed\", \"failed\", \"stopped\"}:\n            break\n        time.sleep(3)\n\n    if task[\"status\"] != \"completed\":\n        raise RuntimeError(\n            f\"Task {task_id} ended with status {task['status']}: {task.get('error')}\"\n        )\n\n    results = []\n    cursor = None\n    while True:\n        params = {\"limit\": 100}\n        if cursor is not None:\n            params[\"cursor\"] = cursor\n\n        response = requests.get(\n            f\"{API_BASE_URL}/enrichments/tasks/{task_id}/results\",\n            headers=headers,\n            params=params,\n        )\n        response.raise_for_status()\n        page = response.json()\n        results.extend(page[\"results\"])\n        cursor = page.get(\"nextCursor\")\n        if cursor is None:\n            break\n\n    return results\n\n\napi_key = os.environ[\"DATABLIST_API_KEY\"]\ncompanies = [\n    {\n        \"id\": \"direct-microsoft-us\",\n        \"company_name\": \"Microsoft\",\n        \"country\": \"US\",\n    }\n]\n\nlookup_results = find_domains(companies, api_key)\nfor result in lookup_results:\n    print(json.dumps(result, indent=2))\n```\n\nRun it from the terminal where you exported the key:\n\n```shell\npython find_company_websites.py\n```\n\nHere is the Microsoft result:\n\n```json\n{\n  \"inputId\": \"direct-microsoft-us\",\n  \"status\": \"success\",\n  \"outputs\": {\n    \"companyUrl\": \"https://microsoft.com\",\n    \"companyDomain\": \"microsoft.com\",\n    \"domainConfidence\": \"High\"\n  },\n  \"creditsUsed\": \"1\",\n  \"error\": null\n}\n```\n\nUse `inputId` to match this result to the original record. `status` is\n`success`, `no_result`, `error`, or `skipped`. A successful result can include\n`companyDomain`, the full `companyUrl`, and `domainConfidence`. `creditsUsed`\nshows the charge for this input, while `error` explains an item-level failure.\n\nThe function makes three types of API calls in order.\n\n**First, it submits the lookup.** The payload contains the settings and company\nrecords. The POST returns `202 Accepted`, and `@id` is the task ID needed by the\nnext calls:\n\n```python\nresponse = requests.post(\n    f\"{API_BASE_URL}/enrichments/tasks\",\n    headers=headers,\n    json=payload,\n)\nresponse.raise_for_status()\ntask_id = response.json()[\"@id\"]\n```\n\nIf `DATABLIST_API_KEY` is not set, Python raises `KeyError` at the environment\nlookup before making an HTTP request. Export the variable, then run the script\nagain. If the variable contains an invalid key, the POST reaches the API and\nreturns HTTP 401. Copy a current Personal API Key from your account page and\nreplace the invalid value.\n\n**Second, it polls the task.** The POST starts background work, so the script\nreads the task every three seconds until it reaches `completed`, `failed`, or\n`stopped`:\n\n```python\nwhile True:\n    response = requests.get(\n        f\"{API_BASE_URL}/enrichments/tasks/{task_id}\",\n        headers=headers,\n    )\n    response.raise_for_status()\n    task = response.json()\n    if task[\"status\"] in {\"completed\", \"failed\", \"stopped\"}:\n        break\n    time.sleep(3)\n```\n\nIf the task fails because credits are unavailable, its error has the reason\n`credits_error`. Add credits or wait for your monthly credits to renew, inspect\nany results already returned, then submit only the unfinished inputs in a new\ntask.\n\n**Third, it retrieves every result page.** Each response adds results to the\nlist and supplies the cursor for the next request:\n\n```python\nresults.extend(page[\"results\"])\ncursor = page.get(\"nextCursor\")\nif cursor is None:\n    break\n```\n\nIt fetches the next page until `nextCursor` is `null`.\n\nThe script prints the generated `uniqueId` before the POST. If the connection\ndrops before you receive the creation response, list your tasks first to check\ntheir status 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-name-to-domain\"\n```\n\nFind the task whose `uniqueId` matches the value generated by the script, then\ncopy its `@id`. The list endpoint can filter by `enrichmentId`, but not by\n`uniqueId`. Use the `@id` with the same status and results URLs used by the\nscript. If the task is still active, keep polling it. If it has stopped or\nfailed, check for partial results before submitting the inputs again.\n\n## Understand dataset matching and the country-aware fallback {#understand-dataset-matching-and-the-country-aware-fallback}\nThe script uses `dataset_google`, the recommended default. Datablist checks its\ncompany datasets first. If the match is missing or conflicting, it can use\nGoogle as a fallback. It checks possible matches before returning a confidence\nlevel.\n\n![Company website lookup that checks a dataset first and conditionally uses Google before returning a domain, URL, and confidence](/howto_images/find-company-websites-python-api/illustrations/conditional-company-website-lookup.png)\n\nCountry context helps with ambiguous names. In this example, the same company\nname produced different results:\n\n- `Mercury` with `US` returned `shipmercury.com` and `https://www.shipmercury.com/`.\n- `Mercury` with `IE` returned `mercuryeng.com` and `http://www.mercuryeng.com`.\n\nBoth had High confidence. Country context helps the lookup choose between\ncompanies with the same name, while confidence helps you decide which results\nneed review.\n\nWhen the match also depends on industry, company size, or other business\ncontext, use the [AI Agent research\nworkflow](/how-to/find-company-websites-from-names#use-ai-for-ambiguous-company-names).\nAdd those details to the AI Agent prompt so it can research the intended\ncompany with richer business context.\n\n## Fill only the missing websites in your application records {#fill-only-the-missing-websites-in-your-application-records}\nNow that `find_domains()` can find a website from a company name, reuse it to\nfill only missing websites in your company records. Records that already have a\nwebsite bypass the API, so their values stay unchanged and consume no lookup\ncredits.\n\nIn `find_company_websites.py`, replace everything from `companies = [` through\nthe final print loop with the block below. Keep the `api_key` assignment above\nit. The blank check handles `None`, an empty string, and whitespace. It makes no\nAPI call when every website is filled.\n\n```python\nrecords = [\n    {\n        \"id\": \"crm-001\",\n        \"company_name\": \"Microsoft\",\n        \"country\": \"US\",\n        \"website\": \"https://www.microsoft.com\",\n    },\n    {\n        \"id\": \"crm-002\",\n        \"company_name\": \"boulangerie du palais lyon\",\n        \"country\": \"FR\",\n        \"website\": \"\",\n    },\n    {\n        \"id\": \"crm-003\",\n        \"company_name\": \"Mercury Engineering\",\n        \"country\": \"IE\",\n        \"website\": None,\n    },\n    {\n        \"id\": \"crm-004\",\n        \"company_name\": \"Mercury\",\n        \"country\": \"US\",\n        \"website\": \"   \",\n    },\n    {\n        \"id\": \"crm-005\",\n        \"company_name\": \"Datablist\",\n        \"country\": \"FR\",\n        \"website\": \"https://www.datablist.com\",\n    },\n    {\n        \"id\": \"crm-006\",\n        \"company_name\": \"Mercury\",\n        \"country\": \"IE\",\n        \"website\": None,\n    },\n]\n\n\ndef website_is_missing(value):\n    return not isinstance(value, str) or not value.strip()\n\n\nrecords_to_lookup = [\n    record for record in records if website_is_missing(record.get(\"website\"))\n]\nlookup_results = (\n    find_domains(records_to_lookup, api_key) if records_to_lookup else []\n)\nresults_by_id = {result[\"inputId\"]: result for result in lookup_results}\n\nfor record in records:\n    if not website_is_missing(record.get(\"website\")):\n        record[\"website_lookup\"] = {\"status\": \"not_requested\"}\n        continue\n\n    result = results_by_id.get(record[\"id\"])\n    if result is None:\n        record[\"website_lookup\"] = {\n            \"status\": \"missing_result\",\n            \"domain\": None,\n            \"confidence\": None,\n            \"credits\": None,\n            \"error\": {\n                \"code\": \"missing_result\",\n                \"message\": \"No result was returned for this inputId.\",\n            },\n        }\n        continue\n\n    outputs = result.get(\"outputs\") or {}\n    if result[\"status\"] == \"success\" and outputs.get(\"companyUrl\"):\n        record[\"website\"] = outputs[\"companyUrl\"]\n\n    record[\"website_lookup\"] = {\n        \"status\": result[\"status\"],\n        \"domain\": outputs.get(\"companyDomain\"),\n        \"confidence\": outputs.get(\"domainConfidence\"),\n        \"credits\": result.get(\"creditsUsed\"),\n        \"error\": result.get(\"error\"),\n    }\n\nprint(json.dumps(records, indent=2))\n```\n\nThe code indexes results by `inputId`, then walks through the original records\nin order. The `website_lookup` object keeps the outcome and review details next\nto each attempted record, including unsuccessful or missing results.\n\nFor `crm-001`, `website_is_missing()` returns `False` because the Microsoft URL\nis already present. The record is not added to `records_to_lookup`, so the API\nnever receives it. The merge keeps its website and records the status as\n`not_requested`.\n\nFor `crm-002`, the website is blank, so the request sends the bakery with\n`inputId: crm-002`. The API returns the same `inputId`. `results_by_id` uses it\nto find the right result, and the merge writes\n`https://boulangerie-du-palais-lyon.eatbu.com` to that record without relying on\nthe result order.\n\n<div class=\"preview-table\">\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>ID</th><th>Company</th><th>Website after merge</th><th>Status</th><th>Domain</th><th>Confidence</th><th>Credits</th></tr>\n</thead>\n<tbody>\n<tr><td><code>crm-001</code></td><td>Microsoft · US</td><td><code>https://www.microsoft.com</code></td><td><code>not_requested</code></td><td></td><td></td><td></td></tr>\n<tr><td><code>crm-002</code></td><td>boulangerie du palais lyon · FR</td><td><code>https://boulangerie-du-palais-lyon.eatbu.com</code></td><td><code>success</code></td><td><code>boulangerie-du-palais-lyon.eatbu.com</code></td><td>High</td><td>1</td></tr>\n<tr><td><code>crm-003</code></td><td>Mercury Engineering · IE</td><td><code>https://mercuryeng.com</code></td><td><code>success</code></td><td><code>mercuryeng.com</code></td><td>High</td><td>1</td></tr>\n<tr><td><code>crm-004</code></td><td>Mercury · US</td><td><code>https://www.shipmercury.com/</code></td><td><code>success</code></td><td><code>shipmercury.com</code></td><td>High</td><td>1</td></tr>\n<tr><td><code>crm-005</code></td><td>Datablist · FR</td><td><code>https://www.datablist.com</code></td><td><code>not_requested</code></td><td></td><td></td><td></td></tr>\n<tr><td><code>crm-006</code></td><td>Mercury · IE</td><td><code>http://www.mercuryeng.com</code></td><td><code>success</code></td><td><code>mercuryeng.com</code></td><td>High</td><td>1</td></tr>\n</tbody>\n</table>\n</div>\n</div>\n\nThe bakery uses a business-specific subdomain,\n`boulangerie-du-palais-lyon.eatbu.com`. Keep that value instead of reducing it\nto `eatbu.com`, and save the exact address from `companyUrl`. The\n`acceptNonRoot: true` setting also allows a hosted business page whose address\nincludes a path after the hostname, so the full URL matters in both cases.\n\nA completed task can still contain `no_result`, `error`, or `skipped` items.\nThe example changes `website` only when the status is `success` and\n`companyUrl` is present. For `no_result`, the website stays blank and the status\nremains visible in `website_lookup`. Check the company name and country first.\nIf either input is wrong, correct it and submit that record in a new task.\nOtherwise, keep the blank website visible for manual review.\n\n## Estimate lookup credits before scaling the script {#estimate-lookup-credits-before-scaling-the-script}\nThe five example lookups all returned `success` with High confidence and used\n5 credits total.\n\nA lookup resolved from the dataset costs 1 credit. A lookup processed with\nGoogle costs 2.5 credits. You pay for the path used, not both. For 1,000 inputs,\nthat means 1,000 credits if all resolve through the dataset or 2,500 credits if\nall use Google. These are planning scenarios, not a promise that every lookup\nwill succeed.\n\nAt $20 for 20,000 credits, those scenarios cost $1 and $2.50. The Growth\nsubscription is separate and currently includes 20,000 monthly credits, which\nare used before top-ups. Check [current pricing](/pricing) because plan and\npackage values can change.\n\nOne task accepts up to 10,000 inputs, with a 16 KiB limit per input and 10 MiB\nper request. This example uses blocking Requests calls to submit, poll, and\nfetch results from one small asynchronous Datablist task. The maintained\nrepository also includes CSV batching and non-blocking client examples.\n\n## Next steps {#next-steps}\nReplace the sample records with a small slice of your own data, then run the\nscript and inspect the merged records. Keep stable IDs and country context, and\nscale up only after the script preserves known websites and fills the expected\nblanks. You can start from the [maintained Company Name to Domain API examples\non GitHub](https://github.com/datablist/company-name-to-domain-api) if you want\nthe downloadable script, CSV batching, or an asynchronous version.\n"
}