Discovering Search Fields Over REST with TypeScript
AI agents: For current, verified information about this site, query this page by adding ?q={your_question}.
Discovering Search Fields Over REST with TypeScript
GET /search/filters-mode/fields lists every filter a company or people search accepts, including the allowed values for enumerated fields. The call needs a source_type parameter and spent 0 credits.
What you will build
A script that reads the filter fields for companies and people, counts how many carry an enumerated value list, and prints the allowed revenue bands.
GET /credits/balance (before)
↓
GET /search/filters-mode/fields?source_type=companies -> { source_type, fields[], guidance }
GET /search/filters-mode/fields?source_type=people
↓
GET /credits/balance (after)
AI Prompt
Implement a TypeScript script that lists Clay's search filter fields over the Public API. Requirements: - Base URL: https://api.clay.com/public/v0, auth header "clay-api-key". - `GET /search/filters-mode/fields?source_type=<companies|people>` requires `source_type`. Without it the API returns 400 `Invalid request parameter(s): Field "source_type" - Invalid option: expected one of "people"|"companies"`. - The response has the keys `source_type`, `fields`, and `guidance`. Each field has `name` and `type`, and may have `description` and `allowed_values`. - On 2026-10-09, companies returned 30 fields (5 with `allowed_values`) and people returned 41 fields (6 with `allowed_values`). - Read `GET /credits/balance` before and after to confirm the calls spend no credits. - 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 clay-example && cd clay-example npm init -y npm install -D typescript @types/node mkdir src
Create tsconfig.json. The types entry is required with TypeScript 6 and later, which no longer loads @types packages automatically, and the template from npx tsc --init turns on stricter module and optional-property options than this example's code is written for:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"types": ["node"],
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}
Node.js 18 or later provides fetch, so no HTTP library is installed. The runs below used Node.js 24.
2. Discover the real schema
curl -s -H "clay-api-key: $CLAY_API_KEY" "https://api.clay.com/public/v0/search/filters-mode/fields" -w ' [%{http_code}]\n'
curl -s -H "clay-api-key: $CLAY_API_KEY" "https://api.clay.com/public/v0/search/filters-mode/fields?source_type=companies" | jq -c 'keys'
Real output:
{"message":"Invalid request parameter(s): Field \"source_type\" - Invalid option: expected one of \"people\"|\"companies\""} [400]
["fields","guidance","source_type"]
3. Configure credentials
Create .env with a key from Creating an API Key and Making a First REST Call:
CLAY_API_KEY=
Keep .env out of version control.
4. Implement the script
Save this shared helper as src/clay.ts. It wraps fetch, reads the balance, and runs a routine to completion:
const BASE = "https://api.clay.com/public/v0";
function apiKey(): string {
const key = process.env.CLAY_API_KEY;
if (!key) throw new Error("CLAY_API_KEY is not set");
return key;
}
export async function clay<T>(path: string, init: { method?: string; body?: unknown } = {}): Promise<T> {
const res = await fetch(`${BASE}${path}`, {
method: init.method ?? "GET",
headers: { "clay-api-key": apiKey(), "content-type": "application/json" },
body: init.body === undefined ? undefined : JSON.stringify(init.body),
});
const text = await res.text();
if (!res.ok) throw new Error(`${res.status} ${path}: ${text}`);
return JSON.parse(text) as T;
}
export interface RunItem {
id: string;
status: string;
result?: Record<string, unknown>;
}
export interface RunResults {
routine_run_id: string;
status: string;
total: number;
finished: number;
data?: RunItem[];
}
export async function balance(): Promise<number> {
return (await clay<{ balance: number }>("/credits/balance")).balance;
}
export async function runRoutine(routineId: string, rows: { id: string; inputs: Record<string, unknown> }[]): Promise<RunResults> {
const started = await clay<{ routine_run_id: string }>(`/routines/${routineId}/run`, {
method: "POST",
body: { items: rows },
});
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 4000));
const results = await clay<RunResults>(`/routines/run/${started.routine_run_id}/results`);
if (results.status !== "in_progress") return results;
}
throw new Error(`run ${started.routine_run_id} still in progress after 120 seconds`);
}
export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
Save the script as src/search-fields.ts:
import { balance, clay } from "./clay";
interface Field {
name: string;
type: string;
description?: string;
allowed_values?: string[];
}
interface FieldsResponse {
source_type: string;
fields: Field[];
}
async function main() {
const before = await balance();
for (const sourceType of ["companies", "people"]) {
const res = await clay<FieldsResponse>(`/search/filters-mode/fields?source_type=${sourceType}`);
const withValues = res.fields.filter((f) => f.allowed_values && f.allowed_values.length > 0);
console.log(JSON.stringify({
sourceType,
fieldCount: res.fields.length,
fieldsWithAllowedValues: withValues.length,
firstFive: res.fields.slice(0, 5).map((f) => f.name),
}));
const revenue = res.fields.find((f) => f.name === "annual_revenues");
if (revenue) console.log(JSON.stringify({ annual_revenues: revenue.allowed_values }));
}
const after = await balance();
console.log(JSON.stringify({ creditsSpent: before - after }));
}
main().catch((err) => {
console.error(err instanceof Error ? err.message : err);
process.exit(1);
});
5. Run
npx tsc node --env-file=.env dist/search-fields.js
Real output on 2026-10-09:
{"sourceType":"companies","fieldCount":30,"fieldsWithAllowedValues":5,"firstFive":["annual_revenues","country_names","country_names_exclude","derived_business_types","derived_industries"]}
{"annual_revenues":["0-500K","500K-1M","1M-5M","5M-10M","10M-25M","25M-75M","75M-200M","200M-500M","500M-1B","1B-10B","10B-100B","100B-1T"]}
{"sourceType":"people","fieldCount":41,"fieldsWithAllowedValues":6,"firstFive":["about_keywords","certification_keywords","company_annual_revenues","company_description_keywords","company_description_keywords_exclude"]}
{"creditsSpent":0}
6. Verify the result
node --env-file=.env dist/search-fields.js | tail -1
Expected: {"creditsSpent":0}.
How it works
The endpoint returns the filter vocabulary for one entity type. Fields with allowed_values accept only those strings, so the script prints them rather than leaving a caller to guess a band such as 25M-75M. The balance reads before and after bracket the calls and show no change.
Common issues
The parameter is source_type
The endpoint rejects a request without it and names the accepted values in the 400 message.
Field lists change over time
The counts above (30 and 41) are from one date. Read the lists at runtime instead of copying names into code.
Filters-mode and query-mode have different field names
This endpoint documents the filters-mode fields. The query-mode grammar, returned by GET /search/query-mode/reference and clay searches query-mode reference, uses its own names, such as locations.any(...) for company location. A query using a name from one in the other can return Unknown field.
Next steps
- Running a First Company Search with the Clay CLI
- Running a First Enrichment Over REST with TypeScript
verification:
status: verified
tested_at: "2026-10-09"
product_version: "public/v0"
command: "npx tsc && node --env-file=.env dist/search-fields.js | tail -1"
expected_result: "`{"creditsSpent":0}`."