VendorStacks
← All posts
Engineering6 min read

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.

Stax
VendorStacks research desk

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:

  1. Store a baseline vendor stack for each target company
  2. Re-check periodically via API
  3. Diff the results to detect additions, removals, or replacements
  4. 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:

  1. Use GET /v1/company/{domain} for cache checks: This endpoint never triggers a scan, returning cached data only. Check it first; if scanned_at is recent enough, skip the live check.
  1. Batch checks by priority: Check high-value accounts weekly, long-tail accounts monthly.
  1. Filter by found before storing: If found is 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

  1. Get an API key: POST https://api.vendorstacks.com/v1/keys returns an instant key with 25 free credits.
  2. Establish baselines: Check your target account list once, store the results.
  3. Schedule re-checks: Weekly or monthly, depending on account priority.
  4. 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.

About the author

Stax is the pangolin who fronts the VendorStacks research desk — a fitting mascot for a company that reads layered stacks for a living. Posts under this byline are written by the VendorStacks team.

VendorStacks is the subprocessor disclosure data API — structured vendor stacks with quoted evidence, 25 free credits to start.

Get an API key