Performance Metrics

GET

The Performance endpoint provides Lighthouse-like performance measurements with detailed Core Web Vitals, resource analysis, and optimization recommendations. It measures real page load performance in controlled lab conditions, analyzes network resources, and provides actionable tips to improve page speed. Results are automatically cached for 24 hours to improve API performance.

Endpoint

GET /performance

Query Parameters

ParameterTypeRequiredDescription
urlstring
Yes
The public URL to measure. Must be a valid HTTP/HTTPS URL. Cannot be localhost or private IPs (SSRF protection).
devicestring
No
Device type for viewport emulation: mobile, tablet, or desktop (default: desktop). Affects viewport size, user agent, and performance characteristics.
waitUntilstring
No
Navigation wait condition: load, domcontentloaded, networkidle0, networkidle2 (default: networkidle2)
timeoutnumber
No
Navigation timeout in milliseconds. Must be between 1000ms (1 second) and 60000ms (60 seconds). Default: 30000

Wait Conditions

The waitUntil parameter determines when the page is considered fully loaded:

load

Waits for the load event (DOM + resources)

Use case: Fast measurements, minimal wait time

domcontentloaded

Waits for DOMContentLoaded event only

Use case: Very fast, but may miss lazy-loaded content

networkidle0

Waits until no network connections for 500ms

Use case: SPA with heavy async content

networkidle2 (default)

Waits until ≤2 network connections for 500ms

Use case: Balance between accuracy and speed

Core Web Vitals

The performance endpoint returns the three Core Web Vitals metrics that Google uses for ranking:

LCP

Largest Contentful Paint

Measures loading performance. Should occur within 2.5s of page start.

INP

Interaction to Next Paint

Measures responsiveness. Pages should have INP of 200ms or less.

CLS

Cumulative Layout Shift

Measures visual stability. Should maintain CLS of 0.1 or less.

Example Request

curl "https://api.webpeek.dev/performance?url=https://stripe.com&device=mobile"

Example Response

{
  "schema_version": "1.0.0",
  "run_id": "perf_2025-01-06T12-00-00_a1b2c3d4",
  "url": "https://example.com/page",
  "mode": "lab",
  "environment": {
    "device_profile": "desktop-4xCPU",
    "network_profile": "no-throttling",
    "collector_version": "webpeek-perf@1.0.0",
    "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
    "page_context": {
      "viewport": "1920x1080",
      "timezone": "America/New_York",
      "language": "en-US",
      "cookies_enabled": true
    }
  },
  "summary": {
    "overall": "good",
    "warnings": 1,
    "errors": 0,
    "suggestions": 3,
    "main_issue": "Optimize images to reduce LCP"
  },
  "core_web_vitals": {
    "lcp": {
      "value_ms": 2800,
      "rating": "needs-improvement",
      "budget_ms": 2500,
      "status": "warn"
    },
    "inp": {
      "value_ms": 150,
      "rating": "good",
      "budget_ms": 200,
      "status": "pass"
    },
    "cls": {
      "value": 0.08,
      "rating": "good",
      "budget": 0.1,
      "status": "pass"
    },
    "fcp": {
      "value_ms": 1200,
      "rating": "good",
      "budget_ms": 1800,
      "status": "pass"
    },
    "ttfb": {
      "value_ms": 450,
      "rating": "good",
      "budget_ms": 800,
      "status": "pass"
    }
  },
  "performance_score": {
    "schema_version": "1.0.0",
    "rule_version": "2025.11.0",
    "value": 78,
    "scale": "0-100",
    "method": "weighted-metrics",
    "weights": {
      "lcp": 0.35,
      "inp": 0.35,
      "cls": 0.15,
      "fcp": 0.1,
      "ttfb": 0.05
    }
  },
  "resources": {
    "total_requests": 45,
    "transfer_kb": 2345.6,
    "decoded_kb": 3456.7,
    "first_party": {
      "requests": 30,
      "transfer_kb": 1500.2,
      "decoded_kb": 2100.5
    },
    "third_party": {
      "requests": 15,
      "transfer_kb": 845.4,
      "decoded_kb": 1356.2
    },
    "by_type": {
      "javascript": {
        "count": 12,
        "transfer_kb": 890.1,
        "decoded_kb": 2345.6,
        "main_thread_ms": 1250
      },
      "image": {
        "count": 18,
        "transfer_kb": 1234.5,
        "decoded_kb": 1456.8,
        "unoptimized": 5
      },
      "stylesheet": {
        "count": 5,
        "transfer_kb": 123.4,
        "decoded_kb": 456.7
      }
    },
    "top_hosts": [
      {
        "host": "example.com",
        "requests": 30,
        "transfer_kb": 1500.2
      }
    ],
    "caching": {
      "cacheable_requests": 40,
      "missing_cache_headers": 5,
      "cache_hit_ratio": 0.75,
      "compression": {
        "gzip_or_br": 38,
        "uncompressed": 7,
        "estimated_savings_kb": 567.8
      }
    }
  },
  "tips": [
    {
      "tip_id": "optimize-images",
      "severity": "high",
      "message": "Optimize images to reduce LCP",
      "learn_more": "https://web.dev/optimize-lcp/"
    }
  ],
  "cache": {
    "cached": false,
    "cache_key": "perf:v1:https://example.com/page:desktop:networkidle2:30000",
    "ttl_seconds": 86400,
    "expires_at": "2025-01-07T12:00:00.000Z"
  },
  "created_at": "2025-01-06T12:00:00.000Z",
  "metadata": {
    "processing_time_ms": 4523,
    "page_load_time_ms": 3456,
    "page_load_rating": "average"
  }
}

Response Fields

Root Level Fields

  • schema_version - API schema version (e.g., "1.0.0") for response structure
  • run_id - Unique identifier for this performance measurement run
  • url - The URL that was measured
  • mode - Measurement mode: lab (controlled) or field (real users)
  • environment - Environment and device configuration used for measurement
  • summary - High-level performance summary with overall rating, warnings, errors, and main issues

Core Web Vitals

Each Core Web Vital includes the measured value (value_ms for time-based metrics, value for dimensionless metrics), rating (good/needs-improvement/poor), budget threshold, and pass/fail status.

  • lcp - Largest Contentful Paint: measures when the largest content element becomes visible (budget: ≤2500ms)
  • inp - Interaction to Next Paint: measures responsiveness to user interactions (budget: ≤200ms)
  • cls - Cumulative Layout Shift: measures visual stability, lower is better (budget: ≤0.1)
  • fcp - First Contentful Paint: measures when first text/image appears (budget: ≤1800ms)
  • ttfb - Time to First Byte: measures server response time (budget: ≤800ms)

Performance Score

Overall performance score (0-100) calculated using weighted metrics based on Lighthouse 10+ methodology:

  • LCP - 35% of score (most important for perceived load speed)
  • INP - 35% of score (critical for interactivity)
  • CLS - 15% of score (important for visual stability)
  • FCP - 10% of score (secondary load metric)
  • TTFB - 5% of score (server optimization)

Resources

Detailed breakdown of all resources loaded on the page:

  • total_requests - Total number of HTTP requests
  • transfer_kb - Total data transferred over network (compressed)
  • decoded_kb - Total data after decompression
  • first_party - Resources from same origin
  • third_party - Resources from different origins
  • by_type - Resources categorized by type (javascript, image, stylesheet, etc.) with optional metadata like main_thread_ms for JS and unoptimized count for images
  • top_hosts - Top 5 hosts by data transfer
  • caching - Cache analysis including cache headers, compression status, and estimated savings

Tips

Actionable optimization recommendations with severity levels (high/medium/low) and links to detailed documentation.

Cache

Results are cached for 24 hours. The cache object includes:

  • cached - Whether this result was served from cache
  • cache_key - Unique cache key based on URL, device, waitUntil, and timeout
  • cached_at - When the result was cached (if applicable)
  • ttl_seconds - Time-to-live in seconds (86400 = 24 hours)
  • expires_at - When the cache expires

Metadata

  • processing_time_ms - Total API processing time
  • page_load_time_ms - Actual page load time (0 for cached results)
  • page_load_rating - Load time rating: fast (<1500ms), average (1500-3000ms), or slow (>3000ms)

Error Responses

400 Bad Request - Validation Error

Returned when request parameters are invalid (missing URL, invalid device, invalid waitUntil, or timeout out of range).

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request parameters",
    "http_status": 400,
    "details": [
      {
        "code": "invalid_type",
        "expected": "string",
        "received": "undefined",
        "path": ["url"],
        "message": "Required"
      }
    ]
  }
}

400 Bad Request - Invalid URL

Returned when URL format is invalid or SSRF protection is triggered (localhost, private IPs).

408 Request Timeout

Returned when page navigation exceeds the timeout value.

502 Bad Gateway - Navigation Failed

Returned when the page fails to load (DNS resolution failure, connection refused, SSL certificate errors, invalid hostname).

503 Service Unavailable - Browser Crash

Returned when the browser crashes during measurement (excessive memory, infinite loops, WebGL/Canvas overload).

Usage Examples

Basic Performance Measurement

Measure performance with default settings (desktop, networkidle2, 30s timeout):

curl -X GET "https://api.example.com/performance?url=https://example.com" \
  -H "X-API-Key: YOUR_API_KEY"

Mobile Performance Test

Test mobile performance with faster timeout:

curl -X GET "https://api.example.com/performance?url=https://example.com&device=mobile&timeout=10000" \
  -H "X-API-Key: YOUR_API_KEY"

SPA with Network Idle

Measure Single Page Application with strict network idle condition:

curl -X GET "https://api.example.com/performance?url=https://app.example.com&waitUntil=networkidle0" \
  -H "X-API-Key: YOUR_API_KEY"

Extract Core Web Vitals

Use jq to extract just the Core Web Vitals scores:

curl -X GET "https://api.example.com/performance?url=https://example.com" \
  -H "X-API-Key: YOUR_API_KEY" | \
  jq '{
    url: .url,
    score: .performance_score.value,
    lcp: .core_web_vitals.lcp.value_ms,
    inp: .core_web_vitals.inp.value_ms,
    cls: .core_web_vitals.cls.value
  }'

Use Cases

  • Monitor website performance over time
  • Compare performance across different pages
  • Identify performance bottlenecks and optimization opportunities
  • Track Core Web Vitals for SEO rankings
  • Generate automated performance reports
  • Competitive analysis and benchmarking
  • CI/CD pipeline integration for performance regression testing

Code Examples

Here's how to use the performance endpoint in different languages:

JavaScript / Node.js

performance.js
async function getPerformanceMetrics(url, options = {}) {
  const params = new URLSearchParams({
    url,
    device: options.device || 'desktop',
    waitUntil: options.waitUntil || 'networkidle2',
    timeout: options.timeout || '30000'
  });

  const response = await fetch(
    `https://api.example.com/performance?${params}`,
    {
      headers: {
        'X-API-Key': process.env.API_KEY
      }
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`API error: ${error.error.message}`);
  }

  return await response.json();
}

// Example usage
const metrics = await getPerformanceMetrics('https://example.com', {
  device: 'mobile',
  timeout: '10000'
});

console.log('Performance Score:', metrics.performance_score.value);
console.log('Overall:', metrics.summary.overall);
console.log('LCP:', metrics.core_web_vitals.lcp.value_ms, 'ms -', metrics.core_web_vitals.lcp.rating);
console.log('INP:', metrics.core_web_vitals.inp.value_ms, 'ms -', metrics.core_web_vitals.inp.rating);
console.log('CLS:', metrics.core_web_vitals.cls.value, '-', metrics.core_web_vitals.cls.rating);

// Check if cached
if (metrics.cache.cached) {
  console.log('Result served from cache, cached at:', metrics.cache.cached_at);
}

// Display optimization tips
if (metrics.tips && metrics.tips.length > 0) {
  console.log('\nOptimization Tips:');
  metrics.tips.forEach(tip => {
    console.log(`- [${tip.severity}] ${tip.message}`);
  });
}

Python

performance.py
import requests
import os

def get_performance_metrics(url, device='desktop', wait_until='networkidle2', timeout=30000):
    response = requests.get(
        'https://api.example.com/performance',
        params={
            'url': url,
            'device': device,
            'waitUntil': wait_until,
            'timeout': timeout
        },
        headers={
            'X-API-Key': os.environ.get('API_KEY')
        }
    )
    response.raise_for_status()
    return response.json()

# Example usage
metrics = get_performance_metrics('https://example.com', device='mobile', timeout=10000)

print(f"Performance Score: {metrics['performance_score']['value']}")
print(f"Overall: {metrics['summary']['overall']}")
print(f"LCP: {metrics['core_web_vitals']['lcp']['value_ms']} ms - {metrics['core_web_vitals']['lcp']['rating']}")
print(f"INP: {metrics['core_web_vitals']['inp']['value_ms']} ms - {metrics['core_web_vitals']['inp']['rating']}")
print(f"CLS: {metrics['core_web_vitals']['cls']['value']} - {metrics['core_web_vitals']['cls']['rating']}")

# Check if cached
if metrics['cache']['cached']:
    print(f"Result served from cache, cached at: {metrics['cache']['cached_at']}")

# Display optimization tips
if metrics.get('tips'):
    print("\nOptimization Tips:")
    for tip in metrics['tips']:
        print(f"- [{tip['severity']}] {tip['message']}")

Monitoring Script

Example script to monitor performance and detect issues:

monitor.js
async function monitorPerformance(urls) {
  const results = [];

  for (const url of urls) {
    const metrics = await getPerformanceMetrics(url, { device: 'mobile' });

    const issues = [];

    // Check summary for overall health
    if (metrics.summary.overall !== 'excellent' && metrics.summary.overall !== 'good') {
      issues.push(`Overall rating is ${metrics.summary.overall}`);
    }

    // Check Core Web Vitals
    if (metrics.core_web_vitals.lcp.status === 'fail' || metrics.core_web_vitals.lcp.status === 'warn') {
      issues.push(`LCP: ${metrics.core_web_vitals.lcp.value_ms}ms (${metrics.core_web_vitals.lcp.rating})`);
    }
    if (metrics.core_web_vitals.inp.status === 'fail' || metrics.core_web_vitals.inp.status === 'warn') {
      issues.push(`INP: ${metrics.core_web_vitals.inp.value_ms}ms (${metrics.core_web_vitals.inp.rating})`);
    }
    if (metrics.core_web_vitals.cls.status === 'fail' || metrics.core_web_vitals.cls.status === 'warn') {
      issues.push(`CLS: ${metrics.core_web_vitals.cls.value} (${metrics.core_web_vitals.cls.rating})`);
    }

    // Check for high-severity optimization tips
    const criticalTips = metrics.tips?.filter(tip => tip.severity === 'high') || [];
    if (criticalTips.length > 0) {
      issues.push(`${criticalTips.length} high-severity optimization tips`);
    }

    results.push({
      url,
      score: metrics.performance_score.value,
      overall: metrics.summary.overall,
      mainIssue: metrics.summary.main_issue,
      issues,
      hasIssues: issues.length > 0
    });
  }

  return results;
}

// Example usage
const urls = [
  'https://example.com',
  'https://example.com/products',
  'https://example.com/about'
];

const results = await monitorPerformance(urls);

// Report pages with issues
results
  .filter(r => r.hasIssues)
  .forEach(r => {
    console.log(`⚠️ ${r.url} (Score: ${r.score}) has performance issues:`);
    if (r.mainIssue) console.log(`  Main issue: ${r.mainIssue}`);
    r.issues.forEach(issue => console.log(`  - ${issue}`));
  });

Best Practices

Device Testing

  • Always test mobile performance - Most traffic is mobile, and Google uses mobile-first indexing
  • Test all device types - Use mobile (375x667), tablet (768x1024), and desktop (1920x1080) viewports
  • Mobile typically scores lower due to smaller viewports, slower CPUs, and higher latency

Wait Conditions

  • Static sites - Use "load" or "domcontentloaded" for faster measurements
  • Traditional server-rendered pages - Use "load"
  • Modern SPAs with some async content - Use "networkidle2" (default)
  • SPAs with heavy async content - Use "networkidle0" for accuracy (slower)

Timeout Settings

  • Fast static sites - 5000-10000ms
  • Average web pages - 30000ms (default)
  • Slow or complex pages - 45000-60000ms
  • Longer timeouts increase API response time - balance accuracy vs. speed

Performance Optimization

  • Prioritize by weight - Focus on LCP (35%) and INP (35%) first, then CLS (15%), FCP (10%), and TTFB (5%)
  • Act on high-severity tips - Fix within 1 week, medium within 1 month, low when convenient
  • Monitor over time - Track performance trends and catch regressions early
  • Understand caching - Results cached for 24 hours. To bypass, change any parameter or add cache-busting query params

Performance Ratings

The API uses these thresholds to rate metrics:

MetricGoodNeeds ImprovementPoor
LCP≤ 2500ms2500ms - 4000ms> 4000ms
INP≤ 200ms200ms - 500ms> 500ms
CLS≤ 0.10.1 - 0.25> 0.25