Documentation de l'API

Une seule route, des paramètres explicites. Authentification par clé d'API (token).

Langage :

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ètreDéfautDescription
tokenVotre clé d'API (obligatoire).
urlL'URL de la page à capturer, encodée (obligatoire).
width1600Largeur du navigateur en pixels (100–3840).
height1200Hauteur du navigateur en pixels (100–4320).
full01 = capture de toute la hauteur de la page.
formatwebppng, jpeg ou webp.
quality80Qualité jpeg/webp (1–100).
delay0Attente en millisecondes après le chargement (max 10000). Utile pour les sites avec animations d'intro.
nocookies11 = fermer/masquer les bannières cookies (défaut). 0 = capturer la page telle quelle.
scrollauto1 = forcer l'auto-scroll lazy-loading, 0 = désactiver. Par défaut : automatique si full=1.
nocache01 = forcer une capture fraîche (ignorer le cache). Plans payants uniquement.
selectorSé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.
noads01 = bloquer les publicités et trackers (basé sur EasyList). Compatible avec nocookies.
cssCSS personnalisé injecté dans la page avant la capture (max 10 Ko). Réservé à l'API, indisponible sur la démo.
jsJavaScript 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.
dark01 = activer le mode sombre (prefers-color-scheme: dark). Ne fonctionne que si le site cible supporte nativement le dark mode.
devicePreset 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.
scale1Facteur 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 :

HeaderDescription
X-Queue-SizeRequests waiting in queue
X-Queue-RunningCaptures currently in progress
X-CacheHIT (cached) or MISS (fresh capture)
X-Quota-UsedCaptures used this month
X-Quota-LimitMonthly quota limit
X-Render-TimeCapture 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).