API Documentation
Everything you need to capture screenshots with the SnapRender API.
Quick Start
Get your API key from the dashboard, then make a request:
curl "https://app.snap-render.com/v1/screenshot?url=https://example.com&format=png" \
-H "X-API-Key: YOUR_API_KEY" \
--output screenshot.png
That's it. The API returns the screenshot binary directly. For JSON metadata + base64 image, add response_type=json.
Cache is OFF by default. Every request returns a fresh screenshot. To save credits on repeated requests for the same URL, add cache=true to your request. Cached hits are free and return in under 200ms. See Caching for details.
Authentication
All API requests require an API key passed in the X-API-Key header.
X-API-Key: sk_live_your_key_here
API keys start with sk_live_ and are 40 characters long.
Create and manage keys in the dashboard. Keys are hashed server-side and shown only once at creation.
Missing or invalid keys return 401 UNAUTHORIZED.
Base URL
https://app.snap-render.com
All endpoints below are relative to this base URL. HTTPS is required.
Endpoints
Screenshots
/v1/screenshot
Capture a screenshot of any public URL. Returns the image binary by default, or JSON with base64 data when response_type=json. Cache is off by default; pass cache=true to return cached results (free, under 200ms).
Requires API key. Counts against monthly quota (cached hits are free).
See Parameters for all options and Response Format for output details.
/v1/screenshot
Capture a screenshot from a URL, raw HTML, or Markdown. Send a JSON body with exactly one source: url, html, or markdown. All screenshot parameters work with all three sources.
Request body (JSON)
| Field | Type | Description |
|---|---|---|
| url | string | Public URL to screenshot |
| html | string | Raw HTML to render (max 2MB) |
| markdown | string | Markdown to render with styled template (max 500KB) |
| format, width, ... | various | All GET parameters as native JSON types (booleans, integers) |
Exactly one of url, html, or markdown is required. Returns the image binary by default, or JSON with "response_type": "json".
Example: render HTML
{
"html": "<h1>Hello World</h1><p>Rendered from raw HTML.</p>",
"format": "png",
"width": 800,
"dark_mode": true
}
Example: render Markdown
{
"markdown": "# Report Title\n\nSome **bold** text and `code`.",
"format": "png",
"width": 1280
}
Example: screenshot a URL via POST
{
"url": "https://example.com",
"format": "jpeg",
"quality": 80,
"full_page": true
}
See POST cURL examples for complete copy-paste commands.
/v1/screenshot/info
Check if a screenshot is cached without taking a new one. Accepts the same query parameters as /v1/screenshot. Does not count against quota.
Response
{
"url": "https://example.com",
"cached": true,
"cache_key": "a1b2c3d4e5...",
"cached_at": "2026-02-22T10:30:00.000Z",
"expires_at": "2026-02-23T10:30:00.000Z",
"content_type": "image/png"
}
/v1/screenshot/sign
Generate a pre-signed URL that renders a screenshot when visited. No API key needed to use the signed URL. Ideal for embedding in emails, documents, or sharing with third parties.
Signing is free and does not count against quota. Each render of the signed URL consumes one credit. URLs are tamper-proof (HMAC-SHA256) and expire after the specified duration.
JSON body: url (required), expires_in (seconds, default: 86400, range: 60 to 2,592,000). All screenshot parameters are supported to customize the signed screenshot.
Requires API key. See Signed URL Examples below or the Signed URLs feature guide.
Sample request body
{
"url": "https://example.com",
"expires_in": 86400,
"format": "png",
"width": 1280
}
Response
{
"signed_url": "https://app.snap-render.com/v1/screenshot/render?url=...&expires=...&sig=...",
"expires_at": "2026-04-09T10:00:00.000Z",
"expires_in": 86400
}
/v1/screenshot/render
Render a screenshot using a signed URL. No API key required. The URL must have been generated via POST /v1/screenshot/sign.
Expired URLs return 410 Gone. Tampered URLs return 403 Forbidden. Revoking the API key that created the signed URL invalidates all its signed URLs.
Content Extraction
Feature guide →/v1/extract
Extract content from any web page. Supports 6 extraction types: markdown, text, html, article, links, metadata.
Requires API key. Counts against monthly quota. See Extract Examples below.
Query parameters
| Field | Type | Description |
|---|---|---|
| url | string | required Public URL to extract content from. |
| type | string | markdown, text, html, article, links, or metadata. Default: markdown |
| selector | string | CSS selector to limit extraction scope. Optional. |
| block_ads | boolean | Block ads and trackers. Default: true |
| block_cookie_banners | boolean | Remove cookie consent banners. Default: true |
| delay | integer | Wait (ms) after page load (0-10000). Default: 0 |
| max_length | integer | Max characters to return (1-500000). Default: 100000 |
| cache | boolean | Use cached result if available. Default: false |
| cache_ttl | integer | Cache TTL in seconds (0-2592000). Default: 86400 |
Response (markdown type)
{
"url": "https://example.com",
"type": "markdown",
"content": "# Example Domain\n\nThis domain is for use in illustrative examples...",
"wordCount": 83,
"processingTimeMs": 1240
}
/v1/extract
Same as GET but accepts a JSON body with native types (booleans, numbers). Recommended for programmatic use and SDKs.
Sample request body
{
"url": "https://example.com",
"type": "article",
"block_ads": true,
"max_length": 50000
}
Batch Screenshots
Feature guide →/v1/screenshot/batch
Create a batch screenshot job for up to 50 URLs. Returns a job ID immediately (202 Accepted). All URLs share the same screenshot options.
Each URL consumes one credit. Failed URLs get credits rolled back automatically.
Request body (JSON)
| Field | Type | Description |
|---|---|---|
| urls | string[] | required Array of URLs to capture (1-50). |
| format | string | png, jpeg (or jpg), webp, or pdf. Default: png |
| width | number | Viewport width (320-3840). Default: 1280 |
| height | number | Viewport height (200-10000). Default: 800 |
| quality | number | JPEG/WebP quality (1-100). Default: 90 |
| full_page | boolean | Capture full scrollable page. Default: false |
| dark_mode | boolean | Force dark color scheme. Default: false |
| device | string | Device preset (e.g. iphone_15_pro). See presets. |
| block_ads | boolean | Block ads and trackers. Default: true |
| block_cookie_banners | boolean | Remove cookie consent banners. Default: true |
| delay | number | Wait (ms) after page load before capture (0-10000). Default: 0 |
| hide_selectors | string | CSS selector(s) to hide before capture. Optional. |
| click_selector | string | CSS selector to click before capture. Optional. |
| user_agent | string | Custom User-Agent string. Optional. |
All screenshot parameters are supported. Options apply to every URL in the batch.
Sample request body
{
"urls": ["https://example.com", "https://github.com", "https://google.com"],
"format": "png",
"width": 1280,
"dark_mode": true
}
Response (202 Accepted)
{
"jobId": "c91a6fb2-9feb-49fa-b187-28451d352473",
"status": "pending",
"statusUrl": "/v1/screenshot/batch/c91a6fb2-...",
"total": 3,
"completed": 0,
"failed": 0,
"estimatedSeconds": 20,
"items": [
{ "url": "https://example.com", "status": "pending" },
{ "url": "https://github.com", "status": "pending" },
{ "url": "https://google.com", "status": "pending" }
],
"createdAt": "2026-04-09T12:00:00.000Z"
}
See Batch Examples below or read the batch optimization guide.
/v1/screenshot/batch/:jobId
Poll the status of a batch screenshot job. Returns progress, download URLs for completed items, and error messages for failed items.
Requires API key. Only the job owner can access their batch jobs.
Response when completed
{
"jobId": "c91a6fb2-...",
"status": "completed",
"statusUrl": "/v1/screenshot/batch/c91a6fb2-...",
"total": 3,
"completed": 2,
"failed": 1,
"items": [
{
"url": "https://example.com",
"status": "completed",
"downloadUrl": "https://...presigned-url...",
"format": "png",
"size": 45231
},
{
"url": "https://github.com",
"status": "completed",
"downloadUrl": "https://...presigned-url...",
"format": "png",
"size": 87402
},
{
"url": "https://unreachable.test",
"status": "failed",
"error": "TARGET_UNREACHABLE"
}
],
"createdAt": "2026-04-09T12:00:00.000Z",
"completedAt": "2026-04-09T12:00:18.000Z"
}
Download URLs are presigned and valid for 24 hours. Failed items include an error code. Credits for failed items are refunded automatically.
Webhooks
Feature guide →/v1/webhooks
Register a webhook URL to receive event notifications. Max 5 per account. Returns the webhook with its HMAC-SHA256 signing secret.
Events: screenshot.completed, quota.warning (80%), quota.exceeded (100%).
Requires API key. See Webhook Examples below.
Sample request body
{
"url": "https://your-server.com/webhook",
"events": ["screenshot.completed", "quota.warning"]
}
Response (201 Created)
{
"id": "a7f3b2c1-...",
"url": "https://your-server.com/webhook",
"events": ["screenshot.completed", "quota.warning"],
"secret": "whsec_...",
"isActive": true
}
/v1/webhooks
List all registered webhooks for the authenticated account.
Response
[
{
"id": "a7f3b2c1-...",
"url": "https://your-server.com/webhook",
"events": ["screenshot.completed", "quota.warning"],
"secret": "whsec_...",
"isActive": true
}
]
/v1/webhooks/:id
Remove a webhook. Future events will no longer be delivered to this URL. Returns 204 No Content.
/v1/webhooks/:id/test
Send a test payload to a webhook to verify it's working. Returns delivery status and HTTP response code.
Response
{
"deliveryId": "d4e5f6a7-...",
"statusCode": 200,
"success": true,
"deliveredAt": "2026-04-09T12:00:00.000Z"
}
Usage
/v1/usage
Get your current monthly usage and plan limits.
Response
{
"plan": "growth",
"period": {
"start": "2026-02-01T00:00:00.000Z",
"end": "2026-02-28T23:59:59.000Z"
},
"usage": {
"screenshots_used": 1423,
"screenshots_limit": 10000,
"screenshots_remaining": 8577
}
}
/v1/usage/daily
Get daily usage breakdown. Useful for charts and usage monitoring.
| Field | Default | Description |
|---|---|---|
| days | 30 | Number of days to look back (1-90) |
Response
{
"days": 30,
"data": [
{ "date": "2026-02-20", "count": 47 },
{ "date": "2026-02-21", "count": 83 },
{ "date": "2026-02-22", "count": 12 }
]
}
Parameters
For GET, pass these as query strings. For POST, pass them as JSON body fields with native types (booleans, integers instead of strings). All features are available on every plan.
| Parameter | Type | Default | Description |
|---|---|---|---|
| url | string | — | URL to screenshot. Must be a public HTTP/HTTPS URL. required |
| format | string | png | Output format: png jpeg (jpg also accepted) webp pdf |
| width | integer | 1280 | Viewport width in pixels. Range: 320–3840. |
| height | integer | 800 | Viewport height in pixels. Range: 200–10,000. |
| full_page | boolean | false | Capture the full scrollable page. Max height capped at 32,768px to prevent OOM. |
| quality | integer | 90 | Output quality for JPEG and WebP. Range: 1–100. Ignored for PNG/PDF. |
| delay | integer | 0 | Milliseconds to wait after page load before capture. Range: 0–10,000. Useful for pages with animations or lazy-loaded content. |
| dark_mode | boolean | false | Emulate prefers-color-scheme: dark. Works with sites that have dark mode CSS. |
| block_ads | boolean | true | Block ads and tracking scripts before capture. |
| block_cookie_banners | boolean | true | Remove cookie consent banners and GDPR dialogs. |
| hide_selectors | string | — | Comma-separated CSS selectors to hide (sets display:none). Example: .header,.footer,#banner |
| click_selector | string | — | CSS selector of an element to click before capture. Useful for dismissing modals or expanding content. |
| device | string | — | Device preset name. Sets viewport, pixel density, user agent, and mobile flag. See Device Presets. |
| user_agent | string | — | Custom User-Agent string. Overrides the default (or device preset) user agent. |
| cache | boolean | false | Return a cached screenshot if one exists. Disabled by default: set to true to return cached results (free, no credit charged, but may be up to 24h old). |
| cache_ttl | integer | 86400 | Cache TTL in seconds. Range: 0–2,592,000 (30 days). Clamped to your plan's max. See Rate Limits. |
| response_type | string | binary | binary returns the image directly. json returns metadata + base64 data URI. See Response Format. |
GET: Boolean parameters accept true or false as string values in the query string.
POST: The JSON body also accepts html (max 2MB) or markdown (max 500KB) as the source instead of url. Use native JSON types: true instead of "true", 1280 instead of "1280". See POST /v1/screenshot for examples.
Device Presets
Use the device parameter to emulate a real device. This sets viewport dimensions, pixel density, mobile mode, and user agent string automatically.
| Preset Name | Viewport | Scale | Mobile | User Agent |
|---|---|---|---|---|
| iphone_14 | 390 × 844 | 3x | Yes | iPhone; CPU iPhone OS 16_0 (Safari 604.1) |
| iphone_15_pro | 393 × 852 | 3x | Yes | iPhone; CPU iPhone OS 17_0 (Safari 604.1) |
| pixel_7 | 412 × 915 | 2.625x | Yes | Pixel 7; Android 13 (Chrome 116) |
| ipad_pro | 1024 × 1366 | 2x | Yes | iPad; CPU OS 16_0 (Safari 604.1) |
| macbook_pro | 1440 × 900 | 2x | No | Mac OS X 10_15_7 (Chrome 120) |
When using a device preset, the width, height, and user_agent parameters are overridden by the preset values.
Response Format
Binary response (default)
The screenshot image is returned directly as the response body with the appropriate Content-Type header (image/png, image/jpeg, image/webp, or application/pdf).
JSON response
Set response_type=json to get metadata and the image as a base64 data URI. Recommended for AI agents and workflows that need to process the image inline.
{
"url": "https://example.com",
"format": "png",
"width": 1280,
"height": 800,
"image": "data:image/png;base64,iVBORw0KGgo...",
"size": 248576,
"cache": "MISS",
"responseTime": "2847ms",
"remainingCredits": 8577
}
Response headers
Screenshot responses include these headers. Error responses additionally include X-Error-Code and X-Error-Message.
| Header | Description |
|---|---|
| X-Cache | HIT or MISS |
| X-Request-Id | Unique request ID for debugging |
| X-Response-Time | Time to generate the screenshot (e.g. 2847ms) |
| X-Remaining-Credits | Remaining screenshots for the current billing period |
| X-RateLimit-Limit | Monthly screenshot quota for your plan |
| X-RateLimit-Remaining | Screenshots remaining in the current billing period |
| X-RateLimit-Burst-Limit | Max requests per minute for your plan |
| Content-Type | image/png, image/jpeg, image/webp, application/pdf, or application/json |
| Content-Length | Size in bytes (binary responses only) |
| X-Error-Code | Machine-readable error code (error responses only) |
| X-Error-Message | Human-readable error description (error responses only) |
Error Handling
All API errors return JSON with a consistent structure:
{
"error": {
"code": "INVALID_URL",
"message": "The provided URL is not a valid HTTP/HTTPS URL.",
"status": 400
}
}
Error codes
| Code | HTTP | Description |
|---|---|---|
| UNAUTHORIZED | 401 | Missing or invalid API key |
| VALIDATION_ERROR | 400 | Invalid query parameters (Zod validation failed) |
| INVALID_URL | 400 | URL is not a valid HTTP or HTTPS URL |
| BLOCKED_URL | 400 | URL points to a private, local, or blocked address (SSRF protection) |
| INVALID_DEVICE | 400 | Unknown device preset name |
| RATE_LIMITED | 429 | Burst rate limit exceeded. Slow down and retry. |
| DOMAIN_RATE_LIMITED | 429 | Too many requests to the same domain. Limit varies by plan (see Rate Limits). |
| QUOTA_EXCEEDED | 429 | Monthly quota reached. Upgrade your plan. |
| RENDER_TIMEOUT | 408 | Page took longer than 30 seconds to render |
| TARGET_TIMEOUT | 408 | Target website took too long to respond (30s limit) |
| RENDER_FAILED | 502 | Chromium failed to render the page |
| TARGET_UNREACHABLE | 502 | Target website could not be reached (DNS failure, site down, or blocking requests) |
| TARGET_SSL_ERROR | 502 | Target website has an SSL/TLS certificate error |
| OUTPUT_TOO_LARGE | 413 | Screenshot exceeds the 10MB size limit |
| SERVICE_UNAVAILABLE | 503 | Service temporarily unavailable |
| SERVER_ERROR | 500 | Internal server error |
For 429 responses, check the Retry-After header for the number of seconds to wait before retrying.
Rate Limits & Quotas
Each plan has a monthly screenshot quota, per-minute burst limit, and per-domain rate limit that scales with your plan.
| Plan | Monthly Quota | Burst Rate | Domain Rate | Max Cache TTL | Price |
|---|---|---|---|---|---|
| Free | 500 | 10/min | 10/min | 1 day | $0 |
| Starter | 2,000 | 30/min | 15/min | 7 days | $9/mo |
| Growth | 10,000 | 60/min | 15/min | 30 days | $29/mo |
| Business | 50,000 | 120/min | 20/min | 30 days | $79/mo |
| Scale | 200,000 | 200/min | 30/min | 30 days | $199/mo |
Quotas reset on the 1st of each month (UTC). Cached responses (cache HIT) do not count toward your monthly quota, only fresh captures (cache MISS) do. Use the /v1/usage endpoint to monitor your usage programmatically.
Caching
Screenshots are cached on Cloudflare R2 for fast repeat access. Caching is available on all plans but disabled by default, so every request returns a fresh capture.
Tip: Enable caching with cache=true to save credits on repeated requests. Cached responses are free, instant (under 200ms), and don't count toward your quota. The tradeoff is that the image may be up to cache_ttl seconds old.
How it works
cache=false(default) — Always take a fresh screenshot. Consumes one credit. You always get the latest version of the page.cache=true— Return a cached screenshot if one exists. Cached responses are free and instant. The image may be up tocache_ttlseconds old.cache_ttl=SECONDS— How long to store the cached result. Default: 86,400 (24 hours). Range: 0 to 2,592,000 (30 days). Clamped to your plan's max TTL.
When to enable cache
- • You capture the same pages repeatedly (link previews, thumbnails, OG images)
- • You want to minimize credit usage
- • The target page content doesn't change often
When to keep cache off
- • You are monitoring a page for visual changes and need the latest state
- • The target page updates frequently (dashboards, live data, news)
- • You are running visual regression tests and need pixel-accurate results
Cache details
The cache key is based on all screenshot parameters (URL, format, viewport, device, etc.), so changing any parameter generates a fresh screenshot.
Check the X-Cache response header: HIT means the result came from cache, MISS means a fresh capture was performed.
Cache hits are free. Only fresh captures (cache MISS) count toward your monthly quota.
Use the /v1/screenshot/info endpoint to check if a screenshot is cached without taking a new one.
SDKs
Official client libraries for popular languages. The API is also a standard REST endpoint that works with any HTTP client.
Node.js
npm install snaprender
import SnapRender from 'snaprender';
import fs from 'node:fs';
const snap = new SnapRender({ apiKey: 'sk_live_your_key_here' });
// Capture as buffer
const buffer = await snap.capture({ url: 'https://example.com', format: 'png' });
fs.writeFileSync('screenshot.png', buffer);
// Capture with options
const result = await snap.capture({
url: 'https://github.com',
format: 'jpeg',
fullPage: true,
darkMode: true,
quality: 85,
device: 'iphone_15_pro',
});
fs.writeFileSync('github-mobile-dark.jpg', result);
// Check usage
const usage = await snap.usage();
console.log(`${usage.remaining} screenshots remaining`);
Python
pip install snaprender
from snaprender import SnapRender
snap = SnapRender("sk_live_your_key_here")
# Capture as bytes
data = snap.capture("https://example.com", format="png")
with open("screenshot.png", "wb") as f:
f.write(data)
# Capture with options
data = snap.capture(
"https://github.com",
format="jpeg",
full_page=True,
dark_mode=True,
quality=85,
device="iphone_15_pro",
)
with open("github-mobile-dark.jpg", "wb") as f:
f.write(data)
# Check usage
usage = snap.usage()
print(f"{usage['remaining']} screenshots remaining")
Go
go get github.com/User0856/snaprender-go
package main
import (
"context"
"log"
"os"
snaprender "github.com/User0856/snaprender-go"
)
func main() {
client := snaprender.NewClient("sk_live_your_key_here")
// Capture a screenshot
img, err := client.Capture(context.Background(), "https://example.com", nil)
if err != nil {
log.Fatal(err)
}
os.WriteFile("screenshot.png", img, 0644)
// With options
img, _ = client.Capture(context.Background(), "https://github.com", &snaprender.CaptureOptions{
Format: "jpeg",
DarkMode: snaprender.Bool(true),
Device: "iphone_15_pro",
})
// Generate signed URL
signed, _ := client.Sign(context.Background(), "https://example.com", &snaprender.SignOptions{
ExpiresIn: 86400,
})
log.Println(signed.SignedURL)
}
PHP
composer require snaprender/snaprender # coming soon
use SnapRender\SnapRender;
$snap = new SnapRender('sk_live_your_key_here');
// Capture a screenshot
$image = $snap->capture('https://example.com');
file_put_contents('screenshot.png', $image);
// With options
$image = $snap->capture('https://github.com', [
'format' => 'jpeg',
'dark_mode' => true,
'device' => 'iphone_15_pro',
]);
// Generate signed URL
$signed = $snap->sign('https://example.com', ['expires_in' => 86400]);
echo $signed['signed_url'];
View source on GitHub Coming to Packagist
Ruby
gem install snaprender # coming soon
require "snaprender"
snap = SnapRender.new("sk_live_your_key_here")
# Capture a screenshot
image = snap.capture("https://example.com")
File.binwrite("screenshot.png", image)
# With options
image = snap.capture("https://github.com",
format: "jpeg",
dark_mode: true,
device: "iphone_15_pro"
)
# Generate signed URL
signed = snap.sign("https://example.com", expires_in: 86400)
puts signed["signed_url"]
View source on GitHub Coming to RubyGems
MCP Server
Connect SnapRender to Claude Desktop, Claude Code, Cursor, or any MCP-compatible AI tool. Two options:
Remote (no install required)
Add to your MCP client config (e.g. claude_desktop_config.json):
{
"mcpServers": {
"snaprender": {
"type": "streamable-http",
"url": "https://app.snap-render.com/mcp",
"headers": {
"Authorization": "Bearer sk_live_your_key_here"
}
}
}
}
Local (npm package)
Runs the MCP server locally via npx:
{
"mcpServers": {
"snaprender": {
"command": "npx",
"args": ["-y", "snaprender-mcp"],
"env": {
"SNAPRENDER_API_KEY": "sk_live_your_key_here"
}
}
}
}
Once connected, ask your AI assistant: "Take a screenshot of github.com" and it will call the SnapRender API through MCP.
Code Examples
Basic screenshot
curl "https://app.snap-render.com/v1/screenshot?url=https://example.com&format=png" \
-H "X-API-Key: YOUR_API_KEY" \
--output screenshot.png
Full page capture
curl "https://app.snap-render.com/v1/screenshot?url=https://en.wikipedia.org/wiki/Screenshot&format=png&full_page=true" \
-H "X-API-Key: YOUR_API_KEY" \
--output full-page.png
Dark mode
curl "https://app.snap-render.com/v1/screenshot?url=https://github.com&format=png&dark_mode=true" \
-H "X-API-Key: YOUR_API_KEY" \
--output github-dark.png
Device emulation
curl "https://app.snap-render.com/v1/screenshot?url=https://example.com&device=iphone_15_pro&format=png" \
-H "X-API-Key: YOUR_API_KEY" \
--output mobile.png
Hide elements
curl "https://app.snap-render.com/v1/screenshot?url=https://example.com&format=png&hide_selectors=.header,.footer,%23sidebar" \
-H "X-API-Key: YOUR_API_KEY" \
--output clean.png
PDF generation
curl "https://app.snap-render.com/v1/screenshot?url=https://example.com&format=pdf&full_page=true" \
-H "X-API-Key: YOUR_API_KEY" \
--output page.pdf
JSON response for AI agents
curl "https://app.snap-render.com/v1/screenshot?url=https://example.com&format=png&response_type=json" \
-H "X-API-Key: YOUR_API_KEY"
Returns a JSON object with the screenshot as a base64 data URI in the image field, plus metadata like size, cache status, and remaining credits.
Render HTML (POST)
curl -X POST "https://app.snap-render.com/v1/screenshot" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html":"<html><body><h1>Hello World</h1></body></html>","format":"png","width":800,"height":600}' \
--output screenshot.png
Renders raw HTML content and captures it as a screenshot. Max 2MB. No URL needed.
Render Markdown (POST)
curl -X POST "https://app.snap-render.com/v1/screenshot" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"markdown":"# Hello World\n\nThis is **bold** and this is `code`.","format":"png"}' \
--output screenshot.png
Renders Markdown content with a clean styled template. Supports headings, code blocks, tables, lists, and more. Max 500KB.
Generate Signed URL
curl -X POST "https://app.snap-render.com/v1/screenshot/sign" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","expires_in":86400}'
Returns a signed URL that anyone can use to render the screenshot without an API key. Signing is free. The URL expires after expires_in seconds (default: 1 day, max: 30 days).
Use Signed URL
# No API key needed, just use the signed_url from the sign response
curl "https://app.snap-render.com/v1/screenshot/render?url=...&expires=...&key_prefix=...&sig=..." \
--output screenshot.png
The signed URL can be used in <img> tags, emails, documents, or shared directly. Tampered URLs return 403, expired URLs return 410.
Extract Content (Markdown)
curl "https://app.snap-render.com/v1/extract?url=https://example.com&type=markdown" \
-H "X-API-Key: YOUR_API_KEY"
Extracts readable content from any web page as clean Markdown. Uses Mozilla Readability to strip navigation, ads, and clutter. Also supports: text, html, article, links, metadata.
Extract Article (POST)
curl -X POST "https://app.snap-render.com/v1/extract" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","type":"article"}'
Returns structured article data: title, author, excerpt, content (as Markdown), and word count. The POST endpoint accepts native JSON types.
Extract Metadata
curl "https://app.snap-render.com/v1/extract?url=https://example.com&type=metadata" \
-H "X-API-Key: YOUR_API_KEY"
Extracts page title, description, canonical URL, Open Graph tags (og:title, og:description, og:image, og:url, og:type), and Twitter Card tags.
Batch Screenshots (Create Job)
Submit up to 50 URLs in one request. Returns a job ID for polling.
curl -X POST "https://app.snap-render.com/v1/screenshot/batch" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"urls": ["https://example.com", "https://github.com", "https://google.com"],
"format": "png",
"width": 1280,
"dark_mode": true
}'
Returns 202 with jobId and statusUrl. Each URL consumes one credit; failed URLs get credits rolled back.
Batch Screenshots (Poll Status)
curl "https://app.snap-render.com/v1/screenshot/batch/JOB_ID" \
-H "X-API-Key: YOUR_API_KEY"
Poll until status is completed or failed. Completed items include downloadUrl (presigned, valid 24h). Jobs expire after 24 hours.
Webhooks (Create)
Register a webhook URL to receive event notifications. Payloads are signed with HMAC-SHA256.
curl -X POST "https://app.snap-render.com/v1/webhooks" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhook",
"events": ["screenshot.completed", "quota.warning"]
}'
Save the returned secret (starts with whsec_) to verify incoming webhook signatures.
Verify Webhook Signature (Node.js)
import { createHmac } from 'crypto';
function verifySignature(payload, signature, secret) {
const expected = createHmac('sha256', secret).update(payload).digest('hex');
const sig = signature.replace('sha256=', '');
return sig === expected;
}
// In your webhook handler:
const isValid = verifySignature(
req.body, // raw body string
req.headers['x-snaprender-signature'], // sha256=...
'whsec_your_webhook_secret'
);
Events: screenshot.completed (batch job done), quota.warning (80% used), quota.exceeded (100% used). Failed deliveries retry up to 5 times with exponential backoff.
Node.js (fetch, no SDK)
const url = new URL('https://app.snap-render.com/v1/screenshot');
url.searchParams.set('url', 'https://example.com');
url.searchParams.set('format', 'png');
url.searchParams.set('dark_mode', 'true');
const res = await fetch(url, {
headers: { 'X-API-Key': 'YOUR_API_KEY' },
});
const buffer = Buffer.from(await res.arrayBuffer());
fs.writeFileSync('screenshot.png', buffer);
console.log(`Cache: ${res.headers.get('x-cache')}, Remaining: ${res.headers.get('x-remaining-credits')}`);
Python (requests, no SDK)
import requests
response = requests.get(
"https://app.snap-render.com/v1/screenshot",
params={"url": "https://example.com", "format": "png", "dark_mode": "true"},
headers={"X-API-Key": "YOUR_API_KEY"},
)
response.raise_for_status()
with open("screenshot.png", "wb") as f:
f.write(response.content)
print(f"Cache: {response.headers['X-Cache']}, Remaining: {response.headers['X-Remaining-Credits']}")
Check usage
curl "https://app.snap-render.com/v1/usage" \
-H "X-API-Key: YOUR_API_KEY"
Ready to start?
Create a free account and get your API key in 30 seconds.