Changelog

Every shipped feature, endpoint, and SDK release. Newest first.

v1.17.0 2026-10-10 Current

A free tool with no signup, and the renderer can no longer be aimed at a private network

New: capture a page without an account

  • getsnap.dev/tools/screenshot.html — paste a URL, get a PNG. No account, no card, no extension.
  • Six successful captures an hour per visitor. Only successful ones count: a typo, an unreachable site or a blocked address costs you nothing, and repeat URLs are served from cache.
  • Fixed at 1280×720 PNG with no options. Everything else — full page, PDF, video, custom viewports, authenticated pages, scheduling, batch — stays in the API, where the free tier is 100 captures a month.

Security: capture URLs are checked against their resolved address

  • Until now only the URL's syntax was validated, which accepts anything the URL parser understands. The renderer could be pointed at file://, at localhost, or at a private or link-local address including the cloud metadata endpoint.
  • Every capture URL is now resolved and judged by the addresses it points to, not by how the hostname reads — a public name resolving to 127.0.0.1 is refused. Redirects are re-checked, because a redirect is a second navigation.
  • Behaviour change: capturing an intranet address directly now returns 400 url_not_allowed. It was never a supported path — it worked only because the server could route there. To reach a private network, use a proxy.

Fixed

  • Rejected requests no longer reach route handlers. Authentication replied correctly and the handler ran anyway, which showed up as a status code that disagreed with its own response body on POST /v1/render-link. Traced to a framework behaviour, reported upstream, and written up.
  • Horizontal scrolling on phones. Several pages were wider than the viewport, which pushed the centred content off to one side — worst on the homepage, where a decorative element extended 115px past the left edge.
  • Links inside paragraphs and lists are underlined again, so they are not distinguished from surrounding text by colour alone.
  • The homepage no longer downloads all six demo captures on load. It fetches the visible one and the next, which cut about 1.2MB from first load.
v1.16.0 2026-10-09 Breaking

API keys are stored hashed, and render links are signed with a server-held secret

We no longer store your API key

  • Only a SHA-256 hash of your key is kept. It is enough to recognise the key you send, and cannot be turned back into the key.
  • Your key is shown once — in the POST /v1/signup response and in your welcome email. Nobody, including us, can retrieve it afterwards. Lost it? Email support@getsnap.dev and we will revoke it and issue a new one.
  • Why it matters beyond the obvious: the database, and every nightly backup of it, previously contained live credentials. Now they contain hashes.
  • SHA-256 rather than a password hash is deliberate. Slow hashes exist to defend low-entropy human-chosen secrets against offline guessing; an API key is 32 random characters, so there is nothing to guess, and a slow hash would be paid on every authenticated request.
  • Nothing to do. Existing keys keep working exactly as before.

Breaking: render links signed before this release stop working

  • Regenerate them with POST /v1/render-link. The URL format is unchanged — still key=pk_… plus a signature — so nothing in your code needs to change.
  • v1.15.0 moved the secret out of the URL but still keyed the signature with a value stored on your account row. Anyone able to read the database, or a backup of it, could therefore mint valid links for any account and burn its quota.
  • The signature is now keyed by a secret held only in the server environment, combined with your account. Reading the database no longer yields anything that can sign.
  • There is deliberately no grace period accepting old signatures: honouring them would preserve exactly the weakness this removes.

Fixed

  • Rejected requests no longer reach route handlers. Replying from an authentication hook stopped halting the request chain once the app registered a second onSend hook — a Fastify behaviour we reproduced in isolation and reported upstream as fastify/fastify#7090. Visible symptom was a status code that disagreed with its response body on POST /v1/render-link.
  • Four unhandled promise rejections that could terminate the API process: a welcome email sent without a catch, an async shutdown handler, an async screencast frame listener, and a selector failure that discarded its original error.
  • The API no longer serves the marketing site, so api.getsnap.dev can no longer answer with a duplicate of a page that belongs on getsnap.dev.
v1.15.0 2026-10-05 Breaking

Signed render links no longer carry your API key, plus a correctness sweep

Breaking: render links use a public handle

  • POST /v1/render-link now returns a URL containing key=pk_…, a non-secret handle for your account, instead of api_key_id=sk_live_….
  • Links generated before this release stop working. Regenerate them. The change is deliberate rather than incidental: /v1/render-link exists so you can put a capture in an <img> tag without exposing a key, and it was doing the opposite — publishing the secret into page source, Referer headers, CDN logs and ours. Because the signature is HMAC'd with that same secret, anyone holding one link could mint any other.
  • The signature is still keyed by your secret, so only you can create a valid link. The secret simply never leaves the server now.
  • Every account gained a public_id, backfilled automatically. Nothing to do beyond regenerating links.

Fixed

  • Scheduled captures that omitted format failed on every run, with page.screenshot: options.quality is unsupported for the png screenshots. A schedule created as {"url": "…"} — the obvious thing to create — never once succeeded. The worker now applies the same defaults a direct /v1/screenshot call gets.
  • Team members could be charged for nothing. /v1/upgrade had no notion of teams, so a member of a free team could buy Starter, have it charged, and see no change — because effective limits come from the team row. It now returns 409 team_billing_required and points owners at /v1/teams/:id/checkout.
  • Cache keys ignored options that change the capture. lazy_load, wait_for_selector and wait_for_network_idle were absent, so a request asking for lazy_load could be served an image taken without it. Also added fail_if_selector_missing and proxy credentials. Existing cache entries are unaffected.
  • A missing x-api-key returned 400 describing the request body, and on routes with required body fields it reported those instead, hiding the auth failure entirely. Now 401 unauthorized with a message naming the header.
  • Validation errors name the part that failed — Invalid request headers / querystring / params — rather than always claiming body.
  • Favicon corners rendered as white pixels on dark browser tabs; the raster copies had been flattened without an alpha channel.

Changed

  • POST /v1/signup returns verification_required, and its message no longer claims the key is ready to use. With email verification enabled the key returns 403 email_not_verified until the activation link is clicked, which the response and the docs now both say.
  • Stripe Checkout metadata carries your public handle instead of your live API key.
  • usage_log rows are deleted automatically after 90 days, which the privacy policy had always claimed and nothing enforced.
  • Logs redact any sk_live_ / sk_test_ token before writing.

Added

  • captures_total is now actually emitted on /v1/metrics, labelled by kind, status and cache. It had been declared and never incremented, so the counter read as a flat zero.
  • /v1/upgrade, /v1/billing and /v1/signup document their full response set in the OpenAPI spec, including the new 409.
  • Per-language quick starts for Python, Node.js, PHP, Ruby, Go, Java and C#, and hosted-vs-self-hosted comparisons for Puppeteer, Playwright, Selenium and html2canvas.
v1.14.0 2026-09-20 Minor

Runtime Discord alerting + full-site a11y sweep + docs completeness

Runtime alerts (Discord)

  • New src/services/alerts.ts service fires color-coded Discord embeds for three classes of prod incident:
    • Stripe webhook signature failure - 1h Redis dedup. Fires when someone posts to /v1/webhook/stripe with a missing / rotated / spoofed signature header. Muted so a bot sniffing the endpoint can't spam the channel.
    • API 5xx response - 10min Redis dedup (env-tunable via ALERT_5XX_DEDUP_SECONDS). Fires on the first 5xx per rolling window with request_id + method + path + error so on-call can grep the droplet logs.
    • Cron script fatal error - no dedup. sendDrips / backupDb / restoreDb all alert on top-level exceptions. The shell wrappers (hourly-drip.sh, daily-backup.sh) also POST to Discord on non-zero exit to catch docker exec-level failures the Node script can't self-report.
  • Wired via Fastify onSend hook (catches all 5xx including explicit reply.code(500) from route handlers, not just uncaught exceptions).
  • All alerts are fire-and-forget from the call site; a broken webhook or Redis outage cannot mask the underlying error being reported. Fail-open on Redis errors (prefer noisy to silent). No retry on Discord POST (5s timeout, drop over back-up).
  • 10 new tests covering dedup + payload shape + failure resilience. See ops/ALERTS.md for the triage runbook.

Deploy notifications (GitHub Actions)

  • New "Notify Discord" step in .github/workflows/deploy.yml fires for every deploy (success / failure / cancelled) with color-coded embeds. Failure variant says "auto-rolled back"; title links to the workflow run URL for one-click log access.
  • CI failures ping Discord too now (extension of the deploy pattern to .github/workflows/ci.yml).

Full-site WCAG 2.1 AA sweep

  • Audited all 26 user-facing pages via the /v1/audit endpoint (dogfooding). Found 18+ violations across 8 pages; fixed all of them.
  • Landmark fixes: <main> added to 11 pages (changelog + 10 blog posts + docs/playground). Fixes landmark-one-main + region violations that flagged 20-30 nodes each.
  • Heading order: footer h4 → h3 on landing; release-body h4 → h3 on changelog; "Pricing Comparison" h3 → h2 on all 4 compare pages.
  • 50 unassociated <label> tags on /docs/playground.html gained for= attributes.
  • Color-contrast: every color:var(--primary) used as text bumped to var(--primary-text). method.delete gained --red-light.
  • Link-in-text-block: global underline rules for .footer-links a, .article p/li/td a, changelog paragraph links, .docs-content h3 a.
  • Scrollable-region-focusable: new landing/a11y.js (250B gz) referenced from all 26 pages adds tabindex="0" to every <pre> so keyboard users can arrow-scroll code blocks.
  • Result: 0 violations across all 26 pages.

Documentation completeness

  • README.md endpoints table went from 19 rows to 30, grouped into 8 categories.
  • /docs/api-reference.html gained a "Complete endpoint index" section listing every operation with links to detailed sections or the interactive Swagger UI.
  • New blog post: Team-owned Stripe subscriptions in 3 endpoints - case study of shipping v1.13 team billing without a per-seat model.
  • New runbook: ops/ALERTS.md with per-event triage playbook + Redis DEL commands for forcing re-fire during triage.

SDKs (getsnap@1.14.0)

  • Python: added __version__ module attribute (was missing since v1.0). Now getsnap.__version__ returns "1.14.0".
  • No API surface changes - identical to 1.13.0 for both npm + PyPI. Bump is for cache invalidation + documentation timestamp.

Config

  • DISCORD_ALERT_WEBHOOK_URL + ALERT_5XX_DEDUP_SECONDS added to .env.example and documented in src/config.ts zod schema.

Regression check

  • Test suite: 254 → 264 tests, all passing (10 new alert tests).
  • Live smoke test: all 12 API paths return expected shapes; auth wall correct; team billing endpoints route through metadata.team_id correctly (verified via direct Stripe API subscription create with metadata + observing team row update).
  • All 25 pages audited: 0 WCAG violations.
See getsnap@1.14.0 on npm →
v1.13.0 2026-09-20 Minor

Team-owned Stripe subscriptions (billing Phase 2)

Team billing

  • Teams now have their own Stripe subscription, decoupled from the creator's personal billing. Previously, at team-creation time the team copied the creator's stripe_customer_id / stripe_subscription_id and any downstream Stripe events only ever touched the creator's api_keys row, never the team — the team's plan silently drifted whenever the creator up/downgraded.
  • The owner now runs POST /v1/teams/:id/checkout which creates a fresh Stripe customer for the team, launches Checkout with metadata.team_id stamped on the resulting subscription, and returns the checkout URL. New team subs get a 7-day trial with missing_payment_method: cancel.
  • Downstream webhook events (customer.subscription.created / updated / deleted / trial_will_end) check subscription.metadata.team_id and route to the team row via new updateTeamPlan / downgradeTeamToFree service functions. Redis cache is punched for every attached api_key so members see the new limits instantly, not on TTL.

API endpoints

  • POST /v1/teams/:id/checkout — owner-only. Body {plan: starter | pro | business}. Returns {url, type: "checkout" | "portal"}; if the team already has an active sub, punts to billing portal.
  • POST /v1/teams/:id/portal — owner-only. Stripe billing portal for the team's subscription. Returns 400 if no subscription exists yet.
  • GET /v1/teams/:id/billing — any team member. Returns plan, limits, subscription_status (active / trialing / past_due / canceled / unpaid / incomplete / null), current_period_end, cancel_at_period_end, has_team_subscription.
  • Total endpoints in Swagger: 27 → 30. Team paths: 7 → 10 (11 operations).
  • DELETE /v1/teams/:id now also cancels the team's Stripe subscription before dropping the DB row (fire-and-forget with error logging) so owners aren't charged after dissolving.

Schema migrations

  • Three new columns on teams via idempotent ALTER TABLE: subscription_status TEXT, current_period_end TEXT, cancel_at_period_end INTEGER DEFAULT 0.
  • POST /v1/teams no longer copies the creator's stripe_customer_id or stripe_subscription_id. New teams start with those fields empty; existing teams keep whatever inherited values they already have (owner can re-run /checkout to migrate).

SDKs

  • getsnap@1.13.0 on npm and PyPI. Node: snap.teams.checkout(teamId, {plan}), snap.teams.portal(teamId), snap.teams.billing(teamId). Python: snap.create_team_checkout(team_id, plan), snap.get_team_billing_portal(team_id), snap.get_team_billing(team_id).
  • CLI: getsnap teams checkout <team_id> <plan>, getsnap teams portal <team_id>, getsnap teams billing <team_id>.

Regression check

  • Test suite: 246 → 254 tests, all passing. 8 new tests cover metadata-based routing, empty-metadata fallthrough, subscription_details.metadata fallback path, team dedup, owner-email lookup for trial_will_end.
  • Live end-to-end smoke: created a test team → ran /checkout (fresh cus_... created with team metadata) → directly created a subscription via Stripe API with metadata.team_id → verified webhook fired and updated teams.plan, monthly_limit, rate_limit_per_min, subscription_status, stripe_subscription_id. Cancelled the sub → team status transitioned correctly.
See getsnap@1.13.0 on npm →
v1.12.0 2026-09-20 Minor

Team lifecycle: transfer ownership + dissolve

API

  • POST /v1/teams/:id/transfer-ownership — owner promotes a member to owner and gets demoted to admin. Every team has exactly one owner at all times; this is the only way to change ownership without dissolving. Body: { new_owner_user_id }.
  • DELETE /v1/teams/:id — owner permanently dissolves the team, detaches every member's api_key back to their personal plan, revokes outstanding invites, drops the team's monthly_usage row. Returns { detached_key_count, invites_revoked }. One-way action, no undo.
  • Total endpoints in Swagger: 25 → 27. Team routes now cover the full lifecycle owner → admin transitions without support tickets.

Usage rollover on team creation

  • When a solo user creates a team, their current-month personal monthly_usage row is now copied into the team's counter as part of the create transaction. Previously usage reset to zero when a solo Pro user made a team on the 20th of the month, effectively giving them a fresh 25,000 quota. Fixed.

SDKs

  • getsnap@1.12.0 on npm and PyPI. Node: snap.teams.transferOwnership(teamId, { new_owner_user_id }) + snap.teams.dissolve(teamId). Python: snap.transfer_team_ownership(team_id, new_owner_user_id) + snap.dissolve_team(team_id).
  • CLI: getsnap teams transfer <team_id> <new_owner_user_id> + getsnap teams dissolve <team_id> --yes. Dissolve requires an explicit --yes flag because it's irreversible.

Docs

  • /docs/teams.html and /docs/referrals.html are now full reference documentation (previously just interactive dashboards). Teams doc gained a permissions matrix, all 8 endpoint references with curl / Node / Python / CLI samples, a data model, and a 10-question FAQ. Referrals doc gained reward-mechanics tables, cookie attribution deep-dive, anti-abuse rules, and an expanded 8-question FAQ.
See getsnap@1.12.0 on npm →
v1.11.0 2026-09-20 Patch

SDK team methods + WCAG AA fix

SDKs

  • getsnap@1.11.0 on npm and PyPI — adds the snap.teams.* namespace (Node) / snap.create_team() et al. (Python) wrapping all six team endpoints with typed request / response shapes.
  • CLI adds a getsnap teams family: list, create, members, invite, accept, leave. Terminal-friendly output; all subcommands accept --json for machine-readable output.

Accessibility

  • Ran our own POST /v1/audit against getsnap.dev and found 60 WCAG AA color-contrast violations, all stemming from three color patterns (primary-on-dark text, white-on-primary buttons, red 'no' cells in the comparison table).
  • Fixed by splitting --primary into two roles: --primary (#4f46e5) for backgrounds and borders where white text sits on top; --primary-text (#a5b4fc) for text on dark backgrounds. Plus --red-light (#fca5a5) for the comparison table crosses.
  • Re-audit: 60 → 0 violations. Full WCAG AA sweep now returns zero total violations.

Bug fixes

  • Team-member api_keys were returning 500 FOREIGN KEY constraint failed on every capture because monthly_usage.api_key_id had a FK to api_keys.id, but the teams refactor started routing usage under the team ID (which doesn't exist in api_keys). Fix: inline migration drops the FK constraint (table-swap since SQLite has no ALTER DROP CONSTRAINT).
See getsnap@1.11.0 on npm →
v1.10.0 2026-09-20 Minor

Team accounts

Six new endpoints

  • POST /v1/teams creates a team, hoists your current plan/quota/rate onto it, and attaches your api_key
  • GET /v1/teams lists teams you belong to with your role in each
  • GET /v1/teams/:id/members lists the roster with emails and roles
  • POST /v1/teams/:id/invites invites a member by email (owner/admin only). Fires a branded invite email with a single-use link that expires in 7 days.
  • POST /v1/teams/accept consumes an invite token, adds the caller to the team, and attaches their api_key to the shared plan
  • POST /v1/teams/:id/leave lets non-owners leave the team; api_keys detach and fall back to personal limits

How the shared pool works

  • All requests from any team member's api_key deduct from ONE shared monthly quota and one 60-second rate-limit window.
  • Individual api_key logging still tracks each user's usage inside usage_log, so you can see who made what.
  • Team members keep their own api_keys — no key sharing, no unrevealed secrets.
  • Solo users are unaffected: api_keys.team_id defaults to NULL and everything works as before.

Roles

  • owner — created the team. Cannot leave via /leave (transfer or dissolve instead). Every team has exactly one.
  • admin — can invite / remove members, cannot dissolve.
  • member — can view team info and use the shared quota.

Anti-abuse

  • Invites verify caller's email against the invited email before accepting
  • Cannot re-invite an existing member (returns 409 already_member)
  • Owners cannot leave via /leave endpoint
  • Role escalation via double-invite is blocked (existing membership takes precedence)
See Teams in Swagger UI →
v1.9.0 2026-09-20 Minor

Accessibility audit endpoint

New endpoint

  • POST /v1/audit runs axe-core against any URL and returns categorised WCAG 2.0 / 2.1 / 2.2 violations grouped by impact (critical, serious, moderate, minor).
  • Every violation includes the rule ID, plain-English description, WCAG success criterion tags, and the failing HTML elements with CSS selectors so you can jump straight to the fix in your codebase.
  • Optional tags filter (wcag2aa, wcag21aa, wcag22aa, section508, ACT, etc.) narrows the audit to a specific conformance level.
  • Optional rules filter (e.g. color-contrast, label, region) runs only specific axe rules for targeted CI gates.
  • Optional include_screenshot flag captures a viewport screenshot alongside the report (+1 credit) so you have visual context in review.

CLI

  • New getsnap audit <url> command with a color-coded, terminal-friendly summary. Shows impact tier counts, top violations with rule ID + help + deque.university docs link, and a status marker (!!, !, *, OK) at the start of each line for CI log skimming.

SDKs

  • getsnap@1.10.0 on npm and PyPI — adds typed audit() methods with full AuditOptions / AuditResponse / AxeViolation shapes.

Pricing

  • 1 credit per audit + 1 more if you request a screenshot. No cache (DOM state is per-invocation) — but audits are 3-5x cheaper than a full capture on average since we skip the heavy image encoding.
Try /v1/audit in Swagger UI →
v1.8.0 2026-09-20 Minor

Visual regression diff + CLI

New endpoint

  • POST /v1/diff captures two URLs at identical viewport dimensions, compares them pixel-by-pixel with pixelmatch, and returns a similarity score (0-100) plus three CDN URLs: before, after, and a red-overlay diff image.
  • Both captures run in parallel to halve latency. Cache HITs on either side reduce the credit charge - a diff of two pages already in cache costs 0 credits.
  • Per-side overrides for wait_for_selector, extra_delay_ms, and css handle sites where each side settles at different rates.
  • pixelmatch tunables exposed: threshold (0-1 YIQ sensitivity), anti_alias, include_aa. Sensible defaults for typical marketing / product pages.

CLI

  • Every SDK install now ships a getsnap executable. No code required for one-off captures:
export GETSNAP_KEY=sk_live_...
npx getsnap screenshot https://news.ycombinator.com --out hn.png
npx getsnap pdf        https://news.ycombinator.com --out hn.pdf --full-page
npx getsnap og         https://blog.example.com/post --out og.png
npx getsnap video      https://getsnap.dev --out demo.mp4 --duration 5
npx getsnap diff       https://staging.mysite.com https://mysite.com
npx getsnap usage
npx getsnap referrals

Every capture flag from the SDK is available on the CLI: --full-page, --viewport 1440x900, --wait-for #hero, --dark-mode, --device mobile, etc. Full reference: npx getsnap --help.

SDKs

  • getsnap@1.9.0 on npm and PyPI — adds typed diff() methods with full DiffOptions / DiffResponse shapes. Node package now ships the CLI too via a bin entry.
Try /v1/diff in Swagger UI →
v1.7.0 2026-09-20 Minor

Referral program

New endpoint

  • GET /v1/referrals returns your unique referral code, ready-to-share URL, reward tier, and aggregated stats (total, paid, pending, reverted, lifetime earnings in cents).

How it works

  • Every account gets an auto-generated 8-character referral code (uppercase alphanumeric, ambiguous chars like 0/O/1/I/L excluded).
  • Share https://getsnap.dev/?ref=YOURCODE. Landing JS captures the code into a 30-day first-party cookie on the referee's visit.
  • When the referee signs up, the code follows them into the request. On their first paid checkout (Starter, Pro, or Business), we attach a Stripe coupon giving them a discount on their first month.
  • When the referee's first invoice is actually paid (i.e. they finished the trial + kept the plan), you get a $5.00 credit added to your Stripe customer balance.

Anti-abuse

  • Self-referral (same email on both sides) is silently ignored.
  • Each referee can only be attributed to one referrer, ever, enforced by a UNIQUE constraint at the DB level.
  • Reward fires only on the FIRST successful invoice - trial cancellations or later refunds don't burn credits.
  • Reward is idempotent under Stripe webhook retries and repeat-billing invoices for the same subscription.
See /v1/referrals in Swagger UI →
v1.6.0 2026-09-20 Minor

OG image endpoint + billing hardening

New endpoint

  • Added POST /v1/og-image: a convenience wrapper over /v1/screenshot with defaults tuned for social share images — 1200×630 PNG, hi-DPI (2x), ad and popup blocking on, waits for network idle.
  • Same billing as /v1/screenshot: 1 credit per fresh capture, 0 for cache hits.
  • Supports response_type: "url" (JSON with CDN URL, default) or "binary" (raw image bytes with Cache-Control: public, max-age=604800, immutable).

Billing reliability

  • Stripe webhook is now idempotent. Retried event deliveries (up to 3 days) no longer risk double-charging or double-applying subscription changes. Backed by a stripe_events UNIQUE-constrained log with transactional rollback.

Onboarding

  • Day-3 activation email lands automatically for new signups who haven't made an API call yet. Real paste-and-run code (curl, Node.js SDK, Python SDK) instead of a marketing pitch.
  • Trial-ending email fires 72 hours before your Stripe trial expires. Includes a personalised billing portal link so you can update or cancel in one click. Wired via Stripe's customer.subscription.trial_will_end webhook.

SDKs

  • getsnap@1.6.0 on npm and PyPI — adds ogImage() / og_image() convenience methods with typed OgImageOptions / OgImageResponse shapes.

Ops

  • Automated production deploys on git tag push. .github/workflows/deploy.yml checks out the tag, rebuilds via docker compose, polls /v1/health for up to 90 seconds, and auto-rolls back on failure.
  • Restore script now supports RESTORE_TARGET_DB_PATH for safe drills against a scratch path. Documented RTO/RPO in ops/RESTORE-DRILL.md.
  • Load test script + baseline results at ops/load-test.js and ops/LOAD-TEST-RESULTS.md. Verified production sustains 51 req/s at p95 <30ms with 12% memory utilisation.
Try /v1/og-image in Swagger UI →
v1.5.0 2026-09-20 Minor

Geo/proxy routing + full OpenAPI schemas

API

  • New proxy_url, proxy_username, proxy_password, proxy_country fields on /v1/screenshot and /v1/video
  • Supports http, https, socks5, socks5h schemes. Credentials stripped before handing to Playwright and SHA-256–truncated in cache keys.
  • Managed geo pool via PROXY_URL_TEMPLATE env var. When configured, clients pass proxy_country: "US" and we substitute.
  • Optional PROXY_ALLOWED_COUNTRIES allow-list restricts which ISO codes are accepted.

Docs

  • Swagger UI at api.getsnap.dev/docs now shows the full request body (up to 74 parameters), response shape, and every error code for each endpoint. Previously it rendered barebones stubs.
  • The interactive playground at /docs/playground exposes every Phase 6–11 field in collapsible sections: proxy, PDF metadata, extract_metadata, clip, slicing, blocking, emulation.

SDKs

  • getsnap@1.5.0 on npm and PyPI — adds proxy_* fields to ScreenshotOptions and VideoOptions, plus HMAC verification example.
See proxy examples →
v1.4.0 2026-09-19 Minor

Scrolling video capture (MP4 + GIF)

New endpoint

  • POST /v1/video records a smoothly-scrolling capture as MP4 (libx264 + faststart, quality-driven CRF) or GIF (two-pass palettegen → paletteuse).
  • 10–30 fps, 2–30 second duration, viewport up to 1920×1080.
  • Uses Chrome DevTools' Page.screencast protocol for high-fps frame sampling (not a screenshot loop) and ffmpeg for encoding.
  • Sync / async webhook / raw binary response modes. Async webhooks include a video.completed event.
  • Costs 1 credit per second of video (rounded up, min 1).

Infrastructure

  • Dockerfile now installs ffmpeg alongside curl.
  • New SCHEDULE_WORKER_CONCURRENCY env var so cron-driven captures don't starve the sync pool.

SDKs

  • getsnap@1.4.0 Node: client.video(), client.videoBinary(), full VideoOptions types.
  • getsnap@1.4.0 Python: gs.video(**kwargs), gs.video_binary(**kwargs). First release published to PyPI.
See video examples →
v1.3.0 2026-09-20 Minor

Scheduled captures with HMAC-signed webhooks

New endpoints

  • POST /v1/schedules, GET /v1/schedules, GET /v1/schedules/:id, PATCH /v1/schedules/:id, DELETE /v1/schedules/:id
  • POST /v1/schedules/:id/run — trigger a schedule immediately for testing
  • GET /v1/schedules/:id/runs — execution history (last 100 runs kept)

Design

  • BullMQ repeatable jobs, Redis-backed, survive restarts via startup DB hydration
  • IANA timezone support (validated with cron-parser)
  • Optional webhook_secret triggers HMAC-SHA256 delivery signing: X-Webhook-Signature: sha256=<hex> and X-Webhook-Timestamp so receivers can verify authenticity and defeat replay attacks

SDKs

  • Node: client.schedules.{create, list, get, update, remove, runNow, runs}
  • Python: create_schedule / list_schedules / get_schedule / update_schedule / delete_schedule / run_schedule_now / list_schedule_runs
See schedules examples →
v1.2.0 2026-09-20 Minor

Feature parity blast: 30+ new screenshot options

Category A — new capture behaviour

  • clip: { x, y, width, height } — capture a specific rectangle of the viewport, overriding full_page and selector
  • full_page_max_height — hard cap on infinite-scroll pages
  • full_page_slices, full_page_slice_height, full_page_slice_overlap_height — split tall captures into overlapping strips for AI vision workflows
  • reduced_motion and media_type: "screen" | "print" emulation
  • click_accept — auto-click cookie-consent Accept buttons (OneTrust, Cookiebot, FundingChoices, generic heuristics)
  • press_escape — dismiss modals via Escape keypress
  • fail_if_selector_missing — hard-fail on missing selectors instead of silently ignoring
  • attachment_name — sets Content-Disposition on binary responses
  • AVIF as a first-class output format

Category B — PDF completeness

  • Paper sizes: A0–A6, Letter, Legal, Tabloid, Ledger
  • Orientation, per-side margins (top/right/bottom/left), pdf_background, pdf_scale
  • Templated pdf_header / pdf_footer (Chromium format: <span class="pageNumber">, totalPages, title, url, date)
  • Page range filter (pdf_page_range: "1-5,8")
  • Document metadata: pdf_title, pdf_subject, pdf_author, pdf_keywords, pdf_creator

Category C — extraction

  • extract_metadata returns {title, favicon, open_graph, fonts, http_status, http_status_text, http_headers} in one field

Category D — request blocking

  • block_images, block_fonts, block_scripts, block_frames, block_urls (wildcard patterns), bypass_csp

Dependencies

  • sharp for AVIF conversion + full-page slicing
  • pdf-lib for PDF metadata post-processing
v1.1.0 2026-08-29 Minor

Rebrand: SnapAPI → getsnap.dev

  • New brand across the entire stack: landing, docs, compare pages, SDKs, email templates, Swagger title, webhook User-Agent
  • Class renamed SnapAPI → GetSnap in the Node SDK; backwards-compat aliases preserved (SnapAPI, SnapAPIError)
  • New comparison landing table with 6 columns and honest research-based rows for ScreenshotAPI, Urlbox, ScreenshotOne, ApiFlash
  • Four dedicated /compare/*.html deep-dive pages
  • Node SDK getsnap published to npm (renamed from a legacy internal name)
See how we compare →
v1.0.0 2026-08-15 Major

Initial public release

Core API

  • POST /v1/screenshot — screenshot or PDF of any URL or HTML string
  • POST /v1/batch — up to 100 URLs in one request
  • POST /v1/render-link + GET /v1/render — signed GET URLs for <img> embedding
  • GET /v1/usage, GET /v1/health

Capture features

  • AI popup removal (90+ heuristic selectors), ad blocking
  • Full-page, mobile/tablet emulation, dark mode, Retina/HiDPI
  • Element capture via selector, transparent PNG
  • Wait-for-selector, lazy-load auto-scroll, click/scroll before capture
  • Custom CSS/JS injection, cookies, headers, locale, timezone, user agent
  • Text and HTML extraction
  • Async webhooks, S3 upload to user-owned buckets

Platform

  • Fastify + Playwright + Redis + SQLite stack on a single droplet
  • DigitalOcean Spaces CDN with hash-based caching
  • Sliding-window rate limiting, monthly quota enforcement
  • Stripe metered billing with 4 plans (Free / Starter / Pro / Business)