Skip to content
Snapshot

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.

App key · 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_….

Master key · 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.

Session cookie

The shared .ounie.com session. This is what the dashboard uses; browsers get it automatically after signing in.

Take a capture

POST/api/capturesbills credits

One call renders the page and returns a signed URL valid for 24 hours. Synchronous — expect a few seconds.

Body

FieldTypeDefaultNotes
urlstringRequired. Scheme optional. Must resolve to a public address.
mode"viewport" | "fullpage" | "pdf" | "text""viewport"What you get back.
widthint1440320–3840. Image modes only.
heightint900240–4320. Viewport mode only.
deviceScaleint11–2. Pixel density.
waitMsint00–8000. Extra settle time before the shutter.
darkModeboolfalseBest effort — see the note below.
blockCookieBannersbooltrueRemove 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

ModeCreditsx402Returns
viewport2$0.024One screen, exactly as a visitor first sees it. Pick any width and height.
fullpage3$0.036The entire scroll height stitched into one tall image, header to footer.
pdf3$0.036The browser's own print render — real pages, selectable text, ready to attach.
text2$0.024The 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

Nothing running inside a page can change the operating system’s 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

A URL that resolves to localhost, a private range, or the cloud metadata address is rejected with 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.

GET/api/capturesfree

Newest first. Optional ?limit= (1–100, default 25), ?offset= and ?mode=.

GET/api/captures/{id}free

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

DELETE/api/captures/{id}free

Deletes the row and the stored artifact.

GET/api/creditsfree
GET/api/pricingpublic

MCP 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_…
ToolWhat it does
capture_urlRender a page and return a 24h signed URL (plus the text inline, in text mode). Bills 2–3 credits.
get_captureFetch one of your captures by id with a fresh signed URL. Free.
list_capturesList your recent captures, newest first. Free.
get_credit_balanceYour spendable Ounie credits. Free.
get_pricingPer-mode prices on both rails. Public — no auth.
whoamiThe 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

POST/api/x402/captureUSDC on Base

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

ModePrice
viewport0.024 USDC (24000 atomic)
fullpage0.036 USDC (36000 atomic)
pdf0.036 USDC (36000 atomic)
text0.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

A settlement on chain is final and has no refund path, so the order here is verify → render → settle. If the page won’t load, the response says "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.

CodeStatusMeaning
unauthorized401Missing or invalid credential — send a session cookie, a snp_live_ Bearer, or ?api_key=.
invalid_request400The URL or an option was rejected. `detail` says which.
insufficient_credits402Wallet too short. Carries required_credits, balance_credits and buy_credits_url. No capture ran.
too_many_running429More than 5 captures in flight for this owner.
capture_failed502The page could not be rendered. Nothing was billed.
capture_timeout504The page took too long. Nothing was billed.
thin_result502The render produced no bytes (or no text). Nothing was billed.
capture_too_large413The artifact exceeded the size limit. Nothing was billed.
capture_unavailable503The renderer is temporarily unavailable. Nothing was billed.

Out of credits? Top up at ounie.com.