Snapi
Documentação para programadores

API Documentação

URL base: https://yhfdghkh6544.com/v1. Todos os endpoints usam HTTP padrão. Os parâmetros podem ser enviados na cadeia de consulta (GET) ou no corpo JSON (POST).

Início rápido

  1. Registe-se gratuitamente e crie uma API Key na página «API Key» da consola.
  2. Inclua a API Key no cabeçalho X-Access-Key e faça um pedido ao endpoint de captura:
curl "https://yhfdghkh6544.com/v1/screenshot?url=https://example.com&full_page=true" \
  -H "X-Access-Key: $SNAPI_KEY" \
  -o screenshot.png

Autenticação

Todos os pedidos exigem uma API Key, que pode ser enviada de uma das seguintes formas:

  • Cabeçalho X-Access-Key: snp_… (recomendado para pedidos a partir do servidor)
  • Parâmetro de consulta access_key=snp_…
  • URL assinado (para incluir o endereço numa página sem expor o segredo)

A API Key só é apresentada em texto simples uma vez, no momento da criação. Guarde-a em segurança. Se for exposta, pode desativá-la na consola.

Capturas de ecrã

GET/v1/screenshot
POST/v1/screenshot

Parâmetros exclusivos das capturas de ecrã:

ParâmetroTipoValor predefinidoDescrição
formatenumpngpng, jpeg ou webp.
device_scale_factornumber1Fator de escala de 1–3. O valor 2 oferece nitidez Retina, com um ficheiro cerca de 4 vezes maior.
qualityinteger80Qualidade JPEG / WebP, 1–100.
full_pagebooleanfalseCaptura a página inteira. Percorre a página ecrã a ecrã para ativar o carregamento diferido, até uma altura máxima de 20,000 píxeis.
selectorstring—Captura apenas o primeiro elemento correspondente a este seletor CSS. Não pode ser usado com full_page.

Parâmetros comuns às capturas de ecrã e aos PDF:

ParâmetroTipoValor predefinidoDescrição
urlstring—Endereço da página a capturar, com http ou https. Use este parâmetro ou html, não ambos. O fragmento # do URL é preservado, permitindo capturar diretamente a página correspondente em aplicações de página única com encaminhamento por hash (como https://app.com/#/report/1).
htmlstring—Envie HTML diretamente (máximo de 2 MB). Apenas POST.
viewport_widthinteger1280Largura da área de visualização, 320–3840. Abaixo de 768, a página é renderizada como num dispositivo móvel.
viewport_heightinteger800Altura da área de visualização, 200–3840.
wait_untilenumnetworkidleEstratégia de espera: domcontentloaded (a mais rápida, ideal para páginas estáticas), load ou networkidle (acompanha todos os pedidos desde a abertura da página e, após o carregamento, só captura quando não houver pedidos em curso durante 500 milissegundos consecutivos; espera até mais 8 segundos, ideal para aplicações de página única).
wait_for_selectorstring—Após carregar a página, espera que um elemento correspondente a este seletor CSS apareça e fique visível antes de capturar, até 10 segundos. Se não aparecer, devolve selector_not_found (sem cobrança). Ideal para aplicações de página única que renderizam conteúdo com atraso através de temporizadores ou animações, sem depender de pedidos de rede, como .chart-ready.
delayinteger0Atraso adicional em milissegundos após a espera, 0–10000. Útil para aguardar animações ou componentes com carregamento diferido.
block_adsbooleantrueBloqueia pedidos a domínios de publicidade e rastreamento.
block_cookie_bannersbooleantrueOculta as janelas de consentimento de cookies.
dark_modebooleanfalseSimula prefers-color-scheme: dark.
hide_selectorsstring—Lista de seletores CSS dos elementos a ocultar, por exemplo .ad,#popup.
cachebooleantrueDefine se deve procurar na cache. Com false, força uma nova renderização, mas o resultado continua a ser guardado na cache.
cache_ttlinteger86400Prazo de validade da cache em segundos, 60–2592000.
response_typeenumbinarybinary devolve o ficheiro diretamente; json devolve o endereço do ficheiro e os metadados.

Capturar aplicações de página única (React, Vue, etc.):A página é capturada num navegador real, após a execução do JavaScript. A opção predefinida networkidle espera que a página termine de obter os dados. Se o conteúdo só for renderizado depois, através de temporizadores ou animações, use wait_for_selector para indicar um elemento que sinalize que o conteúdo está pronto. Se não houver um elemento adequado, use delay para esperar mais um pouco.

PDF

GET/v1/pdf
POST/v1/pdf

Cada PDF consome 2 unidades da quota. Antes da impressão, a página é percorrida para ativar o carregamento diferido. Além dos parâmetros comuns, são suportados:

ParâmetroTipoValor predefinidoDescrição
paperenuma4Formato do papel: a4 ou letter. Se a página definir uma regra @page, essa configuração tem prioridade.
print_backgroundbooleantrueImprime cores e imagens de fundo.
pdf_mediaenumprintprint usa os estilos de impressão; screen usa os estilos de ecrã (o resultado corresponde ao que vê).
Converter HTML em PDF
curl -X POST "https://yhfdghkh6544.com/v1/pdf" \
  -H "X-Access-Key: $SNAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Relatório trimestral</h1><p>Corpo do texto…</p>", "paper": "a4"}' \
  -o report.pdf

Respostas

Por predefinição (response_type=binary), o ficheiro é devolvido diretamente, com Content-Type correspondente ao formato. Cabeçalhos de resposta:

  • X-Request-Id: ID do pedido, a indicar ao comunicar um problema
  • X-Snapi-Cache: HIT ou MISS
  • X-Snapi-Truncated: true: a página inteira foi cortada por exceder 20,000 píxeis
  • X-Snapi-Format-Changed: webp: PNG convertido automaticamente em WebP por exceder 5 MB (não há conversão se format=png for especificado explicitamente)

Com response_type=json, são devolvidos o endereço do ficheiro e os metadados:

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

# Devolve
{
  "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 assinados

Quer incluir o endereço da captura diretamente em <img src> sem expor a API Key? Calcule a assinatura no seu servidor com o segredo associado à API Key (disponível na página API Key da consola):

Regras de assinatura
canonical = Ordene todos os parâmetros, exceto signature, por ordem lexicográfica dos nomes,
            formate cada entrada como k=urlencode(v) (RFC 3986, com os espaços codificados como %20) e una-as com &
signature = hex(HMAC-SHA256(secret, canonical))

Uma assinatura válida autentica o pedido. Qualquer alteração a um parâmetro (por exemplo, a url) invalida a assinatura. O parâmetro opcional expires (segundos Unix) é incluído na assinatura; após a expiração, é devolvido signature_expired. Para atualizar a cache, altere um parâmetro sem efeito (como v=2) e volte a assinar. Os URL assinados só suportam GET.

import crypto from "node:crypto";

// Codificação RFC 3986 (espaços como %20), de acordo com as regras do 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="Pré-visualização da página web" />

Cache

Os pedidos com parâmetros idênticos usam a cache (omitir um parâmetro equivale a enviar explicitamente o valor predefinido; os parâmetros de autenticação e de cache não entram na comparação). Quando há um resultado em cache, o modo binário devolve um redirecionamento 302 para o endereço do ficheiro, com Cache-Control; o navegador segue o redirecionamento e guarda o ficheiro em cache automaticamente. O modo JSON devolve cache_hit: true.

Os resultados obtidos da cache não são faturados.Pedidos simultâneos com os mesmos parâmetros geram apenas uma renderização.

Limites e faturação

  • Só são faturados os pedidos bem-sucedidos (2xx) que não usem a cache: 1 unidade por captura de ecrã e 2 por PDF.
  • Os tempos limite excedidos, as falhas de renderização, os erros do site de destino, os parâmetros inválidos e as falhas de autenticação não consomem quota.
  • A quota é reposta no início de cada mês (UTC). O plano gratuito inclui 100 unidades por mês; após esgotá-las, é devolvido quota_exceeded.
  • Exceder o limite de pedidos por segundo ou de pedidos simultâneos devolve 429. O cabeçalho de resposta Retry-After indica o tempo de espera recomendado em segundos.
  • Tempo máximo de renderização: 30 segundos; volume máximo de transferência por página: 50 MB; tamanho máximo do ficheiro gerado: 50 MB.

Consulte os limites de cada plano: Preços.

Códigos de erro

Todas as respostas de erro usam JSON:

Formato dos erros
{
  "error": {
    "code": "render_failed",
    "message": "target responded with an error status",
    "upstream_status": 404,
    "request_id": "req_01j…"
  }
}
HTTPcodeSignificadoFaturado
400invalid_paramsParâmetro em falta ou fora do intervalo permitido; message indica qualNão
400html_too_largehtml excede 2 MBNão
401unauthorizedAPI Key em falta, inválida ou desativadaNão
401signature_invalidA assinatura não corresponde (parâmetros alterados ou segredo incorreto)Não
401signature_expiredO prazo expires do URL assinado já expirouNão
403url_blockedO acesso ao endereço de destino não é permitido (por exemplo, endereços de rede privada ou localhost)Não
408render_timeoutA renderização excedeu 30 segundosNão
422render_failedO site de destino devolveu 4xx/5xx, houve uma falha de DNS ou um erro de certificado; inclui upstream_statusNão
422selector_not_foundselector não corresponde a nenhum elemento ou a espera por wait_for_selector excedeu o tempo limiteNão
422too_many_redirectsMais de 5 redirecionamentosNão
429rate_limitedLimite de pedidos por segundo excedido; tente novamente de acordo com Retry-AfterNão
429concurrency_exceededLimite de pedidos simultâneos excedidoNão
429quota_exceededQuota mensal esgotada (plano gratuito)Não
429too_many_batchesMais de 5 tarefas em lote por concluir em simultâneoNão
500internalErro do servidor; contacte-nos e indique request_idNão

Lotes assíncronos

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

Envie 1–100 URL de uma só vez. O endpoint devolve imediatamente um ID de tarefa (HTTP 202) e as capturas são processadas uma a uma em segundo plano. Ideal para capturas em lote sem manter a ligação aberta. Pode consultar periodicamente o estado ou definir webhook_url para receber uma notificação quando tudo estiver concluído. A autenticação é igual à do endpoint síncrono (apenas por cabeçalho ou pelo parâmetro access_key; não suporta URL assinados).

ParâmetroTipoValor predefinidoDescrição
typeenumscreenshotscreenshot (captura de ecrã) ou pdf.
urlsstring[]—URL a processar, de 1–100. Se algum for inválido, o pedido inteiro devolve 400 e message indica a posição do URL.
optionsobject{}Parâmetros comuns a todos os URL, iguais aos do endpoint síncrono (como full_page, format, viewport_width, cache_ttl). Não podem incluir url, html ou response_type.
webhook_urlstring—Opcional. Endereço a notificar quando tudo estiver concluído. Consulte «Notificação de conclusão».
Submeter tarefa em lote
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"
  }'

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

Consultar o progresso: o estado da tarefa, status, passa por queued (em fila), running (em processamento) e completed (tudo concluído, com ou sem sucesso). O estado de cada URL é pending, running, succeeded ou failed. Em caso de sucesso, result contém o endereço do ficheiro; em caso de falha, error contém o código de erro (igual ao do endpoint síncrono).

Consultar tarefa em lote
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" }
    }
  ]
}
  • Aplicam-se as mesmas regras de faturação do endpoint síncrono: cada URL processado com sucesso consome 1 unidade por captura de ecrã ou 2 por PDF. Os resultados da cache e as falhas não consomem quota.
  • Se a quota já estiver esgotada ao submeter o pedido, é devolvido quota_exceeded de imediato. Se se esgotar durante o processamento, os URL restantes falham com quota_exceeded.
  • Cada conta pode ter até 5 tarefas em lote por concluir em simultâneo. Acima desse limite, é devolvido too_many_batches.
  • A validade dos ficheiros gerados é definida por options.cache_ttl (por predefinição, 1 dia). Transfira-os e guarde-os antes de expirarem.
  • Nos resultados da consulta e nas notificações, webhook.url é apresentado apenas até ao caminho, sem a cadeia de consulta.

Notificação de conclusão

Se definir webhook_url, enviaremos um pedido POST para esse endereço quando a tarefa estiver totalmente concluída. O corpo do pedido é JSON: { "event": "batch.completed", "batch": { … } }, em que batch é exatamente igual à resposta do endpoint de consulta. Cabeçalhos do pedido:

  • X-Snapi-Signature: t=1760779812,v1=5d3f…: t é a hora de envio (segundos Unix) e v1 é a assinatura
  • X-Snapi-Event: batch.completed, X-Snapi-Delivery-Attempt: 1 (número da tentativa de entrega)
Regras de assinatura
v1 = hex(HMAC-SHA256(secret, t + "." + Corpo original do pedido))
secret é o segredo de assinatura associado à API Key usada para submeter a tarefa (disponível na página API Key da consola).
  • Devolva uma resposta 2xx no prazo de 10 segundos; caso contrário, a entrega será considerada falhada. Em caso de falha, serão feitas novas tentativas após 10 segundos, 1 minuto e 5 minutos (até 4 tentativas no total).
  • Em casos muito raros, pode receber notificações duplicadas para a mesma tarefa. Use batch.id para eliminar duplicados.
  • Para verificar a assinatura, faça o cálculo com os bytes originais recebidos e confirme que a diferença entre t e a hora atual não excede alguns minutos, para evitar ataques de repetição.
  • O URL de callback deve ser um endereço http(s) acessível publicamente e não pode apontar para uma rede interna.
import crypto from "node:crypto";

// rawBody tem de conter os bytes originais recebidos, sem aplicar JSON.parse e voltar 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; // Proteção contra ataques de repetição
  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)

Consultar utilização

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

Verificação de estado

GET/v1/health

Não requer autenticação. Devolve o estado do nó e do conjunto de navegadores (total, ocupados e disponíveis).