Anysite MCP — usage guide
The anysite MCP exposes hundreds of data sources through six universal meta-tools (plus the
crm_* family, see Working with CRM). This skill is the map: how to call them, which sources
cover which GTM need, and how to not waste credits.
The six meta-tools
| Tool |
Purpose |
Credits |
discover(source, category) |
List endpoints + exact params for a source/category |
free |
execute(source, category, endpoint, params) |
Run an endpoint; returns first 10 items + cache_key |
paid |
get_page(cache_key, offset, limit) |
Page through a cached result |
free |
query_cache(cache_key, conditions, sort_by, sort_order, aggregate, group_by, limit, offset) |
Filter/sort/aggregate cached data with SQL-like ops |
free |
export_data(cache_key, output_format, list_unpack) |
Export cached data — output_format json (default) / csv / jsonl; list_unpack = how many nested-array elements to expand into CSV columns (default 1) |
free |
search_requests(source, category, endpoint, query, since, until, limit, offset) |
Find past execute() calls and their cache_keys — 7-day history, works across sessions |
free |
Rules that prevent 90% of failures
- Always
discover before execute. Endpoint names and params are not guessable, and a
wrong source name returns the full source list — a wrong guess self-corrects for free.
execute takes the endpoint NAME exactly as discover returns it (products_reviews),
never a REST path segment (reviews) — resolution is an exact-match lookup.
- Never guess identifiers. LinkedIn aliases, URNs, Crunchbase aliases, Greenhouse board
tokens are unpredictable. Resolve them through the search endpoint of the same source first.
- Re-use the cache — it outlives the session.
execute returns a cache_key; further
filtering, sorting, counting and paging of that result is free, and the cache lives for
7 days across sessions. Before any paid execute, check search_requests (free) for
a recent identical call — same endpoint, matching params — and reuse its cache_key via
query_cache/get_page instead of refetching (verified live: a two-day-old cache_key
from another session served in full). Freshness rule: reuse when the data's age is fine
for the task (enrichment firmographics — usually yes; "what's new today" — no).
- Cheap-first cascade. When several endpoints can answer, call the cached/DB one first
(
*/db/*, *sql* endpoints, ~1 credit) and the live one only for the remainder.
- Estimate volume before bulk runs — plan-aware. First know the user's plan (the CRM
profile stores it after setup; if unknown, ask once: MCP Unlimited or credit-based?).
- Credit-based plan: before anything above ~100 calls, state the estimate
(
N targets × credits-per-call) and get a nod. Prefer cheap DB endpoints, batch hard.
- MCP Unlimited: credit warnings off, but keep batch sizes sane anyway — the real
limits are latency and upstream rate limits, so cap sweeps the same way and say
"this will take ~N minutes" instead of a price.
- Live LinkedIn search fails as an empty list, not an error.
search_users and
search_companies return {"results":[]} on queries that just don't hit ("stripe",
"databar" both came back empty live, while "microsoft" worked) — it is not a broken key.
On empty, switch to the search_sql_* DB endpoints; do NOT retry with broader keywords.
(This is why reverse-lookup via live search_users is best-effort, not "usually one
match".)
gdelt is slow by design, not broken — 10–50s per call is normal (upstream per-IP
throttling), and worst cases exceed the MCP client's silent-call timeout, which looks
like a hang. Endpoints: gdelt/articles/articles_search and articles_context
(timespan like 3d/1w or start_datetime YYYYMMDDHHMMSS; count ≤250). Keep it OUT of
per-account sweep loops (use techmeme / google news — seconds); fine for a one-off deep
media dive with a "takes a minute" warning.
GTM source map
Company discovery (bulk):
linkedin/search/search_sql_companies — the workhorse. Up to 1000 companies per call with
DSL filters (keywords, industry_name, employee_count_min/max, country_hq, founded_on_min/max,
has_website) and a sort param (relevance — default for filtered queries — or
last_modified for freshness/monitoring). Also batch lookup by urn and search by website.
Query craft (naive keywords return wrong-country token soup — measured 1/5 relevant vs
5/5 structured): the anysite-company-sourcing skill.
⚠️ website search is SUBSTRING match, ordered by last_modified. Verification is
MANDATORY on every resolve — position in the results means nothing. Verified live:
{website: "stripe.com", count: 1} → Soundstripe; {website: "stlabs.com", count: 5} →
five *labs.com companies, none of them stlabs.com (common tokens flood the result even in
a single-domain call). The domain-resolve rules:
- Verify exact
website match (normalize both sides: lowercase, strip
protocol/www./path) on EVERY resolve, single or batched. No exact match =
unresolved — never write anything to the CRM for it; wrong-company data lands in
blank fields where nobody will catch it.
- Never
count: 1 on a website resolve — the substring flood means the one row you
get is very likely the wrong company. Default one domain per call at a small count (5+).
OR-DSL batching
({website: "a.com|b.io", count: 10× domains}) is an optimization with a verification
tax: a domain with a common token can be flooded out of the batch entirely — every
domain that didn't come back exact-matched must be re-queried individually.
query_cache filters over the WHOLE cached set (verified) but returns at most limit
rows (default 10) — pass an explicit limit when you expect more matches back. Sanity
rule: aggregate {op: "count"} should equal the total from execute; if not, page
with get_page before concluding anything.
- A whole class of domains never appears in its own substring results (common-token
domains like stlabs.com) — so the website's own page is the STANDARD second step, not
an emergency:
webparser/parse {url: "https://<domain>", extract_minimal: true} →
top-level title says who they are, links[] usually carries their own
linkedin.com/company/... URL → linkedin/company for the exact URN (verified, ~1cr).
Live shape on stlabs.com: title: "STLabs — Intelligent Service Management" at the TOP
level, while metadata came back {} and cleaned_html empty — read title, and treat
metadata as a fallback only, not the primary location.
Secondary fallback: crunchbase/search by name → contacts.linkedin_url. Name search
alone is never a source of truth.
Bonus from a successful resolve: the search_sql_companies row already carries
crunchbase_link (free crunchbase alias — skip the live 20cr search) and
organizational_urn (company:<id> — the numeric id goes straight into search_jobs).
⚠️ For company SIZE use employee_count, never employee_count_range — the two fields
can contradict each other in the same record (verified: Clay returns employee_count: 1465 alongside employee_count_range: "201-500"). The range field looks like the natural
key for size segmentation and would misfile that company by ~3x, silently. Fall back to
the range only when the exact count is empty, and say that you did.
crunchbase/db/db_search — filters by funding stage, last funding date, investors,
employee range; count ≤100, dates as Unix timestamps. employee_count_min/max are
ENUM bands, not free integers (min ∈ {1,11,51,101,251,501,1001,5001,10001}, max ∈
{10,50,100,250,500,1000,5000,10000,10001}) — passing 20 errors out. 1 credit/result.
Response includes funding_rounds[], leadership_hires[], layoffs[], news[],
technologies[], employees[].
crunchbase/search (live, 20cr/50) — adds hiring, event, spotlight,
shares_investors_with, it_spend_*, revenue_*, valuation_* filters. Check discover
for its date format — it differs from db_search.
- Early-stage supplements:
yc/search/search_companies (keyword search, works) and
betalist/startups/startups_search {keyword, count}. NOT tracxn/companies/companies_search
— it has no name/keyword input, only an explore param (a URL/id of a ready-made Tracxn
list) and returns guests only a truncated slice; usable solely if you already hold such a
list URL.
Company detail: crunchbase/company — get its alias for free from the crunchbase_link
that search_sql_companies already returned (live crunchbase/search is the fallback, not
the first step). ⚠️ The alias is CASE-SENSITIVE ('Google' ≠ 'google') — take it verbatim
from the URL slug. Also linkedin/company. One crunchbase/company call carries free extras:
bombora_surges[] (B2B intent topics — but they show what THAT company's staff researches,
i.e. what they BUY; treat as a signal only when a topic matches what the user sells),
related.competitors[], predictions.funding_score, awards[]. Coverage caveat:
leadership_hires[] is often EMPTY for smaller companies — absence of the field is not
absence of hires. Normalize contacts.email (trailing dots observed: "x@y.ai.").
A third resolve path when crunchbase is already fetched: contacts.linkedin_url →
linkedin/company → exact URN (verified; bypasses both fuzzy searches).
Note: owler endpoints need an owler alias and its search has no name/keyword parameter —
not usable for looking up a named account.
Engagement graph (who interacted with content): linkedin/post/post_comments,
post_reactions, post_reposts and linkedin/company/company_posts (~1cr/10) — answers
"who paid attention to this content", incl. people outside your title filters. Identifiers:
comments/reposts carry a vanity alias; reactions give only an obfuscated /in/ACoAA... URL
plus internal_id — user_email accepts the internal_id, never the obfuscated URL.
Honest scaling: volume follows the SEED's audience, not the target's importance (large brand
post → dozens of engagers; 80-person company → 0–2 per post), and on a small account those
few are mostly the company's OWN staff plus engagement farmers (verified: 3 of 4 commenters
were employees) — filter by the author's company first and expect nothing left. Use this on
seeds with a real audience (a competitor's page), not on SMB target lists. For small accounts
the reliable nugget is company_posts → mentioned[]: hiring announcements name new people
with their vanity aliases. linkedin/company/company_employee_stats (1cr) gives
function/skill/location breakdown — cross-check totals against employee_count from
linkedin/company before trusting absolutes, and never sum the locations array: its
buckets are nested (US ⊃ California ⊃ SF Bay Area), so summing double-counts badly. Its
llm_hint promises seniority and growth trends that the response does not contain.
Tech stack & adoption signals: stackshare/companies (a company's declared stack by
slug — the forward direction wappalyzer can't do); producthunt/products/products_customers
(reverse stack: who uses a product, with a testimonial quote — a budget/intent tell).
There is NO ad-transparency source in the catalog (verified: not among the 591 sources,
and linkedin has no ads category) — do not reach for ad-library data, it isn't here.
People:
linkedin/search/search_sql_users — the 856M-profile DB, the bulk workhorse: derived
seniority/function filters, company domain/id (incl. past employers = alumni),
career-shape (months_in_role, tenure, promotions), lookalike graph (similar_to),
deterministic buckets for >1000. Craft guide: the anysite-people-sourcing skill.
Key semantics: over-count result is an unbiased SAMPLE (repeat = same people; walk
bucket_total/bucket_index instead), and has_* flags make coverage narrowing
explicit — set them when filtering by fields not every profile states.
linkedin/search/search_users (live) — one-off lookups and namesake disambiguation
(job_title + current_company/company_keywords; never bare keywords alone).
linkedin/user (full profile, needs alias/URL/URN — never guess the alias),
linkedin/user/user_posts, user_experience, user_comments.
Email finding (cascade, cheap → expensive):
linkedin/user/user_email — batch up to 10 profiles, cheap, low yield. Truths from live
testing: it returns a MIX of personal and work addresses (roughly half and half), one row
per EMAIL — not per profile — and a single person can come back with several rows,
including emails at PAST employers (measured: one alias → 4 rows spanning current and
former company domains). Its found field is always true (useless as a check). So: group
by alias/internal_id, then match the domain against the person's CURRENT company; if
more than one work address survives, treat it as unverified and pass to step 2. Personal
addresses are not outreach-ready.
linkedin/user/user_find_email_by_url {url} — high yield but expensive (50cr), run only
on the remainder after step 1. Takes a VANITY profile URL (/in/satyanadella/);
URN-style URLs (/in/ACoA...) are rejected — get the vanity URL from linkedin/user
first. Response includes email_status and valid_email — check them and pass only
valid work emails onward; an address with a bad status is a bounce, not a find.
- No work email found → keep the lead anyway; CRM contact upserts match by
linkedin_url
too (but note: creating a NEW contact requires an email — no email means update-only).
Reverse lookup (email → person), reliability order:
linkedin/email/email_sql_user (cached DB) → email_user (live) — cheap, but verified
to return empty even for people who are definitely on LinkedIn. Try, don't rely.
- The cascade that works when you know the name (a CRM does): email domain → resolve the
company (verified, see above) →
organizational_urn → search_users {first_name, last_name, current_company: [{"type": "company", "value": "<id>"}]} → usually exactly
one match, delivered WITH the fsd_profile URN needed for user_posts. The company
filter is mandatory — a bare name returns namesakes.
Hiring signals:
linkedin/search/search_jobs — by company; works for any company. The company param
takes [{"type": "company", "value": "<numeric id>"}]. linkedin/search/search_companies
returns urn ALREADY in that object form — pass it through as-is. Only search_sql_companies
returns string URNs (fsd_company:<id>) — there, extract the numeric id yourself. And
verify the company before using its URN: the first search hit is often a namesake
(verified: "Notion" → NOTION Media Production first, the real notionhq second) — check
name + industry + alias.
greenhouse/jobs/jobs_search {board_token, count} — full descriptions via content=true;
ashby/jobs/jobs_search {board_name, count} — descriptions always included. Both need the
company slug; 412 = wrong token, fall back to linkedin jobs.
glassdoor (resolve employer id via companies_search first), builtin, adzuna —
supplements; blind/layoffs/layoffs_search for layoffs.
Tech stack: wappalyzer/technologies — technology slug → who uses it (top_websites
sample), category alternatives (alternatives[]), country/language breakdown. Note: it is a
sample, not an exhaustive site list.
Software reviews: g2/products/products_search (search only),
capterra/products/products_reviews (includes switched_from[] and switching_reason —
direct competitor-switch evidence), trustradius and getapp products_reviews,
gartner/products — competitor review mining. Employer sentiment:
glassdoor/companies/companies_ratings (employer id via companies_search), kununu
(DACH only — country ∈ de/at/ch), comparably, blind/companies/companies_reviews
(+ companies_salaries comp percentiles, companies_posts anonymous chatter).
News & mentions: techmeme/stories/stories_search {keyword, count} (archive) and
stories_front_page; google/news/news_articles_search;
linkedin/search/search_posts (keyword or mentioned company URN; date_posted accepts
only past-24h / past-week / past-month); reddit, hackernews, twitter, bluesky for
community chatter; substack/medium for content signals.
Launches & products: producthunt/launches/launches_search,
producthunt/products/products_alternatives, products_reviews; indiehackers,
kickstarter/indiegogo for niche ICPs.
Web fallback: webparser/parse (static pages) → webparser/render (JS-rendered).
Covers any URL when no named source fits. Web search: duckduckgo/search, brave/search.
Combining into signal chains
The standard pattern for account signals (used by the crm-signals skill):
company domain
→ search_sql_companies {website} + exact verify (firmographics
↳ crunchbase_link → alias FREE ↳ organizational_urn)
→ crunchbase/company {alias} → funding_rounds, leadership_hires, news, layoffs, bombora
→ search_jobs {company: [{type, value from organizational_urn}]} → what they hire for
→ search_posts (company name, past-month) → mentions
Four paid calls per account instead of five — the live crunchbase/search drops out (the
alias comes free from crunchbase_link); five if the domain doesn't resolve and webparser
is needed.
Stack signals: one signal is a guess, 2–3 signals within ~30 days is a pattern worth acting on.
Working with CRM
CRM read/write goes through the crm_* tools, NOT through execute. Before any CRM write,
consult the anysite-crm-profile skill (field mapping law) and the Writing rules in
anysite-crm-setup. The server enforces fill-blank policy, protected fields and write logging
regardless of what you pass.
1---2name: anysite-mcp3description: How to use the anysite MCP server effectively - the six meta-tools (discover, execute, get_page, query_cache, export_data, search_requests), the source map for GTM signals (funding, hiring, tech stack, reviews, news, launches), email finding cascades, domain->company resolution, and cost-aware calling patterns. Consult this before any anysite data work. Use when unsure which source or endpoint covers a data need, how much a call costs / how many credits, why an endpoint is 'not found', how to reuse a cache_key, how to paginate or re-filter cached results, or how to combine sources into a signal chain.4---56# Anysite MCP — usage guide78The anysite MCP exposes hundreds of data sources through six universal meta-tools (plus the9`crm_*` family, see Working with CRM). This skill is the map: how to call them, which sources10cover which GTM need, and how to not waste credits.1112## The six meta-tools1314| Tool | Purpose | Credits |15|---|---|---|16| `discover(source, category)` | List endpoints + exact params for a source/category | free |17| `execute(source, category, endpoint, params)` | Run an endpoint; returns first 10 items + `cache_key` | paid |18| `get_page(cache_key, offset, limit)` | Page through a cached result | free |19| `query_cache(cache_key, conditions, sort_by, sort_order, aggregate, group_by, limit, offset)` | Filter/sort/aggregate cached data with SQL-like ops | free |20| `export_data(cache_key, output_format, list_unpack)` | Export cached data — `output_format` json (default) / csv / jsonl; `list_unpack` = how many nested-array elements to expand into CSV columns (default 1) | free |21| `search_requests(source, category, endpoint, query, since, until, limit, offset)` | Find past execute() calls and their cache_keys — 7-day history, works across sessions | free |2223### Rules that prevent 90% of failures24251. **Always `discover` before `execute`.** Endpoint names and params are not guessable, and a26 wrong source name returns the full source list — a wrong guess self-corrects for free.27 `execute` takes the endpoint NAME exactly as discover returns it (`products_reviews`),28 never a REST path segment (`reviews`) — resolution is an exact-match lookup.292. **Never guess identifiers.** LinkedIn aliases, URNs, Crunchbase aliases, Greenhouse board30 tokens are unpredictable. Resolve them through the search endpoint of the same source first.313. **Re-use the cache — it outlives the session.** `execute` returns a `cache_key`; further32 filtering, sorting, counting and paging of that result is free, and the cache lives for33 **7 days across sessions**. Before any paid `execute`, check `search_requests` (free) for34 a recent identical call — same endpoint, matching params — and reuse its `cache_key` via35 `query_cache`/`get_page` instead of refetching (verified live: a two-day-old cache_key36 from another session served in full). Freshness rule: reuse when the data's age is fine37 for the task (enrichment firmographics — usually yes; "what's new today" — no).384. **Cheap-first cascade.** When several endpoints can answer, call the cached/DB one first39 (`*/db/*`, `*sql*` endpoints, ~1 credit) and the live one only for the remainder.405. **Estimate volume before bulk runs — plan-aware.** First know the user's plan (the CRM41 profile stores it after setup; if unknown, ask once: MCP Unlimited or credit-based?).42 - **Credit-based plan:** before anything above ~100 calls, state the estimate43 (`N targets × credits-per-call`) and get a nod. Prefer cheap DB endpoints, batch hard.44 - **MCP Unlimited:** credit warnings off, but keep batch sizes sane anyway — the real45 limits are latency and upstream rate limits, so cap sweeps the same way and say46 "this will take ~N minutes" instead of a price.476. **Live LinkedIn search fails as an empty list, not an error.** `search_users` and48 `search_companies` return `{"results":[]}` on queries that just don't hit ("stripe",49 "databar" both came back empty live, while "microsoft" worked) — it is not a broken key.50 On empty, switch to the `search_sql_*` DB endpoints; do NOT retry with broader keywords.51 (This is why reverse-lookup via live `search_users` is best-effort, not "usually one52 match".)537. **`gdelt` is slow by design, not broken** — 10–50s per call is normal (upstream per-IP54 throttling), and worst cases exceed the MCP client's silent-call timeout, which looks55 like a hang. Endpoints: `gdelt/articles/articles_search` and `articles_context`56 (`timespan` like 3d/1w or `start_datetime` YYYYMMDDHHMMSS; count ≤250). Keep it OUT of57 per-account sweep loops (use techmeme / google news — seconds); fine for a one-off deep58 media dive with a "takes a minute" warning.5960## GTM source map6162**Company discovery (bulk):**63- `linkedin/search/search_sql_companies` — the workhorse. Up to 1000 companies per call with64 DSL filters (keywords, industry_name, employee_count_min/max, country_hq, founded_on_min/max,65 has_website) and a `sort` param (`relevance` — default for filtered queries — or66 `last_modified` for freshness/monitoring). Also batch lookup by `urn` and search by `website`.67 Query craft (naive keywords return wrong-country token soup — measured 1/5 relevant vs68 5/5 structured): the `anysite-company-sourcing` skill.69 ⚠️ **`website` search is SUBSTRING match, ordered by last_modified. Verification is70 MANDATORY on every resolve — position in the results means nothing.** Verified live:71 `{website: "stripe.com", count: 1}` → Soundstripe; `{website: "stlabs.com", count: 5}` →72 five *labs.com companies, none of them stlabs.com (common tokens flood the result even in73 a single-domain call). The domain-resolve rules:74 1) **Verify exact `website` match** (normalize both sides: lowercase, strip75 protocol/`www.`/path) on EVERY resolve, single or batched. No exact match =76 **unresolved** — never write anything to the CRM for it; wrong-company data lands in77 blank fields where nobody will catch it.78 2) **Never `count: 1`** on a website resolve — the substring flood means the one row you79 get is very likely the wrong company. Default one domain per call at a small count (5+).80 OR-DSL batching81 (`{website: "a.com|b.io", count: 10× domains}`) is an optimization with a verification82 tax: a domain with a common token can be flooded out of the batch entirely — every83 domain that didn't come back exact-matched must be re-queried individually.84 3) `query_cache` filters over the WHOLE cached set (verified) but returns at most `limit`85 rows (default 10) — pass an explicit `limit` when you expect more matches back. Sanity86 rule: `aggregate {op: "count"}` should equal the `total` from execute; if not, page87 with `get_page` before concluding anything.88 4) A whole class of domains never appears in its own substring results (common-token89 domains like stlabs.com) — so the website's own page is the STANDARD second step, not90 an emergency: `webparser/parse {url: "https://<domain>", extract_minimal: true}` →91 top-level `title` says who they are, `links[]` usually carries their own92 linkedin.com/company/... URL → `linkedin/company` for the exact URN (verified, ~1cr).93 Live shape on stlabs.com: `title: "STLabs — Intelligent Service Management"` at the TOP94 level, while `metadata` came back `{}` and `cleaned_html` empty — read `title`, and treat95 `metadata` as a fallback only, not the primary location.96 Secondary fallback: `crunchbase/search` by name → `contacts.linkedin_url`. Name search97 alone is never a source of truth.98 Bonus from a successful resolve: the `search_sql_companies` row already carries99 `crunchbase_link` (free crunchbase alias — skip the live 20cr search) and100 `organizational_urn` (`company:<id>` — the numeric id goes straight into `search_jobs`).101 ⚠️ For company SIZE use `employee_count`, never `employee_count_range` — the two fields102 can contradict each other in the same record (verified: Clay returns `employee_count:103 1465` alongside `employee_count_range: "201-500"`). The range field looks like the natural104 key for size segmentation and would misfile that company by ~3x, silently. Fall back to105 the range only when the exact count is empty, and say that you did.106- `crunchbase/db/db_search` — filters by funding stage, last funding date, investors,107 employee range; count ≤100, dates as Unix timestamps. `employee_count_min/max` are108 ENUM bands, not free integers (min ∈ {1,11,51,101,251,501,1001,5001,10001}, max ∈109 {10,50,100,250,500,1000,5000,10000,10001}) — passing 20 errors out. 1 credit/result.110 Response includes `funding_rounds[]`, `leadership_hires[]`, `layoffs[]`, `news[]`,111 `technologies[]`, `employees[]`.112- `crunchbase/search` (live, 20cr/50) — adds `hiring`, `event`, `spotlight`,113 `shares_investors_with`, `it_spend_*`, `revenue_*`, `valuation_*` filters. Check discover114 for its date format — it differs from db_search.115- Early-stage supplements: `yc/search/search_companies` (keyword search, works) and116 `betalist/startups/startups_search {keyword, count}`. NOT `tracxn/companies/companies_search`117 — it has no name/keyword input, only an `explore` param (a URL/id of a ready-made Tracxn118 list) and returns guests only a truncated slice; usable solely if you already hold such a119 list URL.120121**Company detail:** `crunchbase/company` — get its alias for free from the `crunchbase_link`122that `search_sql_companies` already returned (live `crunchbase/search` is the fallback, not123the first step). ⚠️ The alias is CASE-SENSITIVE ('Google' ≠ 'google') — take it verbatim124from the URL slug. Also `linkedin/company`. One `crunchbase/company` call carries free extras:125`bombora_surges[]` (B2B intent topics — but they show what THAT company's staff researches,126i.e. what they BUY; treat as a signal only when a topic matches what the user sells),127`related.competitors[]`, `predictions.funding_score`, `awards[]`. Coverage caveat:128`leadership_hires[]` is often EMPTY for smaller companies — absence of the field is not129absence of hires. Normalize `contacts.email` (trailing dots observed: "x@y.ai.").130A third resolve path when crunchbase is already fetched: `contacts.linkedin_url` →131`linkedin/company` → exact URN (verified; bypasses both fuzzy searches).132Note: `owler` endpoints need an owler alias and its search has no name/keyword parameter —133not usable for looking up a named account.134135**Engagement graph (who interacted with content):** `linkedin/post/post_comments`,136`post_reactions`, `post_reposts` and `linkedin/company/company_posts` (~1cr/10) — answers137"who paid attention to this content", incl. people outside your title filters. Identifiers:138comments/reposts carry a vanity alias; reactions give only an obfuscated `/in/ACoAA...` URL139plus `internal_id` — `user_email` accepts the `internal_id`, never the obfuscated URL.140Honest scaling: volume follows the SEED's audience, not the target's importance (large brand141post → dozens of engagers; 80-person company → 0–2 per post), and on a small account those142few are mostly the company's OWN staff plus engagement farmers (verified: 3 of 4 commenters143were employees) — filter by the author's company first and expect nothing left. Use this on144seeds with a real audience (a competitor's page), not on SMB target lists. For small accounts145the reliable nugget is `company_posts` → `mentioned[]`: hiring announcements name new people146with their vanity aliases. `linkedin/company/company_employee_stats` (1cr) gives147function/skill/location breakdown — cross-check totals against `employee_count` from148`linkedin/company` before trusting absolutes, and never sum the `locations` array: its149buckets are nested (US ⊃ California ⊃ SF Bay Area), so summing double-counts badly. Its150`llm_hint` promises seniority and growth trends that the response does not contain.151152**Tech stack & adoption signals:** `stackshare/companies` (a company's declared stack by153slug — the forward direction wappalyzer can't do); `producthunt/products/products_customers`154(reverse stack: who uses a product, with a testimonial quote — a budget/intent tell).155There is NO `ad-transparency` source in the catalog (verified: not among the 591 sources,156and `linkedin` has no `ads` category) — do not reach for ad-library data, it isn't here.157158**People:**159- `linkedin/search/search_sql_users` — the 856M-profile DB, the bulk workhorse: derived160 seniority/function filters, company domain/id (incl. past employers = alumni),161 career-shape (months_in_role, tenure, promotions), lookalike graph (`similar_to`),162 deterministic buckets for >1000. Craft guide: the `anysite-people-sourcing` skill.163 Key semantics: over-`count` result is an unbiased SAMPLE (repeat = same people; walk164 `bucket_total`/`bucket_index` instead), and `has_*` flags make coverage narrowing165 explicit — set them when filtering by fields not every profile states.166- `linkedin/search/search_users` (live) — one-off lookups and namesake disambiguation167 (`job_title` + `current_company`/`company_keywords`; never bare `keywords` alone).168- `linkedin/user` (full profile, needs alias/URL/URN — never guess the alias),169 `linkedin/user/user_posts`, `user_experience`, `user_comments`.170171**Email finding (cascade, cheap → expensive):**1721. `linkedin/user/user_email` — batch up to 10 profiles, cheap, low yield. Truths from live173 testing: it returns a MIX of personal and work addresses (roughly half and half), one row174 per EMAIL — not per profile — and a single person can come back with several rows,175 including emails at PAST employers (measured: one alias → 4 rows spanning current and176 former company domains). Its `found` field is always true (useless as a check). So: group177 by `alias`/`internal_id`, then match the domain against the person's CURRENT company; if178 more than one work address survives, treat it as unverified and pass to step 2. Personal179 addresses are not outreach-ready.1802. `linkedin/user/user_find_email_by_url {url}` — high yield but expensive (50cr), run only181 on the remainder after step 1. Takes a VANITY profile URL (`/in/satyanadella/`);182 URN-style URLs (`/in/ACoA...`) are rejected — get the vanity URL from `linkedin/user`183 first. Response includes `email_status` and `valid_email` — check them and pass only184 valid work emails onward; an address with a bad status is a bounce, not a find.1853. No work email found → keep the lead anyway; CRM contact upserts match by `linkedin_url`186 too (but note: creating a NEW contact requires an email — no email means update-only).187188**Reverse lookup (email → person), reliability order:**1891. `linkedin/email/email_sql_user` (cached DB) → `email_user` (live) — cheap, but verified190 to return empty even for people who are definitely on LinkedIn. Try, don't rely.1912. The cascade that works when you know the name (a CRM does): email domain → resolve the192 company (verified, see above) → `organizational_urn` → `search_users {first_name,193 last_name, current_company: [{"type": "company", "value": "<id>"}]}` → usually exactly194 one match, delivered WITH the `fsd_profile` URN needed for `user_posts`. The company195 filter is mandatory — a bare name returns namesakes.196197**Hiring signals:**198- `linkedin/search/search_jobs` — by company; works for any company. The `company` param199 takes `[{"type": "company", "value": "<numeric id>"}]`. `linkedin/search/search_companies`200 returns `urn` ALREADY in that object form — pass it through as-is. Only `search_sql_companies`201 returns string URNs (`fsd_company:<id>`) — there, extract the numeric id yourself. And202 verify the company before using its URN: the first search hit is often a namesake203 (verified: "Notion" → NOTION Media Production first, the real notionhq second) — check204 name + industry + alias.205- `greenhouse/jobs/jobs_search {board_token, count}` — full descriptions via `content=true`;206 `ashby/jobs/jobs_search {board_name, count}` — descriptions always included. Both need the207 company slug; 412 = wrong token, fall back to linkedin jobs.208- `glassdoor` (resolve employer id via `companies_search` first), `builtin`, `adzuna` —209 supplements; `blind/layoffs/layoffs_search` for layoffs.210211**Tech stack:** `wappalyzer/technologies` — technology slug → who uses it (`top_websites`212sample), category alternatives (`alternatives[]`), country/language breakdown. Note: it is a213sample, not an exhaustive site list.214215**Software reviews:** `g2/products/products_search` (search only),216`capterra/products/products_reviews` (includes `switched_from[]` and `switching_reason` —217direct competitor-switch evidence), `trustradius` and `getapp` `products_reviews`,218`gartner/products` — competitor review mining. Employer sentiment:219`glassdoor/companies/companies_ratings` (employer id via `companies_search`), `kununu`220(DACH only — country ∈ de/at/ch), `comparably`, `blind/companies/companies_reviews`221(+ `companies_salaries` comp percentiles, `companies_posts` anonymous chatter).222223**News & mentions:** `techmeme/stories/stories_search {keyword, count}` (archive) and224`stories_front_page`; `google/news/news_articles_search`;225`linkedin/search/search_posts` (keyword or `mentioned` company URN; `date_posted` accepts226only past-24h / past-week / past-month); `reddit`, `hackernews`, `twitter`, `bluesky` for227community chatter; `substack`/`medium` for content signals.228229**Launches & products:** `producthunt/launches/launches_search`,230`producthunt/products/products_alternatives`, `products_reviews`; `indiehackers`,231`kickstarter`/`indiegogo` for niche ICPs.232233**Web fallback:** `webparser/parse` (static pages) → `webparser/render` (JS-rendered).234Covers any URL when no named source fits. Web search: `duckduckgo/search`, `brave/search`.235236## Combining into signal chains237238The standard pattern for account signals (used by the crm-signals skill):239240```241company domain242 → search_sql_companies {website} + exact verify (firmographics243 ↳ crunchbase_link → alias FREE ↳ organizational_urn)244 → crunchbase/company {alias} → funding_rounds, leadership_hires, news, layoffs, bombora245 → search_jobs {company: [{type, value from organizational_urn}]} → what they hire for246 → search_posts (company name, past-month) → mentions247```248Four paid calls per account instead of five — the live crunchbase/search drops out (the249alias comes free from `crunchbase_link`); five if the domain doesn't resolve and webparser250is needed.251252Stack signals: one signal is a guess, 2–3 signals within ~30 days is a pattern worth acting on.253254## Working with CRM255256CRM read/write goes through the `crm_*` tools, NOT through execute. Before any CRM write,257consult the `anysite-crm-profile` skill (field mapping law) and the Writing rules in258`anysite-crm-setup`. The server enforces fill-blank policy, protected fields and write logging259regardless of what you pass.