Find decision makers at a HubSpot company using Apollo and an agent skill
Prerequisites
- Claude Code or another agent that supports the Agent Skills standard
- HubSpot private app token stored as
HUBSPOT_TOKENenvironment variable - Apollo API key stored as
APOLLO_API_KEYenvironment variable
Overview
This approach creates an agent skill that takes a target company (by name or HubSpot ID) and automatically finds VP+ decision makers via Apollo, enriches them for verified emails, and creates associated contacts in HubSpot. Run it on-demand whenever you're working a new target account.
Step 1: Create the skill directory
mkdir -p .claude/skills/find-decision-makers/scriptsStep 2: Write the SKILL.md file
Create .claude/skills/find-decision-makers/SKILL.md:
---
name: find-decision-makers
description: Find VP+ decision makers at a target HubSpot company using Apollo, enrich them, and create associated contacts in HubSpot.
disable-model-invocation: true
allowed-tools: Bash(python *)
---
Find decision makers at a target account and add them to HubSpot.
Usage: /find-decision-makers <company_name_or_hubspot_id>
1. Run: `python $SKILL_DIR/scripts/find_contacts.py --company "$1"`
2. Review the output for contacts found and created
3. Confirm the contacts appear in HubSpot associated with the companyStep 3: Write the script
Create .claude/skills/find-decision-makers/scripts/find_contacts.py:
#!/usr/bin/env python3
"""
Find Decision Makers: HubSpot + Apollo
Searches Apollo for VP+ contacts at a target company,
enriches them for email, and creates HubSpot contacts.
"""
import argparse
import os
import sys
import time
try:
import requests
except ImportError:
os.system("pip install requests -q")
import requests
# --- Config ---
HUBSPOT_TOKEN = os.environ.get("HUBSPOT_TOKEN")
APOLLO_API_KEY = os.environ.get("APOLLO_API_KEY")
if not all([HUBSPOT_TOKEN, APOLLO_API_KEY]):
print("ERROR: Set HUBSPOT_TOKEN and APOLLO_API_KEY environment variables")
sys.exit(1)
HS_HEADERS = {"Authorization": f"Bearer {HUBSPOT_TOKEN}", "Content-Type": "application/json"}
AP_HEADERS = {"Content-Type": "application/json", "X-Api-Key": APOLLO_API_KEY}
TARGET_TITLES = [
"VP Sales", "CRO", "VP Marketing", "CMO", "VP RevOps",
"Head of Sales", "VP Business Development", "VP Engineering", "CTO",
]
TARGET_SENIORITIES = ["vp", "c_suite", "director"]
# --- Functions ---
def find_company(identifier):
"""Find a HubSpot company by ID or name search."""
if identifier.isdigit():
resp = requests.get(
f"https://api.hubapi.com/crm/v3/objects/companies/{identifier}",
headers=HS_HEADERS,
params={"properties": "domain,name"},
)
resp.raise_for_status()
return resp.json()
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/companies/search",
headers=HS_HEADERS,
json={
"filterGroups": [{"filters": [{
"propertyName": "name",
"operator": "CONTAINS_TOKEN",
"value": identifier,
}]}],
"properties": ["domain", "name"],
"limit": 1,
},
)
resp.raise_for_status()
results = resp.json().get("results", [])
if not results:
print(f"No company found matching '{identifier}'")
sys.exit(1)
return results[0]
def search_apollo(domain):
"""Search Apollo for decision makers by company domain."""
resp = requests.post(
"https://api.apollo.io/api/v1/mixed_people/search",
headers=AP_HEADERS,
json={
"q_organization_domains_list": [domain],
"person_titles": TARGET_TITLES,
"person_seniorities": TARGET_SENIORITIES,
"page": 1,
"per_page": 25,
},
)
resp.raise_for_status()
return resp.json().get("people", [])
def enrich_person(person):
"""Enrich a person via Apollo People Match for verified email."""
resp = requests.post(
"https://api.apollo.io/api/v1/people/match",
headers=AP_HEADERS,
json={
"first_name": person.get("first_name"),
"last_name": person.get("last_name"),
"organization_name": person.get("organization", {}).get("name"),
"reveal_personal_emails": False,
},
)
resp.raise_for_status()
p = resp.json().get("person", {})
return {
"email": p.get("email"),
"firstname": p.get("first_name"),
"lastname": p.get("last_name"),
"jobtitle": p.get("title"),
"company": p.get("organization", {}).get("name"),
"phone": (p.get("phone_numbers") or [{}])[0].get("sanitized_number"),
}
def contact_exists(email):
"""Check if a HubSpot contact exists with this email."""
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/contacts/search",
headers=HS_HEADERS,
json={"filterGroups": [{"filters": [{
"propertyName": "email", "operator": "EQ", "value": email,
}]}]},
)
resp.raise_for_status()
results = resp.json().get("results", [])
return results[0]["id"] if results else None
def create_and_associate(person, company_id):
"""Create contact in HubSpot and associate with company."""
if not person.get("email"):
return None
existing = contact_exists(person["email"])
if existing:
contact_id = existing
print(f" EXISTS: {person['email']} (ID: {contact_id})")
else:
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/contacts",
headers=HS_HEADERS,
json={"properties": {k: v for k, v in person.items() if v}},
)
resp.raise_for_status()
contact_id = resp.json()["id"]
print(f" CREATED: {person['email']} — {person.get('jobtitle', '')}")
requests.put(
f"https://api.hubapi.com/crm/v3/objects/contacts/{contact_id}"
f"/associations/companies/{company_id}/contact_to_company",
headers=HS_HEADERS,
)
return contact_id
# --- Main ---
parser = argparse.ArgumentParser()
parser.add_argument("--company", required=True, help="Company name or HubSpot ID")
args = parser.parse_args()
company = find_company(args.company)
name = company["properties"]["name"]
domain = company["properties"].get("domain")
company_id = company["id"]
if not domain:
print(f"ERROR: {name} has no domain in HubSpot. Add a domain first.")
sys.exit(1)
print(f"Company: {name} ({domain}) — HubSpot ID: {company_id}")
print(f"Searching Apollo for decision makers...")
people = search_apollo(domain)
print(f"Found {len(people)} people\n")
created = 0
for person in people:
enriched = enrich_person(person)
result = create_and_associate(enriched, company_id)
if result:
created += 1
time.sleep(0.2)
print(f"\nDone. {created} contacts added/associated with {name}")Step 4: Run the skill
# By company name
/find-decision-makers Acme Corp
# By HubSpot company ID
/find-decision-makers 12345678
# Or run the script directly
python .claude/skills/find-decision-makers/scripts/find_contacts.py --company "Acme Corp"Step 5: Customize target titles
Edit the TARGET_TITLES and TARGET_SENIORITIES lists in the script to match your ICP. Selling a dev tool? Replace the sales titles with engineering titles:
TARGET_TITLES = [
"VP Engineering", "CTO", "Head of Platform",
"VP Infrastructure", "Head of DevOps", "VP Product",
]When to use this approach
- You're doing ad-hoc prospecting on specific accounts during deal prep
- You want to quickly multi-thread a new target account without setting up a recurring workflow
- You want the flexibility to change target titles per company (e.g., different titles for an enterprise vs. SMB account)
- You're already using an AI coding agent and want one-command account research
When to graduate to a dedicated tool
- You need to process dozens of target accounts automatically on a schedule
- Multiple team members need to trigger the workflow without CLI access
- You want execution history and error monitoring in a visual dashboard
Each run costs ~2 Apollo credits per person found (1 for search + 1 for enrichment). A company with 10 decision makers = ~20 credits. The script logs every contact it processes so you can track usage.
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.