Documentation de l'API
Une seule route, des paramètres explicites. Authentification par clé d'API (token).
Point d'entrée
GET https://webshotninja.com/api?token=YOUR_API_KEY&url=https://example.com
Ajoutez votre clé d'API au paramètre token. Vous la trouverez dans votre tableau de bord.
Paramètres
| Paramètre | Défaut | Description |
|---|---|---|
token | — | Votre clé d'API (obligatoire). |
url | — | L'URL de la page à capturer, encodée (obligatoire). |
width | 1600 | Largeur du navigateur en pixels (100–3840). |
height | 1200 | Hauteur du navigateur en pixels (100–4320). |
full | 0 | 1 = capture de toute la hauteur de la page. |
format | webp | png, jpeg ou webp. |
quality | 80 | Qualité jpeg/webp (1–100). |
delay | 0 | Attente en millisecondes après le chargement (max 10000). Utile pour les sites avec animations d'intro. |
nocookies | 1 | 1 = fermer/masquer les bannières cookies (défaut). 0 = capturer la page telle quelle. |
scroll | auto | 1 = forcer l'auto-scroll lazy-loading, 0 = désactiver. Par défaut : automatique si full=1. |
nocache | 0 | 1 = forcer une capture fraîche (ignorer le cache). Plans payants uniquement. |
selector | — | Sélecteur CSS pour capturer un élément précis (ex : #hero, .pricing-table). L'élément est attendu jusqu'à 10s. Renvoie 404 s'il est introuvable. Prioritaire sur full si les deux sont passés. |
noads | 0 | 1 = bloquer les publicités et trackers (basé sur EasyList). Compatible avec nocookies. |
css | — | CSS personnalisé injecté dans la page avant la capture (max 10 Ko). Réservé à l'API, indisponible sur la démo. |
js | — | JavaScript personnalisé exécuté dans le contexte de la page avant la capture (max 10 Ko). Réservé à l'API. Si le script échoue, la capture est quand même réalisée et l'erreur est renvoyée dans le header X-WSN-JS-Error. |
dark | 0 | 1 = activer le mode sombre (prefers-color-scheme: dark). Ne fonctionne que si le site cible supporte nativement le dark mode. |
device | — | Preset d'appareil : iphone14, iphone14pro, pixel7, ipad, galaxys23. Définit le viewport, le user-agent et l'émulation tactile. Les paramètres width/height explicites surchargent le viewport du preset. |
scale | 1 | Facteur d'échelle (1, 2 ou 3). scale=2 produit une image retina (résolution 2x, fichier plus lourd). |
Exemples de code
Capture simple
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)
}
Appareil mobile + mode sombre
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)
Capture par sélecteur CSS
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)
URLs signées
Intégrez des URLs de capture dans votre frontend (ex : img src) sans exposer votre clé API. Signez la query string triée (tous les paramètres sauf sig) avec HMAC-SHA256 en utilisant votre secret de signature. Ajoutez user (votre ID), expires (timestamp Unix) et sig (la signature hex). La capture est servie et décomptée de votre quota. Votre secret est dans votre tableau de bord.
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
Rendu HTML
POST /api/render — transformer du HTML brut en image. Envoyez un body JSON avec un champ html (max 2 Mo) et votre token. Tous les paramètres de capture (width, height, format, css, js, selector, dark, device, scale…) sont acceptés. Idéal pour générer des images Open Graph / social cards. Réservé à l'API, décompté du 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)
File d'attente
Vérifiez l'état de la file d'attente avant d'envoyer une requête.
GET https://webshotninja.com/api/queue
// Response
{ "running": 1, "queued": 3, "maxConcurrent": 2 }
Chaque réponse de l'API inclut également ces en-têtes :
| 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) |
Réponse
L'image brute (Content-Type image/png, image/jpeg ou image/webp). En cas d'erreur, un objet JSON avec un champ error. L'en-tête X-Cache indique HIT (cache) ou MISS (capture fraîche).