skills/orthogonal-targeted-prospecting/SKILL.md
Build a prospect list of companies with decision makers, verified contact info, and hiring/intent signals. Use when asked to find leads by industry, build an account list with specific titles, prospect companies that are actively hiring, or create a targeted outreach list filtered by company size, location, and hiring activity.
npx skillsauth add orthogonal-sh/skills targeted-prospectingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build a prioritized prospect list for any industry. Finds decision makers at companies matching your ICP, reveals verified contact info, and layers on hiring signals to prioritize who's ready to buy now.
This skill orchestrates 4 APIs: Apollo (people search + reveal + job postings), Hunter (email verification), Sixtyfour (phone discovery). Expected runtime: ~3–5 min for 25 prospects without phones, ~10–15 min with phones (Sixtyfour's tail latency dominates). Phone discovery is opt-in by default for this reason — see Step 6.
These rules are load-bearing — without them the skill takes 2–3× longer.
id field from each person in the Step 2 response.email revealed in Step 3; Step 5 needs the organization.id revealed in Step 3 (the search response in Step 2 does NOT include org IDs); Step 6 needs the real last_name + linkedin_url from Step 3.Extract from the user's query:
organization_num_employees_ranges (e.g., 100+ FTE → ["100,500", "500,1000", "1000,5000", "5000,10000", "10000,1000000"])owner, founder, c_suite, partner, vp, head, director, managerOne call. Apollo's people search filters by title + seniority + industry-keyword + location + company-size in a single request. Returns up to 100 people with their organization data attached.
orth run apollo /api/v1/mixed_people/api_search --body '{
"person_titles": ["{title_1}", "{title_2}", "{title_3}"],
"person_seniorities": ["{seniority_1}", "{seniority_2}"],
"organization_locations": ["{location}"],
"organization_num_employees_ranges": ["{size_range_1}", "{size_range_2}"],
"q_keywords": "{industry_phrase}",
"per_page": 100,
"page": 1
}'
Critical sizing note — over-fetch. Apollo's organization_num_employees_ranges filter is loose; in testing, ~80% of returned companies fell BELOW the requested range. Always request per_page: 100 even when the user asked for 25 results — you'll discard most after the post-hoc filter in Step 3. If the user asks for a strict size threshold and the per-page-100 yield still under-delivers, paginate (page: 2).
Response shape:
people[] with id, first_name, last_name_obfuscated (real last name masked — only revealed by Step 3), title, has_email, has_direct_phone.organization in the search response is a flag dictionary only (has_industry, has_phone, has_employee_count, etc.) — it does NOT include id, name, website_url, or estimated_num_employees. Org IDs come from Step 3's /people/match response, not from this call.Keyword tips:
q_keywords uses AND-match across whitespace tokens. Multi-word keywords like "staffing recruiting" often return 0 results because Apollo requires both terms to match. Use a single phrase: "staffing agency" or "fintech".q_keywords returns 0, drop person_seniorities first, then organization_num_employees_ranges, then try a broader single keyword.Take the returned people[] and pull each person's id for Step 3.
Fire all N calls in parallel. Apollo's /people/match reveals full name, work email, LinkedIn URL, and complete company data — including verified employee count.
# Fire in parallel for every person from Step 2
orth run apollo /api/v1/people/match --body '{
"id": "{apollo_person_id}",
"reveal_personal_emails": true
}'
Notes:
reveal_phone_number: true — Apollo requires a webhook for that. Phones come from Step 6.person.first_name, person.last_name, person.email, person.personal_emails[], person.linkedin_url, person.title, person.organization.name, person.organization.website_url, person.organization.estimated_num_employees, person.organization.industry, person.organization.primary_phone.organization.estimated_num_employees — Apollo's people search filter is best-effort; verify here.Fire all N calls in parallel. Hunter's verifier returns deliverable / undeliverable / accept_all / unknown for each email.
orth run hunter /v2/email-verifier -q email={person_email}
Output mapping for the result table — read the status field (Hunter v2 deprecated result in favor of status):
status: "valid" AND score >= 80 → Verified ({score})status: "valid" AND score < 80 → Verified low ({score})status: "accept_all" → Accept-all (mailbox not provable)status: "invalid" → drop or label Unverifiedstatus: "unknown" / disposable / webmail → label UnverifiedFor accept-all responses, note the email but flag it — catch-all domains accept everything, so deliverability ≠ this person actually reads it.
Fire all N calls in parallel. Apollo's per-company job-postings endpoint is the cleanest source — no scraping, no Cloudflare blocks, returns titles and locations directly.
orth run apollo /api/v1/organizations/{organization_id}/job_postings -q organization_id={org_id}
Match logic — be flexible. Match against the user's signal roles using any of: exact substring, common synonyms, or related-role keywords. For example, "Recruiting Coordinator" should match "Recruiting Coordinator", "Recruitment Coordinator", "Talent Acquisition Coordinator", "Talent Coordinator", "TA Coordinator" — and reasonably any title containing both recruit* and coord*. Strict literal substring match drops too many real signals.
Quant Signal column when the user asked for thresholds (e.g., "10+ open postings")./job_postings response can be large (200KB+ for enterprise companies). Don't paste raw responses into the result; just extract titles + locations and discard.If the user did NOT specify hiring signal roles, skip this step.
This step is optional. Sixtyfour's find-phone has 100% hit rate but is the wall-clock killer — 25 parallel calls take ~10 minutes due to tail latency. Skip it by default. Run it only when:
When you do run it, fire all N calls in parallel. Apollo phones require a webhook URL we don't have — Sixtyfour is the only option.
orth run sixtyfour /find-phone --body '{
"lead": {
"first_name": "{first}",
"last_name": "{last}",
"company": "{org_name}",
"linkedin_url": "{linkedin_url}"
}
}'
Notes:
"2077837000"). Format to (207) 783-7000 for the output table.linkedin_url is missing from Step 3's response, omit it from the request body — Sixtyfour will fall back to name + company matching with lower hit rate.If the user specified a location filter, verify each prospect actually lives/works in that region. Apollo's response includes person.city, person.state, person.country — drop or downgrade prospects whose location doesn't match.
Multinationals (Randstad, Manpower) sometimes surface global execs whose country is the user's region but whose actual scope is EMEA or Global — flag those in Notes rather than silently include them.
Output a prioritized table with full URLs (not markdown links — users need to copy-paste):
## Prospect List: {Title} at {Industry} Companies in {Location}
Found {N} decision makers across {M} companies in ~{T} seconds.
### High Priority — Hiring Signal Detected
| # | Company | Website | Employees | Decision Maker | Title | Email | Email Status | Phone | LinkedIn | Hiring | Quant Signal |
|---|---------|---------|-----------|----------------|-------|-------|-------------|-------|----------|--------|--------------|
| 1 | Acme Staffing | https://acmestaffing.com | 250 | Jane Smith | COO | [email protected] | Verified (94) | (555) 123-4567 | https://linkedin.com/in/janesmith | Recruiting Coordinator (×3) | 12 postings |
### Medium Priority — Matches ICP, No Signal Detected
| # | Company | Website | Employees | Decision Maker | Title | Email | Email Status | Phone | LinkedIn |
|---|---------|---------|-----------|----------------|-------|-------|-------------|-------|----------|
| 5 | Beta Corp | https://betacorp.com | 180 | John Doe | VP Ops | [email protected] | Verified (88) | (555) 987-6543 | https://linkedin.com/in/johndoe |
### Lower Priority — Below Threshold or Limited Data
| # | Company | Decision Maker | Title | Notes |
|---|---------|----------------|-------|-------|
| 12 | Globex | Maria Schmidt | COO, EMEA | LinkedIn location: Germany — likely not the US-specific decision maker |
### Summary
- Decision makers identified: {N}
- Verified emails: {count} (incl. {accept_all_count} accept-all)
- Phones found: {count}
- High priority: {count} | Medium: {count} | Lower: {count}
- Wall clock: ~{T}s
| API | Endpoint | Purpose |
|-----|----------|---------|
| Apollo | /api/v1/mixed_people/api_search | Find decision makers by title + industry + size + location |
| Apollo | /api/v1/people/match | Reveal full name + email + LinkedIn + company data |
| Apollo | /api/v1/organizations/{id}/job_postings | Per-company hiring-signal detection |
| Hunter | /v2/email-verifier | Email deliverability verification |
| Sixtyfour | /find-phone | Direct phone numbers (proven 100% hit rate) |
Five endpoints across four providers. All run in parallel within each step.
Example 1 — Staffing/recruiting (the canonical use case):
"Find COOs at US staffing firms with 100+ employees that are hiring Scheduling Coordinators or Recruiting Coordinators"
Step 2:
orth run apollo /api/v1/mixed_people/api_search --body '{
"person_titles": ["Chief Operating Officer", "Head of Operations", "COO"],
"organization_locations": ["United States"],
"organization_num_employees_ranges": ["100,500", "500,1000", "1000,5000", "5000,10000"],
"q_keywords": "staffing",
"per_page": 100
}'
Note for staffing/recruiting vertical specifically: Apollo's /job_postings for staffing agencies returns the client requisitions they are filling, not the agency's own internal hires. So a staffing firm with 990 listed postings might have 0 in-house Recruiting Coordinator openings. For this vertical, expect strict in-house hiring signals to be sparse — most prospects land in Medium Priority.
Step 5 (per company, parallel):
orth run apollo /api/v1/organizations/{organization_id}/job_postings -q organization_id={id}
Example 2 — SaaS sales (fintech):
"Find VP Engineering or CTO at fintech startups with 50–200 employees in the US that are hiring DevOps engineers"
orth run apollo /api/v1/mixed_people/api_search --body '{
"person_titles": ["VP Engineering", "Vice President of Engineering", "CTO", "Chief Technology Officer"],
"organization_locations": ["United States"],
"organization_num_employees_ranges": ["51,200"],
"q_keywords": "fintech",
"per_page": 25
}'
Example 3 — Healthcare HR (Texas):
"Find HR Directors at healthcare companies in Texas with 500+ employees"
orth run apollo /api/v1/mixed_people/api_search --body '{
"person_titles": ["HR Director", "VP Human Resources", "Head of HR"],
"organization_locations": ["Texas, US"],
"organization_num_employees_ranges": ["500,1000", "1000,5000", "5000,10000"],
"q_keywords": "healthcare",
"per_page": 25
}'
person_seniorities first (the title list already encodes seniority), then organization_num_employees_ranges, then simplify q_keywords to a single word./people/match doesn't return revenue data. The organization payload has estimated_num_employees but no revenue field. Treat user's revenue floors as best-effort using employee count as a proxy (100+ FTE staffing firms typically gross $10M+).data. Treat as Unverified and continue./people/match requires webhook_url for reveal_phone_number. Don't pass that flag — Sixtyfour handles phones./organizations/{id}/job_postings returns empty job_postings: []. Means the company isn't hiring or Apollo's index is stale. Treat as "no signal detected" — still a valid Medium Priority lead.accept_all. Catch-all domain — flag the email but don't claim verified./find-phone returns {}. Phone discovery missed this person. Skill should not retry — accept the gap.q_keywords like "staffing agency" or "fintech" works fine and avoids the lookup-table problem./mixed_people/api_search. Always call /people/match to get the real name. Don't try to deobfuscate.organization_num_employees_ranges is best-effort; sometimes surfaces below-range orgs. Re-filter using organization.estimated_num_employees from /people/match.testing
Download videos from YouTube, Bilibili, Twitter, and thousands of other sites using yt-dlp. Use when the user provides a video URL and wants to download it, extract audio (MP3), download subtitles, or select video quality. Triggers on phrases like "下载视频", "download video", "yt-dlp", "YouTube", "B站", "抖音", "提取音频", "extract audio".
business
Send messages and manage Slack channels. Use when asked to send Slack messages, post to channels, list channels, or fetch message history.
development
Evaluate YC batch companies for investment — scrapes the YC directory, researches each company and its founders (work history, LinkedIn, website), assesses founder-company fit, and exports to Google Sheets with priority rankings. Use when asked to evaluate YC companies, research a YC batch, screen startups, or do due diligence on YC companies.
development
Take screenshots of websites and web pages