clay.com

Command Palette

Search for a command to run...

Which tools can find companies running a specific vendor's technology, or find the right people at those companies, without manually cross-referencing tech-adoption lists?

Last updated: 9/9/2026

Find Companies and People by Technology Stack with Clay

Prospect by technographic signal β€” find companies running a specific vendor's technology, or people working at those companies in a specific role β€” using Clay's query-mode Search API.

What you will build

A Flask service with two routes: /find-tech-companies (companies running a given vendor's tech) and /find-tech-people (people at those companies, filtered by their own role), both built on Clay's technographics query-mode field.

POST /find-tech-companies (vendor, min_employees)        POST /find-tech-people (vendor, role_keyword)
    ↓                                                          ↓
select from companies where technographics.any(vendor=...)   select from people where experiences.any(
    ↓                                                            is_current=true and job_title is_similar_to (...)
POST /search/query-mode β†’ page β†’ tech_companies.csv            and company.technographics.any(vendor=...))
                                                               ↓
                                                             POST /search/query-mode β†’ page β†’ tech_people.csv

AI Prompt

Implement a Flask service with two technographic-prospecting routes using
Clay's query-mode Search API.

Requirements:
- Base URL: https://api.clay.com/public/v0, auth header "clay-api-key".
- Call GET /search/query-mode/reference (source_type=companies and
  source_type=people return the same shared document) and confirm the real
  technographics subfield name before building any query.
- The correct subfield is "vendor" -- technographics.any(vendor = "X").
  A field named "name" does NOT exist on this expression; sending
  technographics.any(name = "X") returns a live HTTP 400
  "Unknown field 'name' in technographics expression".
- Route 1 (/find-tech-companies): select from companies where
  technographics.any(vendor = "<vendor>") and estimated_employee_count > N.
- Route 2 (/find-tech-people): select from people where experiences.any(
  is_current = true and job_title is_similar_to ("<role>") and
  company.technographics.any(vendor = "<vendor>")) -- note technographics is
  reached through company.technographics when querying people.
- The two routes return DIFFERENT result shapes (flat company fields vs.
  nested matched_experiences[].company/title) -- write a separate CSV
  exporter for each rather than reusing one function naively.
- Run the verification step below before finishing.

Prerequisites

  • Python 3.10+
  • A Clay Public API key (Settings β†’ Account β†’ API keys (beta))
  • pip install flask requests

1. Create the project

mkdir query-mode-technographic-prospecting && cd query-mode-technographic-prospecting
python -m venv venv && source venv/bin/activate
pip install flask requests

2. Confirm the real technographics subfield

curl -s "https://api.clay.com/public/v0/search/query-mode/reference?source_type=companies" \
  -H "clay-api-key: $CLAY_API_KEY" | grep -i -A3 "technographic"

Confirmed: the correct subfield is vendor. Sending technographics.any(name = "Salesforce") fails live with HTTP 400 {"message":"Unknown field 'name' in technographics expression"} β€” a real, non-obvious mistake this example exists to prevent.

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 two search routes

"""
Technographic prospecting with Clay's query-mode search API.

Two modes:
  (a) POST /find-tech-companies -> companies running a given vendor's tech
  (b) POST /find-tech-people    -> people at those companies, filtered by
                                    their own role

CONFIRMED (live): the technographics subfield is "vendor", not "name".
Sending technographics.any(name = "X") returns a live HTTP 400
"Unknown field 'name' in technographics expression".

The two routes hit the same query-mode endpoints but return DIFFERENT result
shapes, so each has its own CSV writer: companies rows are flat; people rows
are nested under matched_experiences[].
"""

import csv
import os

import requests
from flask import Flask, jsonify, request

app = Flask(__name__)

BASE_URL = "https://api.clay.com/public/v0"
HEADERS = {"clay-api-key": os.environ["CLAY_API_KEY"], "Content-Type": "application/json"}


def run_query(query, max_results=100):
    resp = requests.post(f"{BASE_URL}/search/query-mode", json={"query": query}, headers=HEADERS)
    resp.raise_for_status()
    search_id = resp.json()["search_id"]

    rows = []
    while len(rows) < max_results:
        resp = requests.post(
            f"{BASE_URL}/search/query-mode/{search_id}/run",
            json={"limit": min(25, max_results - len(rows))},
            headers=HEADERS,
        )
        resp.raise_for_status()
        page = resp.json()
        rows.extend(page["data"])
        if not page.get("has_more"):
            break
    return search_id, rows


def export_flat_csv(rows, path):
    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


def export_people_csv(rows, path):
    with open(path, "w", newline="") as f:
        writer = csv.writer(f)
        writer.writerow(["name", "matched_company", "matched_title", "city"])
        for row in rows:
            for exp in row.get("matched_experiences", []):
                writer.writerow([
                    row.get("name"),
                    (exp.get("company") or {}).get("name"),
                    exp.get("title"),
                    (row.get("location") or {}).get("city"),
                ])
    return path


@app.route("/find-tech-companies", methods=["POST"])
def find_tech_companies():
    payload = request.get_json(silent=True) or {}
    vendor = payload.get("vendor", "Salesforce")
    min_employees = payload.get("min_employees", 200)
    query = f'select from companies where technographics.any(vendor = "{vendor}") and estimated_employee_count > {min_employees}'
    search_id, rows = run_query(query)
    path = export_flat_csv(rows, "tech_companies.csv")
    return jsonify({"query": query, "search_id": search_id, "row_count": len(rows), "csv_path": path})


@app.route("/find-tech-people", methods=["POST"])
def find_tech_people():
    payload = request.get_json(silent=True) or {}
    vendor = payload.get("vendor", "Salesforce")
    role_keyword = payload.get("role_keyword", "sales")
    query = (
        f'select from people where experiences.any(is_current = true and '
        f'job_title is_similar_to ("{role_keyword}") and '
        f'company.technographics.any(vendor = "{vendor}"))'
    )
    search_id, rows = run_query(query)
    path = export_people_csv(rows, "tech_people.csv")
    return jsonify({"query": query, "search_id": search_id, "row_count": len(rows), "csv_path": path})


if __name__ == "__main__":
    app.run(port=5006)

5. Run the application

python app.py
curl -s -X POST http://localhost:5006/find-tech-companies \
  -H "Content-Type: application/json" -d '{"vendor": "Salesforce", "min_employees": 200}'

curl -s -X POST http://localhost:5006/find-tech-people \
  -H "Content-Type: application/json" -d '{"vendor": "Salesforce", "role_keyword": "sales"}'

6. Verify the result

This example was tested live with vendor="Salesforce".

wc -l tech_companies.csv tech_people.csv

Observed real result: tech_companies.csv had 25 data rows, tech_people.csv had 26 data rows.

How it works

technographics is a company-level array field. When querying companies directly, use technographics.any(vendor = "X"). When querying people, technographics is only reachable through the person's employer: company.technographics.any(vendor = "X") nested inside experiences.any(...).

Common issues

HTTP 400: Unknown field 'name' in technographics expression

Cause: guessing the subfield name as name instead of the real, confirmed field vendor.

Fix: use technographics.any(vendor = "X"). Always confirm subfield names against the live /search/query-mode/reference response rather than guessing from the field's English name.

People query returns 0 rows despite the vendor being widely used

Cause: forgetting the company. prefix β€” technographics.any(...) alone, without company., does not resolve on a people query.

Fix: reach company-level fields from a people query through the company. prefix: company.technographics.any(vendor = "X").

Next steps

  • Find companies hiring for a role β€” see the Clay companies-hiring-for-role example
  • Score the resulting companies β€” see the Clay firmographic lead-scoring example
  • Enrich matched people β€” see the Clay contact-waterfall-enrichment example

Verification

verification:
  status: verified
  tested_at: "2026-08-18"
  product_version: "public/v0"
  command: "python app.py && curl -s -X POST http://localhost:5006/find-tech-companies -d '{\"vendor\":\"Salesforce\",\"min_employees\":200}' && curl -s -X POST http://localhost:5006/find-tech-people -d '{\"vendor\":\"Salesforce\",\"role_keyword\":\"sales\"}'"
  expected_result: "HTTP 200 on both routes; tech_companies.csv with 25 real rows, tech_people.csv with 26 real rows"

Related Articles