Code examples

Copy-paste snippets for the 5 most common languages.

cURL

curl https://websitescreenshotapi.net/api/v1/screenshot \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com","full_page":true,"block_ads":true}'

PHP

<?php

$ch = curl_init('https://websitescreenshotapi.net/api/v1/screenshot');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . getenv('SCREENSHOTAPI_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode([
        'url'       => 'https://example.com',
        'full_page' => true,
    ]),
    CURLOPT_TIMEOUT        => 60,
]);
$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

$data = json_decode($body, true);
if ($status === 200) {
    echo "OK: {$data['url']} (expires {$data['expires_at']})\n";
} else {
    echo "ERROR [{$data['error']}]: {$data['message']}\n";
}

Python (requests)

import os
import requests

API_KEY = os.environ["SCREENSHOTAPI_KEY"]

r = requests.post(
    "https://websitescreenshotapi.net/api/v1/screenshot",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={"url": "https://example.com", "full_page": True, "block_ads": True},
    timeout=60,
)
data = r.json()

if r.ok:
    print(f"OK: {data['url']} (expires {data['expires_at']})")
else:
    print(f"ERROR [{data['error']}]: {data['message']}")

Python with retries (production-ready)

import time
from requests import Session
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = Session()
session.headers.update({"Authorization": f"Bearer {API_KEY}"})
session.mount("https://", HTTPAdapter(max_retries=Retry(
    total=3, backoff_factor=1, status_forcelist=[502, 503, 504],
)))

def screenshot(url, **opts):
    body = {"url": url, **opts}
    r = session.post("https://websitescreenshotapi.net/api/v1/screenshot", json=body, timeout=60)
    if r.status_code == 429:
        time.sleep(int(r.headers.get("Retry-After", 5)))
        return screenshot(url, **opts)
    r.raise_for_status()
    return r.json()

Node.js (fetch / native)

const API_KEY = process.env.SCREENSHOTAPI_KEY;

async function screenshot(url, opts = {}) {
  const res = await fetch('https://websitescreenshotapi.net/api/v1/screenshot', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type':  'application/json',
    },
    body: JSON.stringify({ url, ...opts }),
  });

  if (res.status === 429) {
    const retry = parseInt(res.headers.get('Retry-After') || '5', 10);
    await new Promise(r => setTimeout(r, retry * 1000));
    return screenshot(url, opts);
  }
  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(`${err.error}: ${err.message}`);
  }
  return res.json();
}

const data = await screenshot('https://example.com', { full_page: true });
console.log(data.url, data.size_bytes, 'bytes');

Ruby

require 'net/http'
require 'json'

API_KEY = ENV.fetch('SCREENSHOTAPI_KEY')

uri = URI('https://websitescreenshotapi.net/api/v1/screenshot')
req = Net::HTTP::Post.new(uri, {
  'Authorization' => "Bearer #{API_KEY}",
  'Content-Type'  => 'application/json',
})
req.body = { url: 'https://example.com', full_page: true }.to_json

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)

if res.code == '200'
  puts "OK: #{data['url']}"
else
  puts "ERROR [#{data['error']}]: #{data['message']}"
end

Go

package main

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

type Resp struct {
    URL              string `json:"url"`
    ExpiresAt        string `json:"expires_at"`
    Format           string `json:"format"`
    SizeBytes        int    `json:"size_bytes"`
    Width            int    `json:"width"`
    Height           int    `json:"height"`
    DurationMs       int    `json:"duration_ms"`
    CreditsRemaining int    `json:"credits_remaining"`
}

func screenshot(url string) (*Resp, error) {
    body, _ := json.Marshal(map[string]any{
        "url":       url,
        "full_page": true,
    })
    req, _ := http.NewRequest("POST", "https://websitescreenshotapi.net/api/v1/screenshot", bytes.NewReader(body))
    req.Header.Set("Authorization", "Bearer "+os.Getenv("SCREENSHOTAPI_KEY"))
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{Timeout: 60 * time.Second}
    res, err := client.Do(req)
    if err != nil { return nil, err }
    defer res.Body.Close()

    raw, _ := io.ReadAll(res.Body)
    if res.StatusCode != 200 {
        return nil, fmt.Errorf("HTTP %d: %s", res.StatusCode, raw)
    }
    var v Resp
    json.Unmarshal(raw, &v)
    return &v, nil
}

func main() {
    v, err := screenshot("https://example.com")
    if err != nil { panic(err) }
    fmt.Printf("%+v\n", v)
}

Async batch (Python)

Submit ≤ 500 URLs at once and poll or wait for the webhook:

import requests, os, time

API_KEY = os.environ["SCREENSHOTAPI_KEY"]

r = requests.post(
    "https://websitescreenshotapi.net/api/v1/bulk",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "urls":           ["https://example.com", "https://github.com", "https://stackoverflow.com"],
        "format":         "png",
        "block_ads":      True,
        "viewport_width": 1280,
        "webhook_url":    "https://yoursite.com/wh/batch",
        "webhook_secret": "shared-secret-32-chars",
    },
    timeout=10,
)
batch_id = r.json()["batch_id"]
print(f"Batch queued: {batch_id}")

# Poll until done
while True:
    s = requests.get(
        f"https://websitescreenshotapi.net/api/v1/batch/{batch_id}?include=results",
        headers={"Authorization": f"Bearer {API_KEY}"},
    ).json()
    if s["status"] in ("completed", "failed"):
        break
    print(f"  {s['processed']}/{s['total']} done")
    time.sleep(2)

for r in s["results"]:
    print(f"  {r['input_url']} → {r['url']}")

Webhook signature verification

When you supply webhook_secret, we sign callbacks with HMAC-SHA256.

import hmac, hashlib

def verify_signature(raw_body: bytes, sig_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig_header)
import crypto from 'node:crypto';

export function verify(rawBody, sigHeader, secret) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret)
                                     .update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(sigHeader), Buffer.from(expected));
}

The signature is in the X-ScreenshotAPI-Signature header. The body is the raw POST body — don't re-serialize.

More