API documentation
A single endpoint, explicit parameters. Authentication by API key (token).
Endpoint
GET https://webshotninja.com/api?token=YOUR_API_KEY&url=https://example.com
Add your API key as the token parameter. You'll find it in your dashboard.
Parameters
| Parameter | Default | Description |
|---|---|---|
token | — | Your API key (required). |
url | — | The URL of the page to capture, URL-encoded (required). |
width | 1600 | Browser width in pixels (100–3840). |
height | 1200 | Browser height in pixels (100–4320). |
full | 0 | 1 = capture the entire page height. |
format | webp | png, jpeg or webp. |
quality | 80 | jpeg/webp quality (1–100). |
delay | 0 | Wait in milliseconds after page load (max 10000). Useful for sites with intro animations. |
nocookies | 1 | 1 = dismiss/hide cookie banners (default). 0 = capture the page as-is. |
scroll | auto | 1 = force lazy-loading auto-scroll, 0 = disable. Default: automatic when full=1. |
nocache | 0 | 1 = force a fresh capture (skip the cache). Paid plans only. |
selector | — | CSS selector to capture a specific element (e.g. #hero, .pricing-table). The element is awaited up to 10s. Returns 404 if not found. Overrides full if both are set. |
noads | 0 | 1 = block ads and trackers (powered by EasyList). Can be combined with nocookies. |
css | — | Custom CSS injected into the page before capture (max 10 KB). API only, not available on the demo. |
js | — | Custom JavaScript executed in the page context before capture (max 10 KB). API only. If the script throws, the capture still completes and the error is returned in the X-WSN-JS-Error header. |
dark | 0 | 1 = enable dark mode (prefers-color-scheme: dark). Only works if the target site supports native dark mode. |
device | — | Device preset: iphone14, iphone14pro, pixel7, ipad, galaxys23. Sets viewport, user-agent and touch emulation. Explicit width/height override the device viewport. |
scale | 1 | Device scale factor (1, 2 or 3). scale=2 produces a retina image (2x resolution, heavier file). |
Code examples
Basic screenshot
curl "https://webshotninja.com/api?token=YOUR_API_KEY&url=https%3A%2F%2Fexample.com&full=1&format=webp" \
-o screenshot.webp
const params = new URLSearchParams({
token: 'YOUR_API_KEY',
url: 'https://example.com',
full: '1',
format: 'webp',
});
const res = await fetch(`https://webshotninja.com/api?${params}`);
const fs = require('fs');
fs.writeFileSync('screenshot.webp', Buffer.from(await res.arrayBuffer()));
import requests
r = requests.get('https://webshotninja.com/api', params={
'token': 'YOUR_API_KEY',
'url': 'https://example.com',
'full': '1',
'format': 'webp',
})
with open('screenshot.webp', 'wb') as f:
f.write(r.content)
$params = http_build_query([
'token' => 'YOUR_API_KEY',
'url' => 'https://example.com',
'full' => '1',
'format' => 'webp',
]);
file_put_contents('screenshot.webp', file_get_contents('https://webshotninja.com/api?' . $params));
require 'net/http'
require 'uri'
uri = URI('https://webshotninja.com/api')
uri.query = URI.encode_www_form(
token: 'YOUR_API_KEY',
url: 'https://example.com',
full: '1',
format: 'webp'
)
File.binwrite('screenshot.webp', Net::HTTP.get(uri))
package main
import (
"io"
"net/http"
"net/url"
"os"
)
func main() {
params := url.Values{
"token": {"YOUR_API_KEY"},
"url": {"https://example.com"},
"full": {"1"},
"format": {"webp"},
}
resp, _ := http.Get("https://webshotninja.com/api?" + params.Encode())
defer resp.Body.Close()
f, _ := os.Create("screenshot.webp")
io.Copy(f, resp.Body)
}
Mobile device + dark mode
curl "https://webshotninja.com/api?token=YOUR_API_KEY&url=https%3A%2F%2Fexample.com&device=iphone14&dark=1&scale=2" \
-o mobile-dark.webp
const res = await fetch(`https://webshotninja.com/api?` + new URLSearchParams({
token: 'YOUR_API_KEY',
url: 'https://example.com',
device: 'iphone14',
dark: '1',
scale: '2',
}));
fs.writeFileSync('mobile-dark.webp', Buffer.from(await res.arrayBuffer()));
r = requests.get('https://webshotninja.com/api', params={
'token': 'YOUR_API_KEY',
'url': 'https://example.com',
'device': 'iphone14',
'dark': '1',
'scale': '2',
})
with open('mobile-dark.webp', 'wb') as f:
f.write(r.content)
$params = http_build_query([
'token' => 'YOUR_API_KEY',
'url' => 'https://example.com',
'device' => 'iphone14',
'dark' => '1',
'scale' => '2',
]);
file_put_contents('mobile-dark.webp', file_get_contents('https://webshotninja.com/api?' . $params));
uri = URI('https://webshotninja.com/api')
uri.query = URI.encode_www_form(token: 'YOUR_API_KEY', url: 'https://example.com',
device: 'iphone14', dark: '1', scale: '2')
File.binwrite('mobile-dark.webp', Net::HTTP.get(uri))
params := url.Values{"token": {"YOUR_API_KEY"}, "url": {"https://example.com"},
"device": {"iphone14"}, "dark": {"1"}, "scale": {"2"}}
resp, _ := http.Get("https://webshotninja.com/api?" + params.Encode())
defer resp.Body.Close()
f, _ := os.Create("mobile-dark.webp")
io.Copy(f, resp.Body)
CSS selector capture
curl "https://webshotninja.com/api?token=YOUR_API_KEY&url=https%3A%2F%2Fexample.com&selector=%23hero&format=png" \
-o hero.png
const res = await fetch(`https://webshotninja.com/api?` + new URLSearchParams({
token: 'YOUR_API_KEY',
url: 'https://example.com',
selector: '#hero',
format: 'png',
}));
fs.writeFileSync('hero.png', Buffer.from(await res.arrayBuffer()));
r = requests.get('https://webshotninja.com/api', params={
'token': 'YOUR_API_KEY',
'url': 'https://example.com',
'selector': '#hero',
'format': 'png',
})
with open('hero.png', 'wb') as f:
f.write(r.content)
$params = http_build_query([
'token' => 'YOUR_API_KEY',
'url' => 'https://example.com',
'selector' => '#hero',
'format' => 'png',
]);
file_put_contents('hero.png', file_get_contents('https://webshotninja.com/api?' . $params));
uri = URI('https://webshotninja.com/api')
uri.query = URI.encode_www_form(token: 'YOUR_API_KEY', url: 'https://example.com',
selector: '#hero', format: 'png')
File.binwrite('hero.png', Net::HTTP.get(uri))
params := url.Values{"token": {"YOUR_API_KEY"}, "url": {"https://example.com"},
"selector": {"#hero"}, "format": {"png"}}
resp, _ := http.Get("https://webshotninja.com/api?" + params.Encode())
defer resp.Body.Close()
f, _ := os.Create("hero.png")
io.Copy(f, resp.Body)
Signed URLs
Embed capture URLs in your frontend (e.g. img src) without exposing your API key. Sign the sorted query string (all params except sig) with HMAC-SHA256 using your signing secret. Add user (your user ID), expires (Unix timestamp) and sig (the hex signature). The capture is served and counted against your quota. Your signing secret is in your dashboard.
GET https://webshotninja.com/api?url=https://example.com&user=USER_ID&expires=TIMESTAMP&sig=HMAC_HEX
# Generate signed URL with openssl
PARAMS="expires=$(( $(date +%s) + 3600 ))&url=https%3A%2F%2Fexample.com&user=YOUR_USER_ID"
SIG=$(echo -n "$PARAMS" | openssl dgst -sha256 -hmac "YOUR_SIGNING_SECRET" | cut -d' ' -f2)
curl "https://webshotninja.com/api?${PARAMS}&sig=${SIG}" -o signed.webp
const crypto = require('crypto');
const params = new URLSearchParams({
url: 'https://example.com',
user: 'YOUR_USER_ID',
expires: String(Math.floor(Date.now() / 1000) + 3600),
});
const sorted = [...params].sort((a, b) => a[0].localeCompare(b[0]))
.map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v)).join('&');
const sig = crypto.createHmac('sha256', 'YOUR_SIGNING_SECRET')
.update(sorted).digest('hex');
const url = `https://webshotninja.com/api?${sorted}&sig=${sig}`;
// Use in <img src> — no API key exposed
import hmac, hashlib, time
from urllib.parse import urlencode
params = sorted({
'url': 'https://example.com',
'user': 'YOUR_USER_ID',
'expires': str(int(time.time()) + 3600),
}.items())
qs = urlencode(params)
sig = hmac.new(b'YOUR_SIGNING_SECRET', qs.encode(), hashlib.sha256).hexdigest()
url = f'https://webshotninja.com/api?{qs}&sig={sig}'
$params = [
'url' => 'https://example.com',
'user' => 'YOUR_USER_ID',
'expires' => (string)(time() + 3600),
];
ksort($params);
$qs = implode('&', array_map(
fn($k, $v) => rawurlencode($k) . '=' . rawurlencode($v),
array_keys($params), $params
));
$sig = hash_hmac('sha256', $qs, 'YOUR_SIGNING_SECRET');
$url = 'https://webshotninja.com/api?' . $qs . '&sig=' . $sig;
require 'openssl'
require 'uri'
params = {
'expires' => (Time.now.to_i + 3600).to_s,
'url' => 'https://example.com',
'user' => 'YOUR_USER_ID',
}.sort.map { |k, v| "#{URI.encode_www_form_component(k)}=#{URI.encode_www_form_component(v)}" }.join('&')
sig = OpenSSL::HMAC.hexdigest('sha256', 'YOUR_SIGNING_SECRET', params)
url = "https://webshotninja.com/api?#{params}&sig=#{sig}"
import (
"crypto/hmac"
"crypto/sha256"
"fmt"
"net/url"
"sort"
"time"
)
params := url.Values{
"url": {"https://example.com"},
"user": {"YOUR_USER_ID"},
"expires": {fmt.Sprint(time.Now().Unix() + 3600)},
}
// Sort and encode
keys := make([]string, 0)
for k := range params { keys = append(keys, k) }
sort.Strings(keys)
// ... build sorted query string, HMAC-SHA256 sign
HTML rendering
POST /api/render — render raw HTML to an image. Send a JSON body with a html field (max 2 MB) and your token. All screenshot parameters (width, height, format, css, js, selector, dark, device, scale…) are accepted. Ideal for generating Open Graph / social card images. API only, counts toward your quota.
POST https://webshotninja.com/api/render
Content-Type: application/json
curl -X POST https://webshotninja.com/api/render \
-H "Content-Type: application/json" \
-d '{"token":"YOUR_API_KEY","html":"<h1>Hello</h1><p>Social card</p>","width":1200,"height":630,"format":"png"}' \
-o card.png
const res = await fetch('https://webshotninja.com/api/render', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: 'YOUR_API_KEY',
html: '<h1>Hello</h1><p>Social card</p>',
width: 1200, height: 630, format: 'png',
}),
});
fs.writeFileSync('card.png', Buffer.from(await res.arrayBuffer()));
r = requests.post('https://webshotninja.com/api/render', json={
'token': 'YOUR_API_KEY',
'html': '<h1>Hello</h1><p>Social card</p>',
'width': 1200, 'height': 630, 'format': 'png',
})
with open('card.png', 'wb') as f:
f.write(r.content)
$ch = curl_init('https://webshotninja.com/api/render');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'token' => 'YOUR_API_KEY',
'html' => '<h1>Hello</h1><p>Social card</p>',
'width' => 1200, 'height' => 630, 'format' => 'png',
]),
]);
file_put_contents('card.png', curl_exec($ch));
require 'net/http'
require 'json'
uri = URI('https://webshotninja.com/api/render')
req = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
req.body = { token: 'YOUR_API_KEY', html: '<h1>Hello</h1>',
width: 1200, height: 630, format: 'png' }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |h| h.request(req) }
File.binwrite('card.png', res.body)
body, _ := json.Marshal(map[string]interface{}{
"token": "YOUR_API_KEY",
"html": "<h1>Hello</h1>",
"width": 1200, "height": 630, "format": "png",
})
resp, _ := http.Post("https://webshotninja.com/api/render", "application/json", bytes.NewReader(body))
defer resp.Body.Close()
f, _ := os.Create("card.png")
io.Copy(f, resp.Body)
Queue status
Check the current capture queue before sending a request.
GET https://webshotninja.com/api/queue
// Response
{ "running": 1, "queued": 3, "maxConcurrent": 2 }
Every API response also includes these headers:
| Header | Description |
|---|---|
X-Queue-Size | Requests waiting in queue |
X-Queue-Running | Captures currently in progress |
X-Cache | HIT (cached) or MISS (fresh capture) |
X-Quota-Used | Captures used this month |
X-Quota-Limit | Monthly quota limit |
X-Render-Time | Capture duration (e.g. 3200ms) |
Response
The raw image (Content-Type image/png, image/jpeg or image/webp). On error, a JSON object with an error field. The X-Cache header reads HIT (cached) or MISS (fresh capture).