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:
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 </head> and gives you the claim link; nothing else is needed to start collecting. The longer version of these instructions is /AGENTS.md.
Machine-readable docs
| URL | What |
|---|---|
| /llms.txt | Index in llmstxt.org format, linking every page as Markdown |
| /llms-full.txt | All docs in one file |
/docs/<page>.md | Any docs page as Markdown, e.g. /docs/cel.md |
| /AGENTS.md | Install instructions an agent can follow verbatim |
| 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:
curl -s https://analytics.capless.sh/new -H 'accept: application/json'
{
"snippet": "<script async src=\"https://c.capless.sh/c.js\" data-key=\"cs_…\"></script>",
"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.claimSecretandclaimUrlprove ownership. GiveclaimUrlto 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
unclaimedDaysafter it was created. - While unclaimed,
Authorization: Bearer csk_…reads the project (allowed endpoints). 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 |
curl -s https://api.capless.sh/v1/signup \
-d '{"email":"jane@example.com","projectName":"acme","claimSecret":"csk_…"}'
201 Created:
{
"user": { "id": "…", "email": "jane@example.com" },
"project": { "id": "prj_…", "name": "acme", "plan": "trial", "trialEndsAt": "…" },
"apiKey": "cpl_…",
"web": {
"key": "cs_…",
"snippet": "<script async src=\"https://c.capless.sh/c.js\" data-key=\"cs_…\"></script>",
"replaySampleRate": 1,
"allowedOrigins": [],
"hosts": []
},
"dashboardUrl": "https://app.capless.sh/…",
"mcp": { "url": "https://mcp.capless.sh/mcp", "headers": { "Authorization": "Bearer cpl_…" } },
"next": ["…"]
}
apiKeyis shown once. Store it where the project keeps secrets (e.g..envasCAPLESS_API_KEY); never commit it and never put it in client-side code.dashboardUrlis a one-time sign-in link valid for 15 minutes. Give it to the human.planistrial, ornoneif 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.409 already_claimed: another account owns the project behindclaimSecret.429 rate_limited: wait and retry later; do not loop.
End-to-end with curl
No CLI, no SDK.
# 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 </head>
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:
claude mcp add --transport http capless https://mcp.capless.sh/mcp --header "Authorization: Bearer cpl_…"
Then call the get_started tool. See MCP server 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 withduplicate: true. - Read with cursors, not offsets. Persist
nextCursor; it never skips or repeats. - Dry-run CEL with
POST /v1/functions/evaluatebefore using it on real data. - Errors carry a stable
code; branch on it, not on the message.