Limits

What counts, what is kept, and for how long.

Limits

What the DOM engine cannot capture

The default engine rebuilds your page from the DOM and computed styles. That is what makes it fast and prompt-free, and it also means a few things are genuinely invisible to it:

OptionTypeDefaultDescription
Cross-origin iframesblankdisplay-mediaSame-origin frames are captured. Anything else the browser will not let us read, so it renders empty — Stripe Elements, embedded maps, third-party video players.
Video framesposter onlydisplay-mediaA <video> renders as its poster, not the current frame. Draw the frame to a canvas yourself first if you need it.
Tainted canvasesblank—A canvas that has drawn a cross-origin image without CORS cannot be read back. Serve those images with Access-Control-Allow-Origin.
WebGL without preserveDrawingBufferblank—The buffer is cleared after each frame unless the context was created with preserveDrawingBuffer: true.
Shadow DOM (closed)blank—Open shadow roots are captured. Closed ones cannot be read at all.
Native UIblankdisplay-mediaSelect dropdowns while open, the print dialog, browser chrome, extensions — none of it is in the DOM.

Where the table says display-media, switching engine fixes it: that path captures real pixels including everything above. The trade is a browser permission prompt, and the user choosing what to share.

Browser support

Chrome, Edge, Firefox and Safari, current and previous major versions. PDF output uses CompressionStream where available and falls back to embedding a JPEG where it is not, so the file is always valid — just larger on older Safari. There is no IE support and there will not be.

What the capture counter counts

With uploads off, no webhook fires. Delivery is decided server-side from the stored capture, so if nothing is stored there is nothing to deliver and nothing appears in your dashboard. The render still counts.

The editor reflects this rather than hiding it: its confirm button reads Done instead of Upload, and the SDK logs a one-time console warning — so it is not something you find out from an empty dashboard three days later.

It counts renders, not stored files. Rendering is the work, and a capture you download costs us what one you upload costs, so both count. That makes the number on your dashboard bigger than the list of captures underneath it, for two separate and entirely normal reasons:

  • Renders you kept. download(), toCanvas(), toBlob(), or any capture with uploads off. Counted, never stored, so there is nothing to list.
  • Renders that expired. Uploaded and stored, then deleted at the end of the retention window below. They stay in the month’s count; they leave the list after three days.

The Overview stat breaks the total into those parts, so the two numbers always reconcile. If you want a render that is not counted at all, the module-level renderToCanvas() has no key and no client and reports nothing.

Retention — three days, seven at most

Captures are deleted from our storage after three days. Set retention_days on a project to choose anything from one to seven. Seven is the ceiling for every account and no plan raises it — we hold pictures of your users’ screens, and a window we can defend is worth more than one that flatters a pricing page.

What that means in practice:

OptionTypeDefaultDescription
Signed URLsstringcapture lifeClamped to the life of the file they point at, so a project on three-day retention never receives a seven-day link. A URL that looks valid and 404s when you finally use it is worse than a short one.
Failed deliveriesresendablecapture lifeKept with their response so you can resend by hand for as long as the files exist. Nothing retries automatically.
Capture recordrow3–7 daysRemoved with the files. Dashboard history goes with it — usage counts survive, aggregated by day.

So if you need a capture kept, copy it somewhere of your own when the webhook arrives. Do not treat a signed URL as durable storage and do not store one for later — fetch the capture by id with a secret key instead, which mints a fresh URL for as long as the capture exists.

Size and quota

Output is capped at maxDimension pixels per side (16384 by default) because browsers refuse to allocate canvases much beyond that. A very long page is scaled to fit rather than failing. Per-project monthly capture limits are set in Settings; exceeding one returns rate_limited rather than silently dropping captures.

Content Security Policy

The SDK inlines images and fonts as data URLs and rasterizes through an SVG foreignObject, so a strict CSP needs to allow both:

img-src 'self' data: blob:;
      font-src 'self' data:;
      connect-src 'self' https://api.screen2api.com;

If you load the bundle from our CDN rather than your own, add https://screen2api.com to script-src and keep the integrity hash — it is what makes that safe.