capless
Docs For agents

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

URLWhat
/llms.txtIndex in llmstxt.org format, linking every page as Markdown
/llms-full.txtAll docs in one file
/docs/<page>.mdAny docs page as Markdown, e.g. /docs/cel.md
/AGENTS.mdInstall instructions an agent can follow verbatim
api.capless.sh/v1/openapi.yamlOpenAPI 3.1 spec
GET /v1/functionsThe 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.
  • 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). 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.

FieldType
emailstring, requiredThe human's email. A verification email is sent to it.
claimSecretstring, optionalA csk_… claim secret. The account claims that project (the snippet stays the same).
passwordstring, optionalOmit it to sign in with emailed links only.
projectNamestring, 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": ["…"]
}
  • 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.
  • 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.

# 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 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.

This page as Markdown

← Quickstart · MCP server →