---
title: HTTP API
description: Every route the server exposes, with authentication, parameters, filters and status codes.
---

All paths are relative to `basePath` (default `/api`). Dates are ISO 8601, or Unix milliseconds on the `Query` objects used in code. Time zones are IANA names and default to the site's time zone. Intervals are `hour`, `day`, `week` or `month`.

## Public routes

CORS is open and `text/plain` bodies are accepted, so the browser sends events without a preflight request.

| Method and path                    | Auth                | Description                                                                                                                                                                                                                                                      |
| ---------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /event`                      | none                | Tracker payload, see below. 202 on accept and on a filtered bot. 400, 403, 404 or 413 otherwise. 429 with a `Retry-After` header when one client sends more than 300 events a minute to a site (per server instance). 8 KB at most.                              |
| `POST /attribution`                | none                | Returns `{ visitorId, sessionId }` computed on the server for this request. Rate limited per hash.                                                                                                                                                               |
| `POST /v1/payments`                | Bearer site API key | Body: `PaymentInput`. Idempotent on `(siteId, id)`. Answers `{ ok, duplicate, payment, attributed }`.                                                                                                                                                            |
| `POST /v1/bots`                    | Bearer site API key | Body: `{ requests: [{ userAgent, path, ts? }] }`, at most 500. Answers `{ ok, recorded, skipped }`. See [Bot traffic](/guides/bot-traffic).                                                                                                                      |
| `POST /v1/annotations`             | Bearer site API key | Body: `{ text, ts? }`. Adds a dated note to the charts. `ts` is Unix ms or an ISO 8601 date, default now; `text` at most 200 characters. Answers 201 `{ ok, note }`. See [Notes on the chart](/guides/dashboard-embedding#notes-on-the-chart).                   |
| `POST /v1/goals`                   | Bearer site API key | Body: `name`, optional `props`, and `visitorId` or `email`.                                                                                                                                                                                                      |
| `POST /webhooks/:provider/:siteId` | signature           | `provider` is `stripe`, `dodo`, `lemonsqueezy`, `paddle`, `polar`, `razorpay`, `creem` or `gumroad`. Verified with the site's webhook secret.                                                                                                                    |
| `POST /v1/cron`                    | Bearer cron secret  | Runs the scheduled email work: the summary if due and tracker-down alerts. Answers `{ ok, summary, down }`. 401 without the `email.cronSecret`. `GET` works too. See [Email notifications](/guides/email-notifications).                                         |
| `GET /health`                      | none                | Health check. Answers `{ ok, dialect }`, or 503 with `ok: false` when the database cannot be reached. The failure `error` message is added only for a logged-in admin or when `debug` is on. Unknown paths answer 404 `not found (routes are under <basePath>)`. |

### Event body

The body is a `TrackerPayload`, compact on purpose, to keep the request small:

| Key   | Type                                                            | Meaning                                                                                                |
| ----- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `w`   | string                                                          | Site id. Required.                                                                                     |
| `t`   | `pageview`, `goal`, `outbound`, `ping`, `identify` or `revenue` | Event type. Required.                                                                                  |
| `u`   | string                                                          | Page URL. Required.                                                                                    |
| `r`   | string                                                          | Referrer.                                                                                              |
| `vid` | string                                                          | Visitor id. Only needed in `persistent` mode.                                                          |
| `sid` | string                                                          | Session id. Only needed in `persistent` mode.                                                          |
| `sw`  | number                                                          | Screen width.                                                                                          |
| `n`   | string                                                          | Goal or event name.                                                                                    |
| `p`   | object                                                          | Properties: strings, numbers, booleans or null.                                                        |
| `d`   | number                                                          | Engaged time in milliseconds.                                                                          |
| `a`   | number                                                          | Revenue amount, major units (19.99).                                                                   |
| `c`   | string                                                          | Currency.                                                                                              |
| `o`   | string                                                          | Order id. `revenue` events are de-duplicated by it.                                                    |
| `m`   | `strict`, `enriched` or `persistent`                            | Privacy mode of the tracker. Without it the event is treated as `strict`, and `vid`/`sid` are ignored. |

```bash
curl -X POST https://analytics.example.com/api/event \
  -H 'Content-Type: text/plain' \
  -d '{"w":"SITE_ID","t":"goal","u":"https://example.com/pricing","n":"signup","p":{"plan":"pro"}}'
```

## Admin routes

Admin routes need a session cookie from `POST /auth/login`, or `Authorization: Bearer <admin token>`. Without one they answer 401.

Most of them also accept an [API key](/guides/api-keys) as `Authorization: Bearer anl_...`, within the key's scope and sites (see [API keys](#api-keys)). Every route a key may use is served under `/v1` as well (`/v1/sites/:id/overview`), with identical behavior; the `/v1` address is the stable one for scripts. `GET /v1/openapi.json` describes them for tools, see [OpenAPI document](/reference/openapi).

### Auth

| Method and path     | Description                                            |
| ------------------- | ------------------------------------------------------ |
| `POST /auth/login`  | Body `{ "password": "..." }`. Sets the session cookie. |
| `POST /auth/logout` | Clears it.                                             |
| `GET /auth/me`      | Whether the current request is authenticated.          |

### Email

| Method and path    | Description                                                                                                                                                                                  |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /email`       | `{ configured, to, settings }`: whether the server has an `email` sender, its default recipient and the saved `EmailSettings` (`to`, `payments`, `summary`, `hour`, `down`, `timezone`).     |
| `PUT /email`       | Saves the fields sent and keeps the rest. 400 for an invalid address, frequency (`off`, `daily`, `weekly`), hour (0 to 23) or time zone. Answers the saved settings.                         |
| `POST /email/test` | Sends a test message to `to` from the body, else the saved or default recipient. Answers `{ ok, to }`; 400 without a sender or address, 502 with the sender's error message when it refuses. |

### Preferences

| Method and path    | Description                                                                                                                      |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `GET /preferences` | The dashboard settings (overview layout, summary numbers and chart, live refresh) as one JSON object; `{}` before any are saved. |
| `PUT /preferences` | Replaces them with the JSON object in the body. The server stores it as is; the dashboard checks each value when it reads it.    |

### Sites

| Method and path              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /sites`                 | List sites.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `GET /summary`               | Every site's visitors, revenue and orders in `from`–`to` (ISO dates, default the last 24 hours) and who is online now, as `SiteSummary[]`. One request for the sites overview, however many sites there are. `series=hour                                                                                                                                                                                                                                                                                                      | day`adds`traffic`(visitors) and`sales`(revenue) arrays with one point per bucket from`from`; `compare=1`adds`previous`, the totals of the window before. |
| `GET /my-ip`                 | The address this request comes from as the server sees it: `{ ip }`, or `null` when no trusted proxy header says. The dashboard's "Add my current IP" uses it.                                                                                                                                                                                                                                                                                                                                                                 |
| `GET /version`               | The running version and the newest release on npm: `{ current, latest }`; `latest` is `null` when the server could not look it up or `updateCheck` is off. The server asks the registry at most every six hours. The dashboard uses it for its update notice.                                                                                                                                                                                                                                                                  |
| `GET /sites/:id/favicon`     | The site's stored icon; the first request for a site that has none reads the site (see `POST /sites/:id/sync`). An icon older than 30 days is returned at once and fetched again behind it. 404 when there is none; a site that could not be read is not asked again for an hour.                                                                                                                                                                                                                                              |
| `POST /sites/:id/sync`       | Reads the site's home page and returns what it found against what is stored: `{ reachable, tracker, changes }`, each change `{ field, current, found, changed }` for `name`, `color` and `icon` (a `data:` URL), and `manual: true` on a name that was typed by hand. `tracker` is `found`, `not-found` or `unknown`. Changes nothing. Generated test sites answer `{ skipped: "test-site" }`.                                                                                                                                 |
| `POST /sites/:id/sync/apply` | Body `{ "fields": ["name", "color", "icon"] }`. Reads the page again and takes over the listed fields it found; answers `{ site, applied }`. 502 when the page cannot be read.                                                                                                                                                                                                                                                                                                                                                 |
| `POST /sites`                | Create a site. Only `domain` is required (a real domain such as `example.com`; `localhost` is rejected). Other fields: `name`, `timezone`, `currency`, `privacyMode`, `retentionDays`, `allowLocalhost`, `ignoredIps` (addresses or CIDR ranges), `ignoreAdmins`, `vitalsSample` (percent of page views, 0 to 100, that measure Core Web Vitals; 0 is off), `targets` (up to 20 of `{ goal, target, period }`, period `week`, `month` or `quarter`), `shareToken`. Answers 201 with `{ site, apiKey }`; the key is shown once. |
| `GET /sites/:id`             | One site. Secrets are never returned: `webhooks` says which providers have a secret, and `shareToken` is the current share token or `null`.                                                                                                                                                                                                                                                                                                                                                                                    |
| `PATCH /sites/:id`           | Change settings, including webhook secrets (`webhookSecrets: { stripe: "..." }`, `null` removes one) and the share link (`shareToken: true` creates a token, `null` turns sharing off). `paused: true` stops tracking until set back to `false`: tracker events and server goals are dropped (202 with `filtered: "paused"`), payments are still recorded.                                                                                                                                                                     |
| `DELETE /sites/:id`          | Delete the site and its data. The body proves it is meant: `{ "confirm": "example.com", "password": "..." }`, the site's domain and the admin password. When admins sign in through `verify()` there is no password, and the domain alone is enough. A wrong domain answers 400; a wrong password 403, and it counts toward the login limit.                                                                                                                                                                                   |
| `POST /sites/:id/rotate-key` | Issue a new site API key. The old one stops working.                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |

### Statistics

All take `from`, `to`, `interval`, `tz` and filters. `GET /sites/:id/realtime` takes none of them.

`overview`, `timeseries` and `breakdown` also take `compare=previous` (the period of the same length just before) or `compare=year` (the same dates a year earlier). The comparison comes back in the same response: every `TimeseriesPoint` and `BreakdownRow` gets a `previous` object (the bucket at the same position, matched by index; the row's numbers, all 0 when the value had no visits), and `overview` measures its changes against that period. Without `compare` nothing extra is read, and with it every one of these requests reads the data twice, so leave it off on a dashboard you keep open all day on a database that bills rows read.

| Method and path                                          | Returns                                                                                                                                                                            |
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /sites/:id/overview`                                | `Overview`. Revenue counts only the site's currency; the rest is in `otherCurrencies`.                                                                                             |
| `GET /sites/:id/timeseries`                              | `TimeseriesPoint[]`: traffic, revenue, orders and rates per bucket                                                                                                                 |
| `GET /sites/:id/breakdown?dimension=`                    | `BreakdownRow[]`                                                                                                                                                                   |
| `GET /sites/:id/properties?name=&key=`                   | `PropertiesResult`: the keys of the goal `name` (up to 20, with event counts), or with `key` its values with visitors and events (up to 50, the rest in `other`). Goal events only |
| `GET /sites/:id/bots`                                    | `BotStats`: crawler requests, filtered by repeated `crawler`, `category` and `page`                                                                                                |
| `GET /sites/:id/vitals`                                  | `VitalsResult`: the 75th percentile of Core Web Vitals for the site and its pages with the most reports, over the range (admin only)                                               |
| `GET /sites/:id/journeys`                                | `JourneysResult`: where sessions go over up to four steps, from the newest 5,000 sessions of the last 30 days of the range (admin only)                                            |
| `GET /sites/:id/retention`                               | `RetentionResult`: weekly cohorts of new visitors and how many came back, at most 12 cohorts (admin only; empty unless the site uses the `persistent` privacy mode)                |
| `GET /sites/:id/targets`                                 | `TargetsResult`: progress of the site's goal targets in their own periods (admin only; takes no range)                                                                             |
| `GET /sites/:id/realtime`                                | `Realtime`: visitors in the last 5 minutes, recent events and their visits on the map                                                                                              |
| `GET /sites/:id/sessions`                                | `Page<SessionSummary>`, see [Lists](#lists)                                                                                                                                        |
| `GET /sites/:id/sessions/:sid`                           | One session with its visitor profile, events and payments (admin only)                                                                                                             |
| `GET /sites/:id/events?name=&type=` (alias `/event-log`) | `Page<EventRow>`, see [Lists](#lists)                                                                                                                                              |
| `GET /sites/:id/payments`                                | `Page<Payment>`, see [Lists](#lists) (admin only)                                                                                                                                  |

Dimensions for `breakdown`: `channel`, `source`, `referrer`, `campaign`, `medium`, `term`, `content`, `ref`, `page`, `entry`, `exit`, `country`, `region`, `city`, `browser`, `os`, `device`, `event`, `outbound`, `goal`.

Every stats route also takes property filters, written `prop:<key>=<value>` (URL-encode the colon): they keep sessions with a goal event whose property equals the value, and when `goal` or `event` is also set, the same event has to match both. Keys use 1 to 64 letters, digits, `_`, `.` and `-`; other keys are ignored.

### Lists

Sessions, events and payments answer one page at a time, newest first: `{ items, nextCursor, total }`. `total` counts every match, not just this page.

| Parameter       | Meaning                                                                                             |
| --------------- | --------------------------------------------------------------------------------------------------- |
| `limit`         | Page size, up to 100.                                                                               |
| `cursor`        | The previous page's `nextCursor`. Only for the default order.                                       |
| `offset`        | Skip this many matches, for numbered pages. `nextCursor` is then `null`.                            |
| `sort`, `order` | Sort key and `asc` or `desc` (default `desc`). `nextCursor` is then `null`. Empty values sort last. |
| `search`        | Case-insensitive substring match over the list's text columns.                                      |
| `visitor`       | Only the rows of this visitor id.                                                                   |

Sort keys: sessions `time duration pageviews source country entry revenue`; events `time type name page`; payments `time amount provider customer attribution`. Sessions search pages, sources, channels, places, browsers, systems and visitor ids; events search names, paths and types; payments search ids, emails, names, customer, order and subscription ids and the provider.

Events also take `name` and `type` (`pageview`, `goal`, `outbound`, `revenue`, `identify`). Payments take `kind` (`payment` or `refund`), `provider` and `attributed` (`true` or `false`). An unknown sort, order or filter value answers 400, and so does a `cursor` combined with `sort` or `offset`.

```bash
curl -b cookies.txt \
  "https://analytics.example.com/api/sites/SITE_ID/payments?kind=refund&sort=amount&order=desc&offset=25&limit=25"
```

### Filters

Filters use the dimension names as query keys: `country=DE&channel=Organic%20Search&page=/pricing`. Repeat a key to match any of several values.

```bash
curl -b cookies.txt \
  "https://analytics.example.com/api/sites/SITE_ID/breakdown?dimension=source&from=2026-10-01&to=2026-11-01&country=DE"
```

### Share links

Admin routes, per site:

| Method and path                      | Description                                                                                                                                                                                                                                                                                                            |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /sites/:id/share-links`        | Body: optional `label` (80 characters), `views` (any of `overview`, `sources`, `pages`, `events`, `sessions`, `bots`, `funnels`, `realtime`; empty means all) `expiresInDays` (1 to 3650) and `ipAllowlist` (see below). Answers 201 `{ link, token }`. The token is shown only now. At most 50 active links per site. |
| `GET /sites/:id/share-links`         | `ShareLink[]`, newest first, revoked ones included, without tokens.                                                                                                                                                                                                                                                    |
| `DELETE /sites/:id/share-links/:lid` | Revokes the link at once. 404 for an unknown link.                                                                                                                                                                                                                                                                     |

A request that carries a token, as `?share=TOKEN`, an `x-analytik-share: TOKEN` header or `Authorization: Bearer TOKEN`, can read these `GET` routes without logging in: `shared` (the site's name, domain, time zone and currency, and the link's views), `overview`, `timeseries`, `breakdown`, `properties`, `realtime`, `sessions`, `events`, `bots`, and the funnel list and funnel results. A link limited to some views reads only the routes behind them; any other route answers 401, as does a token for another site, a revoked or an expired one. That is the aggregate numbers and the session list, including visitor ids, countries, cities, browsers, pages, referrers, and event names and properties. Payments, single-session details, the site settings, export and every write, including the share-link routes, stay admin only.

Only a token in the address is cached publicly (a header or bearer token is never): those answers carry `Cache-Control: public, s-maxage=60` (10 seconds for `realtime`). Tokens are stored as SHA-256 hashes.

The older single token still works: `PATCH /sites/:id` with `{ "shareToken": true }` returns a plain `shareToken` that opens every shareable route, uncached, until `{ "shareToken": null }`. Prefer share links.

### API keys

Created and managed by the signed-in admin only; a key can never read or change keys. Creating, rolling, enabling and deleting need `password` (the admin password) in the body unless admins sign in through `verify()`. A wrong password answers 403 and counts toward the sign-in limit (429 after 10 in 15 minutes). See the [guide](/guides/api-keys).

| Method and path              | Description                                                                                                                                                                                                                                                                                                                                                                         |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /api-keys`              | `ApiKey[]`, newest first, never with a secret: `id`, `label`, `prefix`, `scope`, `siteIds`, `createdAt`, `expiresAt`, `lastUsedAt`, `disabledAt`, `previousUntil`, `ipAllowlist`, `state` (`active`, `disabled`, `expired`).                                                                                                                                                        |
| `POST /api-keys`             | Body: `password`, optional `label` (80 characters), `scope` (`read` default, or `write`), `siteIds` (empty means all sites) and `expiresInDays` (1 to 3650). Answers 201 `{ key, secret }`. The secret starts with `anl_` and is shown only now. At most 20 keys.                                                                                                                   |
| `PATCH /api-keys/:id`        | Body: `password` and `ipAllowlist`, a list of at most 20 IPv4 or IPv6 addresses or CIDR ranges that replaces the key's list; `[]` clears it and allows any address. Entries are stored in canonical form and duplicates dropped. A bad entry answers 400 naming it and saves nothing. Answers the `ApiKey`. 404 for an unknown key.                                                 |
| `POST /api-keys/:id/roll`    | Body: `password` and optional `graceHours` (0 to 168, default 0). Answers `{ key, secret }`. The old secret works for the grace period, a second roll ends it.                                                                                                                                                                                                                      |
| `POST /api-keys/:id/disable` | Needs no password. Every request with the key answers 401 until it is enabled.                                                                                                                                                                                                                                                                                                      |
| `POST /api-keys/:id/enable`  | Body: `password`.                                                                                                                                                                                                                                                                                                                                                                   |
| `DELETE /api-keys/:id`       | Body: `password`. The key stops working at once. 404 for an unknown key.                                                                                                                                                                                                                                                                                                            |
| `GET /api-keys/events`       | The newest 200 events: `created`, `rolled`, `disabled`, `enabled`, `deleted`, `denied` (a call refused for its scope or site, recorded once a minute per key and route), `denied_ip` (a call refused for its address, with the address or `unknown`, once a minute per key and address) and `allowlist_changed` (the number of entries before and after), with time, key and label. |
| `GET /whoami`                | For a key: `{ kind: "key", id, label, prefix, scope, siteIds, expiresAt, rateLimit }`; for an admin `{ kind: "admin" }`.                                                                                                                                                                                                                                                            |

What a key may call:

- **`read`**: every `GET` of its sites that is a statistic or a list: `/sites`, `/sites/:id`, `overview`, `timeseries`, `breakdown`, `properties`, `bots`, `vitals`, `journeys`, `retention`, `targets`, `realtime`, `sessions`, `sessions/:sid`, `events`, `event-log`, `funnels`, `payments`, `export`, `imports`, `shared`, `favicon`, and `/summary`.
- **`write`**: all of the above plus `PATCH /sites/:id`, annotations (create, change, delete), funnels (create, delete), `POST /sites/:id/rematch`, `POST /sites/:id/import` and `DELETE /sites/:id/imports/:source`.
- **Never**: deleting a site, `rotate-key`, `sync`, share links, webhooks, deleting data, e-mail, preferences, defaults, debug, sign-in and the key routes above.

A key with an `ipAllowlist` answers 403 `this key cannot be used from this address` for any other address, and when the address is unknown; the check comes before scope and site and does not count toward the 30 unknown-key limit. A key limited to some sites answers 403 on others and sees only its sites in `/sites` and `/summary`. Errors: 401 (unusable key), 403 (not allowed, the message says why), 429 (limit). Each key may send 120 requests a minute and 12 a minute to `journeys`, `retention`, `export`, `import` and `sessions?search=`; answers carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`, and a 429 adds `Retry-After`. The limits and where they are counted (`keyRateLimit.perMinute`, `heavy`, `store: 'database' | 'memory'`) are server options. Unknown keys are counted per address: 30 in 15 minutes, then 429. Answers to a key are never cached (`Cache-Control: no-store`).

### Outgoing webhooks

Admin only; a share link cannot read or change them. See the [guide](/guides/webhooks-and-api#outgoing-webhooks).

| Method and path                      | Description                                                                                                                                                                          |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `GET /sites/:id/webhooks`            | `Webhook[]` with the host instead of the address, no secrets.                                                                                                                        |
| `POST /sites/:id/webhooks`           | Body: `url` (https, public host), `events` (`goal`, `payment`) and `format` (`json` or `chat`). Answers 201 `{ webhook, secret }`; the secret is shown only now. At most 5 per site. |
| `PATCH /sites/:id/webhooks/:wid`     | Change `events`, `format` or `paused`.                                                                                                                                               |
| `DELETE /sites/:id/webhooks/:wid`    | Remove it.                                                                                                                                                                           |
| `POST /sites/:id/webhooks/:wid/test` | Send one test message. Answers `{ ok, status }`.                                                                                                                                     |

### Annotations

| Method and path                      | Description                                                                   |
| ------------------------------------ | ----------------------------------------------------------------------------- |
| `POST /sites/:id/annotations`        | Body: `{ text, ts? }`, see `POST /v1/annotations`. Answers 201 with the note. |
| `PATCH /sites/:id/annotations/:nid`  | Body: `text` and/or `ts`. Answers the note, or 404.                           |
| `DELETE /sites/:id/annotations/:nid` | 404 for an unknown note.                                                      |

Notes are read with `GET /sites/:id/timeseries`: each point carries the notes dated in its bucket as `notes: [{ id, ts, text, createdAt }]`, at most 100 for the range, left out when there are none.

### Funnels

| Method and path                  | Description                                       |
| -------------------------------- | ------------------------------------------------- |
| `GET /sites/:id/funnels`         | List funnels.                                     |
| `POST /sites/:id/funnels`        | Create one: `{ name, steps: [{ type, match }] }`. |
| `DELETE /sites/:id/funnels/:fid` | Delete one.                                       |
| `GET /sites/:id/funnels/:fid`    | `FunnelResult`.                                   |

### Maintenance

| Method and path                  | Description                                                                                                                                           |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /sites/:id/rematch`        | Retry attribution for payments without a match.                                                                                                       |
| `GET /sites/:id/export?format=`  | `json`: everything for the site in one file. `csv`: one table at a time, chosen with `table=visitors\|sessions\|events\|payments` (default `events`). |
| `DELETE /sites/:id/data?before=` | Delete older data. `before` is an ISO 8601 date or Unix milliseconds. Answers with how many `events`, `sessions` and `visitors` were removed.         |

The purge deletes events, sessions, return-page claims and visitors left without sessions. Payments are kept so revenue numbers stay correct, but their email, name, customer id, visitor id and session id are cleared. See [Retention](/deploy/backups-retention-and-upgrades#retention).

### Debug

Test data for trying the dashboard with many sites, long histories and large numbers. `GET /debug` always answers; the other routes answer 404 unless the server was created with `debug: true`.

| Method and path        | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /debug`           | `{ enabled }`: whether the debug routes are on.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `POST /debug/generate` | Writes made-up visits, events, goals and payments (`TestDataInput`). With `siteId` they go into that site; otherwise into `sites` new sites with ids starting `debug-`. `days` (0 to 730), `visitorsPerDay` (0 to 100,000; a zero in either makes empty sites), `priceScale` and `currency` set the amounts. With `mix: true` the new sites differ the way real ones do (an empty one, a quiet blog, a busy shop, one just launched, a paused one and more), and `visitorsPerDay` and `priceScale` don't apply. One call writes at most 1,000,000 visits. Answers with `TestDataResult`: the sites, counts and how long it took. |
| `DELETE /debug/sites`  | Deletes every site whose id starts with `debug-`, with its data. Answers `{ deleted }`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |

### Payments

`POST /v1/payments` and `analytik.recordPayment()` take a `PaymentInput`. Amounts are minor units. A refund has `kind: "refund"` and the positive amount. For a refund without an id of its own, send `refundedTotal`, the total refunded for the order so far, and the server records only the difference; a retried request adds nothing.

Revenue events from the tracker are stored as payments with the provider `client` and the id `client:<orderId>`. When a verified payment from a provider webhook or your server arrives for the same order, it replaces the client record instead of counting twice.
