Response Format
WebPeek API responses are returned as JSON with data fields at the root level. This guide explains the response format for each endpoint and how to work with the data effectively.
Response Structure Overview
Unlike many APIs that wrap data in a data object, WebPeek returns endpoint-specific fields directly at the root level. All responses include common fields for caching and processing metadata.
{
// Endpoint-specific data fields (varies by endpoint)
"url": "https://example.com",
"title": "Example Domain",
// ... more endpoint-specific fields
// Caching information (all endpoints)
"cache": {
"cached": true,
"cache_key": "abc123...",
"cached_at": "2025-11-06T19:24:11.97789+00:00",
"ttl_seconds": 85424,
"expires_at": "2025-11-07T19:24:11.977Z"
},
// Processing metadata (all endpoints)
"metadata": {
"processing_time_ms": 245,
"page_load_time_ms": 1234
}
}Common Fields
| Field | Type | Description |
|---|---|---|
| cache | object | Caching information (all endpoints) |
| cache.cached | boolean | Whether this response was served from cache |
| cache.cache_key | string | Cache key for this request |
| cache.ttl_seconds | number | Time-to-live in seconds |
| metadata | object | Processing information |
| metadata.processing_time_ms | number | Total processing time in milliseconds |
Metadata Endpoint Response
The metadata endpoint returns comprehensive page metadata including Open Graph, Twitter Cards, structured data, and more:
{
"url": "https://github.com",
"final_url": "https://github.com/",
"fetched_at": "2025-11-06T19:27:12.487Z",
"title": "GitHub · Change is constant. GitHub keeps you ahead. · GitHub",
"description": "Join the world's most widely adopted, AI-powered developer platform...",
"site_name": "GitHub",
"language": "en",
"charset": "utf-8",
"canonical": {
"href": "https://github.com/",
"is_self_canonical": true,
"resolves": true,
"status": 200
},
"viewport": "width=device-width",
"theme_color": "#1e2327",
"og": {
"title": "GitHub · Change is constant. GitHub keeps you ahead.",
"description": "Join the world's most widely adopted, AI-powered developer platform...",
"image": "https://images.ctfassets.net/8aevphvgewt8/4pe4eOtUJ0ARpZRE4fNekf/f52b1f9c52f059a33170229883731ed0/GH-Homepage-Universe-img.png",
"url": "https://github.com/",
"type": "object",
"site_name": "GitHub"
},
"twitter": {
"card": "summary_large_image",
"title": "GitHub · Change is constant. GitHub keeps you ahead.",
"description": "Join the world's most widely adopted, AI-powered developer platform...",
"image": "https://images.ctfassets.net/8aevphvgewt8/4pe4eOtUJ0ARpZRE4fNekf/f52b1f9c52f059a33170229883731ed0/GH-Homepage-Universe-img.png",
"site": "github"
},
"images": [
{
"url": "https://images.ctfassets.net/8aevphvgewt8/4IfncsgGkGPFESlWXlAWfU/6f671f0ff761cb276c2effced9dca773/eyebrow-banner-duck.png",
"alt": "Duck mascot"
}
],
"links": [
{
"href": "https://github.com/features",
"text": "Features",
"rel": null
}
],
"dominant_color": "#1e2327",
"social_handles": {
"twitter": "github",
"instagram": "github",
"linkedin": "github",
"github": "features"
},
"tech_stack": ["React"],
"http": {
"final_url": "https://github.com/",
"status": 200,
"content_type": "text/html; charset=utf-8",
"response_size_bytes": 565552,
"etag": "W/\"e9244edc36b5ba0c811237312ee5d19a\"",
"cache_control": "max-age=0, private, must-revalidate",
"redirect_chain": []
},
"icons": [
{
"url": "https://github.githubassets.com/favicons/favicon.svg",
"type": "image/svg+xml",
"sizes": null
}
],
"indexability": {
"robots_meta": null,
"x_robots_tag": null,
"robots_txt_allowed": true,
"canonical_valid": true,
"robots_effective": "index, follow"
},
"content": {
"content_type": "documentation",
"word_count": 2421,
"read_time_min": 13,
"heading_counts": {
"h1": 4,
"h2": 10,
"h3": 16,
"h4": 0,
"h5": 0,
"h6": 0
}
},
"truncated": {
"images": false,
"links": true
}
}SEO Audit Endpoint Response
The SEO audit endpoint returns comprehensive SEO analysis with scoring, issues, and recommendations:
{
"schema_version": "2025.11.0",
"url": "https://www.webpeek.dev/",
"scoring": {
"overall": 93,
"grade": "A",
"by_category": {
"metadata": 80,
"content": 100,
"technical": 95,
"accessibility": 100,
"performance": 90
},
"deductions": [
{
"category": "metadata",
"reason": "Title is too long (63 characters)",
"points": 10,
"rule_id": "META_TITLE_TOO_LONG"
}
]
},
"issues": [
{
"id": "meta-title-too-long",
"code": "META_TITLE_TOO_LONG",
"severity": "warning",
"category": "metadata",
"message": "Title tag is 63 characters (recommended: 50-60)",
"recommendation": "Shorten title to 50-60 characters for optimal display in search results",
"impact": "medium",
"priority": 2,
"fixable": true
}
],
"metrics": {
"title_length": 63,
"description_length": 219,
"headings": {
"h1": 1,
"h2": 7,
"h3": 23,
"h4": 3,
"h5": 0,
"h6": 0,
"total": 34
},
"links": {
"internal": 17,
"external": 0,
"broken": 0,
"total": 17
},
"images": {
"total": 5,
"with_alt": 5,
"without_alt": 0
},
"word_count": 544
},
"indexability": {
"robots_meta": null,
"x_robots_tag": null,
"canonical": "https://www.webpeek.dev/",
"robots_txt_status": "allowed",
"status": "indexable"
},
"summary": {
"passed": [
"No critical issues found",
"HTTPS enabled",
"Title tag present",
"Meta description present",
"Viewport meta tag present"
],
"top_fixes": [
{
"id": "meta-title-too-long",
"impact": "medium",
"why_it_matters": "Long titles get truncated in search results",
"quick_fix": "Trim title to ≤60 chars",
"fixable": true
}
],
"critical_count": 0,
"warning_count": 3,
"info_count": 1,
"rules": {
"total": 36,
"passed": 32,
"failed": 4
}
},
"scannedAt": "2025-11-06T19:25:26.426Z"
}Performance Endpoint Response
The performance endpoint returns Core Web Vitals and resource analysis:
{
"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"
}
}Snapshot Endpoint Response
The snapshot endpoint returns screenshot data and metadata:
{
"url": "https://example.com",
"screenshot_url": "https://cdn.webpeek.dev/screenshots/abc123.webp",
"width": 1920,
"height": 1080,
"format": "webp",
"size_bytes": 145678,
"checksum": "7eb6a602f113a8b6640f9925271d49eba914722fc63c2d8e1b3795b5e0dd49be",
"viewport": {
"width": 1920,
"height": 1080,
"deviceScaleFactor": 1
},
"cache_ttl_seconds": 86400,
"created_at": "2025-11-06T19:56:25.284Z",
"cache": {
"cached": false,
"cache_key": "def456...",
"ttl_seconds": 86400
},
"metadata": {
"processing_time_ms": 5615,
"page_load_time_ms": 5548,
"requests_blocked": 12
}
}Error Response Format
When errors occur, the API returns a different structure with error details:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": {
// Additional error context
}
}
}| Field | Type | Description |
|---|---|---|
| success | boolean | Always false for error responses |
| error.code | string | Machine-readable error code |
| error.message | string | Human-readable error description |
| error.details | object? | Additional context about the error |
Working with Responses
Parsing Responses Safely
async function fetchMetadata(url) {
try {
const response = await fetch(
`https://api.webpeek.dev/metadata?url=${encodeURIComponent(url)}`,
{
headers: {
'X-API-Key': process.env.WEBPEEK_API_KEY
}
}
);
// Parse JSON
const data = await response.json();
// Check for error response
if (!response.ok || data.success === false) {
throw new Error(data.error?.message || 'Request failed');
}
// WebPeek returns data at root level
return data;
} catch (error) {
console.error('Failed to fetch metadata:', error);
throw error;
}
}
// Usage with safe access
try {
const data = await fetchMetadata('https://github.com');
// Access fields directly
const title = data.title || data.og?.title || 'Untitled';
const description = data.description || data.og?.description || '';
const image = data.og?.image || data.twitter?.image;
console.log({ title, description, image });
// Check cache status
if (data.cache?.cached) {
console.log('Response served from cache');
}
} catch (error) {
console.error('Error:', error.message);
}TypeScript Interfaces
Use these TypeScript interfaces for type safety:
// Common fields across all responses
interface CacheInfo {
cached: boolean;
cache_key: string;
cached_at?: string;
ttl_seconds: number;
expires_at?: string;
}
interface ProcessingMetadata {
processing_time_ms: number;
page_load_time_ms?: number;
[key: string]: any;
}
// Metadata endpoint response
interface MetadataResponse {
url: string;
final_url?: string;
fetched_at?: string;
title?: string;
description?: string;
site_name?: string;
language?: string;
charset?: string;
canonical?: {
href: string;
is_self_canonical: boolean;
resolves: boolean;
status: number;
};
viewport?: string;
theme_color?: string;
og?: {
title?: string;
description?: string;
image?: string;
url?: string;
type?: string;
site_name?: string;
};
twitter?: {
card?: string;
site?: string;
title?: string;
description?: string;
image?: string;
};
images?: Array<{
url: string;
alt?: string;
width?: number;
height?: number;
}>;
links?: Array<{
href: string;
text?: string;
rel?: string | null;
}>;
dominant_color?: string;
social_handles?: Record<string, string>;
tech_stack?: string[];
content?: {
content_type?: string;
word_count?: number;
read_time_min?: number;
heading_counts?: Record<string, number>;
};
cache?: CacheInfo;
metadata?: ProcessingMetadata;
}
// SEO Audit response
interface SEOAuditResponse {
schema_version: string;
url: string;
scoring: {
overall: number;
grade: string;
by_category: Record<string, number>;
deductions: Array<{
category: string;
reason: string;
points: number;
rule_id: string;
}>;
};
issues: Array<{
id: string;
code: string;
severity: 'critical' | 'warning' | 'info';
category: string;
message: string;
recommendation: string;
impact: string;
priority: number;
fixable: boolean;
}>;
metrics: {
title_length: number;
description_length: number;
headings: Record<string, number>;
links: {
internal: number;
external: number;
broken: number;
total: number;
};
images: {
total: number;
with_alt: number;
without_alt: number;
};
word_count: number;
};
summary: {
passed: string[];
top_fixes: Array<{
id: string;
impact: string;
why_it_matters: string;
quick_fix: string;
fixable: boolean;
}>;
critical_count: number;
warning_count: number;
info_count: number;
};
scannedAt: string;
cache?: CacheInfo;
metadata?: ProcessingMetadata;
}
// Performance response
interface PerformanceResponse {
schema_version: string;
run_id: string;
url: string;
mode: 'lab' | 'field';
environment: {
device_profile: string;
network_profile: string;
collector_version: string;
user_agent: string;
page_context: {
viewport: string;
timezone: string;
language: string;
cookies_enabled: boolean;
};
};
summary: {
overall: string;
warnings: number;
errors: number;
suggestions: number;
main_issue?: string;
};
core_web_vitals: {
lcp: { value_ms: number; rating: string; budget_ms: number; status: string };
inp: { value_ms: number; rating: string; budget_ms: number; status: string };
cls: { value: number; rating: string; budget: number; status: string };
fcp: { value_ms: number; rating: string; budget_ms: number; status: string };
ttfb: { value_ms: number; rating: string; budget_ms: number; status: string };
};
performance_score: {
schema_version: string;
rule_version: string;
value: number;
scale: string;
method: string;
weights: {
lcp: number;
inp: number;
cls: number;
fcp: number;
ttfb: number;
};
};
resources: {
total_requests: number;
transfer_kb: number;
decoded_kb: number;
first_party: {
requests: number;
transfer_kb: number;
decoded_kb: number;
};
third_party: {
requests: number;
transfer_kb: number;
decoded_kb: number;
};
by_type: Record<string, {
count: number;
transfer_kb: number;
decoded_kb: number;
main_thread_ms?: number;
unoptimized?: number;
}>;
top_hosts: Array<{
host: string;
requests: number;
transfer_kb: number;
}>;
caching: {
cacheable_requests: number;
missing_cache_headers: number;
cache_hit_ratio: number;
compression: {
gzip_or_br: number;
uncompressed: number;
estimated_savings_kb: number;
};
};
};
tips: Array<{
tip_id: string;
severity: string;
message: string;
learn_more: string;
}>;
cache?: CacheInfo;
created_at: string;
metadata?: ProcessingMetadata & {
page_load_rating?: string;
};
}
// Snapshot response
interface SnapshotResponse {
url: string;
screenshot_url: string;
width: number;
height: number;
format: string;
size_bytes: number;
checksum: string;
viewport: {
width: number;
height: number;
deviceScaleFactor: number;
};
cache_ttl_seconds: number;
created_at: string;
cache?: CacheInfo;
metadata?: ProcessingMetadata;
}
// Error response
interface ErrorResponse {
success: false;
error: {
code: string;
message: string;
details?: Record<string, any>;
};
}Handling Optional Fields
function normalizeMetadata(data) {
// Provide defaults for all optional fields
return {
url: data.url,
title: data.title || data.og?.title || extractTitleFromUrl(data.url),
description: data.description || data.og?.description || '',
image: data.og?.image || data.twitter?.image || null,
siteName: data.site_name || data.og?.site_name || extractDomain(data.url),
type: data.og?.type || 'website',
language: data.language || 'en',
// Social media metadata
social: {
og: data.og || {},
twitter: data.twitter || {}
},
// Links
canonical: data.canonical?.href || data.url,
// Check if cached
fromCache: data.cache?.cached || false,
processingTime: data.metadata?.processing_time_ms
};
}
function extractDomain(url) {
try {
const hostname = new URL(url).hostname;
return hostname.replace('www.', '');
} catch {
return 'Unknown';
}
}
function extractTitleFromUrl(url) {
try {
const hostname = new URL(url).hostname;
const domain = hostname.replace('www.', '').split('.')[0];
return domain.charAt(0).toUpperCase() + domain.slice(1);
} catch {
return 'Unknown';
}
}
// Usage
const rawData = await fetchMetadata('https://github.com');
const normalized = normalizeMetadata(rawData);
// Now safe to use without checking for undefined
console.log(normalized.title); // Always definedBest Practices
Check HTTP Status First
Always check the HTTP status code before parsing JSON. Error responses include a success: false field.
Use Optional Chaining
Many fields are optional. Use optional chaining (?.) and nullish coalescing (??) to safely access nested properties.
Check Cache Status
Use cache.cached to determine if data is fresh. Cached responses may be stale for rapidly changing content.
Monitor Processing Times
Use metadata.processing_time_ms to track performance and identify slow endpoints or URLs.
Handle Truncated Data
The metadata endpoint may truncate large arrays (images, links). Check the truncated field to know if data was cut off.
Use TypeScript
TypeScript interfaces help catch type errors early and provide autocomplete in your IDE for better developer experience.
Related Documentation
API Endpoints
Detailed documentation for each endpoint and their specific response formats.
View Endpoints →