Snapi
開發者文件

API 文件

基底 URL 為 https://yhfdghkh6544.com/v1。所有端點皆使用標準 HTTP,參數可放在查詢字串(GET)或 JSON 本文(POST)中。

快速開始

  1. 免費註冊,並在控制台的「API Key」頁面建立 Key。
  2. 將 Key 放入 X-Access-Key 請求標頭,呼叫截圖端點:
curl "https://yhfdghkh6544.com/v1/screenshot?url=https://example.com&full_page=true" \
  -H "X-Access-Key: $SNAPI_KEY" \
  -o screenshot.png

身分驗證

每個請求都需要 API Key,可透過以下任一方式傳遞:

  • 請求標頭 X-Access-Key: snp_…(建議使用,適合伺服器端呼叫)
  • 查詢參數 access_key=snp_…
  • 簽章 URL(需將網址放入網頁時使用,不會洩露 secret)

API Key 的明文只會在建立時顯示一次,請妥善保存。若外洩,可在控制台停用。

截圖端點

GET/v1/screenshot
POST/v1/screenshot

截圖專用參數:

參數類型預設值說明
formatenumpngpng、jpeg 或 webp。
device_scale_factornumber1縮放比例 1–3。設為 2 可達 Retina 清晰度,檔案大小約為 4 倍。
qualityinteger80JPEG / WebP 品質,1–100。
full_pagebooleanfalse擷取整頁。會逐屏捲動以觸發延遲載入,高度上限為 20,000 像素。
selectorstring—僅擷取符合此 CSS 選擇器的第一個元素。不可與 full_page 同時使用。

截圖與 PDF 共用參數:

參數類型預設值說明
urlstring—要擷取的網頁網址,使用 http 或 https。與 html 擇一使用。網址中的 # 片段會保留,使用 hash 路由的單頁應用程式(如 https://app.com/#/report/1)可直接擷取對應頁面。
htmlstring—直接傳入 HTML(上限 2 MB),僅支援 POST。
viewport_widthinteger1280檢視區寬度,320–3840。小於 768 時以行動裝置模式算繪。
viewport_heightinteger800檢視區高度,200–3840。
wait_untilenumnetworkidle等待策略:domcontentloaded(最快,適合靜態頁面)、load、networkidle(從開啟頁面起追蹤所有請求,頁面載入後,連續 500 毫秒沒有進行中的請求才截圖,最多額外等待 8 秒,適合單頁應用程式)。
wait_for_selectorstring—頁面載入後,等待符合此 CSS 選擇器的元素出現且可見再截圖,最多等待 10 秒;若未出現則回傳 selector_not_found(不計費)。適合不依賴網路請求,而是透過計時器或動畫延遲算繪內容的單頁應用程式,例如 .chart-ready。
delayinteger0等待完成後額外延遲的毫秒數,0–10000。可用於等待動畫或延遲載入元件。
block_adsbooleantrue封鎖對廣告與追蹤網域的請求。
block_cookie_bannersbooleantrue隱藏 Cookie 同意彈窗。
dark_modebooleanfalse模擬 prefers-color-scheme: dark。
hide_selectorsstring—要隱藏的元素,以 CSS 選擇器清單指定,例如 .ad,#popup。
cachebooleantrue是否查詢快取。設為 false 時強制重新算繪,結果仍會寫入快取。
cache_ttlinteger86400快取有效期限(秒),60–2592000。
response_typeenumbinarybinary 直接回傳檔案;json 回傳檔案網址與中繼資料。

擷取單頁應用程式(React、Vue 等):頁面會在真實瀏覽器中執行完 JavaScript 後再截圖。預設的 networkidle 會等待頁面取得資料;若內容是在請求結束後才透過計時器或動畫算繪,請加上 wait_for_selector,指定代表「內容已就緒」的元素;若沒有合適的元素,再用 delay 多等一會。

PDF 端點

GET/v1/pdf
POST/v1/pdf

每次產生 PDF 會扣除 2 次額度。列印前會先捲動整個頁面,以觸發延遲載入的內容。除共用參數外,也支援:

參數類型預設值說明
paperenuma4紙張:a4 或 letter。頁面自帶 @page 規則時,優先使用頁面設定。
print_backgroundbooleantrue列印背景色與背景圖片。
pdf_mediaenumprintprint 使用列印樣式;screen 使用螢幕樣式(所見即所得)。
HTML 轉 PDF
curl -X POST "https://yhfdghkh6544.com/v1/pdf" \
  -H "X-Access-Key: $SNAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>季度報告</h1><p>內文……</p>", "paper": "a4"}' \
  -o report.pdf

回傳結果

預設(response_type=binary)直接回傳檔案,Content-Type 為對應格式。回應標頭:

  • X-Request-Id:本次請求 ID,排查問題時請提供
  • X-Snapi-Cache:HIT 或 MISS
  • X-Snapi-Truncated: true:整頁高度超過 20,000 像素,已截斷
  • X-Snapi-Format-Changed: webp:PNG 超過 5 MB 時自動轉為 WebP(明確指定 format=png 時不會轉換)

設定 response_type=json 時,會回傳檔案網址與中繼資料:

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

# 回傳
{
  "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

想將截圖網址直接寫入 <img src>,又不想洩露 API Key?請在自己的伺服器端,使用 Key 對應的 secret 計算簽章(可在控制台的 API Key 頁面查看 secret):

簽章規則
canonical = 將 signature 以外的所有參數,依參數名稱的字典順序排序,
            每項格式為 k=urlencode(v)(RFC 3986,空格編碼為 %20),並以 & 串接
signature = hex(HMAC-SHA256(secret, canonical))

簽章正確即視為通過身分驗證。任何參數遭到變更(例如更換 url),簽章都會失效。選填參數 expires(Unix 秒)也會參與簽章計算,過期後回傳 signature_expired。若要重新整理快取,只要變更一個不影響功能的參數(如 v=2)並重新簽章即可。簽章 URL 僅支援 GET。

import crypto from "node:crypto";

// RFC 3986 編碼(空格為 %20),必須與伺服器端規則一致
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="網頁預覽" />

快取

參數完全相同的請求會命中快取(省略參數與明確傳入預設值視為相同;驗證參數與快取參數不納入比較)。命中時,二進位模式會回傳 302,重新導向至檔案網址,並附上 Cache-Control;瀏覽器會自動跟隨重新導向並快取。JSON 模式則回傳 cache_hit: true。

命中快取不計費。相同參數的並行請求只會算繪一次。

限額與計費

  • 僅對成功(2xx)且未命中快取的請求計費:截圖扣除 1 次額度,PDF 扣除 2 次。
  • 逾時、算繪失敗、目標網站錯誤、參數錯誤或身分驗證失敗,皆不扣除額度。
  • 額度於每個曆月(UTC)重設。免費方案每月提供 100 次額度,用完後回傳 quota_exceeded。
  • 超過每秒請求數或並行請求數上限時,會回傳 429;回應標頭 Retry-After 會提供建議等待的秒數。
  • 單次算繪最長 30 秒;單頁下載量上限 50 MB;輸出檔案上限 50 MB。

查看各方案的詳細限額:價格。

錯誤碼

錯誤回應一律為 JSON:

錯誤格式
{
  "error": {
    "code": "render_failed",
    "message": "target responded with an error status",
    "upstream_status": 404,
    "request_id": "req_01j…"
  }
}
HTTPcode意義計費
400invalid_params參數缺漏或超出範圍,message 會指出具體參數否
400html_too_largehtml 超過 2 MB否
401unauthorized缺少 API Key,或 Key 無效/已停用否
401signature_invalid簽章不符(參數遭到變更或 secret 錯誤)否
401signature_expired簽章 URL 已超過 expires 指定的時間否
403url_blocked不允許存取目標網址(例如內部網路位址、localhost)否
408render_timeout算繪超過 30 秒否
422render_failed目標網站回傳 4xx/5xx、DNS 解析失敗或憑證錯誤;附帶 upstream_status否
422selector_not_foundselector 未符合任何元素,或 wait_for_selector 等待逾時否
422too_many_redirects重新導向超過 5 次否
429rate_limited超過每秒請求數上限,請依 Retry-After 重試否
429concurrency_exceeded超過並行請求數上限否
429quota_exceeded本月額度已用完(免費方案)否
429too_many_batches同時未完成的批次工作超過 5 個否
500internal伺服器端錯誤,請提供 request_id 並聯絡我們否

非同步批次

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

一次提交 1–100 個網址,端點會立即回傳工作 ID(HTTP 202),並在背景逐一完成截圖。適合批次截圖、不想持續保持連線等待的情境。可輪詢查詢端點,也可填入 webhook_url,全部完成後我們會主動通知你。身分驗證方式與同步端點相同(僅支援請求標頭或 access_key 參數,不支援簽章 URL)。

參數類型預設值說明
typeenumscreenshotscreenshot(截圖)或 pdf。
urlsstring[]—要處理的網址,1–100 個。任一網址無效時,整個請求會回傳 400,message 會指出是第幾個網址。
optionsobject{}所有網址共用的參數,與同步端點的參數相同(如 full_page、format、viewport_width、cache_ttl),不可包含 url、html、response_type。
webhook_urlstring—選填。全部完成後接收通知的網址,請參閱「完成回呼」。
提交批次工作
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"
  }'

# 回傳 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 }
  ]
}

查詢進度:工作狀態 status 依序為 queued(排隊中)、running(處理中)、completed(全部完成,不論成功或失敗)。每個網址的狀態為 pending、running、succeeded 或 failed;成功時會在 result 中提供檔案網址,失敗時則在 error 中提供錯誤碼(與同步端點的錯誤碼相同)。

查詢批次工作
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" }
    }
  ]
}
  • 計費規則與同步端點相同:每個成功的網址,截圖扣除 1 次額度,PDF 扣除 2 次;命中快取或失敗則不扣除額度。
  • 提交時若額度已用完,會直接回傳 quota_exceeded;若處理過程中額度用完,其餘網址會以 quota_exceeded 回報失敗。
  • 每個帳號同時最多可有 5 個未完成的批次工作,超過時回傳 too_many_batches。
  • 結果檔案的有效期限由 options.cache_ttl 決定(預設 1 天),請及時下載保存。
  • 查詢結果與回呼中的 webhook.url 僅顯示至路徑,不會回傳查詢字串。

完成回呼

若填入 webhook_url,所有工作完成後,我們會向該網址傳送 POST 請求,請求本文為 JSON:{ "event": "batch.completed", "batch": { … } },其中 batch 與查詢端點的回傳內容完全相同。請求標頭:

  • X-Snapi-Signature: t=1760779812,v1=5d3f…:t 為傳送時間(Unix 秒),v1 為簽章
  • X-Snapi-Event: batch.completed、X-Snapi-Delivery-Attempt: 1(第幾次傳送)
簽章規則
v1 = hex(HMAC-SHA256(secret, t + "." + 原始請求本文))
secret 是提交任務時使用的 API Key 所對應的簽章密鑰(可在控制台的 API Key 頁面查看)
  • 請在 10 秒內回傳 2xx,否則視為失敗。失敗後會在 10 秒、1 分鐘、5 分鐘後各重試一次(總計最多 4 次)。
  • 極少數情況下,同一個任務可能會收到重複通知,請依 batch.id 排除重複通知。
  • 驗證簽章時,請使用收到的原始位元組計算,並確認 t 與目前時間相差不超過幾分鐘,以防止重放攻擊。
  • 回呼網址必須是可從公網存取的 http(s) 網址,不得指向內部網路。
import crypto from "node:crypto";

// rawBody 必須是收到的原始位元組,不可先 JSON.parse 再序列化
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; // 防重播攻擊
  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)

用量查詢

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

健康檢查

GET/v1/health

無須驗證身分,回傳節點狀態與瀏覽器集區狀態(總數、忙碌、閒置)。