{
  "version": 1,
  "slug": "clean-company-names-python-api",
  "title": "How to Clean Company Names in Python Using an API",
  "excerpt": "Clean company names in Python with Datablist's API, preserve each raw value, and use the cleaned name in natural email introductions for new leads.",
  "cover": {
    "src": "/howto_images/clean-company-names-python-api/clean-company-names-python-api-cover.png",
    "optimized": "https://www.datablist.com/_next/image?url=%2Fhowto_images%2Fclean-company-names-python-api%2Fclean-company-names-python-api-cover.png&w=1200&q=75"
  },
  "url": "https://www.datablist.com/how-to/clean-company-names-python-api",
  "contentMarkdown": "\nYou can clean company names in Python before they turn into awkward email copy.\nIn this guide, you will send eight names to Datablist's Company Name Cleaner\nAPI, wait for the background task, and read every result page. Then you will\nreuse the same function to turn raw company names from a lead provider into\nnatural email introductions.\n\nThis approach handles more than a fixed list of suffixes. It can fix casing and\nspacing, remove a separate legal form or an obvious tagline, and preserve\naccents, numbers, acronyms, and brand punctuation. Keep the raw name on each\nlead, then use the cleaned value where people will read it.\n\n> 📌 **Summary For Those In a Rush**\n>\n> Use the Company Name Cleaner API to turn raw lead-provider company names into\n> readable email introductions while preserving the source data. The Python\n> example first cleans eight names, then reuses the same function to prepare\n> personalization copy.\n>\n> - **Input:** Source names such as `TRansformFactory Limited`, `AT&T`, and a\n>   directory name containing a tagline.\n> - **Output:** A cleaned name and confidence value, followed by an introduction\n>   such as `your team at NGF Immobilien`.\n\n## Set up your API key and Python environment {#set-up-your-api-key-and-python-environment}\nCreate a [Datablist Personal API Key](https://app.datablist.com/account/api).\nThe key follows this shape:\n\n```text\nauth0|exampleuser:exampleaccount:EXAMPLE_TOKEN_NOT_VALID:1\n```\n\nThat value is an example, not a working key. Export your own key as\n`DATABLIST_API_KEY`:\n\n```shell\nexport DATABLIST_API_KEY=\"your-personal-api-key\"\n```\n\nREST API access is available on the Growth plan. Cleaner calls also use\nenrichment credits, which I explain in the cost section.\n\nFor this example, I use the `requests` package. Create a file named\n`clean_company_names.py`, install the package, and run the file with:\n\n```shell\npython -m pip install requests\npython clean_company_names.py\n```\n\n## Build the `clean_company_names()` function {#build-the-cleancompanynames-function}\nPaste this complete script into `clean_company_names.py`:\n\n```python\nimport json\nimport os\nimport time\nimport uuid\n\nimport requests\n\n\ndef clean_company_names(company_records, api_key):\n    \"\"\"Submit one company-name task, wait, 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\": record[\"id\"],\n            \"data\": {\"companyName\": record[\"company_name\"]},\n        }\n        for record in company_records\n    ]\n\n    request_id = str(uuid.uuid4())\n    print(f\"Request ID: {request_id}\", flush=True)\n\n    payload = {\n        \"enrichmentId\": \"company-name-cleaner\",\n        \"uniqueId\": request_id,\n        \"settings\": {\"keepLegalSuffix\": 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.get(\"DATABLIST_API_KEY\")\n    if not api_key:\n        raise RuntimeError(\"Set DATABLIST_API_KEY before running this script.\")\n\n    companies = [\n        {\"id\": \"transform-factory\", \"company_name\": \"TRansformFactory Limited\"},\n        {\"id\": \"ngf-immobilien\", \"company_name\": \"NGF Immobilien GmbH & Co. KG\"},\n        {\"id\": \"lensen-partner\", \"company_name\": \"Lenßen + Partner Immobilien\"},\n        {\"id\": \"studio-54\", \"company_name\": \"Studio 54\"},\n        {\"id\": \"att\", \"company_name\": \"AT&T\"},\n        {\"id\": \"ebay\", \"company_name\": \"ebay\"},\n        {\n            \"id\": \"acme-tagline\",\n            \"company_name\": \"Acme | Leading provider of solar installations\",\n        },\n        {\n            \"id\": \"boston-consulting-group\",\n            \"company_name\": \"Boston Consulting Group\",\n        },\n    ]\n\n    cleaning_results = clean_company_names(companies, api_key)\n    for result in cleaning_results:\n        print(json.dumps(result, ensure_ascii=False))\n```\n\nThe function keeps the three API steps in execution order.\n\nFirst, it turns each company into an API input. The `inputId` is your stable\nrecord ID, while `data.companyName` is the value to clean. A stable ID matters\nbecause you should map a result back to its record by ID, not by its position in\nthe response.\n\nFor one company, the important part of the submitted JSON looks like this:\n\n```json\n{\n  \"enrichmentId\": \"company-name-cleaner\",\n  \"uniqueId\": \"your-generated-request-id\",\n  \"settings\": {\n    \"keepLegalSuffix\": false\n  },\n  \"inputs\": [\n    {\n      \"inputId\": \"transform-factory\",\n      \"data\": {\n        \"companyName\": \"TRansformFactory Limited\"\n      }\n    }\n  ]\n}\n```\n\nYou can submit several inputs in the same task. Each one remains independent,\nso a problem with one name does not change the `inputId` or source data for the\nothers. The script builds this array from `companies`, which means you can swap\nin IDs and names from your own application without changing the API sequence.\n\nNext, the `POST` request creates a task with the `company-name-cleaner`\nenrichment. The script prints its generated `uniqueId` before making that\nrequest. Keep that value: it helps you find the task if your connection drops\nbefore you receive the creation response.\n\nThe API accepts the request with HTTP `202` and returns a task. The response\nincludes an `@id`, its current status, and URLs for the task and its results. A\nshortened response looks like this:\n\n```json\n{\n  \"@id\": \"task-id\",\n  \"enrichmentId\": \"company-name-cleaner\",\n  \"uniqueId\": \"your-generated-request-id\",\n  \"status\": \"queued\",\n  \"links\": {\n    \"self\": \"https://api.datablist.com/v1/enrichments/tasks/task-id\",\n    \"results\": \"https://api.datablist.com/v1/enrichments/tasks/task-id/results\"\n  }\n}\n```\n\nThe function reads `@id` and builds the same task URLs itself. You do not need\nto copy an ID between requests when the script receives the creation response\nnormally.\n\nThe API processes the task in the background. This example uses blocking\nRequests calls, so the script sleeps for three seconds between status checks and\ndoes not continue until the task reaches `completed`, `failed`, or `stopped`.\n\nFinally, the function requests 100 results at a time. When `nextCursor` contains\na value, it sends that value as `cursor` and fetches the next page. It stops only\nwhen `nextCursor` is `null`. You can find the\n[synchronous Requests example](https://github.com/datablist/company-name-cleaner-api/blob/main/python/sync_requests.py)\nand an [async HTTPX example](https://github.com/datablist/company-name-cleaner-api/blob/main/python/async_httpx.py)\nin the Company Name Cleaner API GitHub repository.\n\n## Read the results {#read-the-results}\nHere is the result for `TRansformFactory Limited`:\n\n```json\n{\n  \"inputId\": \"transform-factory\",\n  \"status\": \"success\",\n  \"outputs\": {\n    \"cleanedCompanyName\": \"Transform Factory\",\n    \"companyNameConfidence\": \"High\"\n  },\n  \"creditsUsed\": \"0.5\",\n  \"error\": null\n}\n```\n\nThe eight examples show why cleaning a display name takes more than removing\n`Inc.` or converting everything to title case.\n\n<div class=\"preview-table\">\n<div class=\"table-wrapper\">\n<table>\n<thead>\n<tr><th>Source company name</th><th>Cleaned company name</th></tr>\n</thead>\n<tbody>\n<tr><td><code>TRansformFactory Limited</code></td><td><code>Transform Factory</code></td></tr>\n<tr><td><code>NGF Immobilien GmbH &amp; Co. KG</code></td><td><code>NGF Immobilien</code></td></tr>\n<tr><td><code>Lenßen + Partner Immobilien</code></td><td><code>Lenßen + Partner Immobilien</code></td></tr>\n<tr><td><code>Studio 54</code></td><td><code>Studio 54</code></td></tr>\n<tr><td><code>AT&amp;T</code></td><td><code>AT&amp;T</code></td></tr>\n<tr><td><code>ebay</code></td><td><code>eBay</code></td></tr>\n<tr><td><code>Acme | Leading provider of solar installations</code></td><td><code>Acme</code></td></tr>\n<tr><td><code>Boston Consulting Group</code></td><td><code>Boston Consulting Group</code></td></tr>\n</tbody>\n</table>\n</div>\n</div>\n\nEvery result in this task returned `success`, `High` confidence, and `0.5`\ncredits. The task used 4 credits in total. Confidence gives you a practical\nreview signal. You can use a High-confidence name directly in this small\nexample, while Low or Very Low results are worth comparing with the source name\nbefore you put them into email copy.\n\nA task can also contain `no_result`, `error`, or `skipped` items. Read each\nitem's `status` before using `outputs`, and read `error` when the status is not\n`success`. The email example below creates an introduction only when the status\nis `success` and `cleanedCompanyName` is not empty. An unsuccessful lead stays\nvisible without message copy while successful names continue.\n\nThe script sets `keepLegalSuffix` to `false`, which is the useful default for\nconversational display names. It removed `Limited` and `GmbH & Co. KG` when they\nwere separate from the brand. If you set it to `true`, the cleaner keeps an\nexisting legal form in a shorter conventional form when appropriate. It does not\nadd a legal form that was missing.\n\n![Company Name Cleaner removes legal forms and taglines while preserving brand punctuation and returning a cleaned name with confidence](/howto_images/clean-company-names-python-api/illustrations/identity-aware-company-name-cleaning.png)\n\nSee the [Company Name Cleaner reference](/enrichments/company-name-cleaner) for\ncurrent behavior and settings. If you prefer to clean names in the Datablist\ninterface, the [company-name cleaning methods guide](/how-to/clean-company-names-with-ai)\ncovers the no-code options.\n\n## Use cleaned names in email introductions {#use-cleaned-names-in-email-introductions}\nThe direct call gives you clean names. The practical next step is to put them\nbetween lead delivery and email preparation.\n\nImagine a lead provider sends a record for `NGF Immobilien GmbH & Co. KG`.\nStarting an email with “your team at NGF Immobilien GmbH & Co. KG” sounds like\nyou pasted a database field. Cleaning it first gives you “your team at NGF\nImmobilien” while the provider's raw value stays available on the record.\n\nTo run this follow-up on its own, keep the imports and\n`clean_company_names()` function from the complete script. Replace its\n`if __name__ == \"__main__\":` block with the code below:\n\n```python\nif __name__ == \"__main__\":\n    api_key = os.environ.get(\"DATABLIST_API_KEY\")\n    if not api_key:\n        raise RuntimeError(\"Set DATABLIST_API_KEY before running this script.\")\n\n    leads = [\n        {\n            \"lead_id\": \"lead-001\",\n            \"first_name\": \"Maya\",\n            \"company_name\": \"TRansformFactory Limited\",\n        },\n        {\n            \"lead_id\": \"lead-002\",\n            \"first_name\": \"Jonas\",\n            \"company_name\": \"NGF Immobilien GmbH & Co. KG\",\n        },\n        {\n            \"lead_id\": \"lead-003\",\n            \"first_name\": \"Alex\",\n            \"company_name\": \"AT&T\",\n        },\n        {\n            \"lead_id\": \"lead-004\",\n            \"first_name\": \"Priya\",\n            \"company_name\": \"Acme | Leading provider of solar installations\",\n        },\n    ]\n\n    company_records = [\n        {\"id\": lead[\"lead_id\"], \"company_name\": lead[\"company_name\"]}\n        for lead in leads\n    ]\n    cleaning_results = clean_company_names(company_records, api_key)\n    results_by_id = {\n        result[\"inputId\"]: result for result in cleaning_results\n    }\n\n    for lead in leads:\n        result = results_by_id.get(lead[\"lead_id\"])\n        if not result or result[\"status\"] != \"success\":\n            continue\n\n        cleaned_name = result.get(\"outputs\", {}).get(\"cleanedCompanyName\")\n        if cleaned_name:\n            lead[\"email_intro\"] = (\n                f\"Hi {lead['first_name']}, I wanted to reach out to \"\n                f\"your team at {cleaned_name}.\"\n            )\n\n    print(json.dumps(leads, indent=2, ensure_ascii=False))\n```\n\nThe list comprehension adapts each lead to the same `id` and `company_name`\nshape used in the first example. This submits one task with the same\n`clean_company_names()` function. The follow-up adds only the message-building\nlogic.\n\nThe result index is keyed by `inputId`, so the code does not depend on response\norder. If `lead-002` comes back after another record, its result still maps to\nJonas. The API changes `NGF Immobilien GmbH & Co. KG` to `NGF Immobilien`, and\nthe code produces:\n\n```text\nHi Jonas, I wanted to reach out to your team at NGF Immobilien.\n```\n\nThe raw `company_name` remains `NGF Immobilien GmbH & Co. KG`. If a result is\nunsuccessful or has no cleaned name, the lead remains in the output without an\n`email_intro`, ready for you to inspect.\n\nHere is the resulting list:\n\n```json\n[\n  {\n    \"lead_id\": \"lead-001\",\n    \"first_name\": \"Maya\",\n    \"company_name\": \"TRansformFactory Limited\",\n    \"email_intro\": \"Hi Maya, I wanted to reach out to your team at Transform Factory.\"\n  },\n  {\n    \"lead_id\": \"lead-002\",\n    \"first_name\": \"Jonas\",\n    \"company_name\": \"NGF Immobilien GmbH & Co. KG\",\n    \"email_intro\": \"Hi Jonas, I wanted to reach out to your team at NGF Immobilien.\"\n  },\n  {\n    \"lead_id\": \"lead-003\",\n    \"first_name\": \"Alex\",\n    \"company_name\": \"AT&T\",\n    \"email_intro\": \"Hi Alex, I wanted to reach out to your team at AT&T.\"\n  },\n  {\n    \"lead_id\": \"lead-004\",\n    \"first_name\": \"Priya\",\n    \"company_name\": \"Acme | Leading provider of solar installations\",\n    \"email_intro\": \"Hi Priya, I wanted to reach out to your team at Acme.\"\n  }\n]\n```\n\nThis example prepares the text but does not send an email. For the wider\noutreach workflow, see how to\n[normalize company names for cold outreach](/how-to/normalize-company-names).\n\n## Estimate the API cost {#estimate-the-api-cost}\nEach successfully cleaned, uncached name uses `0.5` credits. Cached results and\nfailed inputs use zero credits. This means 1,000 successful uncached names use\n500 credits.\n\n<div format=\"cost-calculator\"\n     data-heading=\"Estimate your API cost\"\n     data-unit-label=\"Successful uncached names\"\n     data-credits-per-unit=\"0.5\"\n     data-lower-rate-label=\"Company name cleaning\"\n     data-initial-quantity=\"1000\"\n     data-max-quantity=\"100000\">\n<p>Each successfully cleaned uncached name uses 0.5 credits.</p>\n</div>\n\nAPI access requires the Growth plan, which currently includes monthly credits.\nDatablist uses included credits before purchased top-ups. At the current USD\nbase top-up price of $20 for 20,000 credits, 500 credits cost $0.50. Check the\n[pricing page](/pricing) for the current subscription and credit-package prices,\nespecially if your account uses EUR pricing.\n\n## Fix common errors and recover a task {#fix-common-errors-and-recover-a-task}\nThe example stops instead of hiding an HTTP or task error. Use the point of\nfailure to choose the next action:\n\n- **Missing environment variable:** the script raises `RuntimeError` before it\n  sends a request. Export `DATABLIST_API_KEY` and run it again.\n- **Invalid API key:** `raise_for_status()` reports the failed API request.\n  Create or copy the Personal API Key again and confirm that the script sends it\n  in `X-API-Key`.\n- **Insufficient credits:** add credits or wait for your included credits to\n  renew, then decide whether to create a new task.\n- **Unsuccessful item:** inspect its `status` and `error`. Leave that lead\n  without an `email_intro` rather than building copy from an empty value.\n- **Failed or stopped task:** inspect the task and any available results and\n  credit use before submitting the names again.\n\nIf the connection drops while you create a task, the script has already printed\nits `uniqueId`. **List your tasks first to check their status before creating\nanother task.** Put the following snippet in a separate file after copying the\nimports and `api_key` setup from the main script. It lists recent cleaner tasks,\nmatches the printed value locally, and shows the returned `@id`:\n\n```python\nresponse = requests.get(\n    \"https://api.datablist.com/v1/enrichments/tasks\",\n    headers={\"X-API-Key\": api_key},\n    params={\"enrichmentId\": \"company-name-cleaner\", \"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 by `uniqueId`. If your\ntask is not on the first page, follow the history response's `nextCursor` and\ncheck the next page. Once you have the task ID, use the same status and results\nURLs from the main function. The [task documentation](https://platform.datablist.com/docs/enrichments/tasks)\nexplains task states, result access, and interruption behavior.\n\n## Conclusion: use cleaned names in your email copy {#conclusion-use-cleaned-names-in-your-email-copy}\nYou now have one readable Python function that submits company names, waits for\nthe Datablist task, retrieves all result pages, and returns a confidence value.\nThe lead example reuses those results to create natural email introductions\nwhile leaving every raw company name intact.\n\nRun the message-building example over a small sample from your latest lead\nprovider delivery next. Compare Low or Very Low confidence names with their\nsource values before you use the introductions in your outreach workflow.\n"
}