# 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 `</head>` 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/<page>.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": "<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](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": "<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](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 </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:

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