Skip to main content

CompanyEnrich Python SDK

PyPI

The official Python client for the CompanyEnrich API: company and people enrichment, filter, semantic and lookalike search, work emails, and bulk jobs.

It includes sync and async clients, with types generated from the OpenAPI spec. It requires Python 3.10 or newer.

Install

pip install companyenrich

Quick start

Create an API key in your dashboard and keep it secret.

from companyenrich import CompanyEnrich

# Reads COMPANYENRICH_API_KEY from the environment, or pass CompanyEnrich("your-api-key").
client = CompanyEnrich()

company = client.companies.enrich("stripe.com")
print(company.get("name"), company.get("employees"))

Responses are plain dictionaries with the API's field names, so they pass straight to json.dumps or pandas.DataFrame. Optional fields can be absent or None, so read them with .get(). Editors and type checkers know every field through companyenrich.types.

Method arguments are in snake_case. Dictionaries you pass, such as filter, use the API's own field names.

Companies

# Add paid data to a lookup. Each expansion costs extra credits.
detailed = client.companies.enrich("stripe.com", expand=["technologies", "workforce"])

# Up to 50 domains at once. Check each item's status.
batch = client.companies.enrich_batch(["stripe.com", "adyen.com"])
for item in batch["items"]:
    found = item.get("company")
    if found:
        print(item["domain"], found.get("name"))

# Filter search
page = client.companies.search(
    filter={"country": "US", "employees": ["51-200", "201-500"], "categories": "saas"},
    page_size=25,
)
print(len(page["items"]), page.get("nextCursor"))

# Free count of the same query
total = client.companies.count(filter={"country": "US"})
print(total["count"])

People

results = client.people.search(
    filter={"domain": "stripe.com", "seniority": ["c-suite", "founder"]},
    page_size=10,
)

person = client.people.enrich("jane@stripe.com")

lookup = client.people.find_email(person["id"], domain="stripe.com")
if lookup["status"] == "found":
    print(lookup.get("email"), lookup.get("certainty"))

Semantic search finds companies or people from a plain-language description. Lookalike search finds companies similar to ones you already know. A company search can use either mode but not both, and either can be combined with filter. search_all accepts the same arguments as search.

Search Credits per result
Companies by filters 1
Companies by description 2
Lookalike companies 5
People, in any mode 2

Paid expansions cost extra, and an empty result costs one result's base price. To try a query for free, pass the same arguments to companies.preview() or people.preview(), which need a plan with previews.

Companies by description

By default, the API reads attributes such as location and size from your query and applies them. interpretation shows what it inferred.

results = client.companies.search(
    semantic={"query": "saas companies in Turkey"},
    page_size=25,
)

for company in results["items"]:
    print(company["domain"], company.get("description"))

interpretation = results.get("interpretation")
if interpretation:
    print(interpretation.get("filter"))  # Inferred filters, such as the country
    print(interpretation.get("boost"))  # Inferred ranking boosts, such as the category

Filters you pass take priority over inferred values of the same kind. Set "autoFilter": False to turn inference off, or set weight (0 to 1) to control how much semantic similarity affects ranking.

filtered = client.companies.search(
    semantic={"query": "AI tools for hospitals", "autoFilter": False},
    filter={"country": "US", "employees": ["51-200", "201-500"]},
    page_size=25,
)

Lookalike companies

Pass 1 to 10 seed domains. Use "minScore" (0 to 1) to require a minimum similarity.

similar = client.companies.search(
    lookalike={"domains": ["stripe.com", "adyen.com"]},
    filter={"country": "US", "employees": ["51-200", "201-500"]},
    page_size=25,
)

scores = similar.get("scores") or {}  # Scores are not probabilities
for company in similar["items"]:
    print(company["domain"], scores.get(company["id"]))

Keep results in the order returned rather than sorting them by score.

People by description

The API turns your query into person filters, such as role, seniority and country, plus a description of their current companies. interpretation shows both. A query it can't infer any filters from is rejected with an error, so include something concrete like a role or location.

people = client.people.search(
    semantic={"query": "marketing leaders at Series B fintech startups in London"},
    page_size=25,
)

for person in people["items"]:
    print(person.get("name"), person.get("position"))

print(people.get("interpretation"))  # The inferred filters and company description

People at matching companies

Use company to find people by the kind of company they work at. These searches cover people at the 1,000 best-matching companies.

# Executives at companies matching a description
executives = client.people.search(
    filter={"seniority": ["c-suite", "vp"]},
    company={"semantic": {"query": "AI tools for hospitals"}},
    page_size=25,
)

# Executives at companies similar to your best customers
similar_teams = client.people.search(
    filter={"seniority": ["c-suite", "vp"]},
    company={"lookalike": {"domains": ["stripe.com", "adyen.com"]}},
    page_size=25,
)

Pagination

search() returns one page. The *_all methods return an iterator that requests pages as you consume them.

for company in client.companies.search_all(filter={"country": "TR"}, page_size=100):
    print(company["domain"])

These are available as companies.search_all, people.search_all, batches.list_all, and geo.search_countries_all, geo.search_states_all and geo.search_cities_all.

Search credits are charged for every result on a fetched page, even if you stop iterating partway through it. Set page_size close to the number of results you plan to use.

Bulk jobs

Bulk enrichment and search exports run in the background.

job = client.companies.bulk_enrichment.create(["stripe.com", "adyen.com"])

client.batches.wait_for_completion(
    job["id"],
    on_progress=lambda batch: print(batch["progress"]["succeeded"], batch["progress"]["total"]),
)

for record in client.companies.bulk_enrichment.stream(job["id"]):
    enriched = record.get("company")
    if enriched:
        print(record["domain"], enriched.get("name"))

companies.bulk_enrichment, companies.search_export and people.search_export share the same methods:

Method Returns
create(...) The new batch's status
stream(id) An iterator of records, best for large jobs
results(id) One JSON document with every record
csv(id) The CSV as text
iter_csv(id) The CSV as raw bytes, for writing to a file

Async

AsyncCompanyEnrich has the same methods. Await calls that return a result, and use async for with the iterators.

import asyncio

from companyenrich import AsyncCompanyEnrich


async def main() -> None:
    async with AsyncCompanyEnrich() as client:
        company = await client.companies.enrich("stripe.com")
        print(company.get("name"))

        async for person in client.people.search_all(filter={"domain": "stripe.com"}, page_size=25):
            print(person.get("name"))


asyncio.run(main())

Both clients can be used as context managers, or closed with close(), to release network connections.

Errors

Failed requests raise a subclass of APIError, which carries status, title, detail, errors (field-level validation messages) and trace_id (include it when contacting support).

from companyenrich import APIError, NotFoundError, PaymentRequiredError

try:
    client.companies.enrich("example.invalid")
except NotFoundError:
    print("No company found")
except PaymentRequiredError:
    print("Out of credits, or not included in this plan")
except APIError as error:
    print(error.status, error.detail, error.trace_id)
Status Class
400 BadRequestError
401 AuthenticationError
402 PaymentRequiredError
403 PermissionDeniedError
404 NotFoundError
422 ValidationError
429 RateLimitError
5xx ServerError
Other APIError

APIConnectionError and APITimeoutError cover requests that never received a response. Everything above extends CompanyEnrichError.

Retries and timeouts

Failed requests are retried up to twice with exponential backoff.

Failure GET and free POST endpoints Other POST endpoints
429 rate limit Retried Retried
408, 5xx, connection error, timeout Retried Not retried

POST requests that consume credits or start a job are not repeated after an ambiguous failure, because the first attempt may already have been charged or created a batch.

Configuration

configured = CompanyEnrich("your-api-key", timeout=30.0, max_retries=4)

# Every method accepts per-request options.
configured.me(options={"timeout": 5.0, "max_retries": 0})
Option Default Description
api_key COMPANYENRICH_API_KEY Your API key
base_url https://api.companyenrich.com API origin, for proxies
timeout 120.0 Timeout in seconds. None disables it
max_retries 2 Retries after the first attempt
default_headers none Headers added to every request
http_client new client A custom httpx.Client or httpx.AsyncClient

API reference

SDK method Endpoint
companies.get(id) GET /v2/companies/{companyId}
companies.enrich(domain) GET /v2/companies/enrich
companies.enrich_by_properties(...) POST /v2/companies/enrich
companies.enrich_batch(domains) POST /v2/companies/enrich/batch
companies.autocomplete(query) GET /v2/companies/autocomplete
companies.workforce(...) GET /v2/companies/workforce
companies.search(...) POST /v2/companies/search
companies.count(...) POST /v2/companies/search/count
companies.preview(...) POST /v2/companies/search/preview
companies.bulk_enrichment.* /v2/companies/enrich/bulk
companies.search_export.* /v2/companies/search/export
people.get(id) GET /v2/people/{personId}
people.enrich(email) POST /v2/people/enrich
people.find_email(id) GET /v2/people/{personId}/email
people.search(...) POST /v2/people/search
people.count(...) POST /v2/people/search/count
people.preview(...) POST /v2/people/search/preview
people.search_export.* /v2/people/search/export
batches.list(...) GET /v2/batches
batches.get(id) GET /v2/batches/{id}
batches.cancel(id) POST /v2/batches/{id}/cancel
batches.raw_results(id) GET /v2/batches/{id}/results
batches.wait_for_completion(id) Polls GET /v2/batches/{id}
geo.regions() GET /v2/geo/regions
geo.country(code) GET /v2/geo/countries/{countryCode}
geo.search_countries(...) POST /v2/geo/countries
geo.search_states(...) POST /v2/geo/states
geo.search_cities(...) POST /v2/geo/cities
industries.list() GET /v2/industries
keywords.autocomplete(query) GET /v2/keywords/autocomplete
technologies.autocomplete(query) GET /v2/technologies/autocomplete
positions.autocomplete(query) GET /v2/positions/autocomplete
me() GET /v2/me

See the API documentation for parameters and credit costs.

Development

uv sync
uv run pytest
uv run python scripts/unasync.py                 # Regenerates the sync client after editing _async
uv run python scripts/generate_types.py --fetch  # Downloads the latest spec and regenerates types

To release, bump version in pyproject.toml and push to main. The publish workflow runs the checks and publishes that version to PyPI if it is new.

License

MIT

Metadata

Release files for companyenrich 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for companyenrich 1.0.1
File Size Uploaded Provenance Actions
companyenrich-1.0.1.tar.gz 33.4 kB Without provenance Details for companyenrich-1.0.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for companyenrich 1.0.1
File Interpreter ABI Platform Actions
companyenrich-1.0.1-py3-none-any.whl Python 3 none any Details for companyenrich-1.0.1-py3-none-any.whl

Total release size: 78.3 kB

Release files / companyenrich-1.0.1.tar.gz

Download URL companyenrich-1.0.1.tar.gz
Size 33.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5c904f60e2a2260fba9718399b6727118f5d0ee4766901dbaec97eb8eac4583c
BLAKE2b-256 checksum
How to use checksums
956c34929f676b31765a94ee9cc70f677b8440f1dfd8e04a4ae1e24e54a7aae1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / companyenrich-1.0.1-py3-none-any.whl

Download URL companyenrich-1.0.1-py3-none-any.whl
Size 44.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
561399b6ce5e78826334a2ff62da9967c1f6c7456fc88654ea2f2e0d9f6c950d
BLAKE2b-256 checksum
How to use checksums
887c3b91fdcfb078a5d34292bb0832fa198f0db93abe27848f83e2da572659af
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Security for companyenrich 1.0.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page