API reference
Snapshot for developers & agents
Base URL https://snapshot.ounie.com · JSON everywhere · every capture draws Ounie credits and is refused rather than overdrawn.
Authentication
Three credentials work, and all three resolve to one Ounie account whose wallet pays for the call.
snp_live_…Mint one at /dashboard/api-keys (max 5 active, shown once). Send it as Authorization: Bearer snp_live_…or, where headers can’t be set, as ?api_key=snp_live_….
ounie_live_…Your ounie.com developer key works across the Ounie app fleet once you enable “Use across Ounie apps” in ounie.com settings. Same header, same wallet.
The shared .ounie.com session. This is what the dashboard uses; browsers get it automatically after signing in.
Take a capture
/api/capturesbills creditsOne call renders the page and returns a signed URL valid for 24 hours. Synchronous — expect a few seconds.
Body
| Field | Type | Default | Notes |
|---|---|---|---|
| url | string | — | Required. Scheme optional. Must resolve to a public address. |
| mode | "viewport" | "fullpage" | "pdf" | "text" | "viewport" | What you get back. |
| width | int | 1440 | 320–3840. Image modes only. |
| height | int | 900 | 240–4320. Viewport mode only. |
| deviceScale | int | 1 | 1–2. Pixel density. |
| waitMs | int | 0 | 0–8000. Extra settle time before the shutter. |
| darkMode | bool | false | Best effort — see the note below. |
| blockCookieBanners | bool | true | Remove common consent overlays. |
Out-of-range numbers are clamped rather than rejected, so a caller that asks for a width of 99999 gets 3840 and a capture, not a 400.
Modes
| Mode | Credits | x402 | Returns |
|---|---|---|---|
| viewport | 2 | $0.024 | One screen, exactly as a visitor first sees it. Pick any width and height. |
| fullpage | 3 | $0.036 | The entire scroll height stitched into one tall image, header to footer. |
| 3 | $0.036 | The browser's own print render — real pages, selectable text, ready to attach. | |
| text | 2 | $0.024 | The rendered DOM as clean text, after JavaScript has run. No pixels, just words. |
curl -X POST https://snapshot.ounie.com/api/captures \
-H "Authorization: Bearer snp_live_…" \
-H "Content-Type: application/json" \
-d '{
"url": "stripe.com/pricing",
"mode": "fullpage",
"width": 1440,
"waitMs": 1000
}'201 Created
{
"id": "3f9c1b2a-…",
"url": "https://…s3…?X-Amz-Signature=…", // valid 24h
"expires_in_seconds": 86400,
"mode": "fullpage",
"source_url": "https://stripe.com/pricing",
"final_url": "https://stripe.com/pricing",
"title": "Pricing & Fees | Stripe",
"label": "stripe.com/pricing",
"content_type": "image/png",
"bytes": 1841203,
"width": 1440,
"height": 7362,
"requested": { "width": 1440, "height": 900, "device_scale": 1,
"wait_ms": 1000, "dark_mode": false,
"block_cookie_banners": true },
"credits_spent": 3,
"created_at": "2026-08-22T21:40:11.204Z"
}Dark mode is best effort
prefers-color-scheme. What darkMode: true does is set the signals modern sites actually read — the .dark class, data-theme="dark", color-scheme, and the usual localStorage keys — then let the page’s own theme code react. Sites that key only off the media query come back light. We would rather say that here than have you discover it in a capture.Public addresses only
invalid_request. A capture service that will fetch anything on request is an SSRF proxy.Read your captures
Everything in this section is free. Reading work you already paid for never bills again.
/api/capturesfreeNewest first. Optional ?limit= (1–100, default 25), ?offset= and ?mode=.
/api/captures/{id}freeOne capture plus a freshly signed URL. Links are minted per request rather than stored, so a link that leaks expires within a day and every read re-checks that the capture is yours.
/api/captures/{id}freeDeletes the row and the stored artifact.
/api/creditsfree/api/pricingpublicMCP server
Snapshot speaks MCP over Streamable HTTP at /api/mcp (legacy SSE at /api/sse). Works with Claude, Cursor, the AI SDK, and the Ounie AI Team.
Endpoint https://snapshot.ounie.com/api/mcp
Header Authorization: Bearer snp_live_… (or ounie_live_…)
# Hosts that can't set headers — the Ounie AI Team's manual
# HTTP MCP entry, for one — put the key in the URL instead:
https://snapshot.ounie.com/api/mcp?api_key=snp_live_…| Tool | What it does |
|---|---|
| capture_url | Render a page and return a 24h signed URL (plus the text inline, in text mode). Bills 2–3 credits. |
| get_capture | Fetch one of your captures by id with a fresh signed URL. Free. |
| list_captures | List your recent captures, newest first. Free. |
| get_credit_balance | Your spendable Ounie credits. Free. |
| get_pricing | Per-mode prices on both rails. Public — no auth. |
| whoami | The authenticated key's owner and key id. |
Credit safety is server-side: capture_url reserves before it renders, so an agent that runs out of credits gets an insufficient_credits error with a top-up link and no capture happens. It can never overdraw.
x402 — pay per call, no account
/api/x402/captureUSDC on BaseFor agents with a funded wallet and no Ounie account. Same body as /api/captures. Call it without a payment header and you get a 402 quoting the exact price for the mode you asked for; sign it and retry with X-Payment.
| Mode | Price |
|---|---|
| viewport | 0.024 USDC (24000 atomic) |
| fullpage | 0.036 USDC (36000 atomic) |
| 0.036 USDC (36000 atomic) | |
| text | 0.024 USDC (24000 atomic) |
# 1. quote
curl -X POST https://snapshot.ounie.com/api/x402/capture \
-H "Content-Type: application/json" \
-d '{"url":"stripe.com","mode":"viewport"}'
# → 402 { "x402Version": 1, "accepts": [{ "scheme":"exact",
# "network":"base", "maxAmountRequired":"24000",
# "asset":"0x8335…2913", "payTo":"0x…", "extra":{…} }] }
# 2. sign the authorization, then retry
curl -X POST https://snapshot.ounie.com/api/x402/capture \
-H "X-Payment: <base64 payload>" \
-H "Content-Type: application/json" \
-d '{"url":"stripe.com","mode":"viewport"}'We render before we settle
"settled": false and your signed authorization goes unused. You are never charged for a capture that produced nothing.The 402 names the network as the bare base (the naming scheme selects the protocol version), quotes USDC at 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913, and carries the USDC EIP-712 domain in extra so your signature verifies on chain.
Errors
Every error is JSON with an error code and usually a detail. The rule that matters: if there are no bytes, there is no charge — the reserve is refunded to the exact pools it came from before the response is written.
| Code | Status | Meaning |
|---|---|---|
| unauthorized | 401 | Missing or invalid credential — send a session cookie, a snp_live_ Bearer, or ?api_key=. |
| invalid_request | 400 | The URL or an option was rejected. `detail` says which. |
| insufficient_credits | 402 | Wallet too short. Carries required_credits, balance_credits and buy_credits_url. No capture ran. |
| too_many_running | 429 | More than 5 captures in flight for this owner. |
| capture_failed | 502 | The page could not be rendered. Nothing was billed. |
| capture_timeout | 504 | The page took too long. Nothing was billed. |
| thin_result | 502 | The render produced no bytes (or no text). Nothing was billed. |
| capture_too_large | 413 | The artifact exceeded the size limit. Nothing was billed. |
| capture_unavailable | 503 | The renderer is temporarily unavailable. Nothing was billed. |
Out of credits? Top up at ounie.com.