# Configuration

> Configure your Analytics Server.

Manage servers in your Project's **Analytics > Settings**. Only Project owners can change them.

## Projects

A saved server connects a Chunkify Project to one analytics project. Its credentials select the analytics project automatically; browser events do not need a project ID.

Hosted setup provisions the analytics project for you. For a self-hosted server, use public and read-only keys from the same analytics project.

To create another analytics project on a self-hosted server, run from the Docker Compose stack directory:

```sh
docker compose exec analytics \
  chunkify-analytics project create "Production website"
docker compose exec analytics \
  chunkify-analytics project list
```

Creation returns a generated `project_id`, `public_key`, and full-access `secret_key`. Save the keys privately when returned. Use that project ID when creating its read-only API or MCP keys.

Select a saved server when opening reports. **Default for dashboard players** independently selects where Chunkify previews, the playground, and newly generated player snippets send events.

Changing that default does not update players already deployed on your website.

## Credentials

| Key | Where it belongs | Access |
| --- | --- | --- |
| Public Player key, `ck_public_…` | Playback tracker in the browser | Sends events to one analytics project. |
| Read-only API key, `ck_secret_…` | Your application backend | Reads analytics and creates CSV export links. |
| MCP key, `ck_secret_…` | Your private MCP client configuration | Reads analytics through MCP, separately managed from the API key. |
| Full-access secret key | Self-hosted administration backend or secret manager | Reads analytics and manages the analytics project's settings and keys. |

In hosted settings, use the **Keys** tab for the Player and read-only API keys. Use the **MCP** tab for a separate MCP key and its endpoint. New private keys are shown once; save them before closing the dialog.

Rotation invalidates the previous key immediately. Update the clients that use it. Private keys never belong in player code.

For self-hosted servers, create and rotate keys on your server. The read-only key saved in Chunkify lets the dashboard query reports. It does not grant project or key management access.

Analytics keys are separate from Chunkify access tokens. The main [Chunkify SDK](/docs/integration/sdk) manages saved analytics connections through the [connection API](/docs/api-reference/projects/list-analytics-connections).

## Browser access

For hosted analytics, edit **Allowed origins** in the server's configuration. Chunkify dashboard origins are always allowed. Add every website origin from which you send events.

For self-hosted analytics, edit the existing section in `config.toml`:

```toml
[server]
allowed_origins = ["https://app.example.com", "http://localhost:3000", "https://chunkify.dev"]
```

Include the scheme and any port. Do not include paths, trailing slashes, or wildcards. `localhost` and `127.0.0.1` are different origins. A self-hosted project's additional origins extend the installation's shared list.

An empty combined allowlist denies browser requests carrying an `Origin` header.

Only `POST /v1/events` enables ingestion CORS. The server handles JSON and `X-Chunkify-Ingest-Key` preflight requests. Analytics reads belong on your backend, where the private key stays secret.

Configure your HTTPS proxy to forward the API and its `OPTIONS` requests without replacing the server's CORS responses. The tracker needs the full public ingestion URL, such as `https://analytics.example.com/v1/events`.

The public key authorizes event submission. Origin checks restrict browser access; they do not identify a viewer or authenticate the submitted measurements.

### Browser and page context

The Chunkify adapter adds `browser_context`, including browser, OS, device, language, screen dimensions, and the current document's `page_origin` and `page_path`.

Page context excludes query parameters and fragments. In an iframe it describes the iframe document. Paths can still contain application identifiers; use `collectBrowserContext: false` to omit context for that destination.

Page context follows client-side navigation on subsequent events. A retry keeps the context serialized for the original event.

### Location

The server derives approximate continent, country, and city from a local geolocation database. The lookup discards request IP addresses after use. Missing or private-address locations appear as `null`.

For self-hosting behind a proxy, configure the trusted proxy addresses or CIDRs in the existing `[server]` section:

```toml
trusted_proxies = ["10.20.1.2", "2001:db8:abcd::/48"]
```

The proxy must supply the real client address through `X-Forwarded-For`. Only trust proxy hops you control. Missing or invalid forwarded data can make location unavailable.

Pages displaying or using the bundled location results must include an [IP Geolocation by DB-IP](https://db-ip.com) link. See the [geolocation reference](https://github.com/chunkifydev/chunkify-analytics/blob/v0.18.0/docs/geolocation.md).

## Self-hosted settings

Use `config.toml` for application settings. Environment variables cover the ClickHouse connection and secrets; in the Docker Compose stack, `.env` supplies the ClickHouse password. Run `docker compose restart analytics` after editing `config.toml`, or `docker compose up -d` after changing `.env`.

The Docker image reads `/data/config.toml` and sets `CHUNKIFY_BIND=0.0.0.0:4174`, which overrides `server.bind`.

| TOML setting | Purpose |
| --- | --- |
| `server.allowed_origins` | Website origins permitted to send events. |
| `server.public_url` | External base URL used for CSV download links behind a proxy. |
| `server.trusted_proxies` | Proxy addresses trusted to provide client location context. |
| `retention.seconds` | Default receipt-time retention; `2592000` is 30 days and `0` disables expiration. |
| `retention.interval_seconds` | Interval between cleanup sweeps, in seconds. |
| `rate_limits` | Shared project read budgets, concurrency, and export limits. |
| `mcp.allowed_hosts` | Hostnames permitted to reach the MCP endpoint. |
| `clickhouse.url`, `clickhouse.database` | ClickHouse connection. Put the password in `CHUNKIFY_CLICKHOUSE_PASSWORD`. |
| `clickhouse.rollup_max_lag_seconds` | How long reports may lag stored events; `0` makes reports exact. |
| `cache.report_ttl_seconds` | Report cache lifetime on each server, from 0 to 60 seconds; `0` disables it. |

For remote MCP access, add your hostname to the existing section:

```toml
[mcp]
enabled = true
allowed_hosts = ["localhost", "127.0.0.1", "[::1]", "analytics.example.com"]
```

Use the [configuration reference](https://github.com/chunkifydev/chunkify-analytics/blob/v0.18.0/docs/configuration.md) for the complete settings, defaults, and bounds.

### Storage and event delivery

ClickHouse stores events, analytics projects, and key hashes. Analytics servers keep no data, so several servers can share one database behind a load balancer. Ingestion succeeds after ClickHouse stores the events.

A repeated event ID received within the deduplication window, seven days by default, keeps its first payload.

Summary, engagement, quality, time-series, and video reports read hourly rollups and can lag stored events by about a minute. Set `rollup_max_lag_seconds = 0` under `[clickhouse]` for exact reports, which cost more under load.

See the [storage reference](https://github.com/chunkifydev/chunkify-analytics/blob/v0.18.0/docs/storage.md) for scaling, replication, and these settings.

## Ingestion responses

| Response | Meaning |
| --- | --- |
| `201` | A new event was stored. |
| `200` | This event ID was already received within the deduplication window; the first payload is preserved. |
| `400` | The event is invalid. Send complete tracker events with the Chunkify adapter. |
| `401` | The public key is missing or invalid. |
| `403` | The website origin is not allowed for that analytics project. |
| `413` | The body or pending-write budget is exceeded. |
| `415` | The content type or encoding is unsupported. |
| `429` | An ingestion rate limit was reached. |
| `503` | The server is busy or unavailable. Inspect health and logs for a self-hosted server. |

The Chunkify adapter sends individual JSON events. Use the OTLP adapter for external telemetry gateways. See [delivery retries and limits](/docs/integration/analytics/send-events#retries-and-limits).
