Tech Stack Lookup API: Building Vendor Change Detection and Alerts
How to build automated systems that detect when prospects adopt, replace, or remove vendors from their tech stack using deterministic API lookups.
The Problem: Timing Outreach to Stack Changes
The best time to reach a prospect isn't random—it's when their needs change. A company that just adopted Stripe is evaluating payments infrastructure. A company that removed HubSpot might be migrating CRMs. A SaaS product that added OpenAI is building AI features.
These stack changes create timing signals, but only if you can detect them systematically. Manual checks don't scale. This guide shows how to build automated vendor change detection using VendorStacks' tech stack lookup API.
How Vendor Change Detection Works
The core pattern:
- Store a baseline vendor stack for each target company
- Re-check periodically via API
- Diff the results to detect additions, removals, or replacements
- Trigger alerts or workflows based on specific changes
VendorStacks returns deterministic vendor detection from public evidence—privacy policies, JavaScript libraries, DNS records, subprocessor disclosures. When a company changes vendors, that evidence changes, and subsequent API calls reflect it.
Implementation: Basic Change Detection
Here's a minimal implementation that checks a company daily and detects stack changes:
import requests
import json
from datetime import datetime
API_KEY = "vr_live_..."
BASE_URL = "https://api.vendorstacks.com/v1"
def get_vendor_stack(domain):
"""Fetch current vendor stack for a domain."""
headers = {"Authorization": f"Bearer {API_KEY}"}
response = requests.get(
f"{BASE_URL}/check",
params={"url": domain},
headers=headers
)
return response.json()
def extract_vendor_set(stack_response):
"""Extract flat set of vendor names from API response."""
if not stack_response.get("found"):
return set()
vendors = set()
for category, vendor_list in stack_response.get("vendor_stack", {}).items():
for vendor_obj in vendor_list:
vendors.add(vendor_obj["vendor"])
return vendors
def detect_changes(domain, previous_vendors):
"""Check for vendor additions/removals since last scan."""
current_response = get_vendor_stack(domain)
current_vendors = extract_vendor_set(current_response)
added = current_vendors - previous_vendors
removed = previous_vendors - current_vendors
return {
"domain": domain,
"timestamp": datetime.utcnow().isoformat(),
"added": list(added),
"removed": list(removed),
"current_stack": list(current_vendors),
"credits_used": current_response.get("credits_used", 0)
}
# Example: daily check
stored_stack = {"Stripe", "AWS", "Google Analytics"} # from previous scan
changes = detect_changes("example.com", stored_stack)
if changes["added"] or changes["removed"]:
print(f"Stack changed for {changes['domain']}:")
if changes["added"]:
print(f" Added: {', '.join(changes['added'])}")
if changes["removed"]:
print(f" Removed: {', '.join(changes['removed'])}")
This costs 1 credit per company checked, but only when the API finds evidence. If a domain has no public vendor data, credits_used is 0.
Pattern: Category-Specific Alerts
You often care about changes in specific categories, not the entire stack. If you sell a CRM integration tool, you want alerts when companies adopt Salesforce or HubSpot—not when they add Google Analytics.
VendorStacks returns vendors grouped into 24 categories (crm_sales, payments, ai_ml, cloud_infra, etc.). Filter changes by category:
def detect_category_changes(domain, previous_stack, target_category):
"""Detect changes in a specific vendor category."""
headers = {"Authorization": f"Bearer {API_KEY}"}
response = requests.get(
f"{BASE_URL}/check",
params={"url": domain},
headers=headers
).json()
if not response.get("found"):
return None
# Extract vendors in target category
current_category_vendors = set()
category_data = response.get("vendor_stack", {}).get(target_category, [])
for vendor_obj in category_data:
current_category_vendors.add(vendor_obj["vendor"])
# Compare to previous
previous_category_vendors = previous_stack.get(target_category, set())
added = current_category_vendors - previous_category_vendors
removed = previous_category_vendors - current_category_vendors
if added or removed:
return {
"domain": domain,
"category": target_category,
"added": list(added),
"removed": list(removed),
"evidence": {v["vendor"]: v.get("evidence") for v in category_data}
}
return None
# Example: monitor CRM changes
previous = {"crm_sales": {"HubSpot"}}
crm_change = detect_category_changes(
"prospect.com",
previous,
"crm_sales"
)
if crm_change and crm_change["added"]:
print(f"New CRM detected: {crm_change['added'][0]}")
print(f"Evidence: {crm_change['evidence'][crm_change['added'][0]]}")
This is useful for sales teams targeting companies based on specific infrastructure changes (e.g., "alert me when any company in my target list adopts Stripe").
Pattern: Replacement Detection
A vendor removal paired with a category addition often signals a replacement. If a company removes HubSpot and adds Salesforce in the same check window, they likely migrated CRMs:
def detect_replacements(domain, previous_stack_by_category):
"""Identify potential vendor replacements within categories."""
current_response = get_vendor_stack(domain)
if not current_response.get("found"):
return []
replacements = []
current_stack = current_response.get("vendor_stack", {})
for category in current_stack.keys():
current_vendors = {v["vendor"] for v in current_stack[category]}
previous_vendors = previous_stack_by_category.get(category, set())
added = current_vendors - previous_vendors
removed = previous_vendors - current_vendors
# If both added and removed in same category, likely a replacement
if added and removed:
replacements.append({
"category": category,
"removed": list(removed),
"added": list(added)
})
return replacements
# Example usage
previous = {
"crm_sales": {"HubSpot"},
"payments": {"Stripe"}
}
replacements = detect_replacements("company.com", previous)
for r in replacements:
print(f"{r['category']}: {r['removed'][0]} → {r['added'][0]}")
Replacement signals are high-intent. A company migrating off a competitor is actively evaluating alternatives.
Pattern: Bulk Monitoring with Scheduled Jobs
For monitoring hundreds or thousands of companies, schedule periodic checks and store historical stacks in a database:
import time
from datetime import datetime, timedelta
def monitor_account_list(domains, db_connection, check_interval_days=7):
"""
Check each domain periodically, store results, alert on changes.
Respects check_interval to avoid redundant scans.
"""
for domain in domains:
# Get last check time from DB
last_check = db_connection.get_last_check(domain)
if last_check and (datetime.utcnow() - last_check) < timedelta(days=check_interval_days):
continue # Skip, too soon
# Fetch current stack
response = get_vendor_stack(domain)
if not response.get("found"):
continue # No evidence, no charge, skip
current_vendors = extract_vendor_set(response)
# Compare to stored baseline
previous_vendors = db_connection.get_baseline_stack(domain)
if previous_vendors:
added = current_vendors - previous_vendors
removed = previous_vendors - current_vendors
if added or removed:
# Store change event and trigger alert
db_connection.log_change(domain, added, removed)
send_alert(domain, added, removed)
# Update baseline
db_connection.update_baseline(domain, current_vendors, datetime.utcnow())
time.sleep(0.5) # Rate limiting courtesy
# Run daily via cron
domains_to_monitor = ["company1.com", "company2.com", ...] # target account list
monitor_account_list(domains_to_monitor, db, check_interval_days=7)
Weekly checks balance cost and freshness. High-priority accounts can be checked more frequently.
Cost Management: Avoiding Redundant Scans
VendorStacks charges 1 credit per successful lookup. Key cost optimizations:
- Use
GET /v1/company/{domain}for cache checks: This endpoint never triggers a scan, returning cached data only. Check it first; ifscanned_atis recent enough, skip the live check.
- Batch checks by priority: Check high-value accounts weekly, long-tail accounts monthly.
- Filter by
foundbefore storing: Iffoundis false, the company has no detectable stack. Don't re-check frequently.
def smart_check(domain, max_cache_age_days=7):
"""Check cache first, only scan if stale."""
headers = {"Authorization": f"Bearer {API_KEY}"}
# Try cache-only endpoint first
cache_response = requests.get(
f"{BASE_URL}/company/{domain}",
headers=headers
).json()
if cache_response.get("scanned_at"):
scan_age = datetime.utcnow() - datetime.fromisoformat(
cache_response["scanned_at"].replace("Z", "+00:00")
)
if scan_age < timedelta(days=max_cache_age_days):
return cache_response # Fresh enough, no cost
# Cache miss or stale, trigger live check
return requests.get(
f"{BASE_URL}/check",
params={"url": domain},
headers=headers
).json()
This avoids paying for re-scans of recently checked companies.
Real-World Use Cases
Teams use change detection for:
- Sales timing: Alert SDRs when a target account adopts a complementary vendor (e.g., you sell analytics tools, they just added Stripe—now processing payments and need revenue analytics).
- Competitor monitoring: Track when customers adopt or remove competitor products.
- Market research: Measure vendor adoption trends across a segment (e.g., "15 B2B SaaS companies added OpenAI in Q1").
- Customer success: Detect when a customer removes your integration partner, signaling potential churn risk.
Evidence-Based Detection Limits
VendorStacks detects vendors from public evidence only. This means:
- Additions appear when: A company updates their privacy policy, adds JavaScript tags, publishes subprocessor lists, or otherwise makes vendor usage publicly visible.
- Removals appear when: That evidence disappears (e.g., privacy policy updated to remove a vendor).
- Timing lag: Changes reflect when public evidence updates, not the exact moment a contract is signed. A company might adopt a vendor internally before it appears in public documentation.
This is deterministic detection, not speculation. The API returns the evidence row that triggered each vendor match, so you can verify the signal yourself.
Getting Started
- Get an API key:
POST https://api.vendorstacks.com/v1/keysreturns an instant key with 25 free credits. - Establish baselines: Check your target account list once, store the results.
- Schedule re-checks: Weekly or monthly, depending on account priority.
- Set up alerts: Email, Slack, or CRM task creation when specific changes occur.
VendorStacks indexes 1,000 companies across 313 distinct vendors in 24 categories. The most common vendors in the index are Google Analytics (306 companies), AWS (282), Stripe (185), HubSpot (130), and Google Cloud (124)—if you're targeting companies using these, there's substantial coverage.
Change detection turns tech stack data into a timing signal. Instead of cold outreach, you reach prospects when their infrastructure signals readiness.