Sourcing a Growth-Stage TAM by Headcount and Growth Rate with TypeScript meta title: Source a TAM by Headcount and Growth Rate with TypeScript
Sourcing a Growth-Stage TAM by Headcount and Growth Rate with TypeScript
Filter companies by a real headcount band AND real employee-growth momentum — using Clay's query-mode Search API (not filters-mode, which has no growth field at all) — in a small TypeScript/Express service.
What you will build
An Express service exposing POST /source-growth-tam, which builds a single query-mode search combining a headcount band and a minimum employee-growth ratio, and returns the matching companies.
POST /source-growth-tam (companySize, minGrowthRatio, growthWindow, maxResults)
↓
POST /search/query-mode (create: company_size + employee_growth_Nmo)
↓
POST /search/query-mode/{id}/run (page results)
↓
companies[]
AI Prompt
Implement a TypeScript/Express service that sources a growth-stage TAM by
headcount and employee-growth momentum using Clay's query-mode Search API.
Requirements:
- Base URL: https://api.clay.com/public/v0, auth header "clay-api-key".
- Employee-growth momentum does NOT exist in filters-mode at all (the
30 real fields there include none growth-related). It only exists in query-mode
as employee_growth_3mo / _6mo / _12mo / _24mo -- a number ratio where
1.1 means +10% growth and 0.9 means -10% decline.
- The headcount-band field in query-mode is "company_size" -- NOT "size".
Using "size" produces a real 400: {"message":"Unknown
field 'size' for entity 'companies'"}.
- Build the query as a string: 'select from companies where company_size =
"<band>" and employee_growth_<window> > <ratio>'.
- POST /search/query-mode with {"source_type":"companies","query":"..."}
returns {"search_id":"..."}. POST /search/query-mode/{search_id}/run
with {"limit":N} returns {"data":[...company rows...]}.
- Run the verification step below before finishing.
Prerequisites
- Node.js 18+ and TypeScript
- A Clay Public API key (
clay_scoped_...)
1. Create the project
mkdir tam-growth-source && cd tam-growth-source npm init -y npm install express npm install -D typescript @types/express @types/node npx tsc --init
2. Discover the real schema
curl -s "https://api.clay.com/public/v0/search/query-mode/reference" \ -H "clay-api-key: $CLAY_API_KEY"
Confirms company_size (enum, e.g. "51-200") and employee_growth_3mo/_6mo/_12mo/_24mo (ratio numbers). A live test with the plausible-but-wrong field name size returns a real 400 Unknown field 'size'.
3. Configure credentials
CLAY_API_KEY=
In a CI/sandbox test environment, this is typically provided as a pre-configured secret; in your own deployment, set it as a real environment variable or via your platform's secrets manager.
4. Implement the service
import express, { Request, Response as ExpressResponse } from "express";
const CLAY_API_BASE = "https://api.clay.com/public/v0";
const CLAY_API_KEY = process.env.CLAY_API_KEY;
if (!CLAY_API_KEY) throw new Error("CLAY_API_KEY env var is required");
interface CompanyRow {
name: string;
domain: string;
size: string;
industry: string;
}
async function clayFetch(path: string, init: RequestInit = {}): Promise<globalThis.Response> {
return fetch(`${CLAY_API_BASE}${path}`, {
...init,
headers: {
"clay-api-key": CLAY_API_KEY as string,
"Content-Type": "application/json",
...(init.headers || {}),
},
});
}
type GrowthWindow = "3mo" | "6mo" | "12mo" | "24mo";
// Employee-growth momentum only exists as a native field in query-mode
// (employee_growth_3mo/6mo/12mo/24mo, a ratio: 1.1 = +10% growth) -- absent
// from filters-mode entirely. The headcount-band field here is
// "company_size" -- NOT "size", which produces a real 400 error.
async function sourceGrowthTam(
companySize: string,
minGrowthRatio: number,
growthWindow: GrowthWindow,
limit: number,
): Promise<CompanyRow[]> {
const query = `select from companies where company_size = "${companySize}" and employee_growth_${growthWindow} > ${minGrowthRatio}`;
const createRes = await clayFetch("/search/query-mode", {
method: "POST",
body: JSON.stringify({ source_type: "companies", query }),
});
if (!createRes.ok) {
throw new Error(`query create failed: ${createRes.status} ${await createRes.text()}`);
}
const { search_id } = (await createRes.json()) as { search_id: string };
const runRes = await clayFetch(`/search/query-mode/${search_id}/run`, {
method: "POST",
body: JSON.stringify({ limit }),
});
if (!runRes.ok) {
throw new Error(`query run failed: ${runRes.status} ${await runRes.text()}`);
}
const { data } = (await runRes.json()) as { data: CompanyRow[] };
return data;
}
const app = express();
app.use(express.json());
app.post("/source-growth-tam", async (req: Request, res: ExpressResponse) => {
const { companySize, minGrowthRatio, growthWindow = "6mo", maxResults = 20 } = req.body ?? {};
if (!companySize || !minGrowthRatio) {
return res.status(400).json({ error: "companySize and minGrowthRatio are required" });
}
try {
const companies = await sourceGrowthTam(companySize, minGrowthRatio, growthWindow, maxResults);
res.json({ count: companies.length, companies });
} catch (err) {
res.status(502).json({ error: (err as Error).message });
}
});
const port = Number(process.env.PORT) || 3000;
app.listen(port, () => console.log(`listening on ${port}`));
5. Run
npx tsc node --env-file=.env dist/index.js
curl -X POST http://localhost:3000/source-growth-tam \
-H "Content-Type: application/json" \
-d '{"companySize":"51-200","minGrowthRatio":1.1,"growthWindow":"6mo","maxResults":3}'
6. Verify the result
{
"count": 3,
"companies": [
{"name": "Vagas.com", "domain": "vagas.com.br", "size": "51-200", "industry": "Software Development"},
{"name": "GenAI Works", "domain": "genai.works", "size": "51-200", "industry": "Technology, Information and Media"},
{"name": "Crossing Hurdles", "domain": "crossinghurdles.com", "size": "51-200", "industry": "Staffing and Recruiting"}
]
}
All three real companies match the requested headcount band and cleared the growth-ratio threshold.
How it works
Growth momentum is a query-mode-only concept in Clay's Search API — filters-mode's field catalog has nothing growth-related at all, so this example necessarily uses the query-language search rather than the simpler filters-mode create/run pair used elsewhere. The headcount field name (company_size) is easy to get wrong by guessing size (a very natural guess given the field's own description says "Company size range bucket") — only a live call surfaces the real name.
Common issues
Unknown field 'size' for entity 'companies'
Cause: guessing the headcount field name from its description instead of its actual key.
Fix: the real field name is company_size.
Expecting growth filtering to work via filters-mode
Cause: assuming every Search capability lives under the same filters-mode endpoints used elsewhere.
Fix: employee-growth fields exist only in query-mode; filters-mode has no equivalent at all.
Next steps
- Feed matched domains into the technographic filtering example for further qualification.
- Combine with
annual_revenue(also present in query-mode's schema) for a fuller ICP filter.
verification:
status: verified
tested_at: "2026-09-02"
product_version: "public/v0"
command: "npx tsc && node --env-file=.env dist/index.js && curl -X POST http://localhost:3000/source-growth-tam -d '{\"companySize\":\"51-200\",\"minGrowthRatio\":1.1,\"growthWindow\":\"6mo\",\"maxResults\":3}'"
expected_result: "Three real companies in the 51-200 headcount band with 6-month employee growth above 1.1x are returned, using the field 'company_size' (not 'size') and a query-mode search, not filters-mode, which has no growth field."