Code & Snippets

PitchBook Competitor Enrichment (loop helper).

Per-competitor PitchBook enrichment used as the loop body of the Map Competitors automation — bio, most-recent financing, financials, and investor names for a single PB ID.

Sourcing
Python · 205 lines
C.1

Download

Grab the source file. Drop it into your service, set the required environment variables, and deploy.

C.2

How it works

The gist

Helper step for the Map Competitors automation. Given a single PitchBook company ID, pull the bio, most-recent financing, financials, and full investor list — everything you need to render one row of a competitor table. The parent automation loops this snippet once per competitor PB ID it discovers.

This file groups two independent Zapier "Run Python" steps so you can read the whole helper in one place:

  1. competitor_full_summary — bio + most-recent financing + most-recent financials → one JSON blob.
  2. competitor_investors — investor names for the same PB ID, with an auth-header fallback loop because PitchBook's investor endpoint accepts a different header scheme depending on tenant and API version.

The flow (per competitor in the loop)

  1. Inputsapi_key and pb_id via input_data. The parent automation supplies the PB ID; it does not resolve the competitor by name. Resolve to PB ID first (see the PitchBook ID Lookup snippet) before calling this.
  2. Step 1 — full summary. Three parallel GETs against the v1 API:
    • /companies/{pb_id}/bio — name, description, HQ, website, employees, founded year, total raised.
    • /companies/{pb_id}/most-recent-financing — last round date, type, size, post-money valuation.
    • /companies/{pb_id}/most-recent-financials — revenue and period. Everything is wrapped in safe_get so a single 5xx on one endpoint doesn't take down the whole row.
  3. Step 2 — investors. Hits the v2 endpoint /companies/{pb_id}/investors. The auth header isn't consistent across PitchBook tenants, so the snippet tries four common variants (Authorization: <key>, Authorization: Bearer <key>, X-Api-Key: <key>, PB-Token <key>) and remembers which one worked in a debug field. Names are de-duped while preserving PitchBook's ordering.
  4. Return shape is flat and Zapier-friendly: Step 1 returns one stringified JSON blob (company_full_summary), Step 2 returns a joined string + array (investor_names, investor_names_array) plus the debug object.

Money normalization

money() flattens PitchBook's money objects ({"amount": 123, "currency": "USD"}) to a single string like "123 USD". parse_revenue_fields() extracts revenue and period from the financials object without throwing when fields are missing.

Why this exists

The Map Competitors automation needs the same enrichment for every competitor it finds — but the discovery step (LLM / PB similar companies) returns a list, not a single record. This snippet is the per-row enrichment that runs inside the loop, so the parent automation stays a thin orchestrator: discover competitors, loop, enrich each, write the table back to the CRM / memo.

Pairing

Used in conjunction with the Map Competitors automation as the loop body. The parent flow handles discovery and iteration; this snippet handles the actual data fetch for one company at a time.

C.3

Used in this automation

This snippet powers the end-to-end automation below. Open it for the full tool chain, prompts, and job-to-be-done context.

C.4

Source

Full source, exactly as shipped. The download above is byte-identical.

pitchbook_competitor_enrich.pyPython
# PitchBook Competitor Enrichment — pulls bio, most-recent financing, and
# investors for a single PitchBook company ID. Designed to run once per
# competitor in a loop driven by the "Map Competitors" automation.
#
# Two independent Zapier "Run Python" steps live in this file:
#   1) competitor_full_summary  — bio + financing + financials -> one JSON blob
#   2) competitor_investors      — investor names for the same pb_id
#
# Both steps take the same inputs:
#   input_data = { "api_key": "...", "pb_id": "10618-03" }
#
# ----------------------------------------------------------------------
# STEP 1 — Full company summary (bio + financing + financials)
# ----------------------------------------------------------------------
import requests
import json


# Expected input_data:
# {
#   "api_key": "...",
#   "pb_id": "10618-03"
# }

api_key = input_data["api_key"]
pb_id = input_data["pb_id"]

headers_v1 = {"Authorization": f"PB-Token {api_key}"}


def safe_get(url, headers, timeout=30):
    try:
        r = requests.get(url, headers=headers, timeout=timeout)
        if r.status_code == 200 and r.text and r.text.strip():
            return r.json()
        return {}
    except Exception:
        return {}


def money(obj):
    """
    PitchBook money object:
    {"amount": 123, "currency": "USD", ...}
    """
    if not isinstance(obj, dict):
        return None
    amt = obj.get("amount")
    cur = obj.get("currency") or obj.get("nativeCurrency")
    if amt is None:
        return None
    return f"{amt} {cur}" if cur else str(amt)


def parse_revenue_fields(financials):
    """
    Extract revenue + revenue period only.
    """
    if not isinstance(financials, dict):
        return (None, None)

    rev_obj = financials.get("revenue")
    revenue = money(rev_obj) if isinstance(rev_obj, dict) else None
    period = financials.get("period")
    return (revenue, period)


# ------------------------------------------------------------------
# Pull endpoints
# ------------------------------------------------------------------
financing = safe_get(
    f"https://api.pitchbook.com/companies/{pb_id}/most-recent-financing",
    headers_v1,
)

financials = safe_get(
    f"https://api.pitchbook.com/companies/{pb_id}/most-recent-financials",
    headers_v1,
)

bio = safe_get(
    f"https://api.pitchbook.com/companies/{pb_id}/bio",
    headers_v1,
)

# ------------------------------------------------------------------
# Revenue extraction
# ------------------------------------------------------------------
revenue, revenue_period = parse_revenue_fields(financials)

# ------------------------------------------------------------------
# Build final summary object
# ------------------------------------------------------------------
result = {
    "pb_id": pb_id,
    "company_name": (
        bio.get("companyName", {}).get("formalName")
        if isinstance(bio.get("companyName"), dict)
        else bio.get("companyName")
    ),
    "description": bio.get("description"),
    "hq_location": bio.get("hqLocation"),
    "website": bio.get("website"),
    "employees": bio.get("employees"),
    "year_founded": bio.get("yearFounded"),

    "total_raised": money(bio.get("totalMoneyRaised")),

    "last_round_date": financing.get("lastFinancingDate"),
    "last_round_type": (
        financing.get("lastFinancingDealType", {}).get("description")
        if isinstance(financing.get("lastFinancingDealType"), dict)
        else financing.get("lastFinancingDealType")
    ),
    "last_round_amount": money(financing.get("lastFinancingSize")),
    "last_round_post_valuation": money(financing.get("lastKnownValuation")),

    # Revenue fields (clean)
    "revenue": revenue,
    "revenue_period": revenue_period,

    # Raw financials retained for coverage variance
    "most_recent_financials": financials,
}

# ------------------------------------------------------------------
# Return ONE variable for Zapier
# ------------------------------------------------------------------
# return {
#     "company_full_summary": json.dumps(result, ensure_ascii=False)
# }


# ======================================================================
# STEP 2 — Investor names for the same pb_id
# ======================================================================
# Drop the block above and use this as a separate Zapier "Run Python" step.
#
# import requests
# import json
#
# api_key = (input_data.get("api_key") or "").strip()
# pb_id = (input_data.get("pb_id") or "").strip()
#
# url = f"https://api-v2.pitchbook.com/companies/{pb_id}/investors"
#
#
# def try_request(headers):
#     resp = requests.get(url, headers=headers, timeout=30)
#     text = resp.text or ""
#     return resp.status_code, text, resp
#
#
# # PitchBook's auth header varies by tenant / API version. Try the
# # common variants until one returns 200, then remember which worked.
# candidates = [
#     ("Authorization: <key>", {"Authorization": api_key}),
#     ("Authorization: Bearer <key>", {"Authorization": f"Bearer {api_key}"}),
#     ("X-Api-Key: <key>", {"X-Api-Key": api_key}),
#     ("PB-Token (v1 style)", {"Authorization": f"PB-Token {api_key}"}),
# ]
#
# investor_names = []
# debug = {
#     "url": url,
#     "attempts": [],
#     "success_auth_variant": None,
# }
#
# data = None
# for label, headers in candidates:
#     status, text, resp = try_request(headers)
#     debug["attempts"].append({
#         "auth_variant": label,
#         "status_code": status,
#         "response_preview": text[:300],
#     })
#
#     if status == 200 and text.strip():
#         debug["success_auth_variant"] = label
#         try:
#             data = resp.json()
#         except Exception:
#             data = json.loads(text)
#         break
#
# if isinstance(data, dict):
#     inv_list = data.get("investors", []) or []
#     for inv in inv_list:
#         if isinstance(inv, dict):
#             name = inv.get("investorName")
#             if name:
#                 investor_names.append(name)
#
#     # de-dupe while preserving order
#     seen = set()
#     investor_names = [x for x in investor_names if not (x in seen or seen.add(x))]
#
# return {
#     "pb_id": pb_id,
#     "investor_names": ", ".join(investor_names),
#     "investor_names_array": investor_names,
#     "debug": debug,
# }