# 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 stream called `web.events` and replays in `web.replay`, so everything below applies to your web data too.
## Start here
* [Quickstart](https://capless.sh/docs/quickstart.md): account, snippet, first query. Five minutes.
* [For agents](https://capless.sh/docs/agents.md): the signup API, MCP and a curl-only flow an agent can run end to end.
* [MCP server](https://capless.sh/docs/mcp.md): connect Claude Code, Cursor or any MCP client.
## Reference
* [Web SDK](https://capless.sh/docs/web-sdk.md): snippet, JavaScript API, collected fields, replay privacy.
* [Streams & cursors](https://capless.sh/docs/streams.md): batches, positions, durability, idempotency.
* [Reading & change feed](https://capless.sh/docs/reading.md): `/read`, `/changes`, pagination and limits.
* [Time seeking](https://capless.sh/docs/time-seeking.md): jump to a commit time with `/locate`.
* [CEL functions](https://capless.sh/docs/cel.md): variables, number and regex rules, examples.
* [Erasures & GDPR](https://capless.sh/docs/erasures.md): delete by predicate, everywhere.
* [ClickHouse materializations](https://capless.sh/docs/materializations.md): tables, sources, lineage, status.
* [API reference](https://capless.sh/docs/api.md): every endpoint group; full [OpenAPI spec](https://capless.sh/openapi.yaml).
* [Limits & quotas](https://capless.sh/docs/limits.md) · [Security & privacy](https://capless.sh/docs/security.md)
## Conventions
* Base URL `https://api.capless.sh`. All endpoints are under `/v1`.
* Authenticate with your project API key: `authorization: Bearer cpl_…`.
* Bodies are JSON. Timestamps are UTC ISO 8601 with millisecond precision.
* IDs (`str_`, `ent_`, `era_`, `mat_`, `src_`) and cursors (`cur_`) are opaque strings.
* 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://capless.sh/docs/index.md)). [/llms.txt](https://capless.sh/llms.txt) indexes them, and [/llms-full.txt](https://capless.sh/llms-full.txt) is everything in one file.
---
# Quickstart
From zero to a SQL-free answer about your traffic in five minutes. You need an email address and a website you can edit.
## 1. Create an account
Enter your email on the [home page](https://capless.sh/#signup) or call the signup API yourself:
```sh
curl -s https://api.capless.sh/v1/signup \
-d '{"email":"you@example.com","siteName":"my-site","allowedOrigins":["https://example.com"]}'
```
The response contains everything you need. Save `apiKey`: it is shown once.
```json
{
"user": { "id": "…", "email": "you@example.com" },
"project": { "id": "prj_…", "name": "…", "plan": "trial", "trialEndsAt": "…" },
"apiKey": "cpl_…",
"site": { "key": "cs_…", "snippet": "" },
"dashboardUrl": "https://app.capless.sh/…",
"mcp": { "url": "https://mcp.capless.sh/mcp", "headers": { "Authorization": "Bearer cpl_…" } }
}
```
* `dashboardUrl` is a one-time sign-in link, valid for 15 minutes. Later, sign in at [app.capless.sh/login](https://app.capless.sh/login).
* We send a verification email. Your 14-day trial starts now, no credit card.
* `409 email_taken` means you already have an account: [log in](https://app.capless.sh/login) instead.
## 2. Add the snippet
Paste the snippet into every page, right before ``:
```html
```
That's it: pageviews, clicks, form submits, errors, Web Vitals and session replays are collected automatically. Single-page apps are detected (`pushState`, back/forward). See [Web SDK](https://capless.sh/docs/web-sdk.md) for custom events and user identification.
## 3. Check that data arrives
Open your site in a browser, then read the newest events:
```sh
export CAPLESS_API_KEY=cpl_…
curl -s https://api.capless.sh/v1/streams/web.events -H "authorization: Bearer $CAPLESS_API_KEY"
# → {"name":"web.events","entryCount":3,"lastCommittedAt":"…", …}
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}"}'
```
Or open the dashboard at [app.capless.sh](https://app.capless.sh) to watch sessions and replays.
## 4. Ask questions
* **From your coding agent**: [connect the MCP server](https://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://capless.sh/docs/cel.md)).
* **With SQL** (Team plan): [materialize](https://capless.sh/docs/materializations.md) `web.events` into ClickHouse and run real SQL.
## 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. Instructions: https://capless.sh/llms.txt
```
The agent follows [/AGENTS.md](https://capless.sh/AGENTS.md): create an account with the signup API, add the snippet, verify that events arrive, and hand you the dashboard link.
## Machine-readable docs
| URL | What |
|---|---|
| [/llms.txt](https://capless.sh/llms.txt) | Index in [llmstxt.org](https://llmstxt.org) format, linking every page as Markdown |
| [/llms-full.txt](https://capless.sh/llms-full.txt) | All docs in one file |
| `/docs/.md` | Any docs page as Markdown, e.g. [/docs/cel.md](https://capless.sh/docs/cel.md) |
| [/AGENTS.md](https://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 |
## Signup API
`POST https://api.capless.sh/v1/signup` creates a user, a project on the 14-day trial, an API key and (with `siteName`) a web analytics site, in one call. No authentication.
| Field | Type | |
|---|---|---|
| `email` | string, required | The human's email. A verification email is sent to it. |
| `password` | string, optional | Omit it to sign in with emailed links only. |
| `projectName` | string, optional | |
| `siteName` | string, optional | Creates a site and returns its snippet. |
| `allowedOrigins` | string[], optional | Origins allowed to send events for the site, e.g. `["https://example.com"]`. |
```sh
curl -s https://api.capless.sh/v1/signup \
-d '{"email":"jane@example.com","projectName":"acme","siteName":"acme.com","allowedOrigins":["https://acme.com"]}'
```
`201 Created`:
```json
{
"user": { "id": "…", "email": "jane@example.com" },
"project": { "id": "prj_…", "name": "acme", "plan": "trial", "trialEndsAt": "…" },
"apiKey": "cpl_…",
"site": { "key": "cs_…", "snippet": "" },
"dashboardUrl": "https://app.capless.sh/…",
"mcp": { "url": "https://mcp.capless.sh/mcp", "headers": { "Authorization": "Bearer cpl_…" } }
}
```
* `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. The site key `cs_…` is public and write-only, so it is safe in HTML.
* `dashboardUrl` is a one-time sign-in link valid for 15 minutes. Give it to the human.
* `409 email_taken`: the human already has an account. Ask them for an API key from [the dashboard](https://app.capless.sh/login) instead of creating another account.
* `429 rate_limited`: wait and retry later; do not loop.
## End-to-end with curl
No CLI, no SDK. Replace the email and origin.
```sh
# 1. Account + site
curl -s https://api.capless.sh/v1/signup \
-d '{"email":"jane@example.com","siteName":"acme.com","allowedOrigins":["https://acme.com"]}' > capless.json
export CAPLESS_API_KEY=$(node -p 'require("./capless.json").apiKey')
node -p 'require("./capless.json").site.snippet' # paste before
node -p 'require("./capless.json").dashboardUrl' # give to the human
H="authorization: Bearer $CAPLESS_API_KEY"
# 2. After the site is loaded in a browser: events arrived?
curl -s https://api.capless.sh/v1/streams/web.events -H "$H" # entryCount > 0
curl -s https://api.capless.sh/v1/streams/web.events/read -H "$H" -d '{"limitResultEntries":5}'
# 3. 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"}'
# 4. GDPR: erase one user everywhere
curl -s https://api.capless.sh/v1/streams/web.events/erasures -H "$H" -d '{"filter":"entry.userId == \"user_42\""}'
```
Delete `capless.json` once the key is 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://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://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 |
| `list_sites` | Web analytics sites and their snippets |
| `create_site` | Create a site (`name`, `allowedOrigins`, `replaySampleRate`) |
| `list_streams` | Streams with entry counts and heads |
| `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://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. 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:
```sh
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":"