# Agent & CLI API

Lightweight Analytics is agent-first: everything you can do in the dashboard (register sites, pull stats, manage goals) is available through a small JSON API designed to be driven by AI agents (Claude, Codex, or your favorite) and our upcoming CLI.

**Working with an agent?** Point it at the raw markdown version of this page: `https://lightweightanalytics.com/docs/api.md`, or at `https://lightweightanalytics.com/llms.txt`. Both are plain text, no HTML to scrape.

## Base URL & authentication

```
https://lightweightanalytics.com/api/v1
```

Create an API key at [Settings → API keys](/settings/api-keys) and send it on every request:

```bash
curl -H "Authorization: Bearer la_YOUR_KEY" \
  https://lightweightanalytics.com/api/v1/me
```

`X-Api-Key: la_YOUR_KEY` works too if setting an Authorization header is awkward. Keys act on your whole account, can be named (one per agent/machine is a good habit), and can be revoked instantly from the same page. We store only a hash of the key, so copy it when it's shown.

In the examples below, export it once:

```bash
export LA_API_KEY="la_YOUR_KEY"
```

## Rate limits & errors

Limits are generous: **600 requests/minute per key** (bursts up to 120 are fine). Over the limit you get `429` with a `Retry-After` header. Wait a second and retry.

Every error has the same flat shape:

```json
{ "error": "not_found", "message": "No site with that id belongs to this account. GET /api/v1/sites lists yours." }
```

| Status | `error` codes you'll see |
|--------|--------------------------|
| 400 | `domain_required`, `invalid_domain`, `invalid_range`, `invalid_bucket`, `invalid_breakdown`, `confirmation_required`, `invalid_goal_name`, `invalid_goal_type`, `invalid_match_type`, `pattern_required`, `invalid_value` |
| 401 | `missing_api_key`, `invalid_api_key` |
| 403 | `subscription_required`, `site_limit_reached` |
| 404 | `not_found` |
| 409 | `domain_taken`, `goal_exists` |
| 429 | `rate_limited` |

## Date ranges

Stats endpoints accept either a preset `range` or explicit dates. All dates are UTC.

- `?range=` one of `today`, `yesterday`, `7d`, `30d` (default), `90d`, `365d`, `thisMonth`, `lastMonth`, `thisYear`, `all`
- `?from=2026-08-01&to=2026-08-11`: explicit dates (`to` is inclusive, whole day)
- `?segment=dev`: switch from production traffic to non-production traffic (localhost, staging hosts, etc.)

---

## Account

### GET /me

Sanity-check your key and see account status.

```bash
curl -H "Authorization: Bearer $LA_API_KEY" \
  https://lightweightanalytics.com/api/v1/me
```

```json
{
  "email": "you@example.com",
  "name": "Ada Lovelace",
  "keyName": "Claude on my laptop",
  "keyPrefix": "la_9f2c4e01",
  "plan": "annual",
  "subscriptionStatus": "active",
  "sites": 3,
  "siteLimit": 100,
  "apiCallsToday": 42,
  "rateLimit": "600 requests/minute per key (burst 120)"
}
```

---

## Sites

### GET /sites

List every site on the account. Add `?includeStats=true` for all-time pageviews/visitors (slightly slower).

```bash
curl -H "Authorization: Bearer $LA_API_KEY" \
  "https://lightweightanalytics.com/api/v1/sites?includeStats=true"
```

```json
{
  "sites": [
    {
      "siteId": "11111111-2222-3333-4444-555555555555",
      "domain": "example.com",
      "name": "My blog",
      "useVisitorCookie": true,
      "useLivePings": false,
      "isFavorite": true,
      "publicDashboardEnabled": false,
      "goals": 2,
      "allTimePageviews": 48210,
      "allTimeVisitors": 19877
    }
  ],
  "count": 1,
  "siteLimit": 100
}
```

### POST /sites (register a site)

Only `domain` is required. The response includes the tracking snippet to paste into the site's `<head>`.

```bash
curl -X POST -H "Authorization: Bearer $LA_API_KEY" -H "Content-Type: application/json" \
  -d '{"domain": "example.com", "name": "My blog"}' \
  https://lightweightanalytics.com/api/v1/sites
```

```json
{
  "siteId": "11111111-2222-3333-4444-555555555555",
  "domain": "example.com",
  "name": "My blog",
  "useVisitorCookie": true,
  "useLivePings": false,
  "isFavorite": false,
  "publicDashboardEnabled": false,
  "trackingSnippet": "<script defer src=\"https://cdn.lightweightanalytics.com/script.min.js\" data-siteid=\"11111111-2222-3333-4444-555555555555\" data-c_mode=\"true\" data-l_ping=\"false\"></script>",
  "dashboardUrl": "https://lightweightanalytics.com/dashboard?siteId=11111111-2222-3333-4444-555555555555"
}
```

Optional body fields: `useVisitorCookie` (default `true`, first-party visitor id for accurate uniques) and `useLivePings` (default `false`, 30-second keep-alive for the live counter).

### GET /sites/{siteId}

Site details, settings, all-time stats, goal count and the current tracking snippet.

```bash
curl -H "Authorization: Bearer $LA_API_KEY" \
  https://lightweightanalytics.com/api/v1/sites/11111111-2222-3333-4444-555555555555
```

### PATCH /sites/{siteId} (update settings)

Send only the fields you want to change: `name`, `useVisitorCookie`, `useLivePings`.

```bash
curl -X PATCH -H "Authorization: Bearer $LA_API_KEY" -H "Content-Type: application/json" \
  -d '{"useLivePings": true}' \
  https://lightweightanalytics.com/api/v1/sites/11111111-2222-3333-4444-555555555555
```

Changing tracking options changes the snippet. The response includes the updated one to re-paste.

### DELETE /sites/{siteId}?confirm={domain} (unregister a site)

Permanently deletes the site **and all its analytics data**. As a guard you must echo the site's domain in `confirm`:

```bash
curl -X DELETE -H "Authorization: Bearer $LA_API_KEY" \
  "https://lightweightanalytics.com/api/v1/sites/11111111-2222-3333-4444-555555555555?confirm=example.com"
```

```json
{ "deleted": true, "siteId": "11111111-2222-3333-4444-555555555555", "domain": "example.com" }
```

---

## Stats

### GET /sites/{siteId}/stats/summary

The headline numbers for a period, the same as the dashboard's top row.

```bash
curl -H "Authorization: Bearer $LA_API_KEY" \
  "https://lightweightanalytics.com/api/v1/sites/11111111-2222-3333-4444-555555555555/stats/summary?range=7d"
```

```json
{
  "siteId": "11111111-2222-3333-4444-555555555555",
  "domain": "example.com",
  "from": "2026-08-05T00:00:00Z",
  "to": "2026-08-11T23:59:59.9999999Z",
  "pageviews": 4821,
  "visitors": 1977,
  "sessions": 2410,
  "bounceRate": 0.44,
  "avgSessionSeconds": 74.2,
  "pagesPerSession": 2.0,
  "newVisitors": 1544,
  "returningVisitors": 433,
  "liveVisitors": 3
}
```

### GET /sites/{siteId}/stats/timeseries

Pageviews and visitors over time, zero-filled so every bucket is present. `bucket` is `auto` (the default: hourly for ranges up to 2 days, daily otherwise), `hour` or `day`.

```bash
curl -H "Authorization: Bearer $LA_API_KEY" \
  "https://lightweightanalytics.com/api/v1/sites/11111111-2222-3333-4444-555555555555/stats/timeseries?range=7d"
```

```json
{
  "granularity": "day",
  "points": [
    { "date": "2026-08-05", "pageviews": 512, "visitors": 231 },
    { "date": "2026-08-06", "pageviews": 748, "visitors": 342 }
  ]
}
```

### GET /sites/{siteId}/stats/breakdown

Top-N tables, one endpoint for every dimension. `by` is one of:
`pages`, `referrers`, `countries`, `browsers`, `devices`, `os`, `languages`, `resolutions`, `hosts`, `campaigns`. `limit` defaults to 10 (max 100).

```bash
curl -H "Authorization: Bearer $LA_API_KEY" \
  "https://lightweightanalytics.com/api/v1/sites/11111111-2222-3333-4444-555555555555/stats/breakdown?by=referrers&range=30d&limit=5"
```

```json
{
  "by": "referrers",
  "items": [
    { "key": "news.ycombinator.com", "count": 1204 },
    { "key": "google.com", "count": 892 },
    { "key": "direct", "count": 611 }
  ]
}
```

`by=campaigns` returns your UTM campaign traffic (always the last 30 days) with a different item shape:

```json
{
  "by": "campaigns",
  "items": [
    { "source": "newsletter", "medium": "email", "campaign": "launch-week", "count": 302 }
  ]
}
```

### GET /sites/{siteId}/stats/live

Visitors active in the last 5 minutes. Not cached, so poll it as often as you like within the rate limit.

```bash
curl -H "Authorization: Bearer $LA_API_KEY" \
  https://lightweightanalytics.com/api/v1/sites/11111111-2222-3333-4444-555555555555/stats/live
```

```json
{ "siteId": "11111111-2222-3333-4444-555555555555", "liveVisitors": 3 }
```

---

## Goals & campaigns

Goals track conversions (sign-ups, purchases, clicks). Three types:

- `manual`: you fire the event yourself (JS `window.la('event', 'Signup')` or the collect endpoint below)
- `click`: fired declaratively via a `data-la-goal` attribute on an element
- `url`: fired automatically when a visited URL matches `pattern` (`matchType`: `contains`, `starts` or `regex`)

Values are integer **cents** to avoid floating point.

### GET /sites/{siteId}/goals

```bash
curl -H "Authorization: Bearer $LA_API_KEY" \
  https://lightweightanalytics.com/api/v1/sites/11111111-2222-3333-4444-555555555555/goals
```

```json
{
  "goals": [
    { "goalId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee", "name": "Signup", "type": "manual", "matchType": null, "pattern": null, "defaultValueCents": null },
    { "goalId": "ffffffff-1111-2222-3333-444444444444", "name": "Checkout", "type": "url", "matchType": "contains", "pattern": "/thank-you", "defaultValueCents": 2900 }
  ]
}
```

### POST /sites/{siteId}/goals (create a goal)

```bash
curl -X POST -H "Authorization: Bearer $LA_API_KEY" -H "Content-Type: application/json" \
  -d '{"name": "Checkout", "type": "url", "matchType": "contains", "pattern": "/thank-you", "defaultValueCents": 2900}' \
  https://lightweightanalytics.com/api/v1/sites/11111111-2222-3333-4444-555555555555/goals
```

### PATCH /goals/{goalId} (update a goal)

Send only what changes, e.g. adjust the default conversion value for a campaign push:

```bash
curl -X PATCH -H "Authorization: Bearer $LA_API_KEY" -H "Content-Type: application/json" \
  -d '{"defaultValueCents": 4900}' \
  https://lightweightanalytics.com/api/v1/goals/ffffffff-1111-2222-3333-444444444444
```

### DELETE /goals/{goalId}

Deletes the goal definition and (by default) its recorded conversions. Keep the historical events with `?purgeEvents=false`.

```bash
curl -X DELETE -H "Authorization: Bearer $LA_API_KEY" \
  https://lightweightanalytics.com/api/v1/goals/ffffffff-1111-2222-3333-444444444444
```

### GET /sites/{siteId}/goals/stats

Conversions and revenue per goal for a period.

```bash
curl -H "Authorization: Bearer $LA_API_KEY" \
  "https://lightweightanalytics.com/api/v1/sites/11111111-2222-3333-4444-555555555555/goals/stats?range=30d"
```

```json
{
  "goals": [
    { "goalId": "ffffffff-1111-2222-3333-444444444444", "name": "Checkout", "type": "url", "conversions": 41, "revenue": 1189.00, "avgValue": 29.00 }
  ]
}
```

---

## Recording pageviews & events

Normal pageviews need **no API at all**. The tracking snippet handles them. To record custom events or conversions from a server, script or agent, POST to the public collect endpoint (no API key; it validates the `siteId`):

```bash
curl -X POST -H "Content-Type: application/json" \
  -d '{"siteId": "11111111-2222-3333-4444-555555555555", "url": "/api-event", "event": "Signup", "value": 2900}' \
  https://analytics.lightweightanalytics.com/collect
```

Returns `202` on success. `event` names (1-40 chars, letters/numbers/spaces/`-`/`_`) that don't exist yet are auto-created as manual goals, so they show up in the dashboard immediately. `value` is in cents.

---

## Notes for agents

- Fetch this document any time at `https://lightweightanalytics.com/docs/api.md` (markdown). The short index is at `https://lightweightanalytics.com/llms.txt`.
- `GET /me` is the cheapest way to verify a key before doing real work.
- Site and goal ids are GUIDs; discover them via `GET /sites` and `GET /sites/{siteId}/goals` rather than guessing.
- Deletes are destructive and require explicit confirmation parameters by design. Don't retry a `confirmation_required` error without checking with your human.
- A native CLI (macOS & Windows) that wraps this API is coming soon; everything it will do is already possible with plain HTTP.
