API reference

Every option, method and result, and every error you can be handed.

API reference

createClient options

Settings for the client itself, as opposed to one capture.

OptionTypeDefaultDescription
publishableKeystring—Your pk_live_… or pk_test_… key. Safe to ship in a browser bundle. Without one, nothing is uploaded and nothing counts against your allowance — captures still render and can still be downloaded.
upload.enabledbooleantrueSet false to skip the network entirely: the capture renders, you get the blobs, and nothing reaches our servers — no storage, no dashboard entry, no quota used. This is the setting to reach for when the pixels must not leave the browser. Not to be confused with capture({ webhook: false }), which still uploads and only suppresses delivery.
upload.apiHoststring'https://api.screen2api.com'Override the ingest host. For self-hosted or proxied setups.
captureCaptureOptions{}Defaults for every capture() call, overridden per call. Everything in the table below can go here.
editorEditorOptions{}Whether the annotation editor opens, and how it looks — tools, palette, labels, theme and branding. Project settings apply underneath.
pdfPdfOptions{}PDF defaults for every capture and for the editor’s own Save a copy, so the file your user downloads matches the one your webhook receives.
watermarkstring | WatermarkOptions—Watermark every capture, in every format. Usually a policy rather than a per-call decision, which is why it belongs here.
onComplete(result: CaptureResult) => void—Fired once the upload finishes and the webhook has been queued.
onCancel() => void—Fired when the user dismisses the editor. capture() resolves to null in that case rather than rejecting — a cancel is not an error.
onError(error: Screen2ApiError) => void—Fired on any failure, in addition to the promise rejecting. Useful for logging without wrapping every call.
debugbooleanfalseLogs a collapsed group per capture with stage timings and any warnings. The first thing to turn on when something looks wrong.

capture options

Every option below can be passed per call to capture(), or set once as capture on createClient() and overridden per call. Project defaults set in the dashboard apply underneath both.

OptionTypeDefaultDescription
targetstring | Element | (() => Element | null) | nullnullCSS selector, element, or a function resolved at capture time. Omitted means the whole page. A selector matching several elements captures the first — use captureAll() for one per match.
area'element' | 'viewport' | 'document''element' / 'document'What to include. element is the target’s full box including parts scrolled out of view, and the default when you pass a target. document is the whole scrollable page, and the default when you don’t. viewport stops at the fold.
engine'dom' | 'display-media''dom'dom clones the DOM and rasterizes it, with no permission prompt. display-media uses the Screen Capture API — pixel-exact including plugins and cross-origin iframes, but the browser asks the user for consent and they choose what to share.
formatsFileFormat[]['png']Any of png, jpeg, webp, svg, pdf, html, csv. The scene renders once and encodes N times, so asking for three formats costs far less than three captures.
expandScrollbooleanfalseCapture scrollable regions in full instead of only the part on screen. Off by default, because a screenshot should show what the person is looking at. Turn it on when the capture is a record — a chart, a table, a report — and the whole thing matters more than the scroll position. Either way the capture warns in result.warnings when a region is hiding content.
scalenumberdevicePixelRatio, capped at 3Pixel density multiplier. 2 on a normal display gives retina-sharp output at four times the bytes. Your project has a max_scale that clamps this.
qualitynumber (0–1)0.92For lossy formats only — jpeg and webp. Ignored by png and svg.
backgroundstring | nullthe element's own backgroundPainted under the capture. Pass null for transparency, which png and webp keep and jpeg cannot.
excludestring[][]Selectors removed from the capture entirely, as though they were never in the page.
redactstring[][]Selectors pixelated before encoding. The original pixels are destroyed in the browser and never transmitted.
paddingnumber (CSS px)12Blank margin around the capture, filled with the same background the capture resolved — so it reads as the element having room, not as a frame. Content touching the border looks cropped, especially on a light page. Set 0 for flush edges, which is what you want when the capture is being composited into something else.
maxDimensionnumber16384Hard cap on output pixels per side. Browsers refuse to allocate canvases much beyond this, so a very long page is scaled down rather than failing.
waitForAssetsbooleantrueWait for webfonts and images to settle before capturing. Turning it off is faster and risks capturing a half-loaded page.
assetTimeoutnumber (ms)3000How long to wait before giving up on assets and capturing anyway. A capture missing one image beats no capture.
tolerateAssetErrorsbooleanfalseSkip cross-origin images that fail to fetch instead of failing the capture. Worth enabling if you embed third-party avatars.
watermarkstring | WatermarkOptions—Stamps text across the capture in every format. Set repeat: true to tile it, which is what survives a crop — see Watermarking. Also settable once on the client.
pdfPdfOptions—Page size, orientation, fit, margins, background, header, logo, footer and watermark. Ignored unless pdf is in formats — see PDF options.
paramsRecord<string, unknown>{}Your own metadata, echoed back verbatim on the webhook. This is how you tie a capture to an invoice, a ticket or a user without a second lookup.
webhookbooleantrueDeliver this capture to your endpoints. Set false for an export meant only for the person who clicked it. The capture is still stored and still appears in your dashboard — use download() to keep it off our servers entirely.
captionstring—Preset the caption, or read what the end user typed from the result.
filenamestringderived from document.titleStem for downloads and uploads. The extension is added per format.
onProgress(progress: number, stage: string) => void—Called with 0–1 and a stage label. Worth wiring for whole-page captures, which can take a second or two.
csvCsvOptions—Settings for the csv format. Ignored unless csv is in formats.

The result

capture() resolves with a CaptureResult, or null when the user dismissed the editor. Dismissal is a normal outcome, not an error, so it does not reject — check for null before using the result.

const result = await s2a.capture({ target: '#invoice', formats: ['png', 'pdf'] })
      if (!result) return   // the user closed the editor

      result.id                    // 'cap_9f3c…', once uploaded
      result.files                 // RenderedFile[]
      result.file('pdf')?.blob     // the first file of a format
      result.file('png')?.url      // remote URL, present only after upload
      result.width, result.height  // output pixels
      result.scale                 // the density actually used
      result.timings               // { render: 412, encode: 88, upload: 210 }
      result.warnings              // e.g. an image that could not be inlined
      result.uploaded              // false when enabled:false or no key

      await result.copy()          // PNG to the clipboard; false if the browser refused
      result.dispose()             // revoke every object URL this result holds

Object URLs are not garbage collected — call dispose() when you’re finished, or a long-lived page that captures repeatedly will hold every blob it ever made.

Client methods

OptionTypeDefaultDescription
capture(options?)Promise<CaptureResult | null>—Render, optionally annotate, encode and upload. Null when the editor is dismissed.
captureAll(options?)Promise<{ results, errors }>—One capture per element matching the selector, run sequentially so the page stays responsive. Skips the editor. One failure does not abandon the rest.
download(options?)Promise<CaptureResult | null>—Capture and trigger a browser download. Skips the network entirely by default, so nothing reaches us — pass { upload: true } when the export is for the user and the capture belongs in your records, and it uploads and fires your webhooks as well. Counts against your allowance either way.
toCanvas(options?)Promise<HTMLCanvasElement>—Render only — no encoding, no upload. For custom pipelines.
toBlob(format?, options?)Promise<Blob>'png'One encoded blob, nothing else.
attach(trigger, options?)() => void—Wire a button to a capture. Returns a function that unbinds it — call it on unmount.
autoBind(options?, root?)() => voiddocumentActivate every [data-screen2api] element. Returns a function that removes everything it added.
preload(target?)Promise<void>—Warm the font and asset caches so the first real capture is not the slow one. Call it on idle, or when the user hovers your export button.
configure(config)this—Merge new settings into an existing client without recreating it. Useful when the key arrives after your app boots.

Editor options

OptionTypeDefaultDescription
enabledbooleanfalseShow the annotation editor before sending. Can also be turned on project-wide from the dashboard without a frontend deploy.
toolsEditorTool[]allWhich tools appear, in order: select, pen, arrow, line, rect, ellipse, highlight, text, step, spotlight, blur, crop.
defaultToolEditorTool'arrow'Preselected when the editor opens.
caption / captionPlaceholderboolean / stringtrueThe caption field under the canvas, and its placeholder text.
palette / defaultColorstring[] / string—Stroke colours offered, and which one starts selected.
confirmLabelstring'Upload'The text on the button that finishes the capture — the most important word in the widget, because it is the last thing somebody reads before their screen leaves their machine. The default says Upload rather than Send for that reason. Change it to whatever is true in your product: 'Send to support', 'Attach to ticket', 'File in patient record'. Say where it goes, not just that it goes.
cancelLabelstring'Cancel'The text on the button that discards the capture and sends nothing.
theme'light' | 'dark' | 'auto''auto'Follows the user’s system preference unless pinned.
allowFormatChoicebooleanfalseLet the end user pick which formats get exported.
branding{ show, prefix, label, logo, href }screen2apiReplace the credit in the editor header with your own product’s name and mark, or set show:false to remove it.

Errors

Everything the SDK throws is a Screen2ApiError with a code. Branch on the code, never on the message — messages get rewritten, codes do not.

OptionTypeDefaultDescription
no_targetclient—The selector matched nothing. Usually a capture fired before the element rendered, or after it unmounted.
render_failedclient—The scene could not be rasterized. Check result warnings and the console; a tainted canvas or a cross-origin stylesheet is the usual cause.
encode_failedclient—Every requested format failed to encode. Nearly always a capture too large for the browser to allocate — lower scale or maxDimension.
upload_failednetwork—The files were produced but did not reach us. The blobs are still in the result, so you can retry or fall back to a download.
unauthorizednetwork—Bad or revoked key — or a live key used from an origin that is not on the allowlist. Check the origin first; it is the more common of the two.
rate_limitednetwork—Too many captures too quickly, or the monthly quota is spent. Back off and retry; the response carries Retry-After.
permission_deniedclient—display-media only: the user declined the screen-share prompt. Offer the dom engine instead.
cancelledclient—The capture was aborted in flight. Dismissing the editor does not raise this — that resolves null.
unsupportedclient—Called outside a browser — server-side rendering, a Node test, a worker. Guard with typeof window !== "undefined".
import { Screen2ApiError } from '@screen2api/sdk'

      try {
        await s2a.capture({ target: '#invoice' })
      } catch (error) {
        if (!(error instanceof Screen2ApiError)) throw error

        switch (error.code) {
          case 'rate_limited':
            return toast('Too many exports right now — try again in a minute.')
          case 'unauthorized':
            // An origin problem, not a user problem. Tell yourself, not them.
            return report(error)
          case 'upload_failed':
            // The files exist even though the upload failed.
            return offerDownload()
          default:
            return toast('That export failed. Please try again.')
        }
      }

A client-wide onError handler catches everything if you would rather not wrap each call — though capture() still rejects, so keep the catch when you need to branch.