New: meet Clu, and send email, LinkedIn and call steps with Sequences. Watch the videos →
API Reference (v2)
Company Search API

Company Search API

Cleanlist's Company Search API finds B2B accounts by industry, headcount and location, and charges 0 credits to run. POST /api/v2/search/companies returns matched companies with domain, industry and employee count, plus a task_id for enrichment. A companion endpoint, POST /search/companies/similar, returns lookalikes for a seed company. Search is free; you spend 1 credit per company only when you enrich one.

🔌

Audience: developers. API keys need a Pro or Enterprise plan (legacy Pro and Scale plans included) or an AppSumo Tier 5, 6 or 7 license, and they never work during a trial. Calls also need the companies:read scope. To search from Claude instead, use the Cleanlist MCP.

The three company search endpoints are live on /api/v2 but are not in the served OpenAPI spec (/openapi-public-v2.json). Call them over HTTP as shown here.

How do I call the Cleanlist Company Search API?

POST https://api.cleanlist.ai/api/v2/search/companies with a clapi_ Bearer token and the companies:read scope. The body takes filters (or the criteria form), limit (1 to 500, default 25) and an optional cursor.

curl -X POST https://api.cleanlist.ai/api/v2/search/companies \
  -H "Authorization: Bearer clapi_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "filters": {
      "industries": ["Software Development"],
      "employee_count_ranges": ["51-200", "201-500"],
      "locations": ["San Francisco"]
    },
    "limit": 25
  }'

Which company filters does the API accept?

These flat keys are the complete list. Each row's aliases mean the same thing, so you can pipe company_* keys from a people search straight in.

Key (aliases)ExampleNotes
industries (industry, linkedin_industries)["Financial Services"]LinkedIn industry names
employee_count_ranges (employee_count_range, company_headcount, headcount_range, headcount)["51-200", "201-500"]Headcount bands
locations (company_locations, company_hq, hq_location)["San Francisco"]HQ location
hq_country["Canada"]HQ country
funding_stages (funding_stage, last_funding_round_type)["Series B"]Last funding round. Matched verbatim against the stored label; see below
names (company_names, company_name)["Shopify"]Company display name
domains (company_domains, company_website_domain)["shopify.com"]Company web domain; the reliable identifier
year_founded[2019]Founding year
company_type["Privately Held"]LinkedIn company type
⚠️

Any other key is rejected. revenue_ranges, technologies and typos such as industrys return 400 validation_error with "Unknown company filter key" and the list of accepted keys. Nothing is dropped silently. Sending no filters at all also returns 400.

Get suggested values for industries, employee_count_ranges, locations and funding_stages from GET /search/companies/filters?field=<field>&query=<text> (free).

Advanced callers can send the raw company filter shape ({"op": "and", "conditions": [...]}) in filters; it passes through without key checks.

Why does funding_stages return zero results?

Because the flat key matches the stored last-round label exactly, and a friendly label doesn't always match it. In our August 2026 test, {"industries": ["Software Development"], "employee_count_ranges": ["51-200"], "funding_stages": ["Series B"]} returned total: 0, while the same filters without funding_stages returned thousands. An empty result is HTTP 200 with total: 0, not an error.

If you need funding or revenue filtering, use the criteria form where it's enabled: fundingRoundTypes translates labels like "Series B" into the stored values, and revenueMin / revenueMax filter on estimated annual revenue. Otherwise, use headcount as a proxy: the 51-200 and 201-500 bands are mostly post-Series-A and pre-enterprise.

{
  "filters": {
    "industries": ["Software Development", "Financial Services"],
    "employee_count_ranges": ["51-200", "201-500"],
    "locations": ["San Francisco", "New York", "Toronto"]
  },
  "limit": 200
}

Because search costs 0 credits, you can run several variations and compare total counts before committing to one.

Why is industries: ["Finance"] returning nothing useful?

Industries use the LinkedIn industry taxonomy, which uses specific labels. "Finance" is not a taxonomy value; "Financial Services" is. Likewise "Software" is not a value and "Software Development" is. Use GET /search/companies/filters?field=industries&query=fin to find the right label.

For locations, send a bare city name like ["San Francisco"] rather than the full "San Francisco, California, United States" literal. Records are stored in different forms ("Toronto, ON", "North York"), and the long literal misses many of them.

The structured criteria form

Instead of filters, you can send criteria: the company filter form from the Find People page (Companies tab), compiled on the server the same way. Send exactly one of filters or criteria. It reaches fields flat filters can't, such as revenueMin and revenueMax, fundingRoundTypes, growthMin, headcountLatestMin, hqCountriesIso3, investorsInclude and descriptionKeyword. The full list is the CompanySearchCriteria schema in the OpenAPI spec.

{
  "criteria": {
    "industriesInclude": ["Software Development"],
    "headcountRanges": ["51-200"],
    "fundingRoundTypes": ["Series B"]
  },
  "limit": 25
}

The criteria form isn't available to AppSumo-only workspaces, which get a 403 instead. Criteria that compile to no filter return 400 empty_criteria.

How do I find companies similar to my best customers?

POST /api/v2/search/companies/similar with a seed company. Send company_domain or company_id, and an optional limit (1 to 100, default 25).

curl -X POST https://api.cleanlist.ai/api/v2/search/companies/similar \
  -H "Authorization: Bearer clapi_your_key" \
  -H "Content-Type: application/json" \
  -d '{"company_domain": "shopify.com", "limit": 25}'
{
  "reference_company": "Shopify",
  "results": [
    { "company_id": "...", "name": "Example Commerce", "domain": "example.com", "similarity_score": 0.75 }
  ]
}

Results are ranked by similarity_score (0 to 1): the share of the seed's facets (industries, headcount band, last funding round, categories) that the candidate also has. It is a deterministic shared-facet count, not a trained lookalike model, so use it to widen a segment from accounts you know convert. If the seed can't be resolved, results is empty.

The route deducts no credits. (POST /credits/estimate with tool: "find_similar_companies" returns 1, but you don't need a quote to call it.)

What does a Company Search response look like?

{
  "task_id": "cl-task_...",
  "entity_kind": "company",
  "results": [
    {
      "company_id": "...",
      "name": "Example Corp",
      "domain": "example.com",
      "industry": "Software Development",
      "employee_count": 180,
      "revenue_range": null,
      "hq_location": "San Francisco, California, United States",
      "funding_stage": null
    }
  ],
  "total": 1432,
  "cursor": "H4sIA...",
  "credits_charged": 0,
  "truncation_warning": null
}

total is the full match count regardless of limit, so one free call tells you how big a market is. Pass columns (for example ["Revenue", "Funding", "Last funding", "Founded", "LinkedIn"]) to add more fields to each row, such as founded_year, linkedin_url, employee_count_range and last_funding_date.

How do I get the people at those companies?

Company search returns accounts. To get contacts, take the domains and feed them into the People Finder API:

{
  "filters": {
    "company_domains": ["example.com", "acme.com"],
    "management_levels": ["VP", "Director"],
    "departments": ["Sales", "Marketing"]
  },
  "limit": 100
}

For a larger company set described by a filter, use POST /people/find-at-companies. Both are free; you pay only when you enrich a person for an email or a phone number, at 1 and 10 credits respectively.

What does company enrichment cost?

OperationCredits
Company search0
Similar companies0 (no credits deducted)
Company enrichment1 per company
Person enrichment, verified email1
Person enrichment, direct-dial phone10
Person enrichment, email and phone11

Company enrichment is synchronous. Person enrichment is asynchronous and returns a workflow_id you poll. See Enrichment and Polling & Async.

How do I paginate through company results?

Pass the cursor from the previous response back on the next request, with the same filters. Cursors are opaque; don't construct them. cursor: null means you have reached the end. There is no offset or page-number pagination.

What are the rate limits on company search?

60 requests per minute per organization (30 for AppSumo licenses without a paid plan) and 30 per minute per API key.

Company search is not subject to the 60-searches-per-UTC-day cap; that cap applies only to POST /people/find with API keys. If you are building an account-based workflow, do the company work first and pull people in fewer, larger People Finder calls.

On Pro and Enterprise, searches are unlimited within these rate limits. On AppSumo, each company search and similar-companies call draws from the workspace's monthly search allowance (50 a month, shared with people search); when it runs out you get 403 search_quota_exhausted.

Can an AI agent call the Company Search API?

Yes, and that is the intended pattern. Because search costs nothing and every paid operation needs a signed quote from POST /credits/estimate, an agent can explore accounts freely and still can't spend credits without pricing the job first. The quote is HMAC-signed with a 5-minute TTL and checked on the server against the actual cohort.

The Cleanlist MCP gives Claude the same search_companies and find_similar_companies tools directly, so Claude connects with your Cleanlist sign-in and no API key.

What error does a bad company search return?

Errors from the endpoint use the public envelope, wrapped in an error object:

{
  "error": {
    "code": "validation_error",
    "problem": "Unknown company filter key: 'revenue_ranges'",
    "fix": "Use one of the documented public flat-filter keys: [...]. For raw CompanyDB column access, pass the advanced {op, conditions} shape directly.",
    "retryable": false,
    "docs_url": "https://docs.cleanlist.ai/errors/validation-error",
    "request_id": "req_01J..."
  }
}

An empty result set is not an error: a search that matches nothing returns HTTP 200 with total: 0. Check total rather than the status code. Full table in Errors & Rate Limits.

FAQ

Is the Cleanlist Company Search API free?

The search call is free and returns credits_charged: 0. API keys need a Pro or Enterprise plan (or legacy Pro or Scale) or an AppSumo Tier 5 to 7 license. Company enrichment costs 1 credit per company and is billed only when data is returned.

Does Cleanlist have technographic or intent data?

No. There is no technographic filtering and no intent data. The technologies filter key is rejected with a 400 error.

How large can a company search be?

limit accepts 1 to 500 and defaults to 25. Use cursor pagination for larger cohorts. total reports the full match count either way.

What is the difference between names and domains?

names matches the company's display name and is ambiguous across companies that share one. domains matches the company's web domain and is the reliable identifier. Prefer domains whenever you have them.

Where does company data come from?

Cleanlist is an orchestration layer over external providers rather than a data vendor with an owned database. Company search and company enrichment are served by a search provider; contact enrichment runs a multi-provider waterfall with verification before results are returned.

Last updated: October 7, 2026. Accepted and rejected filter keys, the similar-companies body and the error shapes on this page were checked against the API code on that date. The funding_stages result was observed in a live test in August 2026.