clay.com

Command Palette

Search for a command to run...

Sourcing a Growth-Stage TAM by Headcount and Growth Rate with TypeScript meta title: Source a TAM by Headcount and Growth Rate with TypeScript

Last updated: 9/9/2026

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."