# Convalytics > Full-stack analytics for Convex apps. No account needed, agent sets up everything, human claims later. Full product manual: https://convalytics.dev/llms-full.txt OpenAPI spec: https://convalytics.dev/openapi.json Pricing: https://convalytics.dev/pricing.md ## How it works 1. Run `npx convalytics init` — no write key needed, auto-provisions a project 2. CLI returns a claim URL — share it with the human to connect to their account 3. Agent reads convex/schema.ts and all mutations/actions to understand the data model 4. Agent proposes a tracking plan (event names, files, functions, props) 5. User approves → agent instruments the approved events 6. Run `npx convalytics verify` to confirm Events flow immediately — no need to wait for claiming. ## Zero-config setup npx convalytics init Auto-provisions a project, installs the SDK, patches config, sets the env var, adds the browser script tag, and installs the agent skill file. If the user already has a write key: npx convalytics init WRITE_KEY ## Event discovery After install, read convex/schema.ts and every file in convex/. For each mutation/action that represents a user action, propose an event: - Event name: snake_case noun_verb (user_signed_up, payment_succeeded) - File and function where it should be added - Props to attach from existing args/data Aim for 5-15 events covering the core user journey. Skip internal/admin functions. Wait for user approval before instrumenting. ## Tracking import { analytics } from "./analytics"; const identity = await ctx.auth.getUserIdentity(); await analytics.track(ctx, { name: "user_signed_up", userId: String(userId), userEmail: identity?.email, // first-class field — NOT in props props: { plan: "pro" }, }); ## Server-side track() API await analytics.track(ctx, { name: string, // required userId: string, // required — stable user ID userEmail?: string, // optional — human-readable email for dashboard userName?: string, // optional — human-readable name for dashboard sessionId?: string, // optional timestamp?: number, // optional — unix ms props?: Record, }); Works from mutations and actions. Never throws. When userEmail/userName is provided, the dashboard shows it instead of raw IDs. ## Browser-side track() API The script tag also exposes window.convalytics.track() for frontend events: convalytics.track("button_clicked", { page: "settings", action: "save" }) No import needed — available globally once the script loads. Uses the same visitor/session IDs as page views. ## User Identity (identify / reset) For apps with auth, call identify() after sign-in so page views and events show the real user instead of an anonymous UUID: convalytics.identify(userId, { email: "dan@example.com", name: "Dan" }) On sign-out, call reset() to revert to anonymous tracking: convalytics.reset() Identity persists in localStorage across page reloads until reset() is called. The dashboard shows email > name > anonymous ID with cascading priority. ## MCP server (read-only queries for AI assistants) Once events are flowing, developers can query their analytics conversationally via Claude Desktop, Claude Code, Cursor, Windsurf, or any MCP-capable client. POST https://api.convalytics.dev/mcp Authorization: Bearer cnv_... # API token from /tokens Nine read-only tools: list_projects, get_usage, top_pages, top_referrers, pageviews_count, events_count, recent_events, weekly_digest (project summary with period-over-period comparison), user_activity (per-user snapshot — matches by userEmail or visitorId). Gated to Solo+ plans. Tokens are team-scoped; each MCP tool takes an explicit project argument. Full docs at https://convalytics.dev/mcp and https://convalytics.dev/.well-known/mcp/server-card.json. ## Bulk Ingest API For high-volume tracking (e.g. logging every step of an agent workflow), use the batch endpoint to send up to 100 events in a single request. This reduces request-level overhead and latency compared to sending individual requests. Each valid event in the batch still counts against the 1000 events/min quota, but batching helps avoid hitting per-request rate limits. POST /ingest/batch Body: { "writeKey": "wk_...", "events": [ { "name": "step_started", "userId": "u1", "sessionId": "s1", "timestamp": 1234567890000, "props": { "step": "validate" } }, { "name": "step_completed", "userId": "u1", "sessionId": "s1", "timestamp": 1234567891000 } ] } Response: { "accepted": 2, "rejected": 0, "results": [{ "status": "ok" }, { "status": "ok" }] } - Max 100 events per request - Write key is validated once for the whole batch - Per-event results: status "ok" or "error" with an error message - Rate limit is checked atomically for the full batch count - page_view events in a batch are routed to the pageviews table automatically ## Rate Limits All rate limit errors return JSON with machine-readable fields: { "error": "rate_limit_exceeded", "message": "...", "retryAfter": 42, // seconds until reset "resetAt": 1234567890000 // unix ms } The HTTP response also includes a standard Retry-After header (seconds). ## Authentication (for agents) Convalytics uses a **public write key** as its sole API credential. - Safe to ship in client code and public repos. Not a secret. - Authorizes writes to exactly one project. Cannot read data. - No OAuth, no bearer token, no scopes, no refresh flow. Agent flow, no human-in-the-loop: 1. POST /api/provision (no auth) to get a fresh write key. 2. Use the write key immediately on POST /ingest, POST /ingest/batch, GET /verify. 3. Return the claimUrl to the human to link the project to their account later. Where the key goes: - POST /ingest, POST /ingest/batch: `writeKey` field in the JSON body - GET /verify: ?writeKey=... query parameter - Browser script: