Full source, exactly as shipped. The download above is byte-identical.
pitchbook_enrich.pyPython
"""
Enrich a company from PitchBook
Designed to run inside a Zapier "Run Python" step (or any environment that
exposes `input_data`). Given a company's name and/or website from Attio,
resolve the PitchBook ID, then pull:
- Investor list (deduplicated)
- Optional match against YOUR firm's top-investor watchlist
- HQ geography (city, state, country)
- Most recent financing round type
- Total funding raised, last round size, last round valuation, last round date
All money fields are USD-enforced — the script raises if PitchBook returns
a non-USD currency so you don't silently mix currencies in the CRM.
# ──────────────────────────────────────────────────────────────────────
# TOP_INVESTORS watchlist
#
# Populate the TOP_INVESTORS set below with your own firm's preferred
# co-investors (one canonical spelling each, matching how they appear in
# your CRM). Add common alternate spellings to ALIASES so noisy PitchBook
# strings map back to your canonical names.
#
# Don't need the watchlist? Just leave TOP_INVESTORS empty — the rest of
# the enrichment (investors, geo, funding, valuation, round type) still
# works; `matched_investors` will simply come back as "None".
# ──────────────────────────────────────────────────────────────────────
"""
import requests
api_key = input_data['api_key'] # PB API key
company_name = input_data.get('company_name') # from Attio
company_website = input_data.get('company_website') # from Attio
BASE_URL = "https://api.pitchbook.com"
# choose website if available, else name
search_query = company_website if company_website else company_name
headers = {
"Authorization": f"PB-Token {api_key}",
"Accept": "application/json"
}
# -------------------------
# Top investor set (canonical = CRM naming)
# Populate with your firm's watchlist, or leave empty to skip matching.
# -------------------------
TOP_INVESTORS = {
# "Example Capital",
# "Example Ventures",
}
# Aliases: alternate spelling/format (lowercased) → canonical name in TOP_INVESTORS
ALIASES = {
# "example cap": "Example Capital",
}
# Case-insensitive lookup from canonical set
canonical_map = {name.lower(): name for name in TOP_INVESTORS}
# -------------------------
# Helper: safe GET (returns {} on any failure)
# -------------------------
def safe_get(url):
try:
resp = requests.get(url, headers=headers, timeout=15)
if resp.ok:
return resp.json() if resp.text else {}
return {}
except Exception:
return {}
# -------------------------
# Helper: enforce USD-only
# -------------------------
def assert_usd(money_obj, field_name):
if not money_obj:
return
currency = money_obj.get("currency") or money_obj.get("nativeCurrency")
if currency and currency != "USD":
raise Exception(f"Non-USD currency detected for {field_name}: {currency}")
# -------------------------
# Empty/no-data output shape (every field always present)
# -------------------------
def no_data_output(pb_id=None):
return {
"data_status": "no data",
"pitchbook_company_id": pb_id,
"search_used": search_query,
"investors": "",
"matched_investors": "None",
"match_count": 0,
"company_geography": "",
"last_round_type": None,
"total_funding_amount": None,
"last_funding_amount": None,
"last_funding_valuation": None,
"last_funding_date": None
}
# -------------------------
# 0. SEARCH → GET PB ID (graceful)
# -------------------------
if not search_query:
return no_data_output()
search_resp = safe_get(f"{BASE_URL}/search?query={search_query}")
items = search_resp.get("items", [])
if not items:
return no_data_output()
pb_id = None
for item in items:
if item.get("primaryFirmType", {}).get("type") == "COMPANY":
pb_id = item.get("pbId")
break
if not pb_id:
pb_id = items[0].get("pbId")
if not pb_id:
return no_data_output()
# -------------------------
# 1. INVESTORS
# -------------------------
investors_resp = safe_get(f"{BASE_URL}/companies/{pb_id}/investors")
investors_array = investors_resp.get("investors", [])
if not isinstance(investors_array, list):
investors_array = []
seen = set()
investor_names = []
for inv in investors_array:
name = inv.get("investorName")
if name and name not in seen:
seen.add(name)
investor_names.append(name)
investors_string = ", ".join(investor_names)
# -------------------------
# 2. MATCH TOP INVESTORS
# -------------------------
matched = []
matched_seen = set()
for name in investor_names:
lower = name.lower()
canonical = ALIASES.get(lower) or canonical_map.get(lower)
if canonical and canonical not in matched_seen:
matched.append(canonical)
matched_seen.add(canonical)
matched_investors = ", ".join(matched) if matched else "None"
match_count = len(matched)
# -------------------------
# 3. COMPANY GEO (BIO)
# -------------------------
bio_resp = safe_get(f"{BASE_URL}/companies/{pb_id}/bio")
hq = bio_resp.get("hqLocation", {})
city = hq.get("city")
state = hq.get("stateProvince")
country = hq.get("country")
geo_parts = [p for p in [city, state, country] if p]
company_geography = ", ".join(geo_parts)
# -------------------------
# 4. MOST RECENT FINANCING
# -------------------------
fin_resp = safe_get(f"{BASE_URL}/companies/{pb_id}/most-recent-financing")
deal_type = fin_resp.get("lastFinancingDealType", {})
last_round_type = deal_type.get("description")
# -------------------------
# 5. FUNDING FIELDS (+ USD enforcement)
# -------------------------
assert_usd(bio_resp.get("totalMoneyRaised"), "totalMoneyRaised")
assert_usd(fin_resp.get("lastFinancingSize"), "lastFinancingSize")
assert_usd(fin_resp.get("lastFinancingValuation"), "lastFinancingValuation")
assert_usd(fin_resp.get("lastKnownValuation"), "lastKnownValuation")
total_funding_amount = (bio_resp.get("totalMoneyRaised") or {}).get("amount")
last_funding_amount = (fin_resp.get("lastFinancingSize") or {}).get("amount")
last_funding_date = fin_resp.get("lastFinancingDate")
last_funding_valuation = (
(fin_resp.get("lastFinancingValuation") or {}).get("amount")
or (fin_resp.get("lastKnownValuation") or {}).get("amount")
)
# -------------------------
# OUTPUT
# -------------------------
return {
"data_status": "data exists",
"pitchbook_company_id": pb_id,
"search_used": search_query,
"investors": investors_string,
"matched_investors": matched_investors,
"match_count": match_count,
"company_geography": company_geography,
"last_round_type": last_round_type,
"total_funding_amount": total_funding_amount,
"last_funding_amount": last_funding_amount,
"last_funding_valuation": last_funding_valuation,
"last_funding_date": last_funding_date
}