Find decision makers at a HubSpot company using Apollo and an agent skill

low complexityCost: Usage-based

Prerequisites

Prerequisites
  • Claude Code or another agent that supports the Agent Skills standard
  • HubSpot private app token stored as HUBSPOT_TOKEN environment variable
  • Apollo API key stored as APOLLO_API_KEY environment 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/scripts

Step 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 company

Step 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
Apollo credit awareness

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.