Web Scraping for Automotive & Dealerships: How AI Agents Track Inventory, Pricing, Reviews & Market Data in 2026

Published: March 13, 2026 ยท 18 min read ยท By the Mantis Team

The global automotive industry generates over $3 trillion annually. In the United States alone, more than 15 million new vehicles and 40 million used vehicles change hands each year, making the used car market worth over $800 billion. Dealerships, lenders, insurers, and automotive startups all depend on accurate, real-time market data to price vehicles, source inventory, and outmaneuver competitors.

Yet the data that powers these decisions โ€” dealer inventory listings, market valuations, auction results, customer reviews โ€” is scattered across dozens of platforms. Traditional automotive data providers like vAuto (Cox Automotive), DealerSocket, and MarketCheck charge $1,000โ€“$10,000+ per month for access. Meanwhile, much of this data is publicly available on sites like AutoTrader, Cars.com, CarGurus, KBB, and Edmunds.

In this guide, you'll build a complete automotive intelligence system using Python, the Mantis WebPerception API, and GPT-4o โ€” covering dealer inventory tracking, competitive pricing analysis, review monitoring, auction intelligence, and AI-powered market predictions.

Why Automotive Businesses Need Web Scraping

The automotive market is one of the most data-intensive industries in the world. Every vehicle is unique (year, make, model, trim, mileage, condition, options, history), which makes pricing both critical and complex:

Build Automotive Intelligence Agents with Mantis

Scrape dealer inventory, vehicle pricing, reviews, and auction data from any automotive site with one API call. AI-powered extraction handles every listing format.

Get Free API Key โ†’

Architecture: The 6-Step Automotive Intelligence Pipeline

  1. Dealer inventory scraping โ€” Monitor competitor lots, new listings, and days-on-market across AutoTrader, Cars.com, CarGurus, and dealer websites
  2. Pricing & market value tracking โ€” Pull KBB, Edmunds, NADA values and compare against actual listing prices
  3. Review & reputation monitoring โ€” Track Google Reviews, DealerRater, Cars.com dealer reviews for sentiment shifts
  4. Auction intelligence โ€” Monitor Manheim Market Report, Copart, and IAAI for wholesale pricing trends
  5. GPT-4o market analysis โ€” Generate pricing recommendations, predict depreciation, identify arbitrage opportunities
  6. Alert delivery โ€” Route underpriced vehicles, competitor price changes, new negative reviews, and recall alerts to your team via Slack

Step 1: Define Your Automotive Data Models

from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
from enum import Enum

class ListingSource(str, Enum):
    AUTOTRADER = "autotrader"
    CARSCOM = "cars.com"
    CARGURUS = "cargurus"
    DEALER_WEBSITE = "dealer_website"
    FACEBOOK_MARKETPLACE = "facebook_marketplace"
    CRAIGSLIST = "craigslist"

class VehicleListing(BaseModel):
    """A vehicle listing from a dealer or marketplace."""
    vin: Optional[str]
    year: int
    make: str
    model: str
    trim: Optional[str]
    mileage: int
    price: float
    original_msrp: Optional[float]
    exterior_color: Optional[str]
    interior_color: Optional[str]
    transmission: Optional[str]
    drivetrain: Optional[str]  # AWD, FWD, RWD, 4WD
    fuel_type: Optional[str]  # gasoline, diesel, electric, hybrid, plug-in hybrid
    engine: Optional[str]
    body_style: Optional[str]  # sedan, SUV, truck, coupe, van
    dealer_name: str
    dealer_city: Optional[str]
    dealer_state: Optional[str]
    days_on_market: Optional[int]
    listing_source: ListingSource
    carfax_one_owner: Optional[bool]
    accidents_reported: Optional[int]
    source_url: str
    scraped_at: datetime

class DealerInventory(BaseModel):
    """Aggregated dealer inventory snapshot."""
    dealer_name: str
    dealer_id: Optional[str]
    total_vehicles: int
    new_count: int
    used_count: int
    certified_count: int
    avg_price: float
    avg_mileage: float
    avg_days_on_market: Optional[float]
    top_makes: List[str]
    price_range: str  # "$15,000 - $85,000"
    scraped_at: datetime

class MarketValue(BaseModel):
    """Market value estimate from pricing sources."""
    vin: Optional[str]
    year: int
    make: str
    model: str
    trim: Optional[str]
    mileage: int
    condition: str  # excellent, good, fair, rough
    kbb_private_party: Optional[float]
    kbb_dealer_retail: Optional[float]
    kbb_trade_in: Optional[float]
    edmunds_tmv: Optional[float]
    cargurus_imv: Optional[float]  # Instant Market Value
    avg_market_price: Optional[float]  # average of available estimates
    price_vs_market: Optional[str]  # "below market", "at market", "above market"
    scraped_at: datetime

class AuctionResult(BaseModel):
    """Wholesale auction result from Manheim, Copart, or IAAI."""
    vin: Optional[str]
    year: int
    make: str
    model: str
    trim: Optional[str]
    mileage: int
    condition_grade: Optional[float]  # Manheim 1.0-5.0 scale
    sale_price: float
    buy_now_price: Optional[float]
    auction_house: str  # "manheim", "copart", "iaai"
    auction_location: Optional[str]
    sale_date: datetime
    damage_type: Optional[str]  # for salvage: "front end", "flood", etc.
    title_status: Optional[str]  # clean, salvage, rebuilt
    retail_estimate: Optional[float]
    wholesale_to_retail_spread: Optional[float]
    source_url: str

Step 2: Scrape Dealer Inventory & Competitor Listings

from mantis import MantisClient
import asyncio

mantis = MantisClient(api_key="your-mantis-api-key")

async def scrape_dealer_inventory(
    dealer_urls: List[dict],  # [{"dealer": "ABC Motors", "url": "https://..."}]
    max_pages: int = 5
) -> List[VehicleListing]:
    """
    Scrape vehicle listings from dealer websites and aggregator platforms.
    Mantis handles JS rendering for React/Angular dealer sites.
    """
    listings = []
    
    for dealer in dealer_urls:
        for page in range(1, max_pages + 1):
            url = f"{dealer['url']}?page={page}" if page > 1 else dealer["url"]
            
            result = await mantis.scrape(
                url=url,
                extract={
                    "vehicles": [{
                        "year": "number",
                        "make": "string",
                        "model": "string",
                        "trim": "string or null",
                        "mileage": "number",
                        "price": "number",
                        "exterior_color": "string or null",
                        "transmission": "string or null",
                        "drivetrain": "string or null",
                        "fuel_type": "string or null",
                        "engine": "string or null",
                        "body_style": "string or null",
                        "vin": "string or null",
                        "days_on_market": "number or null",
                        "carfax_one_owner": "boolean or null",
                        "detail_url": "string"
                    }],
                    "total_results": "number or null"
                }
            )
            
            for v in result.get("vehicles", []):
                listing = VehicleListing(
                    vin=v.get("vin"),
                    year=v.get("year", 0),
                    make=v.get("make", ""),
                    model=v.get("model", ""),
                    trim=v.get("trim"),
                    mileage=v.get("mileage", 0),
                    price=v.get("price", 0),
                    exterior_color=v.get("exterior_color"),
                    transmission=v.get("transmission"),
                    drivetrain=v.get("drivetrain"),
                    fuel_type=v.get("fuel_type"),
                    engine=v.get("engine"),
                    body_style=v.get("body_style"),
                    dealer_name=dealer["dealer"],
                    days_on_market=v.get("days_on_market"),
                    listing_source=detect_source(dealer["url"]),
                    carfax_one_owner=v.get("carfax_one_owner"),
                    source_url=v.get("detail_url", url),
                    scraped_at=datetime.now()
                )
                listings.append(listing)
            
            # Stop if no more results
            if len(result.get("vehicles", [])) == 0:
                break
    
    return listings

async def scrape_market_search(
    make: str, model: str, year_min: int, year_max: int,
    zip_code: str = "10001", radius_miles: int = 100
) -> List[VehicleListing]:
    """
    Search major automotive marketplaces for comparable vehicles.
    Builds competitive pricing intelligence for specific models.
    """
    search_urls = [
        f"https://www.autotrader.com/cars-for-sale/all-cars/{make}/{model}?zip={zip_code}&searchRadius={radius_miles}&startYear={year_min}&endYear={year_max}",
        f"https://www.cars.com/shopping/results/?stock_type=all&makes[]={make}&models[]={make}-{model}&year_min={year_min}&year_max={year_max}&zip={zip_code}&maximum_distance={radius_miles}",
        f"https://www.cargurus.com/Cars/inventorylisting/viewDetailsFilterViewInventoryListing.action?zip={zip_code}&showNegotiable=true&sortDir=ASC&sourceContext=carGurusHomePageModel&distance={radius_miles}&entitySelectingHelper.selectedEntity={make.lower()}-{model.lower()}"
    ]
    
    all_listings = []
    
    for url in search_urls:
        result = await mantis.scrape(
            url=url,
            extract={
                "vehicles": [{
                    "year": "number",
                    "make": "string",
                    "model": "string",
                    "trim": "string or null",
                    "mileage": "number",
                    "price": "number",
                    "dealer_name": "string",
                    "dealer_city": "string or null",
                    "dealer_state": "string or null",
                    "days_on_market": "number or null",
                    "detail_url": "string"
                }]
            }
        )
        
        for v in result.get("vehicles", []):
            listing = VehicleListing(
                year=v.get("year", 0),
                make=v.get("make", make),
                model=v.get("model", model),
                trim=v.get("trim"),
                mileage=v.get("mileage", 0),
                price=v.get("price", 0),
                dealer_name=v.get("dealer_name", ""),
                dealer_city=v.get("dealer_city"),
                dealer_state=v.get("dealer_state"),
                days_on_market=v.get("days_on_market"),
                listing_source=detect_source(url),
                source_url=v.get("detail_url", url),
                scraped_at=datetime.now()
            )
            all_listings.append(listing)
    
    return all_listings

Tracking Days-on-Market & Inventory Aging

import sqlite3

def analyze_inventory_aging(
    listings: List[VehicleListing],
    db_path: str = "automotive_intel.db"
) -> dict:
    """
    Track how long vehicles sit on dealer lots.
    Aging inventory = motivated sellers = negotiation opportunities.
    """
    conn = sqlite3.connect(db_path)
    
    aging_analysis = {
        "fresh_listings": [],      # < 7 days
        "normal_aging": [],        # 7-30 days
        "aging_inventory": [],     # 30-60 days
        "stale_inventory": [],     # 60+ days โ€” likely price drops coming
        "newly_removed": []        # was listed, now gone โ€” sold or pulled
    }
    
    for listing in listings:
        dom = listing.days_on_market or 0
        
        if dom < 7:
            aging_analysis["fresh_listings"].append(listing)
        elif dom < 30:
            aging_analysis["normal_aging"].append(listing)
        elif dom < 60:
            aging_analysis["aging_inventory"].append(listing)
        else:
            aging_analysis["stale_inventory"].append(listing)
        
        # Store for historical tracking
        conn.execute("""
            INSERT OR REPLACE INTO inventory_tracking 
            (vin, dealer, price, days_on_market, scraped_at)
            VALUES (?, ?, ?, ?, ?)
        """, (listing.vin, listing.dealer_name, listing.price, 
              dom, listing.scraped_at.isoformat()))
    
    # Check for removed listings (previously tracked, now missing)
    tracked_vins = set(l.vin for l in listings if l.vin)
    previously_tracked = conn.execute("""
        SELECT DISTINCT vin, dealer, price FROM inventory_tracking
        WHERE scraped_at > datetime('now', '-7 days')
    """).fetchall()
    
    for vin, dealer, last_price in previously_tracked:
        if vin and vin not in tracked_vins:
            aging_analysis["newly_removed"].append({
                "vin": vin, "dealer": dealer, 
                "last_price": last_price, "likely_sold": True
            })
    
    conn.commit()
    conn.close()
    
    return {
        "summary": {
            "total_tracked": len(listings),
            "fresh_pct": round(len(aging_analysis["fresh_listings"]) / max(len(listings), 1) * 100, 1),
            "stale_pct": round(len(aging_analysis["stale_inventory"]) / max(len(listings), 1) * 100, 1),
            "avg_dom": round(sum(l.days_on_market or 0 for l in listings) / max(len(listings), 1), 1),
            "likely_sold_this_week": len(aging_analysis["newly_removed"])
        },
        "details": aging_analysis
    }

Step 3: Market Value Comparison & Pricing Intelligence

async def get_market_values(
    vehicles: List[dict]  # [{"year": 2023, "make": "Toyota", "model": "RAV4", "trim": "XLE", "mileage": 25000}]
) -> List[MarketValue]:
    """
    Pull market value estimates from KBB, Edmunds, and CarGurus.
    Cross-reference multiple sources for accurate pricing.
    """
    values = []
    
    for vehicle in vehicles:
        ymm = f"{vehicle['year']} {vehicle['make']} {vehicle['model']}"
        
        # KBB values
        kbb_url = f"https://www.kbb.com/{'_'.join([vehicle['make'].lower(), vehicle['model'].lower(), str(vehicle['year'])])}/"
        kbb_result = await mantis.scrape(
            url=kbb_url,
            extract={
                "private_party_value": "number or null",
                "dealer_retail_value": "number or null",
                "trade_in_value": "number or null",
                "fair_market_range_low": "number or null",
                "fair_market_range_high": "number or null"
            }
        )
        
        # Edmunds TMV
        edmunds_url = f"https://www.edmunds.com/{vehicle['make'].lower()}/{vehicle['model'].lower()}/{vehicle['year']}/appraisal/"
        edmunds_result = await mantis.scrape(
            url=edmunds_url,
            extract={
                "true_market_value": "number or null",
                "private_party_value": "number or null",
                "dealer_retail_value": "number or null"
            }
        )
        
        # CarGurus IMV
        cargurus_url = f"https://www.cargurus.com/Cars/price-trends/{vehicle['make'].lower()}-{vehicle['model'].lower()}-d{vehicle['year']}"
        cargurus_result = await mantis.scrape(
            url=cargurus_url,
            extract={
                "instant_market_value": "number or null",
                "price_trend_30d": "string or null",
                "avg_listing_price": "number or null"
            }
        )
        
        # Compute average across available sources
        available_values = [
            v for v in [
                kbb_result.get("dealer_retail_value"),
                edmunds_result.get("true_market_value"),
                cargurus_result.get("instant_market_value")
            ] if v is not None
        ]
        avg_value = sum(available_values) / len(available_values) if available_values else None
        
        mv = MarketValue(
            year=vehicle["year"],
            make=vehicle["make"],
            model=vehicle["model"],
            trim=vehicle.get("trim"),
            mileage=vehicle.get("mileage", 0),
            condition=vehicle.get("condition", "good"),
            kbb_private_party=kbb_result.get("private_party_value"),
            kbb_dealer_retail=kbb_result.get("dealer_retail_value"),
            kbb_trade_in=kbb_result.get("trade_in_value"),
            edmunds_tmv=edmunds_result.get("true_market_value"),
            cargurus_imv=cargurus_result.get("instant_market_value"),
            avg_market_price=avg_value,
            scraped_at=datetime.now()
        )
        values.append(mv)
    
    return values

def identify_pricing_opportunities(
    listings: List[VehicleListing],
    market_values: List[MarketValue]
) -> dict:
    """
    Find underpriced and overpriced vehicles by comparing
    listing prices against market value estimates.
    """
    opportunities = {"underpriced": [], "overpriced": [], "at_market": []}
    
    value_lookup = {
        f"{mv.year}-{mv.make}-{mv.model}": mv 
        for mv in market_values
    }
    
    for listing in listings:
        key = f"{listing.year}-{listing.make}-{listing.model}"
        mv = value_lookup.get(key)
        
        if not mv or not mv.avg_market_price:
            continue
        
        diff_pct = ((listing.price - mv.avg_market_price) / mv.avg_market_price) * 100
        
        entry = {
            "listing": listing,
            "market_value": mv.avg_market_price,
            "diff_pct": round(diff_pct, 1),
            "potential_savings": round(mv.avg_market_price - listing.price, 0)
        }
        
        if diff_pct < -8:
            opportunities["underpriced"].append(entry)
        elif diff_pct > 8:
            opportunities["overpriced"].append(entry)
        else:
            opportunities["at_market"].append(entry)
    
    # Sort underpriced by biggest discount
    opportunities["underpriced"].sort(key=lambda x: x["diff_pct"])
    
    return opportunities

Step 4: Review & Reputation Monitoring

async def scrape_dealer_reviews(
    dealers: List[dict]  # [{"name": "ABC Motors", "google_url": "...", "dealerrater_url": "..."}]
) -> dict:
    """
    Monitor dealer reputation across Google Reviews, DealerRater,
    and Cars.com. Catch negative trends before they impact leads.
    """
    all_reviews = {}
    
    for dealer in dealers:
        dealer_reviews = {"google": [], "dealerrater": [], "carscom": []}
        
        # Google Reviews
        if dealer.get("google_url"):
            google = await mantis.scrape(
                url=dealer["google_url"],
                extract={
                    "overall_rating": "number",
                    "total_reviews": "number",
                    "reviews": [{
                        "rating": "number",
                        "text": "string",
                        "author": "string",
                        "date": "string",
                        "response": "string or null"
                    }]
                }
            )
            dealer_reviews["google"] = google.get("reviews", [])
        
        # DealerRater
        if dealer.get("dealerrater_url"):
            dr = await mantis.scrape(
                url=dealer["dealerrater_url"],
                extract={
                    "overall_rating": "number",
                    "total_reviews": "number",
                    "recommended_pct": "number or null",
                    "reviews": [{
                        "rating": "number",
                        "title": "string",
                        "text": "string",
                        "department": "string or null",
                        "employee_mentioned": "string or null",
                        "date": "string"
                    }]
                }
            )
            dealer_reviews["dealerrater"] = dr.get("reviews", [])
        
        all_reviews[dealer["name"]] = dealer_reviews
    
    return all_reviews

async def analyze_dealer_reputation(reviews: dict) -> dict:
    """Use GPT-4o to analyze dealer review patterns and trends."""
    from openai import OpenAI
    client = OpenAI()
    
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "system",
            "content": """Analyze these dealer reviews and provide:
            1. REPUTATION SCORE: Overall assessment (1-10) with trend direction
            2. STRENGTHS: What customers consistently praise
            3. WEAKNESSES: Recurring complaints (sales pressure, service quality, pricing transparency)
            4. DEPARTMENT BREAKDOWN: Sales vs. Service vs. Finance ratings
            5. COMPETITIVE POSITIONING: How this dealer compares to others in the market
            6. RESPONSE QUALITY: Are they responding to reviews? How effectively?
            7. RED FLAGS: Any patterns suggesting serious issues (bait-and-switch, hidden fees, etc.)
            8. RECOMMENDATIONS: Top 3 improvements to boost reviews
            
            Be specific. Quote actual review language when relevant."""
        }, {
            "role": "user",
            "content": f"Dealer reviews to analyze:\n{json.dumps(reviews, indent=2)}"
        }],
        temperature=0.2
    )
    
    return {"analysis": response.choices[0].message.content}

Step 5: Auction Intelligence & Wholesale Pricing

async def track_auction_results(
    search_criteria: List[dict]
) -> List[AuctionResult]:
    """
    Monitor wholesale auction prices from Copart and IAAI.
    Essential for dealers sourcing inventory and lenders
    calculating residual values.
    """
    results = []
    
    for criteria in search_criteria:
        # Copart โ€” salvage and clean title auctions
        copart_url = f"https://www.copart.com/lotSearchResults/?free=true&query={criteria['make']}+{criteria['model']}&searchCriteria=%7B%22query%22%3A%5B%22{criteria['make']}+{criteria['model']}%22%5D%7D"
        
        copart_data = await mantis.scrape(
            url=copart_url,
            extract={
                "lots": [{
                    "lot_number": "string",
                    "year": "number",
                    "make": "string",
                    "model": "string",
                    "vin": "string",
                    "mileage": "number",
                    "current_bid": "number",
                    "buy_now_price": "number or null",
                    "damage_type": "string or null",
                    "title_status": "string",
                    "sale_date": "string or null",
                    "location": "string"
                }]
            }
        )
        
        for lot in copart_data.get("lots", []):
            result = AuctionResult(
                vin=lot.get("vin"),
                year=lot.get("year", 0),
                make=lot.get("make", criteria["make"]),
                model=lot.get("model", criteria["model"]),
                mileage=lot.get("mileage", 0),
                sale_price=lot.get("current_bid", 0),
                buy_now_price=lot.get("buy_now_price"),
                auction_house="copart",
                auction_location=lot.get("location"),
                sale_date=datetime.now(),
                damage_type=lot.get("damage_type"),
                title_status=lot.get("title_status"),
                source_url=copart_url
            )
            results.append(result)
    
    return results

def calculate_wholesale_retail_spread(
    auction_results: List[AuctionResult],
    market_values: List[MarketValue]
) -> dict:
    """
    Calculate the wholesale-to-retail spread for sourcing decisions.
    Higher spread = more profitable inventory acquisition.
    """
    spreads = {}
    
    value_lookup = {
        f"{mv.year}-{mv.make}-{mv.model}": mv.avg_market_price
        for mv in market_values if mv.avg_market_price
    }
    
    for result in auction_results:
        key = f"{result.year}-{result.make}-{result.model}"
        retail_value = value_lookup.get(key)
        
        if not retail_value or result.sale_price <= 0:
            continue
        
        spread = retail_value - result.sale_price
        spread_pct = (spread / retail_value) * 100
        
        # Estimate profit after reconditioning
        recon_cost_estimate = 1500  # average reconditioning
        net_profit = spread - recon_cost_estimate
        
        spreads[key] = {
            "wholesale_price": result.sale_price,
            "retail_value": retail_value,
            "gross_spread": round(spread, 0),
            "spread_pct": round(spread_pct, 1),
            "est_recon_cost": recon_cost_estimate,
            "est_net_profit": round(net_profit, 0),
            "title_status": result.title_status,
            "worth_sourcing": net_profit > 2000 and result.title_status == "clean"
        }
    
    return spreads

Step 6: AI-Powered Market Analysis & Alerts

from openai import OpenAI

openai_client = OpenAI()

async def generate_automotive_intelligence(
    listings: List[VehicleListing],
    market_values: List[MarketValue],
    auction_results: List[AuctionResult],
    reviews: dict,
    recall_alerts: List[dict] = None
) -> dict:
    """
    Generate a comprehensive automotive market intelligence briefing.
    """
    response = openai_client.chat.completions.create(
        model="gpt-4o",
        messages=[{
            "role": "system",
            "content": """You are an automotive market analyst. Produce an actionable 
            intelligence briefing covering:
            
            1. INVENTORY INTELLIGENCE
               - New listings of interest (underpriced, rare trims, low mileage)
               - Aging inventory at competitor dealers (negotiation opportunities)
               - Vehicles removed (likely sold โ€” validates demand)
            
            2. PRICING TRENDS
               - Models appreciating or depreciating faster than average
               - Price positioning vs. market (are we competitive?)
               - Recommended price adjustments for our inventory
            
            3. WHOLESALE OPPORTUNITIES
               - Best auction deals this week (highest spread potential)
               - Models to avoid (high recon risk, slow sellers)
               - Emerging wholesale trends
            
            4. MARKET SIGNALS
               - EV vs ICE demand shifts
               - Seasonal patterns approaching
               - OEM incentive changes affecting used values
            
            5. REPUTATION SNAPSHOT
               - Review trend direction
               - New negative reviews requiring response
               - Competitive reputation comparison
            
            6. RECALL ALERTS
               - New NHTSA recalls affecting inventory or customer vehicles
               - Service revenue opportunities from recalls
            
            7. TOP 5 ACTIONS THIS WEEK
               - Specific, prioritized, with expected impact
            
            Data-driven analysis. Include dollar amounts and percentages."""
        }, {
            "role": "user",
            "content": f"""Inventory: {len(listings)} vehicles tracked
            Market values: {len(market_values)} models valued
            Auction results: {len(auction_results)} lots monitored
            Reviews: {json.dumps({k: len(v.get('google', []) + v.get('dealerrater', [])) for k, v in reviews.items()})}
            Recalls: {json.dumps(recall_alerts or [])}
            
            Sample listings: {json.dumps([l.model_dump() for l in listings[:20]], default=str)}
            Sample values: {json.dumps([v.model_dump() for v in market_values[:10]], default=str)}
            Sample auctions: {json.dumps([a.model_dump() for a in auction_results[:10]], default=str)}"""
        }],
        temperature=0.2
    )
    
    return {
        "briefing": response.choices[0].message.content,
        "generated_at": datetime.now().isoformat()
    }

async def deliver_automotive_alerts(
    opportunities: dict,
    reviews: dict,
    slack_webhook: str
):
    """Route automotive alerts by urgency and type."""
    import httpx
    
    msg_parts = []
    
    # Underpriced vehicles โ€” buy opportunities
    if opportunities.get("underpriced"):
        msg_parts.append("๐Ÿš— *Underpriced Vehicles Detected*\n")
        for opp in opportunities["underpriced"][:5]:
            l = opp["listing"]
            msg_parts.append(
                f"โ€ข {l.year} {l.make} {l.model} โ€” ${l.price:,.0f} "
                f"({opp['diff_pct']}% below market) at {l.dealer_name}\n"
                f"  Potential savings: ${abs(opp['potential_savings']):,.0f} | {l.source_url}\n"
            )
    
    # Stale inventory โ€” negotiation targets
    if opportunities.get("stale"):
        msg_parts.append("\nโฐ *Stale Inventory (60+ days)*\n")
        for item in opportunities["stale"][:5]:
            msg_parts.append(
                f"โ€ข {item.year} {item.make} {item.model} โ€” "
                f"{item.days_on_market} days at {item.dealer_name}\n"
            )
    
    if msg_parts:
        await httpx.AsyncClient().post(slack_webhook, json={
            "text": "".join(msg_parts),
            "unfurl_links": False
        })

Advanced: NHTSA Recall Monitoring

async def monitor_nhtsa_recalls(
    makes_models: List[dict],  # [{"make": "Toyota", "model": "RAV4", "years": [2022, 2023, 2024]}]
) -> List[dict]:
    """
    Monitor NHTSA for new recalls affecting your inventory
    or customer vehicles. Creates service revenue opportunities.
    """
    recalls = []
    
    for vehicle in makes_models:
        for year in vehicle["years"]:
            url = f"https://api.nhtsa.gov/recalls/recallsByVehicle?make={vehicle['make']}&model={vehicle['model']}&modelYear={year}"
            
            result = await mantis.scrape(
                url=url,
                extract={
                    "recalls": [{
                        "nhtsa_campaign_number": "string",
                        "component": "string",
                        "summary": "string",
                        "consequence": "string",
                        "remedy": "string",
                        "report_date": "string",
                        "units_affected": "number or null"
                    }]
                }
            )
            
            for recall in result.get("recalls", []):
                recalls.append({
                    "year": year,
                    "make": vehicle["make"],
                    "model": vehicle["model"],
                    **recall,
                    "affects_inventory": True,
                    "service_opportunity": True
                })
    
    return recalls

Cost Comparison: AI Agents vs. Automotive Data Platforms

PlatformMonthly CostBest For
vAuto (Cox Automotive)$2,000โ€“$8,000Inventory management, market pricing, appraisal tools
DealerSocket$1,000โ€“$5,000CRM, inventory management, digital marketing
CarGurus Dealer Tools$500โ€“$3,000Listing optimization, IMV pricing, lead generation
MarketCheck API$1,000โ€“$10,000Automotive data API for developers
Black Book / J.D. Power$500โ€“$5,000Wholesale values, residual forecasting
Manheim Market Report$200โ€“$1,000Wholesale auction analytics
AI Agent + Mantis$29โ€“$299Custom inventory tracking, pricing, reviews, auctions โ€” fully tailored

Honest caveat: Platforms like vAuto and DealerSocket offer direct integrations with DMS systems (CDK, Reynolds & Reynolds), OEM incentive feeds, and proprietary transaction data from millions of wholesale and retail sales. Their value is strongest for large dealer groups managing 500+ vehicles across multiple rooftops. For independent dealers, automotive startups, and lenders wanting custom market intelligence for specific segments, an AI agent approach delivers 80โ€“90% of the pricing intelligence at 5โ€“10% of the cost โ€” especially for competitive monitoring and review tracking that enterprise tools often handle poorly.

Use Cases by Automotive Segment

1. Franchise Dealerships โ€” Competitive Pricing & Market Share

Monitor competing franchise and independent dealers within your PMA (Primary Market Area). Track their inventory mix, pricing relative to market, days-on-market, and promotional activity. Identify when competitors receive new allocations of high-demand models. Use review monitoring to benchmark your CSI scores against local competition. Essential for GMs and used car managers making daily pricing decisions on 200โ€“500 vehicle inventories.

2. Independent Used Car Dealers โ€” Sourcing & Pricing

Find underpriced vehicles across AutoTrader, Cars.com, Facebook Marketplace, and Craigslist before other buyers spot them. Track wholesale-to-retail spreads at Manheim and Copart to identify profitable sourcing opportunities. Monitor aging inventory to know when to price-drop vs. wholesale out. For independent dealers working on tighter margins, automated market intelligence is the difference between a $2,000 front-end gross and breaking even.

3. Auto Lenders & Insurance Companies

Track real-time market values for collateral valuation, residual forecasting, and claims processing. Monitor depreciation curves by make, model, and trim to adjust lending terms. Detect market shifts (e.g., used truck prices dropping after new model launches) that affect portfolio risk. Build automated valuation models (AVMs) using scraped listing data as training inputs.

4. Automotive Startups & Tech Companies

Product companies building car-buying apps, valuation tools, or dealer analytics platforms need massive datasets of vehicle listings, pricing, and reviews. Instead of licensing from MarketCheck or DataOne at $10K+/mo, build custom data pipelines tailored to your specific product needs. Power recommendation engines, price prediction models, and market trend visualizations.

Compliance & Best Practices

Getting Started

  1. Define your market โ€” which makes, models, and geographic areas matter most to your business?
  2. Set up Mantis API access โ€” sign up for a free API key (100 calls/month free)
  3. Start with competitor inventory โ€” scrape the 5โ€“10 dealers in your PMA to understand the competitive landscape
  4. Add market values โ€” cross-reference KBB, Edmunds, and CarGurus values to calibrate your pricing
  5. Monitor reviews weekly โ€” track Google and DealerRater reviews for your dealership and top 3 competitors
  6. Automate alerts โ€” route underpriced vehicles, stale inventory, and negative reviews to Slack for immediate action