Snapi
Documentación para desarrolladores

API Documentación

URL base: https://yhfdghkh6544.com/v1. Todos los endpoints usan HTTP estándar. Puedes enviar los parámetros en la cadena de consulta (GET) o en el cuerpo JSON (POST).

Inicio rápido

  1. Regístrate gratis y crea una API Key en la página «API Key» de la consola.
  2. Incluye la API Key en la cabecera X-Access-Key y llama al endpoint de capturas:
curl "https://yhfdghkh6544.com/v1/screenshot?url=https://example.com&full_page=true" \
  -H "X-Access-Key: $SNAPI_KEY" \
  -o screenshot.png

Autenticación

Todas las peticiones requieren una API Key. Puedes enviarla de cualquiera de estas formas:

  • Cabecera X-Access-Key: snp_… (recomendada para llamadas desde el servidor)
  • Parámetro de consulta access_key=snp_…
  • URL firmada (para incluir la URL en una página web sin exponer el secreto)

La API Key solo se muestra en texto plano una vez, al crearla. Guárdala en un lugar seguro. Si se filtra, puedes desactivarla en la consola.

Endpoint de capturas

GET/v1/screenshot
POST/v1/screenshot

Parámetros exclusivos de las capturas:

ParámetroTipoValor predeterminadoDescripción
formatenumpngpng, jpeg o webp.
device_scale_factornumber1Factor de escala, 1–3. Con 2 se obtiene nitidez Retina y un tamaño de archivo aproximadamente 4 veces mayor.
qualityinteger80Calidad JPEG / WebP, 1–100.
full_pagebooleanfalseCaptura la página completa. Se desplaza por ella para activar la carga diferida, con un límite de altura de 20,000 píxeles.
selectorstring—Captura solo el primer elemento que coincida con este selector CSS. No se puede combinar con full_page.

Parámetros comunes a capturas y PDF:

ParámetroTipoValor predeterminadoDescripción
urlstring—URL de la página que quieres capturar, con http o https. Debes indicar este parámetro o html, pero no ambos. Se conserva el fragmento # de la URL, por lo que puedes capturar directamente páginas de aplicaciones de página única con rutas hash, como https://app.com/#/report/1.
htmlstring—HTML enviado directamente (máximo 2 MB). Solo admite POST.
viewport_widthinteger1280Ancho del área de visualización, 320–3840. Con valores inferiores a 768, se renderiza como en un dispositivo móvil.
viewport_heightinteger800Alto del área de visualización, 200–3840.
wait_untilenumnetworkidleEstrategia de espera: domcontentloaded (la más rápida, ideal para páginas estáticas), load o networkidle (supervisa todas las peticiones desde que se abre la página; tras la carga, espera hasta que no haya peticiones en curso durante 500 milisegundos, con un máximo de 8 segundos adicionales; ideal para aplicaciones de página única).
wait_for_selectorstring—Tras cargar la página, espera hasta 10 segundos a que aparezca y sea visible un elemento que coincida con este selector CSS antes de capturar. Si no aparece, devuelve selector_not_found (sin coste). Ideal para aplicaciones de página única que renderizan contenido con temporizadores o animaciones, sin depender de peticiones de red, como .chart-ready.
delayinteger0Espera adicional en milisegundos una vez completada la espera inicial, 0–10000. Útil para animaciones o componentes con carga diferida.
block_adsbooleantrueBloquea las peticiones a dominios de publicidad y seguimiento.
block_cookie_bannersbooleantrueOculta los avisos de consentimiento de cookies.
dark_modebooleanfalseEmula prefers-color-scheme: dark.
hide_selectorsstring—Lista de selectores CSS de los elementos que quieres ocultar, por ejemplo, .ad,#popup.
cachebooleantrueIndica si se consulta la caché. Con false, fuerza un nuevo renderizado, pero el resultado se sigue guardando en caché.
cache_ttlinteger86400Tiempo de validez de la caché en segundos, 60–2592000.
response_typeenumbinarybinary devuelve el archivo directamente; json devuelve la URL del archivo y sus metadatos.

Capturar aplicaciones de página única (React, Vue, etc.):La página ejecuta JavaScript en un navegador real antes de capturarse. La opción predeterminada, networkidle, espera a que termine la descarga de datos. Si el contenido se renderiza después mediante temporizadores o animaciones, añade wait_for_selector para indicar un elemento que confirme que está listo. Si no hay un elemento adecuado, usa delay para esperar un poco más.

Endpoint de PDF

GET/v1/pdf
POST/v1/pdf

Cada PDF consume 2 créditos. Antes de imprimir, se recorre la página para activar el contenido con carga diferida. Además de los parámetros comunes, admite:

ParámetroTipoValor predeterminadoDescripción
paperenuma4Formato de papel: a4 o letter. Si la página incluye una regla @page, se da prioridad a sus ajustes.
print_backgroundbooleantrueImprime los colores y las imágenes de fondo.
pdf_mediaenumprintprint usa los estilos de impresión; screen usa los estilos de pantalla (tal como se ve en el navegador).
De HTML a PDF
curl -X POST "https://yhfdghkh6544.com/v1/pdf" \
  -H "X-Access-Key: $SNAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Informe trimestral</h1><p>Contenido…</p>", "paper": "a4"}' \
  -o report.pdf

Respuestas

Por defecto (response_type=binary), se devuelve el archivo directamente, con Content-Type correspondiente a su formato. Cabeceras de respuesta:

  • X-Request-Id: ID de esta petición; inclúyelo al comunicar un problema
  • X-Snapi-Cache: HIT o MISS
  • X-Snapi-Truncated: true: la página completa se ha recortado por superar los 20,000 píxeles
  • X-Snapi-Format-Changed: webp: el PNG supera los 5 MB y se ha convertido automáticamente a WebP (no se convierte si se especifica format=png de forma explícita)

Con response_type=json, se devuelven la URL del archivo y sus metadatos:

Respuesta 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"}'

# Devuelve
{
  "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 firmadas

¿Quieres incluir la URL de la captura directamente en <img src> sin exponer tu API Key? Calcula la firma en tu servidor con el secreto asociado a la API Key (puedes consultarlo en la página API Key de la consola):

Reglas de firma
canonical = Ordena todos los parámetros, salvo signature, por nombre en orden lexicográfico.
            Codifica cada par k=urlencode(v) según RFC 3986 (los espacios como %20) y únelos con &
signature = hex(HMAC-SHA256(secret, canonical))

Una firma válida autentica la petición. Si se modifica cualquier parámetro (por ejemplo, url), la firma deja de ser válida. El parámetro opcional expires (segundos Unix) se incluye en la firma; una vez caducado, se devuelve signature_expired. Para actualizar la caché, cambia un parámetro sin efecto (como v=2) y vuelve a firmar. Las URL firmadas solo admiten GET.

import crypto from "node:crypto";

// Codificación RFC 3986 (espacios como %20); debe coincidir con las reglas del servidor
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="Vista previa de la página web" />

Caché

Las peticiones con los mismos parámetros usan la caché (omitir un parámetro equivale a enviar su valor predeterminado; los parámetros de autenticación y caché no se comparan). Si hay un resultado en caché, el modo binario devuelve una redirección 302 a la URL del archivo junto con Cache-Control; el navegador sigue la redirección y guarda el archivo en caché automáticamente. El modo JSON devuelve cache_hit: true.

Las respuestas desde caché no se facturan.Las peticiones simultáneas con los mismos parámetros solo generan un renderizado.

Límites y facturación

  • Solo se facturan las peticiones correctas (2xx) que no usan la caché: 1 crédito por captura y 2 por PDF.
  • Los tiempos de espera agotados, los fallos de renderizado, los errores del sitio de destino, los parámetros incorrectos y los fallos de autenticación no consumen créditos.
  • Los créditos se renuevan cada mes natural (UTC). El plan gratuito incluye 100 créditos al mes; al agotarlos, se devuelve quota_exceeded.
  • Si se supera el límite de peticiones por segundo o de concurrencia, se devuelve 429. La cabecera Retry-After indica cuántos segundos se recomienda esperar.
  • Tiempo máximo de renderizado: 30 segundos; descarga máxima por página: 50 MB; tamaño máximo del archivo de salida: 50 MB.

Consulta los límites de cada plan en Precios.

Códigos de error

Todas las respuestas de error usan JSON:

Formato de error
{
  "error": {
    "code": "render_failed",
    "message": "target responded with an error status",
    "upstream_status": 404,
    "request_id": "req_01j…"
  }
}
HTTPcodeSignificadoSe factura
400invalid_paramsFalta un parámetro o su valor está fuera de rango; message indica cuálNo
400html_too_largehtml supera los 2 MBNo
401unauthorizedFalta la API Key, no es válida o está desactivadaNo
401signature_invalidLa firma no coincide (se han modificado los parámetros o el secreto es incorrecto)No
401signature_expiredLa URL firmada ha caducado según expiresNo
403url_blockedNo se permite acceder a la URL de destino (por ejemplo, direcciones de red interna o localhost)No
408render_timeoutEl renderizado ha superado los 30 segundosNo
422render_failedEl sitio de destino devuelve 4xx/5xx, falla el DNS o hay un error de certificado; incluye upstream_statusNo
422selector_not_foundselector no coincide con ningún elemento o se ha agotado el tiempo de espera de wait_for_selectorNo
422too_many_redirectsSe han superado las 5 redireccionesNo
429rate_limitedSe ha superado el límite de peticiones por segundo; vuelve a intentarlo según Retry-AfterNo
429concurrency_exceededSe ha superado el límite de peticiones simultáneasNo
429quota_exceededSe han agotado los créditos del mes (plan gratuito)No
429too_many_batchesHay más de 5 tareas por lotes sin finalizar a la vezNo
500internalError del servidor; contacta con nosotros e incluye request_idNo

Lotes asíncronos

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

Envía entre 1 y 100 URL en una sola petición. El endpoint devuelve de inmediato un ID de tarea (HTTP 202) y las capturas se procesan una a una en segundo plano. Es ideal para capturar páginas por lotes sin mantener la conexión abierta. Puedes consultar el estado periódicamente o indicar webhook_url para recibir una notificación cuando todo haya terminado. La autenticación es la misma que en el endpoint síncrono (solo mediante cabecera o el parámetro access_key; no admite URL firmadas).

ParámetroTipoValor predeterminadoDescripción
typeenumscreenshotscreenshot (captura) o pdf.
urlsstring[]—URL que se van a procesar, entre 1 y 100. Si alguna no es válida, toda la petición devuelve 400 y message indica su posición.
optionsobject{}Parámetros comunes a todas las URL, iguales a los del endpoint síncrono (como full_page, format, viewport_width y cache_ttl). No puede incluir url, html ni response_type.
webhook_urlstring—Opcional. URL a la que se enviará una notificación cuando todo haya terminado. Consulta «Notificación al finalizar».
Enviar una tarea por lotes
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"
  }'

# Devuelve 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 }
  ]
}

Consulta del progreso: el estado de la tarea, status, pasa por queued (en cola), running (en curso) y completed (todo finalizado, con éxito o con errores). El estado de cada URL es pending, running, succeeded o failed. Si se procesa correctamente, result contiene la URL del archivo; si falla, error contiene el código de error (los mismos que en el endpoint síncrono).

Consultar una tarea por lotes
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" }
    }
  ]
}
  • Se aplican las mismas reglas de facturación que en el endpoint síncrono: cada URL procesada correctamente consume 1 crédito por captura o 2 por PDF. Los resultados en caché y los fallos no consumen créditos.
  • Si ya no quedan créditos al enviar la tarea, se devuelve quota_exceeded directamente. Si se agotan durante el procesamiento, las URL restantes fallan con quota_exceeded.
  • Cada cuenta puede tener como máximo 5 tareas por lotes sin finalizar a la vez. Si se supera el límite, se devuelve too_many_batches.
  • La validez de los archivos resultantes depende de options.cache_ttl (por defecto, 1 día). Descárgalos y guárdalos antes de que caduquen.
  • En los resultados de consulta y las notificaciones, webhook.url solo se muestra hasta la ruta, sin la cadena de consulta.

Notificación al finalizar

Si indicas webhook_url, enviaremos una petición POST a esa URL cuando finalice toda la tarea. El cuerpo es JSON: { "event": "batch.completed", "batch": { … } }, donde batch es idéntico a la respuesta del endpoint de consulta. Cabeceras:

  • X-Snapi-Signature: t=1760779812,v1=5d3f…: t es la hora de envío (segundos Unix) y v1 es la firma
  • X-Snapi-Event: batch.completed, X-Snapi-Delivery-Attempt: 1 (número de intento de entrega)
Reglas de firma
v1 = hex(HMAC-SHA256(secret, t + "." + Cuerpo original de la solicitud))
secret es el secreto de firma de la API Key utilizada al enviar la tarea (disponible en la página API Key de la consola).
  • Devuelve una respuesta 2xx en un plazo de 10 segundos; de lo contrario, se considerará un fallo. En caso de fallo, se reintentará al cabo de 10 segundos, 1 minuto y 5 minutos (hasta 4 intentos en total).
  • En casos excepcionales, puedes recibir notificaciones duplicadas de una misma tarea. Usa batch.id para descartar los duplicados.
  • Para verificar la firma, calcúlala a partir de los bytes originales recibidos y comprueba que la diferencia entre t y la hora actual no supere unos minutos para evitar ataques de repetición.
  • La URL de callback debe ser una dirección http(s) accesible públicamente y no puede apuntar a una red interna.
import crypto from "node:crypto";

// rawBody debe contener los bytes originales recibidos, sin aplicar JSON.parse y volver a serializar
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; // Protección contra ataques de repetición
  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)

Consulta de uso

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", ... }

Comprobación de estado

GET/v1/health

No requiere autenticación. Devuelve el estado del nodo y del pool de navegadores (total, ocupados y libres).