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}"
endGo
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();
}
}