# 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":"