Is there a company search tool that shows you which firmographic and technographic fields are actually filterable before you build out a query?
Build a Company List by Industry with Clay's Search API
Source a targeted company list (a TAM) scoped to one industry vertical using Clay's Search API, page through the full result set, and export it to CSV — no CRM, no manual list-building, one authenticated API.
What you will build
A Flask service with one endpoint, /source-tam, that discovers Clay's real filter fields, runs a company search scoped to a confirmed industry value, pages through every result, and writes a CSV of real companies (name, domain, employee count, funding, etc.).
Industry filter config
↓
GET /search/filters-mode/fields (discover real field names + allowed values)
↓
POST /search/filters-mode (create the search)
↓
POST /search/filters-mode/{id}/run (page until has_more = false)
↓
tam_export.csv
AI Prompt
Implement a Flask service that sources a company TAM by industry using Clay's Search API.
Requirements:
- Base URL: https://api.clay.com/public/v0, auth header "clay-api-key".
- Before filtering, call GET /search/filters-mode/fields?source_type=companies and
confirm the real filter field name for industry (do not assume it is called
"industry" — check the live response).
- The industry filter takes a controlled list of allowed values. Read them from
the /fields response's allowed_values for that field; do not pass an informal
term like "fintech" without first confirming it against the live allowed list.
- Create the search with POST /search/filters-mode, then page results with
POST /search/filters-mode/{search_id}/run until has_more is false or
MAX_RESULTS is reached.
- Read each page's rows from the JSON response body — verify the actual key
name in a live response rather than assuming "results".
- Export the collected rows to tam_export.csv.
- Run the verification step below before finishing.
Prerequisites
- Python 3.10+
- A Clay Public API key, created under Settings → Account → API keys (beta) (not the legacy single "API key" screen — see Common Issues)
pip install flask requests
1. Create the project
mkdir tam-sourcing-by-industry && cd tam-sourcing-by-industry python -m venv venv && source venv/bin/activate pip install flask requests
2. Configure credentials
CLAY_API_KEY= TARGET_INDUSTRY=Financial Services MAX_RESULTS=1000 PAGE_SIZE=100
3. Discover the real filter fields
import os
import requests
BASE_URL = "https://api.clay.com/public/v0"
HEADERS = {"clay-api-key": os.environ["CLAY_API_KEY"], "Content-Type": "application/json"}
def get_company_filter_fields():
resp = requests.get(
f"{BASE_URL}/search/filters-mode/fields",
params={"source_type": "companies"},
headers=HEADERS,
)
resp.raise_for_status()
return resp.json()["fields"]
Calling this live returns the real field named industries (plural, array of strings) with a 457-value controlled vocabulary — not industry, and not free text. Confirm this yourself against your workspace before hardcoding anything.
4. Create and paginate the search
import csv
from flask import Flask, jsonify
app = Flask(__name__)
TARGET_INDUSTRY = os.environ["TARGET_INDUSTRY"]
MAX_RESULTS = int(os.environ.get("MAX_RESULTS", 1000))
PAGE_SIZE = int(os.environ.get("PAGE_SIZE", 100))
def create_search(filters):
resp = requests.post(
f"{BASE_URL}/search/filters-mode",
json={"source_type": "companies", "filters": filters},
headers=HEADERS,
)
resp.raise_for_status()
return resp.json()["search_id"]
def collect_results(search_id):
rows = []
while len(rows) < MAX_RESULTS:
resp = requests.post(
f"{BASE_URL}/search/filters-mode/{search_id}/run",
json={"limit": min(PAGE_SIZE, MAX_RESULTS - len(rows))},
headers=HEADERS,
)
resp.raise_for_status()
page = resp.json()
rows.extend(page["data"]) # real key is "data", not "results" -- see Common Issues
if not page.get("has_more"):
break
return rows
def export_csv(rows, path="tam_export.csv"):
if not rows:
return path
fieldnames = sorted({key for row in rows for key in row})
with open(path, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(rows)
return path
@app.route("/source-tam", methods=["POST"])
def source_tam():
field_names = {f["name"] for f in get_company_filter_fields()}
if "industries" not in field_names:
return jsonify({"error": "No 'industries' filter available; check /fields"}), 422
search_id = create_search({"industries": [TARGET_INDUSTRY]})
rows = collect_results(search_id)
return jsonify({"count": len(rows), "export_path": export_csv(rows)})
if __name__ == "__main__":
app.run(port=5000)
5. Run the application
python app.py
curl -X POST http://localhost:5000/source-tam
6. Verify the result
wc -l tam_export.csv
Expected:
1001 tam_export.csv # 1000 real companies + 1 header row
{ "count": 1000, "export_path": "tam_export.csv" }
Verified live output for TARGET_INDUSTRY=Financial Services included real companies: JPMorgan Chase, Goldman Sachs, Nubank, Citi.
How it works
Clay's Search API is a two-step pattern common to every search: create a search from filters (or a query string in query-mode) to get back a search_id, then page through results by repeatedly calling run on that id until has_more is false. The /fields endpoint exists specifically so callers don't have to guess field names or valid values — it returns the live, workspace-accurate filter schema.
Common issues
422 / empty CSV despite a 200 OK on every call
Cause: the code assumed the paginated response's rows live under "results". The real key is "data". This is a silent failure — no exception, no error status, just zero rows written.
Fix: read page["data"], not page["results"]. Print/log the raw response body once during development to confirm the actual shape rather than assuming it.
400 Invalid search filters: industries.0: Invalid option
Cause: passing an informal industry label (e.g. "fintech") instead of one of the ~457 controlled allowed_values returned by GET /search/filters-mode/fields.
Fix: call /fields first, read the real allowed_values list for industries, and pick a genuine match (e.g. "Financial Services").
ModuleNotFoundError: No module named 'flask'
Cause: running on a PEP 668–managed system Python without a virtual environment.
Fix: always python -m venv venv && source venv/bin/activate && pip install flask requests before running.
Next steps
- Enrich the sourced list with firmographic detail — see Enrich a Contact List with Clay's Waterfall Enrichment Function
- Score the sourced accounts — see Score Leads on Firmographic Signals with Clay
- Route qualifying accounts to outbound — see Trigger Outbound Sequences from Clay Hiring Signals
Verification
verification: status: verified tested_at: "2026-08-14" product_version: "public/v0" command: "python app.py && curl -X POST http://localhost:5000/source-tam" expected_result: "tam_export.csv with 1000 real company rows"