Import recently funded companies from Crunchbase into HubSpot using an agent skill
Prerequisites
- Claude Code or another agent that supports the Agent Skills standard
- Crunchbase API key stored as
CRUNCHBASE_API_KEYenvironment variable (requires Enterprise or API/Data License plan) - HubSpot private app token stored as
HUBSPOT_TOKENenvironment variable - Custom HubSpot company properties for funding data (
funding_stage,funding_amount,funding_date,investors)
The search endpoints used here require a Crunchbase Enterprise or API/Data License plan. The Pro plan only provides CSV exports and Zapier integration, not direct API access. Verify your plan before building this.
Overview
This agent skill searches Crunchbase for companies that raised a Series A or later in the last 30 days, filters them against your ICP criteria, and imports matching companies into HubSpot with funding details. Run it weekly to keep your target account list fresh with companies that have budget to spend.
Step 1: Create the skill directory
mkdir -p .claude/skills/funded-companies/scriptsStep 2: Write the SKILL.md file
Create .claude/skills/funded-companies/SKILL.md:
---
name: funded-companies
description: Import recently funded companies from Crunchbase into HubSpot. Searches for Series A+ funding rounds in the last 30 days, filters by ICP, and creates company records with funding details.
disable-model-invocation: true
allowed-tools: Bash(python *)
---
Import recently funded companies into HubSpot from Crunchbase.
Usage: /funded-companies [days_back]
1. Run: `python $SKILL_DIR/scripts/import_funded.py --days ${1:-30}`
2. Review the output showing which companies were imported
3. Confirm companies appear in HubSpot with funding dataStep 3: Write the script
Create .claude/skills/funded-companies/scripts/import_funded.py:
#!/usr/bin/env python3
"""
Funded Companies Import: Crunchbase → HubSpot
Finds recently funded companies and imports them as target accounts.
"""
import argparse
import os
import sys
import time
from datetime import datetime, timedelta
from urllib.parse import urlparse
try:
import requests
except ImportError:
os.system("pip install requests -q")
import requests
# --- Config ---
CRUNCHBASE_API_KEY = os.environ.get("CRUNCHBASE_API_KEY")
HUBSPOT_TOKEN = os.environ.get("HUBSPOT_TOKEN")
if not all([CRUNCHBASE_API_KEY, HUBSPOT_TOKEN]):
print("ERROR: Set CRUNCHBASE_API_KEY and HUBSPOT_TOKEN environment variables")
sys.exit(1)
CB_HEADERS = {"X-cb-user-key": CRUNCHBASE_API_KEY, "Content-Type": "application/json"}
HS_HEADERS = {"Authorization": f"Bearer {HUBSPOT_TOKEN}", "Content-Type": "application/json"}
# ICP config — adjust these for your business
FUNDING_STAGES = ["series_a", "series_b", "series_c", "series_d"]
TARGET_EMPLOYEE_RANGES = [
"c_0051_0100", "c_0101_0250", "c_0251_0500", "c_0501_1000",
]
# --- Functions ---
def search_funding_rounds(days_back):
"""Search Crunchbase for recent funding rounds."""
since_date = (datetime.now() - timedelta(days=days_back)).strftime("%Y-%m-%d")
all_entities = []
after_id = None
while True:
body = {
"field_ids": [
"identifier", "announced_on", "money_raised",
"funded_organization_identifier", "investment_type",
"investor_identifiers",
],
"query": [
{"type": "predicate", "field_id": "announced_on",
"operator_id": "gte", "values": [since_date]},
{"type": "predicate", "field_id": "investment_type",
"operator_id": "includes", "values": FUNDING_STAGES},
],
"limit": 100,
}
if after_id:
body["after_id"] = after_id
resp = requests.post(
"https://api.crunchbase.com/api/v4/searches/funding_rounds",
headers=CB_HEADERS, json=body,
)
resp.raise_for_status()
entities = resp.json().get("entities", [])
all_entities.extend(entities)
if len(entities) < 100:
break
after_id = entities[-1]["uuid"]
return all_entities
def get_organization(uuid):
"""Fetch organization details from Crunchbase."""
resp = requests.get(
f"https://api.crunchbase.com/api/v4/entities/organizations/{uuid}",
headers=CB_HEADERS,
params={"field_ids": "short_description,categories,num_employees_enum,website_url,founded_on"},
)
resp.raise_for_status()
return resp.json().get("properties", {})
def extract_domain(url):
"""Extract clean domain from URL."""
if not url:
return None
parsed = urlparse(url if url.startswith("http") else f"https://{url}")
domain = parsed.netloc or parsed.path
return domain.replace("www.", "").strip("/")
def company_exists(domain):
"""Check if company exists in HubSpot."""
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/companies/search",
headers=HS_HEADERS,
json={"filterGroups": [{"filters": [{
"propertyName": "domain", "operator": "EQ", "value": domain,
}]}]},
)
resp.raise_for_status()
return len(resp.json().get("results", [])) > 0
def create_company(org, funding_round):
"""Create company in HubSpot with funding metadata."""
domain = extract_domain(org.get("website_url"))
props = funding_round.get("properties", {})
investors = ", ".join(
inv.get("value", "") for inv in (props.get("investor_identifiers") or [])
)
money = props.get("money_raised", {})
amount = money.get("value") if money else None
resp = requests.post(
"https://api.hubapi.com/crm/v3/objects/companies",
headers=HS_HEADERS,
json={"properties": {
"domain": domain,
"name": org.get("identifier", {}).get("value", ""),
"description": org.get("short_description", ""),
"industry": (org.get("categories") or [{}])[0].get("value", ""),
"funding_stage": props.get("investment_type", ""),
"funding_amount": str(amount) if amount else "",
"funding_date": props.get("announced_on", ""),
"investors": investors,
}},
)
resp.raise_for_status()
return resp.json()["id"]
# --- Main ---
parser = argparse.ArgumentParser()
parser.add_argument("--days", type=int, default=30, help="Look back N days for funding rounds")
args = parser.parse_args()
print(f"Searching Crunchbase for funding rounds in the last {args.days} days...")
rounds = search_funding_rounds(args.days)
print(f"Found {len(rounds)} funding rounds ({', '.join(FUNDING_STAGES)})\n")
seen_domains = set()
created = 0
skipped = 0
icp_miss = 0
for rnd in rounds:
org_ref = rnd.get("properties", {}).get("funded_organization_identifier", {})
org_uuid = org_ref.get("uuid")
if not org_uuid:
continue
org = get_organization(org_uuid)
time.sleep(0.2)
# ICP filter
emp_range = org.get("num_employees_enum", "")
if emp_range not in TARGET_EMPLOYEE_RANGES or not org.get("website_url"):
icp_miss += 1
continue
domain = extract_domain(org.get("website_url"))
if not domain or domain in seen_domains:
continue
seen_domains.add(domain)
if company_exists(domain):
name = org.get("identifier", {}).get("value", domain)
print(f" EXISTS: {name} ({domain})")
skipped += 1
continue
money = rnd.get("properties", {}).get("money_raised", {})
amount_str = f"${money.get('value', 0):,.0f}" if money.get("value") else "undisclosed"
stage = rnd.get("properties", {}).get("investment_type", "unknown")
name = org.get("identifier", {}).get("value", "Unknown")
company_id = create_company(org, rnd)
print(f" CREATED: {name} — {stage} — {amount_str}")
created += 1
time.sleep(0.2)
print(f"\n--- Summary ---")
print(f"Funding rounds found: {len(rounds)}")
print(f"Companies created: {created}")
print(f"Already in HubSpot: {skipped}")
print(f"Didn't match ICP: {icp_miss}")Step 4: Run the skill
# Default: last 30 days of funding rounds
/funded-companies
# Look back 7 days (for weekly runs)
/funded-companies 7
# Look back 90 days (for initial backfill)
/funded-companies 90
# Or run the script directly
python .claude/skills/funded-companies/scripts/import_funded.py --days 30Step 5: Customize ICP filters
Edit the script to adjust which companies match your ICP:
# Target only Series A and B (earlier-stage, actively buying)
FUNDING_STAGES = ["series_a", "series_b"]
# Target companies with 100-1000 employees
TARGET_EMPLOYEE_RANGES = ["c_0101_0250", "c_0251_0500", "c_0501_1000"]Step 6: Schedule (optional)
Option A: Cron
# crontab — run weekly on Monday at 8 AM
0 8 * * 1 cd /path/to/project && python .claude/skills/funded-companies/scripts/import_funded.py --days 7Option B: GitHub Actions
name: Import Funded Companies
on:
schedule:
- cron: '0 13 * * 1' # Monday 8 AM ET
workflow_dispatch: {}
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install requests
- run: python .claude/skills/funded-companies/scripts/import_funded.py --days 7
env:
CRUNCHBASE_API_KEY: ${{ secrets.CRUNCHBASE_API_KEY }}
HUBSPOT_TOKEN: ${{ secrets.HUBSPOT_TOKEN }}When to use this approach
- You want to run the import on demand before quarterly planning or account list reviews
- You want to customize ICP filters quickly without editing a visual workflow
- You're doing a one-time backfill (e.g., last 90 days of funding rounds)
- You want the import logic version-controlled and reviewable in PRs
When to graduate to a dedicated tool
- You need daily automated imports without depending on someone running the skill
- Multiple team members need to trigger or modify the import criteria
- You want execution history and error monitoring in a visual dashboard
Each funding round search counts as 1 API call. Each organization lookup is another call. Processing 100 funding rounds = ~200 API calls. Crunchbase Enterprise plans typically include generous API limits, but check your plan's quota.
Need help implementing this?
We build and optimize automation systems for mid-market businesses. Let's discuss the right approach for your team.