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"}'
Response 200
{
  "url": "https://cdn.example/screenshots/9f2c….jpeg",
  "width": 1280,
  "height": 720,
  "cached": false,
  "render_ms": 1240
}
New accounts include 100 free renders per month — no card required. Create an account and grab your key from the dashboard.

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_...
Keep keys server-side. Anyone with your key can spend your render quota — never embed it in client-side JavaScript or a public repo.

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).

PlanRequests / minRenders / month
Free10100
Starter602,000
Pro30020,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).

Don’t render URLs that contain secrets (session tokens, signed query params, API keys). Results are cached and hosted at stable, publicly-reachable URLs derived from the request parameters — so a URL with a secret produces an image anyone requesting the same URL can retrieve. Render public pages, or ones you’ve authorized another way.

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"
}
StatusCodeWhen
400invalid_paramsA body field failed validation — the message field says which one and why.
400invalid_urlThe URL is not valid http(s), or it resolves to a private / internal address.
401invalid_api_keyThe X-API-Key header is missing or not recognized.
422unrenderable_urlThe host does not resolve, or the page failed to load within 15 seconds.
429rate_limit_exceededPer-minute request limit reached. reset_at is when the window resets.
429monthly_quota_exceededMonthly render quota used up. reset_at is the 1st of next month (UTC).
503service_unavailableAll 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.

ParameterTypeDefaultDescription
url requiredstringPage to capture. http(s) only; private and internal hosts are rejected.
widthinteger1280Viewport width in pixels. Max depends on plan: 1280 (Free), 1920 (Starter), 2560 (Pro).
heightinteger720Viewport height in pixels. Max depends on plan: 1280 (Free), 1080 (Starter), 1440 (Pro).
scaleinteger1Device pixel ratio. 2 = retina/HiDPI (doubles output resolution). Pro plan only.
full_pagebooleanfalseCapture the full scroll height instead of just the viewport (auto-scrolls to load lazy content). Capped at 20000px.
formatstring"jpeg"Output: "jpeg", "png", "webp", "avif", or "pdf".
qualityinteger85Quality 1–100 for jpeg/webp/avif. Ignored for png/pdf.
delayinteger0Extra wait after page load — useful for animations and late JS. Max depends on plan: 5s (Free), 15s (Starter), 25s (Pro).
dark_modebooleanfalseEmulate prefers-color-scheme: dark.
wait_for_selectorstringWait until this CSS selector appears before capturing (up to 15s).
selectorstringCapture only the element matching this CSS selector, instead of the page.
hide_selectorsstring[]CSS selectors to hide before capture (e.g. modals, chat widgets).
block_cookiesbooleanfalseHide common cookie/consent banners.
block_adsbooleanfalseBlock requests to known ad/tracker/analytics hosts.
inject_cssstringCustom CSS injected before capture.
inject_jsstringCustom 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
  }'
Response 200
{
  "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.

ParameterTypeDefaultDescription
title requiredstringCard headline, up to 80 characters. Clamped to 2 lines.
descriptionstringSupporting text, up to 160 characters. Clamped to 2 lines.
site_namestringSmall accent-colored label under the text, up to 40 characters.
logo_urlstringA 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_urlstringScreenshot this website and use it as the card background, with your text overlaid.
bg_imagestringUse this image URL (or data: URI) as the card background instead of a color.
overlaynumber0.45Darkening scrim over a background image, 0–1, for text legibility.
bg_colorstring"#ffffff"Background color, hex (ignored when a background image is set).
accent_colorstring"#000000"Top bar and site_name color, hex.
text_colorstring"#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"
  }'
Response 200
{
  "url": "https://cdn.example/og/def4….jpeg",
  "cached": false,
  "render_ms": 380
}
Want to see it before you call it? Design your card visually in the free OG playground — it generates the exact API request for you.

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.

ParameterTypeDefaultDescription
url requiredstringPage to record.
secondsinteger5Recording length. Max: 5s (Free), 15s (Starter), 30s (Pro).
formatstring"mp4"Output: "mp4", "webm", or "gif".
scrollbooleanfalseSmoothly scroll from top to bottom over the duration.
widthinteger1280Frame width (plan-capped, same as screenshots).
heightinteger720Frame height (plan-capped).
delayinteger0Wait after load before recording starts.
dark_modebooleanfalseEmulate prefers-color-scheme: dark.
block_adsbooleanfalseBlock ad/tracker hosts.
block_cookiesbooleanfalseHide 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 }
Max length is plan-based: 5s (Free), 15s (Starter), 30s (Pro). Because video is far more compute-intensive, it costs 1 credit per 2 seconds (rounded up) against your monthly quota — e.g. a 10s video = 5 credits. Screenshots and OG cards are 1 credit each. The response includes a 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…" }
GET /v1/batch/:id
{
  "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://…" }
  ]
}
Each item counts against your monthly quota as it renders. If you hit the quota mid-batch, the remaining items are marked 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
}
Templates work on /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 })} />
The same scheme works for /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_..."
Response 200
{
  "plan": "starter",
  "renders_used": 487,
  "renders_limit": 2000,
  "period_start": "2026-07-01T00:00:00.000Z",
  "period_end": "2026-08-01T00:00:00.000Z"
}