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