Snapi
Documentation développeur

API Documentation

URL de base : https://yhfdghkh6544.com/v1. Tous les endpoints utilisent HTTP standard. Les paramètres peuvent être transmis dans la chaîne de requête (GET) ou dans le corps JSON (POST).

Démarrage rapide

  1. Inscrivez-vous gratuitement, puis créez une API Key sur la page « API Key » de la console.
  2. Placez votre API Key dans l’en-tête X-Access-Key, puis appelez l’endpoint de capture :
curl "https://yhfdghkh6544.com/v1/screenshot?url=https://example.com&full_page=true" \
  -H "X-Access-Key: $SNAPI_KEY" \
  -o screenshot.png

Authentification

Chaque requête nécessite une API Key, à transmettre de l’une des façons suivantes :

  • En-tête X-Access-Key: snp_… (recommandé pour les appels côté serveur)
  • Paramètre de requête access_key=snp_…
  • URL signée (pour intégrer l’URL dans une page web sans exposer le secret)

L’API Key n’est affichée en clair qu’une seule fois, lors de sa création. Conservez-la en lieu sûr. En cas de fuite, vous pouvez la désactiver dans la console.

API de capture

GET/v1/screenshot
POST/v1/screenshot

Paramètres propres aux captures :

ParamètreTypePar défautDescription
formatenumpngpng, jpeg ou webp.
device_scale_factornumber1Facteur d’échelle, 1–3. La valeur 2 offre une netteté Retina, pour un fichier environ 4 fois plus volumineux.
qualityinteger80Qualité JPEG / WebP, 1–100.
full_pagebooleanfalseCapture la page entière. Fait défiler la page écran par écran pour déclencher le chargement différé, jusqu’à une hauteur de 20,000 pixels.
selectorstring—Capture uniquement le premier élément correspondant à ce sélecteur CSS. Incompatible avec full_page.

Paramètres communs aux captures et aux PDF :

ParamètreTypePar défautDescription
urlstring—URL de la page à capturer, en http ou https. Utilisez ce paramètre ou html, mais pas les deux. Le fragment # de l’URL est conservé : vous pouvez ainsi capturer directement une page d’une application monopage utilisant le routage par fragment, comme https://app.com/#/report/1.
htmlstring—HTML fourni directement (2 MB maximum). Uniquement en POST.
viewport_widthinteger1280Largeur de la fenêtre d’affichage, 320–3840. En dessous de 768, le rendu se fait en mode mobile.
viewport_heightinteger800Hauteur de la fenêtre d’affichage, 200–3840.
wait_untilenumnetworkidleStratégie d’attente : domcontentloaded (la plus rapide, adaptée aux pages statiques), load, networkidle (suit toutes les requêtes dès l’ouverture de la page et ne lance la capture qu’après 500 millisecondes sans requête en cours une fois la page chargée, avec une attente supplémentaire de 8 secondes maximum ; adaptée aux applications monopages).
wait_for_selectorstring—Après le chargement de la page, attend qu’un élément correspondant à ce sélecteur CSS soit présent et visible avant de lancer la capture, pendant 10 secondes maximum. Sinon, renvoie selector_not_found (non facturé). Adapté aux applications monopages dont le contenu s’affiche après un délai ou une animation, sans requête réseau, comme .chart-ready.
delayinteger0Délai supplémentaire en millisecondes après la fin de l’attente, 0–10000. Utile pour les animations ou les composants chargés à la demande.
block_adsbooleantrueBloque les requêtes vers les domaines publicitaires et de suivi.
block_cookie_bannersbooleantrueMasque les fenêtres de consentement aux cookies.
dark_modebooleanfalseÉmule prefers-color-scheme: dark.
hide_selectorsstring—Liste de sélecteurs CSS des éléments à masquer, par exemple .ad,#popup.
cachebooleantrueIndique s’il faut consulter le cache. La valeur false force un nouveau rendu, dont le résultat est tout de même mis en cache.
cache_ttlinteger86400Durée de validité du cache en secondes, 60–2592000.
response_typeenumbinarybinary renvoie directement le fichier ; json renvoie l’URL du fichier et les métadonnées.

Capturer une application monopage (React, Vue, etc.) :Le JavaScript de la page s’exécute dans un véritable navigateur avant la capture. Par défaut, networkidle attend la fin du chargement des données. Si le contenu s’affiche ensuite après un délai ou une animation, ajoutez wait_for_selector pour désigner un élément indiquant que le contenu est prêt. Si aucun élément ne convient, utilisez delay pour prolonger l’attente.

API PDF

GET/v1/pdf
POST/v1/pdf

Chaque PDF consomme 2 unités du quota. Avant l’impression, la page est parcourue pour déclencher le chargement différé. En plus des paramètres communs, les paramètres suivants sont disponibles :

ParamètreTypePar défautDescription
paperenuma4Format du papier : a4 ou letter. Si la page définit une règle @page, celle-ci est prioritaire.
print_backgroundbooleantrueImprime les couleurs et les images d’arrière-plan.
pdf_mediaenumprintprint utilise les styles d’impression ; screen utilise les styles d’affichage à l’écran (rendu identique à l’écran).
Convertir du HTML en PDF
curl -X POST "https://yhfdghkh6544.com/v1/pdf" \
  -H "X-Access-Key: $SNAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Rapport trimestriel</h1><p>Contenu…</p>", "paper": "a4"}' \
  -o report.pdf

Réponses

Par défaut (response_type=binary), le fichier est renvoyé directement, avec le Content-Type correspondant au format. En-têtes de réponse :

  • X-Request-Id : identifiant de la requête, à fournir pour le dépannage
  • X-Snapi-Cache : HIT ou MISS
  • X-Snapi-Truncated: true : la capture de la page entière a été tronquée au-delà de 20,000 pixels
  • X-Snapi-Format-Changed: webp : un PNG de plus de 5 MB est automatiquement converti en WebP (sauf si format=png est explicitement spécifié)

Avec response_type=json, la réponse contient l’URL du fichier et les métadonnées :

Réponse JSON
curl -X POST "https://yhfdghkh6544.com/v1/screenshot" \
  -H "X-Access-Key: $SNAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com", "response_type": "json"}'

# Renvoie
{
  "url": "https://yhfdghkh6544.com/i/3f/3f9c….png",
  "cache_hit": false,
  "duration_ms": 1284,
  "format": "png",
  "width": 1280,
  "height": 800,
  "request_id": "req_01j…"
}

URL signées

Vous souhaitez intégrer l’URL d’une capture directement dans <img src>, sans exposer votre API Key ? Calculez la signature sur votre serveur avec le secret associé à votre API Key (disponible sur la page API Key de la console) :

Règles de signature
canonical = Triez tous les paramètres, sauf signature, par ordre lexicographique de leur nom,
            formatez chaque entrée en k=urlencode(v) (RFC 3986, espaces encodés en %20), puis reliez-les avec &
signature = hex(HMAC-SHA256(secret, canonical))

Une signature valide suffit à authentifier la requête. Toute modification d’un paramètre (par exemple url) invalide la signature. Le paramètre facultatif expires (horodatage Unix en secondes) est inclus dans la signature ; une fois expiré, la requête renvoie signature_expired. Pour actualiser le cache, modifiez un paramètre sans effet (comme v=2), puis recalculez la signature. Les URL signées ne prennent en charge que GET.

import crypto from "node:crypto";

// Encodage RFC 3986 (espaces encodés en %20), identique à celui utilisé côté serveur
const enc = (v) =>
  encodeURIComponent(v).replace(/[!'()*]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase());

function signedUrl(params, accessKey, secret) {
  const all = { ...params, access_key: accessKey };
  const canonical = Object.keys(all).sort().map((k) => `${k}=${enc(String(all[k]))}`).join("&");
  const signature = crypto.createHmac("sha256", secret).update(canonical).digest("hex");
  return `https://yhfdghkh6544.com/v1/screenshot?${canonical}&signature=${signature}`;
}

const src = signedUrl({ url: "https://example.com", viewport_width: 1200 }, KEY, SECRET);
// <img src={src} alt="Aperçu de la page web" />

Cache

Les requêtes dont les paramètres sont identiques utilisent le cache (un paramètre omis équivaut à sa valeur par défaut explicite ; les paramètres d’authentification et de cache sont exclus de la comparaison). En cas de résultat en cache, le mode binaire renvoie une redirection 302 vers l’URL du fichier, avec Cache-Control ; le navigateur suit automatiquement la redirection et met le fichier en cache. Le mode JSON renvoie cache_hit: true.

Les résultats servis depuis le cache ne sont pas facturés.Les requêtes simultanées avec les mêmes paramètres ne déclenchent qu’un seul rendu.

Limites et facturation

  • Seules les requêtes réussies (2xx) non servies depuis le cache sont facturées : 1 unité pour une capture, 2 pour un PDF.
  • Les délais dépassés, échecs de rendu, erreurs du site cible, paramètres invalides et échecs d’authentification ne consomment pas de quota.
  • Le quota est réinitialisé chaque mois civil (UTC). L’offre gratuite inclut 100 unités par mois ; une fois le quota épuisé, les requêtes renvoient quota_exceeded.
  • Tout dépassement du nombre de requêtes par seconde ou de requêtes simultanées renvoie 429. L’en-tête Retry-After indique le délai d’attente conseillé en secondes.
  • Durée maximale de rendu : 30 secondes ; volume téléchargé par page : 50 MB maximum ; taille du fichier de sortie : 50 MB maximum.

Consultez les limites de chaque offre : Tarifs.

Codes d’erreur

Toutes les réponses d’erreur sont au format JSON :

Format des erreurs
{
  "error": {
    "code": "render_failed",
    "message": "target responded with an error status",
    "upstream_status": 404,
    "request_id": "req_01j…"
  }
}
HTTPcodeSignificationFacturé
400invalid_paramsParamètre manquant ou hors limites ; message précise lequelNon
400html_too_largehtml dépasse 2 MBNon
401unauthorizedAPI Key manquante, invalide ou désactivéeNon
401signature_invalidSignature incorrecte (paramètres modifiés ou secret incorrect)Non
401signature_expiredL’URL signée a dépassé la date d’expiration expiresNon
403url_blockedAccès interdit à l’URL cible (par exemple une adresse réseau privée ou localhost)Non
408render_timeoutLe rendu a dépassé 30 secondesNon
422render_failedLe site cible a renvoyé une erreur 4xx/5xx, une erreur DNS ou une erreur de certificat ; inclut upstream_statusNon
422selector_not_foundAucun élément ne correspond à selector, ou le délai d’attente de wait_for_selector est dépasséNon
422too_many_redirectsPlus de 5 redirectionsNon
429rate_limitedLimite de requêtes par seconde dépassée ; réessayez en suivant Retry-AfterNon
429concurrency_exceededLimite de requêtes simultanées dépasséeNon
429quota_exceededQuota mensuel épuisé (offre gratuite)Non
429too_many_batchesPlus de 5 lots en coursNon
500internalErreur serveur ; contactez-nous en joignant request_idNon

Lots asynchrones

POST/v1/batch
GET/v1/batch/{id}

Envoyez de 1 à 100 URL en une requête. L’endpoint renvoie immédiatement un identifiant de tâche (HTTP 202), puis les captures sont réalisées une par une en arrière-plan. Idéal pour les captures par lots sans garder la connexion ouverte. Vous pouvez interroger régulièrement l’endpoint de suivi ou renseigner webhook_url pour recevoir une notification une fois le traitement terminé. L’authentification est identique à celle des endpoints synchrones (uniquement par en-tête ou paramètre access_key, sans prise en charge des URL signées).

ParamètreTypePar défautDescription
typeenumscreenshotscreenshot (capture d’écran) ou pdf.
urlsstring[]—URL à traiter, de 1 à 100. Si l’une d’elles est invalide, la requête entière renvoie 400 ; message indique sa position.
optionsobject{}Paramètres communs à toutes les URL, identiques à ceux des endpoints synchrones (par exemple full_page, format, viewport_width, cache_ttl). Ne peut pas contenir url, html ni response_type.
webhook_urlstring—Facultatif. URL à notifier une fois le traitement terminé. Voir « Notification de fin ».
Envoyer un lot
curl -X POST "https://yhfdghkh6544.com/v1/batch" \
  -H "X-Access-Key: $SNAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "screenshot",
    "urls": ["https://example.com", "https://example.org"],
    "options": { "full_page": true, "format": "webp" },
    "webhook_url": "https://your-app.com/hooks/snapi"
  }'

# Renvoie 202
{
  "id": "bat_01j…",
  "type": "screenshot",
  "status": "queued",
  "total": 2, "succeeded": 0, "failed": 0,
  "created_at": "2026-10-18T09:30:00Z", "completed_at": null,
  "webhook": { "url": "https://your-app.com/hooks/snapi", "status": "none", "attempts": 0 },
  "items": [
    { "index": 0, "url": "https://example.com", "status": "pending", "result": null, "error": null },
    { "index": 1, "url": "https://example.org", "status": "pending", "result": null, "error": null }
  ]
}

Suivi de la progression : le statut status du lot passe par queued (en attente), running (en cours), puis completed (traitement terminé, avec ou sans erreurs). Chaque URL a pour statut pending, running, succeeded ou failed. En cas de réussite, result contient l’URL du fichier ; en cas d’échec, error contient le code d’erreur (identique à ceux des endpoints synchrones).

Consulter un lot
curl "https://yhfdghkh6544.com/v1/batch/bat_01j…" -H "X-Access-Key: $SNAPI_KEY"

{
  "id": "bat_01j…",
  "status": "completed",
  "total": 2, "succeeded": 1, "failed": 1,
  "completed_at": "2026-10-18T09:30:12Z",
  "items": [
    {
      "index": 0, "url": "https://example.com", "status": "succeeded", "duration_ms": 1830,
      "result": { "url": "https://yhfdghkh6544.com/i/3f/3f9c….webp", "mime": "image/webp", "width": 1280, "height": 2410, "cache_hit": false },
      "error": null
    },
    {
      "index": 1, "url": "https://example.org", "status": "failed", "duration_ms": 640,
      "result": null,
      "error": { "code": "render_failed", "message": "target responded with an error status" }
    }
  ]
}
  • La facturation est identique à celle des endpoints synchrones : chaque URL traitée avec succès consomme 1 unité pour une capture ou 2 pour un PDF. Les résultats en cache et les échecs ne consomment pas de quota.
  • Si le quota est déjà épuisé lors de l’envoi, la requête renvoie immédiatement quota_exceeded. S’il s’épuise pendant le traitement, les URL restantes échouent avec quota_exceeded.
  • Chaque compte peut avoir au maximum 5 lots en cours. Au-delà, la requête renvoie too_many_batches.
  • La durée de validité des fichiers est définie par options.cache_ttl (1 jour par défaut). Téléchargez-les et enregistrez-les avant leur expiration.
  • Dans les résultats de consultation et les notifications, webhook.url n’affiche que le chemin, sans la chaîne de requête.

Notification de fin

Si vous renseignez webhook_url, nous y envoyons une requête POST une fois le lot entièrement traité. Le corps JSON est { "event": "batch.completed", "batch": { … } }, où batch est strictement identique à la réponse de l’endpoint de suivi. En-têtes :

  • X-Snapi-Signature: t=1760779812,v1=5d3f… : t est l’heure d’envoi (horodatage Unix en secondes) et v1 la signature
  • X-Snapi-Event: batch.completed, X-Snapi-Delivery-Attempt: 1 (numéro de la tentative d’envoi)
Règles de signature
v1 = hex(HMAC-SHA256(secret, t + "." + Corps brut de la requête))
secret est le secret de signature associé à l’API Key utilisée pour soumettre la tâche (disponible sur la page API Key de la console).
  • Renvoyez une réponse 2xx sous 10 secondes, sinon l’envoi est considéré comme ayant échoué. En cas d’échec, trois nouvelles tentatives sont effectuées après 10 secondes, 1 minute et 5 minutes (4 tentatives au maximum au total).
  • Dans de très rares cas, vous pouvez recevoir plusieurs notifications pour une même tâche. Dédupliquez-les à l’aide de batch.id.
  • Pour vérifier la signature, utilisez les octets bruts reçus et assurez-vous que t ne diffère pas de l’heure actuelle de plus de quelques minutes afin d’éviter les attaques par rejeu.
  • L’URL de callback doit être une adresse http(s) accessible publiquement et ne doit pas pointer vers un réseau privé.
import crypto from "node:crypto";

// rawBody doit contenir les octets bruts reçus, sans passer par JSON.parse puis une nouvelle sérialisation.
function verifySnapiWebhook(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false; // Protection contre le rejeu
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  const got = Buffer.from(parts.v1 ?? "");
  return got.length === expected.length && crypto.timingSafeEqual(Buffer.from(expected), got);
}

// verifySnapiWebhook(req.rawBody, req.headers["x-snapi-signature"], SNAPI_SECRET)

Suivi de l’utilisation

GET/v1/usage
shell
curl "https://yhfdghkh6544.com/v1/usage" -H "X-Access-Key: $SNAPI_KEY"

{ "month": "202610", "used": 37, "limit": 100, "remaining": 63, "overage": 0, "resets_at": "2026-11-01T00:00:00Z", ... }

État du service

GET/v1/health

Sans authentification. Renvoie l’état du nœud et du pool de navigateurs (nombre total, occupés et disponibles).