API Reference
The Screenmint base URL is https://api.screenmint.dev. Every endpoint accepts and returns JSON. Render a page in one call: you get back a hosted image URL, ready to drop into an <img> tag or a meta tag.
curl -X POST https://api.screenmint.dev/v1/screenshot \
-H "X-API-Key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'{
"url": "https://cdn.example/screenshots/9f2c….jpeg",
"width": 1280,
"height": 720,
"cached": false,
"render_ms": 1240
}Authentication
Authenticate every request with an X-API-Key header. Keys are created and revoked in the dashboard; the plaintext key is shown exactly once at creation, so store it somewhere safe.
X-API-Key: sk_live_...
Rate limits & caching
Two limits apply: a per-minute request rate (per API key, fixed one-minute windows) and a monthly render quota (per account, resets on the 1st, UTC).
| Plan | Requests / min | Renders / month |
|---|---|---|
| Free | 10 | 100 |
| Starter | 60 | 2,000 |
| Pro | 300 | 20,000 |
Identical requests are cached — screenshots for 1 hour, OG cards for 24 hours. Cache hits return in milliseconds with "cached": true and do not consume monthly renders (they still count toward the per-minute rate).
Errors
Errors return a JSON envelope with a stable error code, an optional human-readable message, and reset_at on 429s.
{
"error": "rate_limit_exceeded",
"reset_at": "2026-07-22T18:04:00.000Z"
}| Status | Code | When |
|---|---|---|
| 400 | invalid_params | A body field failed validation — the message field says which one and why. |
| 400 | invalid_url | The URL is not valid http(s), or it resolves to a private / internal address. |
| 401 | invalid_api_key | The X-API-Key header is missing or not recognized. |
| 422 | unrenderable_url | The host does not resolve, or the page failed to load within 15 seconds. |
| 429 | rate_limit_exceeded | Per-minute request limit reached. reset_at is when the window resets. |
| 429 | monthly_quota_exceeded | Monthly render quota used up. reset_at is the 1st of next month (UTC). |
| 503 | service_unavailable | All renderers are busy. Retry with exponential backoff. |
POST/v1/screenshot
Capture a screenshot of any public URL. The page gets a fresh, isolated browser context, waits for the load event (up to 15 seconds), then captures, optimizes, and hosts the image for you.
| Parameter | Type | Default | Description |
|---|---|---|---|
| url required | string | — | Page to capture. http(s) only; private and internal hosts are rejected. |
| width | integer | 1280 | Viewport width in pixels. Max depends on plan: 1280 (Free), 1920 (Starter), 2560 (Pro). |
| height | integer | 720 | Viewport height in pixels. Max depends on plan: 1280 (Free), 1080 (Starter), 1440 (Pro). |
| scale | integer | 1 | Device pixel ratio. 2 = retina/HiDPI (doubles output resolution). Pro plan only. |
| full_page | boolean | false | Capture the full scroll height instead of just the viewport (auto-scrolls to load lazy content). Capped at 20000px. |
| format | string | "jpeg" | Output: "jpeg", "png", "webp", "avif", or "pdf". |
| quality | integer | 85 | Quality 1–100 for jpeg/webp/avif. Ignored for png/pdf. |
| delay | integer | 0 | Extra wait after page load — useful for animations and late JS. Max depends on plan: 5s (Free), 15s (Starter), 25s (Pro). |
| dark_mode | boolean | false | Emulate prefers-color-scheme: dark. |
| wait_for_selector | string | — | Wait until this CSS selector appears before capturing (up to 15s). |
| selector | string | — | Capture only the element matching this CSS selector, instead of the page. |
| hide_selectors | string[] | — | CSS selectors to hide before capture (e.g. modals, chat widgets). |
| block_cookies | boolean | false | Hide common cookie/consent banners. |
| block_ads | boolean | false | Block requests to known ad/tracker/analytics hosts. |
| inject_css | string | — | Custom CSS injected before capture. |
| inject_js | string | — | Custom JavaScript run in the page before capture. |
curl -X POST https://api.screenmint.dev/v1/screenshot \
-H "X-API-Key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"width": 1280,
"height": 720,
"full_page": false,
"format": "jpeg",
"quality": 85,
"delay": 0,
"dark_mode": false
}'{
"url": "https://cdn.example/screenshots/9f2c….jpeg",
"width": 1280,
"height": 720,
"cached": false,
"render_ms": 1240
}POST/v1/og
Generate a branded 1200×630 Open Graph card from text and colors — no design tools, no templates to host. Output is an optimized JPEG, cached for 24 hours.
| Parameter | Type | Default | Description |
|---|---|---|---|
| title required | string | — | Card headline, up to 80 characters. Clamped to 2 lines. |
| description | string | — | Supporting text, up to 160 characters. Clamped to 2 lines. |
| site_name | string | — | Small accent-colored label under the text, up to 40 characters. |
| logo_url | string | — | A direct, publicly-reachable image URL (png/jpg/svg/webp) or a data: URI — shown above the title at 40 px. Not a webpage, and the host must allow hotlinking. |
| bg_url | string | — | Screenshot this website and use it as the card background, with your text overlaid. |
| bg_image | string | — | Use this image URL (or data: URI) as the card background instead of a color. |
| overlay | number | 0.45 | Darkening scrim over a background image, 0–1, for text legibility. |
| bg_color | string | "#ffffff" | Background color, hex (ignored when a background image is set). |
| accent_color | string | "#000000" | Top bar and site_name color, hex. |
| text_color | string | "#000000" | Title and description color, hex (defaults to white over a background image). |
curl -X POST https://api.screenmint.dev/v1/og \
-H "X-API-Key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"title": "How we scaled to 1M renders",
"description": "Lessons from running headless Chromium in production.",
"site_name": "Engineering Blog",
"bg_color": "#0F0F11",
"accent_color": "#8B5CF6",
"text_color": "#ffffff"
}'{
"url": "https://cdn.example/og/def4….jpeg",
"cached": false,
"render_ms": 380
}POST/v1/video
Record a page as a short video — great for capturing animations or a scroll-through of a long page. Output is mp4 (default), webm, or an animated gif.
| Parameter | Type | Default | Description |
|---|---|---|---|
| url required | string | — | Page to record. |
| seconds | integer | 5 | Recording length. Max: 5s (Free), 15s (Starter), 30s (Pro). |
| format | string | "mp4" | Output: "mp4", "webm", or "gif". |
| scroll | boolean | false | Smoothly scroll from top to bottom over the duration. |
| width | integer | 1280 | Frame width (plan-capped, same as screenshots). |
| height | integer | 720 | Frame height (plan-capped). |
| delay | integer | 0 | Wait after load before recording starts. |
| dark_mode | boolean | false | Emulate prefers-color-scheme: dark. |
| block_ads | boolean | false | Block ad/tracker hosts. |
| block_cookies | boolean | false | Hide common cookie banners. |
POST https://api.screenmint.dev/v1/video
{ "url": "https://stripe.com", "seconds": 6, "scroll": true, "format": "mp4" }
→ { "url": "https://cdn.example/videos/e46e….mp4",
"width": 1280, "height": 720, "seconds": 6, "render_ms": 7100 }credits field.POST/v1/batch
Queue up to 50 renders in one call. Each item is a screenshot (default) or an OG card ("kind": "og") and takes the same params as the single endpoints — including template. You get a job_id back immediately (202); poll it or listen for the batch.completed webhook.
POST https://api.screenmint.dev/v1/batch
{
"items": [
{ "url": "https://a.com", "full_page": true },
{ "url": "https://b.com", "format": "pdf" },
{ "kind": "og", "template": "e7c1…", "title": "Hello" }
]
}
→ 202 { "job_id": "a3d6…", "status": "processing", "total": 3,
"poll": "/v1/batch/a3d6…" }{
"status": "completed", "total": 3, "completed": 3, "failed": 0,
"items": [
{ "idx": 0, "kind": "screenshot", "status": "done", "result_url": "https://…" },
{ "idx": 1, "kind": "screenshot", "status": "done", "result_url": "https://…" },
{ "idx": 2, "kind": "og", "status": "done", "result_url": "https://…" }
]
}failed with the reason.Templates
Save a reusable set of defaults in the dashboard (an OG-card design or a screenshot config), then reference it by id with template. Any fields you also pass in the request override the template — so you design once and vary only what changes per render.
POST https://api.screenmint.dev/v1/og
{
"template": "e7c1…", // your saved OG template
"title": "This week's post" // overrides the template's title
}/v1/screenshot, /v1/og, and signed URLs. A screenshot template can even omit the URL and take it per-call.Signed URLs
Render by hitting a URL directly — perfect for an <img src> or an og:image meta tag, with no API key header and no server round-trip. Pass your render options as query params plus a token (your key’s signing token) and a sig (an HMAC over the params). On success it 302-redirects to the hosted image, so browsers and CDNs cache it normally.
Build the signature server-side (the signing secret must never ship to the browser): take every param except sig, sort by key, join as encodeURIComponent(k)=encodeURIComponent(v) with &, and HMAC-SHA256 it with your signing secret (hex).
import crypto from 'node:crypto';
const TOKEN = '2b9f…'; // key's signing token (public)
const SECRET = 'a1b2c3…'; // signing secret (server-side only)
function signedUrl(params) {
const q = { token: TOKEN, ...params };
const canonical = Object.keys(q).sort()
.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(q[k]))
.join('&');
const sig = crypto.createHmac('sha256', SECRET).update(canonical).digest('hex');
return `https://api.screenmint.dev/v1/screenshot?${canonical}&sig=${sig}`;
}
// Drop straight into HTML:
// <img src={signedUrl({ url: 'https://stripe.com', width: 1200, full_page: true })} />/v1/og. Signed renders count against your quota and rate limit exactly like keyed requests. Get your token and secret from API Keys (secret is shown once at creation).GET/v1/usage
Check your current billing period programmatically — handy for alerting before you hit the quota.
curl https://api.screenmint.dev/v1/usage \
-H "X-API-Key: sk_live_..."{
"plan": "starter",
"renders_used": 487,
"renders_limit": 2000,
"period_start": "2026-07-01T00:00:00.000Z",
"period_end": "2026-08-01T00:00:00.000Z"
}