Code Examples

Complete, production-ready code examples for integrating WebPeek API into your application. All examples include error handling, rate limiting, and best practices.

JavaScript / Node.js

Basic Metadata Extraction

metadata-basic.js
const WEBPEEK_API_KEY = 'your_api_key_here';
const BASE_URL = 'https://api.webpeek.dev';

async function getMetadata(url) {
  const response = await fetch(
    `${BASE_URL}/metadata?url=${encodeURIComponent(url)}`,
    {
      headers: {
        'X-API-Key': WEBPEEK_API_KEY
      }
    }
  );

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

  return await response.json();
}

// Usage
try {
  const metadata = await getMetadata('https://github.com');
  console.log('Title:', metadata.title);
  console.log('Description:', metadata.description);
  console.log('OG Image:', metadata.og?.image);
  console.log('Language:', metadata.language);
} catch (error) {
  console.error('Error:', error.message);
}

Advanced: Field Filtering & Rate Limiting

metadata-advanced.js
class WebPeekClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = 'https://api.webpeek.dev';
    this.requestQueue = [];
    this.processing = false;
    this.requestsPerMinute = 100; // Adjust based on your plan
  }

  async request(endpoint, params = {}) {
    return new Promise((resolve, reject) => {
      this.requestQueue.push({ endpoint, params, resolve, reject });
      this.processQueue();
    });
  }

  async processQueue() {
    if (this.processing || this.requestQueue.length === 0) return;

    this.processing = true;
    const { endpoint, params, resolve, reject } = this.requestQueue.shift();

    try {
      const queryString = new URLSearchParams(params).toString();
      const url = `${this.baseUrl}${endpoint}?${queryString}`;

      const response = await fetch(url, {
        headers: {
          'X-API-Key': this.apiKey
        }
      });

      // Check rate limit headers
      const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0');
      const resetAfter = parseInt(response.headers.get('X-RateLimit-Reset-After') || '0');

      console.log(`Rate limit remaining: ${remaining}`);

      // Handle rate limit
      if (response.status === 429) {
        const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
        console.log(`Rate limited. Retrying after ${retryAfter}s`);

        await new Promise(r => setTimeout(r, retryAfter * 1000));
        this.requestQueue.unshift({ endpoint, params, resolve, reject });
        this.processing = false;
        this.processQueue();
        return;
      }

      if (!response.ok) {
        const error = await response.json();
        reject(new Error(error.message || 'API Error'));
      } else {
        resolve(await response.json());
      }
    } catch (error) {
      reject(error);
    } finally {
      this.processing = false;

      // Small delay between requests
      if (this.requestQueue.length > 0) {
        setTimeout(() => this.processQueue(), 100);
      }
    }
  }

  async getMetadata(url, options = {}) {
    const params = {
      url,
      ...options
    };
    return this.request('/metadata', params);
  }

  async getSeoAudit(url) {
    return this.request('/seo-audit', { url });
  }

  async getPerformance(url, options = {}) {
    return this.request('/performance', { url, ...options });
  }

  async captureSnapshot(url, options = {}) {
    const response = await fetch(`${this.baseUrl}/snapshot`, {
      method: 'POST',
      headers: {
        'X-API-Key': this.apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ url, ...options })
    });

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

    return await response.json();
  }
}

// Usage examples
const client = new WebPeekClient('your_api_key_here');

// Get only basic metadata and social tags (smaller payload)
const metadata = await client.getMetadata('https://github.com', {
  fields: 'basic,og,twitter'
});

// Get metadata with mobile user agent
const mobileMetadata = await client.getMetadata('https://stripe.com', {
  device: 'mobile',
  max_images: 10
});

// Perform SEO audit
const seoAudit = await client.getSeoAudit('https://example.com');
console.log('SEO Score:', seoAudit.overall_score);
console.log('Issues:', seoAudit.issues.filter(i => i.severity === 'error'));

// Capture screenshot
const snapshot = await client.captureSnapshot('https://vercel.com', {
  viewport: { width: 1920, height: 1080 },
  format: 'jpeg',
  quality: 90,
  full_page: false
});
console.log('Screenshot URL:', snapshot.url);

Batch Processing URLs

batch-processing.js
async function batchProcessUrls(urls, apiKey) {
  const client = new WebPeekClient(apiKey);
  const results = [];
  const errors = [];

  console.log(`Processing ${urls.length} URLs...`);

  for (let i = 0; i < urls.length; i++) {
    const url = urls[i];

    try {
      const metadata = await client.getMetadata(url, {
        fields: 'basic,og,twitter'
      });

      results.push({
        url,
        title: metadata.title,
        description: metadata.description,
        image: metadata.og?.image,
        success: true
      });

      console.log(`[${i + 1}/${urls.length}] ✓ ${url}`);
    } catch (error) {
      errors.push({ url, error: error.message });
      console.error(`[${i + 1}/${urls.length}] ✗ ${url}: ${error.message}`);
    }

    // Progress update every 10 URLs
    if ((i + 1) % 10 === 0) {
      console.log(`Progress: ${i + 1}/${urls.length} completed`);
    }
  }

  return { results, errors };
}

// Usage
const urls = [
  'https://github.com',
  'https://stripe.com',
  'https://vercel.com',
  'https://openai.com',
  // ... more URLs
];

const { results, errors } = await batchProcessUrls(urls, 'your_api_key_here');

console.log(`\nCompleted: ${results.length} successful, ${errors.length} failed`);

// Save results to JSON
const fs = require('fs');
fs.writeFileSync('results.json', JSON.stringify(results, null, 2));

Python

Basic Implementation

webpeek_client.py
import requests
from typing import Dict, Any, Optional, List
import time

class WebPeekClient:
    """WebPeek API Client for Python"""

    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.webpeek.dev"
        self.session = requests.Session()
        self.session.headers.update({
            "X-API-Key": api_key
        })

    def _request(self, endpoint: str, params: Optional[Dict] = None) -> Dict[str, Any]:
        """Make a GET request to the API"""
        url = f"{self.base_url}{endpoint}"
        response = self.session.get(url, params=params)

        # Check rate limit headers
        remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
        print(f"Rate limit remaining: {remaining}")

        # Handle rate limiting
        if response.status_code == 429:
            retry_after = int(response.headers.get('Retry-After', 60))
            print(f"Rate limited. Waiting {retry_after} seconds...")
            time.sleep(retry_after)
            return self._request(endpoint, params)

        response.raise_for_status()
        return response.json()

    def get_metadata(
        self,
        url: str,
        fields: Optional[str] = None,
        device: Optional[str] = None,
        max_images: Optional[int] = None,
        max_links: Optional[int] = None
    ) -> Dict[str, Any]:
        """Extract metadata from a URL"""
        params = {"url": url}

        if fields:
            params["fields"] = fields
        if device:
            params["device"] = device
        if max_images:
            params["max_images"] = max_images
        if max_links:
            params["max_links"] = max_links

        return self._request("/metadata", params)

    def get_seo_audit(self, url: str) -> Dict[str, Any]:
        """Perform SEO audit on a URL"""
        return self._request("/seo-audit", {"url": url})

    def get_performance(
        self,
        url: str,
        device: Optional[str] = None
    ) -> Dict[str, Any]:
        """Get performance metrics for a URL"""
        params = {"url": url}
        if device:
            params["device"] = device
        return self._request("/performance", params)

    def capture_snapshot(
        self,
        url: str,
        viewport: Optional[Dict[str, int]] = None,
        format: str = "png",
        quality: int = 80,
        full_page: bool = False
    ) -> Dict[str, Any]:
        """Capture a screenshot of a URL"""
        payload = {
            "url": url,
            "format": format,
            "quality": quality,
            "full_page": full_page
        }

        if viewport:
            payload["viewport"] = viewport

        response = self.session.post(
            f"{self.base_url}/snapshot",
            json=payload
        )

        if response.status_code == 429:
            retry_after = int(response.headers.get('Retry-After', 60))
            print(f"Rate limited. Waiting {retry_after} seconds...")
            time.sleep(retry_after)
            return self.capture_snapshot(url, viewport, format, quality, full_page)

        response.raise_for_status()
        return response.json()


# Usage examples
if __name__ == "__main__":
    client = WebPeekClient("your_api_key_here")

    # Get metadata
    metadata = client.get_metadata(
        "https://github.com",
        fields="basic,og,twitter"
    )
    print(f"Title: {metadata['title']}")
    print(f"Description: {metadata['description']}")

    # SEO Audit
    seo = client.get_seo_audit("https://example.com")
    print(f"SEO Score: {seo['overall_score']}")

    # Performance metrics
    perf = client.get_performance("https://stripe.com", device="mobile")
    print(f"Performance Score: {perf['performance_score']}")

    # Screenshot
    snapshot = client.capture_snapshot(
        "https://vercel.com",
        viewport={"width": 1920, "height": 1080},
        format="jpeg",
        quality=90
    )
    print(f"Screenshot URL: {snapshot['url']}")

Async Implementation with asyncio

webpeek_async.py
import asyncio
import aiohttp
from typing import Dict, Any, List, Optional

class AsyncWebPeekClient:
    """Async WebPeek API Client using aiohttp"""

    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.webpeek.dev"
        self.session = None

    async def __aenter__(self):
        self.session = aiohttp.ClientSession(headers={
            "X-API-Key": self.api_key
        })
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        await self.session.close()

    async def _request(
        self,
        endpoint: str,
        params: Optional[Dict] = None
    ) -> Dict[str, Any]:
        """Make async GET request"""
        url = f"{self.base_url}{endpoint}"

        async with self.session.get(url, params=params) as response:
            # Check rate limit
            remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
            print(f"Rate limit remaining: {remaining}")

            # Handle rate limiting
            if response.status == 429:
                retry_after = int(response.headers.get('Retry-After', 60))
                print(f"Rate limited. Waiting {retry_after}s...")
                await asyncio.sleep(retry_after)
                return await self._request(endpoint, params)

            response.raise_for_status()
            return await response.json()

    async def get_metadata(self, url: str, **kwargs) -> Dict[str, Any]:
        """Get metadata asynchronously"""
        params = {"url": url, **kwargs}
        return await self._request("/metadata", params)

    async def batch_get_metadata(
        self,
        urls: List[str],
        **kwargs
    ) -> List[Dict[str, Any]]:
        """Fetch metadata for multiple URLs concurrently"""
        tasks = [self.get_metadata(url, **kwargs) for url in urls]
        return await asyncio.gather(*tasks, return_exceptions=True)


# Usage
async def main():
    urls = [
        "https://github.com",
        "https://stripe.com",
        "https://vercel.com",
        "https://openai.com"
    ]

    async with AsyncWebPeekClient("your_api_key_here") as client:
        # Batch process URLs concurrently
        results = await client.batch_get_metadata(
            urls,
            fields="basic,og,twitter"
        )

        for url, result in zip(urls, results):
            if isinstance(result, Exception):
                print(f"Error for {url}: {result}")
            else:
                print(f"{url}: {result['title']}")

# Run async code
if __name__ == "__main__":
    asyncio.run(main())

PHP

WebPeekClient.php
<?php

class WebPeekClient {
    private $apiKey;
    private $baseUrl = 'https://api.webpeek.dev';

    public function __construct($apiKey) {
        $this->apiKey = $apiKey;
    }

    private function request($endpoint, $params = []) {
        $url = $this->baseUrl . $endpoint;

        if (!empty($params)) {
            $url .= '?' . http_build_query($params);
        }

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'X-API-Key: ' . $this->apiKey
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $headers = curl_getinfo($ch);

        curl_close($ch);

        // Handle rate limiting
        if ($httpCode === 429) {
            $retryAfter = 60; // Default
            // Parse Retry-After from headers if available
            sleep($retryAfter);
            return $this->request($endpoint, $params);
        }

        if ($httpCode !== 200) {
            $error = json_decode($response, true);
            throw new Exception($error['message'] ?? 'API Error');
        }

        return json_decode($response, true);
    }

    public function getMetadata($url, $options = []) {
        $params = array_merge(['url' => $url], $options);
        return $this->request('/metadata', $params);
    }

    public function getSeoAudit($url) {
        return $this->request('/seo-audit', ['url' => $url]);
    }

    public function getPerformance($url, $device = null) {
        $params = ['url' => $url];
        if ($device) {
            $params['device'] = $device;
        }
        return $this->request('/performance', $params);
    }

    public function captureSnapshot($url, $options = []) {
        $endpoint = $this->baseUrl . '/snapshot';
        $payload = array_merge(['url' => $url], $options);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $endpoint);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'X-API-Key: ' . $this->apiKey,
            'Content-Type: application/json'
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode !== 200) {
            $error = json_decode($response, true);
            throw new Exception($error['message'] ?? 'API Error');
        }

        return json_decode($response, true);
    }
}

// Usage
$client = new WebPeekClient('your_api_key_here');

try {
    // Get metadata
    $metadata = $client->getMetadata('https://github.com', [
        'fields' => 'basic,og,twitter'
    ]);

    echo "Title: " . $metadata['title'] . "\n";
    echo "Description: " . $metadata['description'] . "\n";

    // SEO Audit
    $seo = $client->getSeoAudit('https://example.com');
    echo "SEO Score: " . $seo['overall_score'] . "\n";

    // Screenshot
    $snapshot = $client->captureSnapshot('https://vercel.com', [
        'viewport' => ['width' => 1920, 'height' => 1080],
        'format' => 'jpeg',
        'quality' => 90
    ]);

    echo "Screenshot URL: " . $snapshot['url'] . "\n";

} catch (Exception $e) {
    echo "Error: " . $e->getMessage() . "\n";
}

?>

Ruby

webpeek_client.rb
require 'net/http'
require 'json'
require 'uri'

class WebPeekClient
  BASE_URL = 'https://api.webpeek.dev'

  def initialize(api_key)
    @api_key = api_key
  end

  def get_metadata(url, options = {})
    params = { url: url }.merge(options)
    request('/metadata', params)
  end

  def get_seo_audit(url)
    request('/seo-audit', { url: url })
  end

  def get_performance(url, device: nil)
    params = { url: url }
    params[:device] = device if device
    request('/performance', params)
  end

  def capture_snapshot(url, options = {})
    uri = URI.parse("#{BASE_URL}/snapshot")
    payload = { url: url }.merge(options)

    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true

    request = Net::HTTP::Post.new(uri.path)
    request['X-API-Key'] = @api_key
    request['Content-Type'] = 'application/json'
    request.body = payload.to_json

    response = http.request(request)
    handle_response(response)
  end

  private

  def request(endpoint, params = {})
    uri = URI.parse("#{BASE_URL}#{endpoint}")
    uri.query = URI.encode_www_form(params) unless params.empty?

    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true

    request = Net::HTTP::Get.new(uri.request_uri)
    request['X-API-Key'] = @api_key

    response = http.request(request)

    # Handle rate limiting
    if response.code == '429'
      retry_after = (response['Retry-After'] || 60).to_i
      puts "Rate limited. Waiting #{retry_after} seconds..."
      sleep(retry_after)
      return request(endpoint, params)
    end

    handle_response(response)
  end

  def handle_response(response)
    unless response.is_a?(Net::HTTPSuccess)
      error = JSON.parse(response.body)
      raise "API Error: #{error['message']}"
    end

    JSON.parse(response.body)
  end
end

# Usage
client = WebPeekClient.new('your_api_key_here')

begin
  # Get metadata
  metadata = client.get_metadata('https://github.com',
    fields: 'basic,og,twitter'
  )

  puts "Title: #{metadata['title']}"
  puts "Description: #{metadata['description']}"

  # SEO Audit
  seo = client.get_seo_audit('https://example.com')
  puts "SEO Score: #{seo['overall_score']}"

  # Performance
  perf = client.get_performance('https://stripe.com', device: 'mobile')
  puts "Performance Score: #{perf['performance_score']}"

  # Screenshot
  snapshot = client.capture_snapshot('https://vercel.com',
    viewport: { width: 1920, height: 1080 },
    format: 'jpeg',
    quality: 90
  )

  puts "Screenshot URL: #{snapshot['url']}"

rescue => e
  puts "Error: #{e.message}"
end

Go

webpeek_client.go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "net/url"
    "strconv"
    "time"
)

const BaseURL = "https://api.webpeek.dev"

type WebPeekClient struct {
    APIKey     string
    HTTPClient *http.Client
}

type MetadataOptions struct {
    Fields    string
    Device    string
    MaxImages int
    MaxLinks  int
}

func NewClient(apiKey string) *WebPeekClient {
    return &WebPeekClient{
        APIKey: apiKey,
        HTTPClient: &http.Client{
            Timeout: 30 * time.Second,
        },
    }
}

func (c *WebPeekClient) request(method, endpoint string, params url.Values, body interface{}) (map[string]interface{}, error) {
    reqURL := BaseURL + endpoint

    if params != nil {
        reqURL += "?" + params.Encode()
    }

    var reqBody io.Reader
    if body != nil {
        jsonBody, err := json.Marshal(body)
        if err != nil {
            return nil, err
        }
        reqBody = bytes.NewBuffer(jsonBody)
    }

    req, err := http.NewRequest(method, reqURL, reqBody)
    if err != nil {
        return nil, err
    }

    req.Header.Set("X-API-Key", c.APIKey)
    if body != nil {
        req.Header.Set("Content-Type", "application/json")
    }

    resp, err := c.HTTPClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    // Handle rate limiting
    if resp.StatusCode == 429 {
        retryAfter := 60
        if retry := resp.Header.Get("Retry-After"); retry != "" {
            if val, err := strconv.Atoi(retry); err == nil {
                retryAfter = val
            }
        }

        fmt.Printf("Rate limited. Waiting %d seconds...\n", retryAfter)
        time.Sleep(time.Duration(retryAfter) * time.Second)
        return c.request(method, endpoint, params, body)
    }

    // Parse response
    var result map[string]interface{}
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, err
    }

    if resp.StatusCode != 200 {
        if msg, ok := result["message"].(string); ok {
            return nil, fmt.Errorf("API Error: %s", msg)
        }
        return nil, fmt.Errorf("API Error: status %d", resp.StatusCode)
    }

    return result, nil
}

func (c *WebPeekClient) GetMetadata(targetURL string, opts *MetadataOptions) (map[string]interface{}, error) {
    params := url.Values{}
    params.Set("url", targetURL)

    if opts != nil {
        if opts.Fields != "" {
            params.Set("fields", opts.Fields)
        }
        if opts.Device != "" {
            params.Set("device", opts.Device)
        }
        if opts.MaxImages > 0 {
            params.Set("max_images", strconv.Itoa(opts.MaxImages))
        }
        if opts.MaxLinks > 0 {
            params.Set("max_links", strconv.Itoa(opts.MaxLinks))
        }
    }

    return c.request("GET", "/metadata", params, nil)
}

func (c *WebPeekClient) GetSEOAudit(targetURL string) (map[string]interface{}, error) {
    params := url.Values{}
    params.Set("url", targetURL)
    return c.request("GET", "/seo-audit", params, nil)
}

func (c *WebPeekClient) GetPerformance(targetURL, device string) (map[string]interface{}, error) {
    params := url.Values{}
    params.Set("url", targetURL)
    if device != "" {
        params.Set("device", device)
    }
    return c.request("GET", "/performance", params, nil)
}

type SnapshotOptions struct {
    Viewport  map[string]int `json:"viewport,omitempty"`
    Format    string         `json:"format,omitempty"`
    Quality   int            `json:"quality,omitempty"`
    FullPage  bool           `json:"full_page,omitempty"`
}

func (c *WebPeekClient) CaptureSnapshot(targetURL string, opts *SnapshotOptions) (map[string]interface{}, error) {
    body := map[string]interface{}{
        "url": targetURL,
    }

    if opts != nil {
        if opts.Viewport != nil {
            body["viewport"] = opts.Viewport
        }
        if opts.Format != "" {
            body["format"] = opts.Format
        }
        if opts.Quality > 0 {
            body["quality"] = opts.Quality
        }
        body["full_page"] = opts.FullPage
    }

    return c.request("POST", "/snapshot", nil, body)
}

// Usage example
func main() {
    client := NewClient("your_api_key_here")

    // Get metadata
    metadata, err := client.GetMetadata("https://github.com", &MetadataOptions{
        Fields: "basic,og,twitter",
    })
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    fmt.Printf("Title: %v\n", metadata["title"])
    fmt.Printf("Description: %v\n", metadata["description"])

    // SEO Audit
    seo, err := client.GetSEOAudit("https://example.com")
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    fmt.Printf("SEO Score: %v\n", seo["overall_score"])

    // Screenshot
    snapshot, err := client.CaptureSnapshot("https://vercel.com", &SnapshotOptions{
        Viewport: map[string]int{"width": 1920, "height": 1080},
        Format:   "jpeg",
        Quality:  90,
    })
    if err != nil {
        fmt.Printf("Error: %v\n", err)
        return
    }

    fmt.Printf("Screenshot URL: %v\n", snapshot["url"])
}

cURL Examples

Metadata Endpoint

# Basic metadata extraction
curl "https://api.webpeek.dev/metadata?url=https://github.com" \
  -H "X-API-Key: YOUR_API_KEY"

# With field filtering
curl "https://api.webpeek.dev/metadata?url=https://github.com&fields=basic,og,twitter" \
  -H "X-API-Key: YOUR_API_KEY"

# Mobile device metadata
curl "https://api.webpeek.dev/metadata?url=https://stripe.com&device=mobile&max_images=10" \
  -H "X-API-Key: YOUR_API_KEY"

SEO Audit Endpoint

curl "https://api.webpeek.dev/seo-audit?url=https://example.com" \
  -H "X-API-Key: YOUR_API_KEY"

Performance Endpoint

# Desktop performance
curl "https://api.webpeek.dev/performance?url=https://vercel.com" \
  -H "X-API-Key: YOUR_API_KEY"

# Mobile performance
curl "https://api.webpeek.dev/performance?url=https://vercel.com&device=mobile" \
  -H "X-API-Key: YOUR_API_KEY"

Snapshot Endpoint

# Basic screenshot
curl -X POST "https://api.webpeek.dev/snapshot" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://github.com",
    "viewport": {"width": 1920, "height": 1080},
    "format": "png"
  }'

# Full page screenshot with options
curl -X POST "https://api.webpeek.dev/snapshot" \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://stripe.com",
    "viewport": {"width": 1920, "height": 1080},
    "format": "jpeg",
    "quality": 90,
    "full_page": true,
    "block_ads": true,
    "remove_cookie_banners": true
  }'

Common Patterns

Error Handling Best Practices

async function robustApiCall(url, options = {}) {
  const maxRetries = 3;
  let lastError;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      // Handle rate limiting
      if (response.status === 429) {
        const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
        console.log(`Rate limited (attempt ${attempt}). Waiting ${retryAfter}s...`);
        await new Promise(r => setTimeout(r, retryAfter * 1000));
        continue;
      }

      // Handle server errors with exponential backoff
      if (response.status >= 500) {
        const backoff = Math.pow(2, attempt) * 1000;
        console.log(`Server error (attempt ${attempt}). Retrying in ${backoff}ms...`);
        await new Promise(r => setTimeout(r, backoff));
        continue;
      }

      // Handle client errors (don't retry)
      if (!response.ok) {
        const error = await response.json();
        throw new Error(`API Error (${response.status}): ${error.message}`);
      }

      return await response.json();

    } catch (error) {
      lastError = error;

      // Network errors - retry with exponential backoff
      if (attempt < maxRetries) {
        const backoff = Math.pow(2, attempt) * 1000;
        console.log(`Network error (attempt ${attempt}). Retrying in ${backoff}ms...`);
        await new Promise(r => setTimeout(r, backoff));
      }
    }
  }

  throw lastError;
}

Caching Responses

class CachedWebPeekClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.cache = new Map();
    this.cacheTTL = 3600000; // 1 hour
  }

  getCacheKey(endpoint, params) {
    return `${endpoint}:${JSON.stringify(params)}`;
  }

  async getMetadata(url, options = {}) {
    const cacheKey = this.getCacheKey('/metadata', { url, ...options });

    // Check cache
    const cached = this.cache.get(cacheKey);
    if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
      console.log('Cache hit:', cacheKey);
      return cached.data;
    }

    // Fetch fresh data
    console.log('Cache miss:', cacheKey);
    const response = await fetch(
      `https://api.webpeek.dev/metadata?url=${encodeURIComponent(url)}`,
      {
        headers: { 'X-API-Key': this.apiKey }
      }
    );

    if (!response.ok) {
      throw new Error('API Error');
    }

    const data = await response.json();

    // Store in cache
    this.cache.set(cacheKey, {
      data,
      timestamp: Date.now()
    });

    return data;
  }

  clearCache() {
    this.cache.clear();
  }
}

Next Steps