Capless
Docs Web SDK

Web SDK

One tag. 2.0 KB (brotli) for analytics; session replay is a separate 1.7 KB chunk that is only downloaded for sampled sessions. No cookies, no dependencies.

<script async src="https://c.capless.sh/c.js" data-site="cs_…"></script>

Put it before </head> on every page. The site key (cs_…) is public and write-only.

Sites

A project can have several sites (one per domain or app). Sites are managed with the project API key:

H="authorization: Bearer $CAPLESS_API_KEY"

# Create
curl -s https://api.capless.sh/v1/web/sites -H "$H" \
  -d '{"name":"shop","allowedOrigins":["https://shop.example"],"replaySampleRate":1}'
# → {"key":"cs_…","name":"shop","allowedOrigins":["https://shop.example"],"replaySampleRate":1,"snippet":"<script async …>", …}

# List, update, delete
curl -s https://api.capless.sh/v1/web/sites -H "$H"
curl -s -X PATCH https://api.capless.sh/v1/web/sites/cs_… -H "$H" -d '{"replaySampleRate":0.25}'
curl -s -X DELETE https://api.capless.sh/v1/web/sites/cs_… -H "$H"
Field
namerequired
allowedOriginsoptionalRestricts which origins may send to the site key, e.g. ["https://shop.example"].
replaySampleRate0–1, default 1Fraction of sessions recorded.

JavaScript API

// Optional stub so calls made before the script loads are queued:
window.capless = window.capless || function () { (capless.q = capless.q || []).push(arguments) };

capless("track", "checkout_started", { plan: "pro" }); // custom event
capless("identify", "user_42", { email: "jane@example.com" }); // attach a user id (+ traits)
capless("reset");   // forget the user id (logout)
capless("optout");  // stop collecting on this browser; capless("optin") to resume
capless("page");    // record a pageview manually (SPA routing is detected automatically)

Name important elements for click analytics with data-capless:

<button data-capless="upgrade-cta">Upgrade</button>

What is collected automatically

Event typeWhenNotable fields
pageviewload, pushState/replaceState, back/forwardtitle, referrer, referrerHost, navigation, ttfbMs, loadMs
pageleavenavigating away / tab closingvisibleMs, scrollDepthPct, vitals (lcpMs, fcpMs, cls, inpMs)
clickany clickelement (tag, id, classes, text, href, selector, name from data-capless), x, y, rage
submitform submission (never field values)form (id, name, action, method)
erroruncaught errors and unhandled rejectionserror (kind, message, stack, file, line, column)
hiddentab hidden
customcapless("track", …)name, properties
identifycapless("identify", …)traits
replay_starta recording starts on a page load

Every event also carries:

  • timestamp (client), receivedAt (server), site
  • anonymousId (per browser, localStorage), sessionId (30 minutes of inactivity ends a session, shared across tabs), tabId, userId
  • url, host, path, query, hash, utm (source, medium, campaign, term, content), clickIds (gclid, fbclid, …)
  • device (browser, browserVersion, os, deviceType, userAgent, screen, viewport, pixelRatio, language, timezone)
  • geo (country, region, city, postalCode, continent, timezone, latitude, longitude, asn, asOrganization)
  • isBot, and ipHash (keyed hash; raw IPs are never stored)

Streams

Each project's sites write to two streams, created on first use:

  • web.events: one record per event (above). Query it with /read + CEL, or materialize it into ClickHouse.
  • web.replay: session replay chunks with sessionId, tabId, seq, part, startedAt, url, fromMs, toMs, frames.
# Rage clicks and what was clicked
curl -s https://api.capless.sh/v1/streams/web.events/read -H "$H" \
  -d '{"filter":"entry.type == \"click\" && has(entry.rage)","map":"{\"path\": entry.path, \"element\": entry.element}"}'

Erasures work on both streams, e.g. entry.userId == 'user_42'.

Session replay privacy

Private by default:

  • Input values are masked (*); password fields are never recorded. Opt specific fields in with data-capless-unmask.
  • Text inside .capless-mask / [data-capless-mask] is masked.
  • .capless-block / [data-capless-block] subtrees are replaced by an empty box.
  • Script contents and inline event handlers are never recorded.
<div class="capless-mask">Jane Doe · jane@example.com</div>
<iframe data-capless-block src="…"></iframe>
<input name="coupon" data-capless-unmask>

Content Security Policy

If your site sends a CSP, allow the script and its collector:

script-src https://c.capless.sh; connect-src https://c.capless.sh