# Capless documentation (all pages) > Capless is tiny, agent-native web analytics and session replay (one script tag: 2 KB analytics, 1.7 KB lazily loaded replay) on a headless data backend: append-only streams, CEL functions and ClickHouse materializations, with an HTTP API, an MCP server and a signup API that agents can call directly. # Capless documentation Capless is analytics infrastructure for software that is written, run and queried by AI agents. It has two halves that share one data model: * **Web analytics + session replay**: one script tag. 2.0 KB (brotli) for analytics; session replay is a separate 1.7 KB chunk loaded only for sampled sessions. No cookies. * **A headless data backend**: three primitives behind one HTTP API at `https://api.capless.sh/v1`. 1. **Streams**: durable, ordered, append-only collections of JSON records. 2. **Functions**: deterministic, bounded [CEL](https://cel.dev) expressions for validation, filtering, mapping and erasure. 3. **Materializations**: independently maintained copies of streams in ClickHouse, configured with real SQL. Web analytics events land in a built-in stream called `$web.events` and replays in `$web.replay`, so everything below applies to your web data too. ## Start here * [Quickstart](https://analytics.capless.sh/docs/quickstart.md): snippet, claim link, first query. Five minutes. * [For agents](https://analytics.capless.sh/docs/agents.md): `analytics.capless.sh/new`, the signup API, MCP and a curl-only flow an agent can run end to end. * [MCP server](https://analytics.capless.sh/docs/mcp.md): connect Claude Code, Cursor or any MCP client. ## Reference * [Web SDK](https://analytics.capless.sh/docs/web-sdk.md): snippet, JavaScript API, collected fields, replay privacy. * [Streams & cursors](https://analytics.capless.sh/docs/streams.md): batches, positions, durability, idempotency. * [Reading & change feed](https://analytics.capless.sh/docs/reading.md): `/read`, `/changes`, pagination and limits. * [Time seeking](https://analytics.capless.sh/docs/time-seeking.md): jump to a commit time with `/locate`. * [CEL functions](https://analytics.capless.sh/docs/cel.md): variables, number and regex rules, examples. * [Erasures & GDPR](https://analytics.capless.sh/docs/erasures.md): delete by predicate, everywhere. * [ClickHouse materializations](https://analytics.capless.sh/docs/materializations.md): tables, sources, lineage, status. * [API reference](https://analytics.capless.sh/docs/api.md): every endpoint group; full [OpenAPI spec](https://analytics.capless.sh/openapi.yaml). * [Limits & quotas](https://analytics.capless.sh/docs/limits.md): plan allowances, spend cap, API, read, CEL and collector limits. * [Security & privacy](https://analytics.capless.sh/docs/security.md): keys, privacy defaults, isolation, infrastructure, supply chain. ## Conventions * Base URL `https://api.capless.sh`. All endpoints are under `/v1`. * Authenticate with your project API key: `authorization: Bearer cpl_…`. An unclaimed project can be read with its claim secret (`Bearer csk_…`). * Bodies are JSON. Timestamps are UTC ISO 8601 with millisecond precision. * IDs (`prj_`, `str_`, `ent_`, `era_`, `mat_`, `src_`) and cursors (`cur_`) are opaque strings. * In shell commands, single-quote URLs that contain `$web.events` or `$web.replay`. * Errors are `{"error": {"code", "message", "details"?, "requestId"}}` with a stable `code`. * Lists are paginated with `limit` + `pageToken` → `{data, nextPageToken}`. Every page of these docs is also available as Markdown: append `.md` to its URL (this page: [/docs/index.md](https://analytics.capless.sh/docs/index.md)). [/llms.txt](https://analytics.capless.sh/llms.txt) indexes them, and [/llms-full.txt](https://analytics.capless.sh/llms-full.txt) is everything in one file. --- # Quickstart From zero to an answer about your traffic in five minutes. You need a website you can edit. An account is optional until you want to keep the data. ## 1. Get a snippet Copy the snippet from the [home page](https://analytics.capless.sh/#install). Its key was generated in your browser and belongs to you. In a terminal, or from a coding agent, use: ```sh curl -s https://analytics.capless.sh/new ``` It prints a snippet and a claim link. With `-H 'accept: application/json'` it returns the same as JSON: ```json { "snippet": "", "key": "cs_…", "claimSecret": "csk_…", "claimUrl": "https://app.capless.sh/claim#csk_…", "unclaimedDays": 14 } ``` * The key (`cs_…`) is public and can only send events. It is safe in HTML. * The claim secret (`csk_…`) is the only way to see the data and to take ownership of the project. Keep it out of pages and version control. ## 2. Add the snippet Paste the snippet into every page, right before ``: ```html ``` Pageviews, clicks, form submits, errors, Web Vitals and session replays are collected automatically. Single-page apps are detected (`pushState`, back/forward). One snippet works on any number of domains. See [Web SDK](https://analytics.capless.sh/docs/web-sdk.md) for custom events and user identification. The first event creates the project. Until someone claims it, the project is **unclaimed**: it collects up to 250,000 events, 2,500 replays and 10 GB, has no SQL, API keys or members, and is deleted 14 days after it was created. ## 3. Open the claim link Open the claim link (`https://app.capless.sh/claim#csk_…`). It shows what the project has collected so far, without an account, and has a form to claim it with your email. On the home page, the same form is below the snippet. Claiming starts a 14-day trial with no credit card (or asks for a plan if your account already had a trial). The data collected so far is kept, and the key stays the same, so the snippet does not change. The claim secret also works as a read-only bearer token while the project is unclaimed: ```sh curl -s 'https://api.capless.sh/v1/streams/$web.events' -H "authorization: Bearer csk_…" # → {"name":"$web.events","entryCount":3,"lastCommittedAt":"…", …} ``` ## 4. Check that data arrives After claiming, use an API key (`cpl_…`, created in the dashboard or returned by the [signup API](#signup-api-for-agents)): ```sh export CAPLESS_API_KEY=cpl_… curl -s 'https://api.capless.sh/v1/streams/$web.events' -H "authorization: Bearer $CAPLESS_API_KEY" curl -s 'https://api.capless.sh/v1/streams/$web.events/read' -H "authorization: Bearer $CAPLESS_API_KEY" \ -d '{"filter":"entry.type == \"pageview\"","map":"{\"path\": entry.path, \"at\": entry.timestamp}"}' ``` Single-quote URLs that contain `$web.events`, so the shell does not expand `$web`. Or open the dashboard at [app.capless.sh](https://app.capless.sh) to watch sessions and replays. ## 5. Ask questions * **From your coding agent**: [connect the MCP server](https://analytics.capless.sh/docs/mcp.md) and ask "which pages had the most rage clicks this week?". * **With CEL**: filter and reshape events server-side on `/read` ([CEL functions](https://analytics.capless.sh/docs/cel.md)). * **With SQL**: [materialize](https://analytics.capless.sh/docs/materializations.md) `$web.events` into ClickHouse and run real SQL. ## Signup API (for agents) An agent that needs an account and an API key at once can call the signup API instead. With `claimSecret`, the new account takes over the project behind that snippet; without it, the account gets a new project and key. ```sh curl -s https://api.capless.sh/v1/signup \ -d '{"email":"you@example.com","claimSecret":"csk_…"}' ``` The response contains `apiKey` (shown once), `web` (the snippet and settings) and `dashboardUrl` (a one-time sign-in link, valid for 15 minutes). `409 email_taken` means the address already has an account: [log in](https://app.capless.sh/login) and open the claim link instead. Details: [For agents](https://analytics.capless.sh/docs/agents.md#signup-api). ## Track your own events ```js capless("track", "checkout_started", { plan: "pro" }); capless("identify", "user_42", { email: "jane@example.com" }); ``` Custom events are `type: "custom"` records with `name` and `properties` in the same `$web.events` stream. --- # For agents Capless is built to be set up and operated by coding agents. Everything a human can do in the dashboard, an agent can do with one HTTP API or the MCP server, and every docs page is plain Markdown. ## The one-liner Tell your coding agent: ```text Add Capless analytics to this site. Run `curl -s https://analytics.capless.sh/new` and follow what it prints. ``` `GET https://analytics.capless.sh/new` returns a fresh snippet and a claim link. The agent adds the snippet before `` and gives you the claim link; nothing else is needed to start collecting. The longer version of these instructions is [/AGENTS.md](https://analytics.capless.sh/AGENTS.md). ## Machine-readable docs | URL | What | |---|---| | [/llms.txt](https://analytics.capless.sh/llms.txt) | Index in [llmstxt.org](https://llmstxt.org) format, linking every page as Markdown | | [/llms-full.txt](https://analytics.capless.sh/llms-full.txt) | All docs in one file | | `/docs/.md` | Any docs page as Markdown, e.g. [/docs/cel.md](https://analytics.capless.sh/docs/cel.md) | | [/AGENTS.md](https://analytics.capless.sh/AGENTS.md) | Install instructions an agent can follow verbatim | | [api.capless.sh/v1/openapi.yaml](https://api.capless.sh/v1/openapi.yaml) | OpenAPI 3.1 spec | | `GET /v1/functions` | The CEL environment: variables per context, limits, number and regex rules | ## New snippet `GET https://analytics.capless.sh/new` needs no authentication and stores nothing. It returns plain text for a terminal, or JSON with `accept: application/json`: ```sh curl -s https://analytics.capless.sh/new -H 'accept: application/json' ``` ```json { "snippet": "", "key": "cs_…", "claimSecret": "csk_…", "claimUrl": "https://app.capless.sh/claim#csk_…", "unclaimedDays": 14 } ``` * `key` (`cs_…`) is public and write-only, so it is safe in HTML. It is derived from the claim secret, and the project ID is derived from the key, so the project needs no setup call: the snippet's first event creates it. * `claimSecret` and `claimUrl` prove ownership. Give `claimUrl` to the human; never commit it or put it on a page. * Until it is claimed, the project is unclaimed: it collects up to 250,000 events, 2,500 replays and 10 GB, has no SQL, API keys or members, and is deleted `unclaimedDays` after it was created. * While unclaimed, `Authorization: Bearer csk_…` reads the project ([allowed endpoints](https://analytics.capless.sh/docs/api.md#claiming)). Use it to check that events arrive. ## Signup API `POST https://api.capless.sh/v1/signup` creates a user, a project on the 14-day trial and an API key in one call. No authentication. With `claimSecret`, the account takes over the project behind that snippet instead of creating a new one. | Field | Type | | |---|---|---| | `email` | string, required | The human's email. A verification email is sent to it. | | `claimSecret` | string, optional | A `csk_…` claim secret. The account claims that project (the snippet stays the same). | | `password` | string, optional | Omit it to sign in with emailed links only. | | `projectName` | string, optional | | ```sh curl -s https://api.capless.sh/v1/signup \ -d '{"email":"jane@example.com","projectName":"acme","claimSecret":"csk_…"}' ``` `201 Created`: ```json { "user": { "id": "…", "email": "jane@example.com" }, "project": { "id": "prj_…", "name": "acme", "plan": "trial", "trialEndsAt": "…" }, "apiKey": "cpl_…", "web": { "key": "cs_…", "snippet": "", "replaySampleRate": 1, "allowedOrigins": [], "hosts": [] }, "dashboardUrl": "https://app.capless.sh/…", "mcp": { "url": "https://mcp.capless.sh/mcp", "headers": { "Authorization": "Bearer cpl_…" } }, "next": ["…"] } ``` * `apiKey` is shown once. Store it where the project keeps secrets (e.g. `.env` as `CAPLESS_API_KEY`); never commit it and never put it in client-side code. * `dashboardUrl` is a one-time sign-in link valid for 15 minutes. Give it to the human. * `plan` is `trial`, or `none` if this email already used its trial (then a plan is needed to collect). * `409 email_taken`: the human already has an account. Give them the claim link (they claim it after logging in), or ask for an API key from [the dashboard](https://app.capless.sh/login). * `409 already_claimed`: another account owns the project behind `claimSecret`. * `429 rate_limited`: wait and retry later; do not loop. ## End-to-end with curl No CLI, no SDK. ```sh # 1. A snippet and its claim link curl -s https://analytics.capless.sh/new -H 'accept: application/json' > capless.json node -p 'require("./capless.json").snippet' # paste before node -p 'require("./capless.json").claimUrl' # give to the human export CAPLESS_CLAIM_SECRET=$(node -p 'require("./capless.json").claimSecret') # 2. After the site is loaded in a browser: events arrived? (read-only while unclaimed) C="authorization: Bearer $CAPLESS_CLAIM_SECRET" curl -s 'https://api.capless.sh/v1/streams/$web.events' -H "$C" # entryCount > 0 curl -s 'https://api.capless.sh/v1/streams/$web.events/read' -H "$C" -d '{"limitResultEntries":5}' # 3. Optional: an account and an API key now, claiming the project curl -s https://api.capless.sh/v1/signup \ -d "{\"email\":\"jane@example.com\",\"claimSecret\":\"$CAPLESS_CLAIM_SECRET\"}" > account.json export CAPLESS_API_KEY=$(node -p 'require("./account.json").apiKey') node -p 'require("./account.json").dashboardUrl' # give to the human H="authorization: Bearer $CAPLESS_API_KEY" # 4. Your own data: a stream with a validator, an idempotent insert, a filtered read curl -s https://api.capless.sh/v1/streams -H "$H" -d '{"name":"orders","validator":"entry.amount >= 0"}' curl -s https://api.capless.sh/v1/streams/orders/entries -H "$H" \ -d '{"idempotencyKey":"import-2026-10-30","entries":[{"user":"u1","amount":42},{"user":"u2","amount":7}]}' curl -s https://api.capless.sh/v1/streams/orders/read -H "$H" -d '{"filter":"entry.amount > 10"}' # 5. GDPR: erase one user everywhere curl -s 'https://api.capless.sh/v1/streams/$web.events/erasures' -H "$H" -d '{"filter":"entry.userId == \"user_42\""}' ``` Single-quote URLs that contain `$web.events`, so the shell does not expand `$web`. Delete `capless.json` and `account.json` once the secrets are stored safely. ## MCP The signup response includes a ready-made MCP config. Install it for Claude Code: ```sh claude mcp add --transport http capless https://mcp.capless.sh/mcp --header "Authorization: Bearer cpl_…" ``` Then call the `get_started` tool. See [MCP server](https://analytics.capless.sh/docs/mcp.md) for other clients and the full tool list. ## Rules of thumb for agents * Retry writes with the same `idempotencyKey`; a retry returns the original result with `duplicate: true`. * Read with cursors, not offsets. Persist `nextCursor`; it never skips or repeats. * Dry-run CEL with `POST /v1/functions/evaluate` before using it on real data. * Errors carry a stable `code`; branch on it, not on the message. --- # MCP server Capless speaks the [Model Context Protocol](https://modelcontextprotocol.io), so Claude Code, Cursor and any MCP client can set up tracking, read your data and run SQL for you. * URL: `https://mcp.capless.sh/mcp` (Streamable HTTP) * Auth: header `Authorization: Bearer cpl_…` (your project API key) ## Install Claude Code: ```sh claude mcp add --transport http capless https://mcp.capless.sh/mcp --header "Authorization: Bearer cpl_…" ``` Cursor (`.cursor/mcp.json`), and most other clients: ```json { "mcpServers": { "capless": { "url": "https://mcp.capless.sh/mcp", "headers": { "Authorization": "Bearer cpl_…" } } } } ``` No API key yet? The [signup API](https://analytics.capless.sh/docs/agents.md#signup-api) returns one, together with this exact config in `mcp`. Keep the key out of version control: use your client's user-level config or an environment variable. ## Tools | Tool | What it does | |---|---| | `get_started` | Start here: what is set up in the project and what to do next | | `get_snippet` | The project's snippet (one for every site), its settings, and the hostnames that have sent events | | `update_web_settings` | Change `replaySampleRate` or the optional `allowedOrigins` | | `list_streams` | Streams with entry counts and heads (web analytics writes to `$web.events` and `$web.replay`) | | `create_stream` | Create a stream with an optional CEL validator | | `insert_entries` | Insert a batch atomically (idempotent with a key) | | `read_stream` | Read entries with CEL `filter`/`map`, cursor pagination | | `locate` | Find the cursor for a commit time | | `erase_entries` | Erase entries matching a CEL predicate | | `list_materializations` | ClickHouse materializations | | `create_materialization` | Create an isolated ClickHouse database | | `run_sql` | Run one ClickHouse SQL statement in a materialization | | `connect_stream` | Connect a stream to a table (backfill, then follow) | | `materialization_status` | Insert and deletion freshness per source | | `get_usage` | This month's events and replays against your plan's allowance | | `cel_reference` | The CEL environment: variables, limits, number and regex rules | | `evaluate_cel` | Dry-run a CEL expression against a sample record before using it in a read, erasure or materialization | Each tool maps onto the [HTTP API](https://analytics.capless.sh/docs/api.md) with the same semantics, limits and error codes. ## Things to ask * "Is Capless installed correctly on this site? Check that events are arriving." * "Which pages had rage clicks in the last 7 days?" * "Set up a ClickHouse table for pageviews and show me the top referrers this month." * "Delete all data for user `user_42` and confirm when it is purged." * "How close are we to this month's event limit?" --- # 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. ```html ``` Put it before `` on every page. Get one from [capless.sh](https://analytics.capless.sh/#install) or with `curl -s https://analytics.capless.sh/new` ([Quickstart](https://analytics.capless.sh/docs/quickstart.md)); a project's snippet is also in the dashboard and in `GET /v1/web`. The key (`cs_…`) is public and can only write events. Each project has one key, and one snippet works on any number of domains. The project's sites are the hostnames that have sent events; they appear in `hosts` without any setup. ## Settings Read the snippet, the settings and the hosts with the project API key, a dashboard session, or the claim secret of an unclaimed project: ```sh H="authorization: Bearer $CAPLESS_API_KEY" curl -s https://api.capless.sh/v1/web -H "$H" # → {"key":"cs_…","snippet":"