# Authentication

Clicktag authenticates private reports and site management with a Firebase ID token, valid for about an hour, or a long-lived management key, both sent in `Authorization: Bearer`. Tracking through `latest.js` or `POST /events` and public site stats need no token at all.

## Authentication model

Administrative endpoints and private site statistics require a valid Firebase ID token or a [management key](/docs/authentication#management-keys) in the standard Authorization header:

```text
Authorization: Bearer <FIREBASE_ID_TOKEN>
```

Key credential rules:

Clicktag keeps the `tt_` and `tt_mk_` key prefixes and `X-Totallytics-Site` header unchanged. Existing credentials and integrations continue working.

- Management endpoints accept a Firebase ID token or a [management key](/docs/authentication#management-keys) (`tt_mk_...`), never service account JSON credentials. Site API keys (`tt_...`) only authenticate [API analytics ingest](/docs/api-requests#send-request-metrics).
- Simple Analytics credentials cannot authenticate Clicktag API calls. They are only used as source configuration for [historical imports](/docs/imports).
- The client Firebase configuration API key is not an access token and will be rejected.

## Obtaining an access token

Access tokens are short-lived JWTs (typically valid for approximately one hour).

To acquire your token for development or testing:

1. Click **Sign in** in the interactive panel on this page and continue with Google. You will return here after signing in.
2. Click **Copy access token**. This copies your active Firebase ID token directly to your clipboard, refreshing credentials if necessary.

## Using tokens in shell scripts

To prevent tokens from appearing in shell history or committed code, read the token into an environment variable using silent terminal input:

```bash
read -rsp "Clicktag ID Token: " TT_TOKEN && export TT_TOKEN
```

Pass the variable in the Authorization header of your API requests:

```bash
curl -sS --fail-with-body \
  -H "Authorization: Bearer $TT_TOKEN" \
  -H "Accept: application/json" \
  https://clicktag.io/api/sites
```

Keep your token secure. Never include ID tokens in frontend client bundles, public repositories, or tracking script attributes.

## Management keys

Management keys are long-lived owner credentials for scripts, CI and agents. One key works for every site you own, limited by the scopes you pick. Create one under **Workspace settings**, **API keys**. The secret (`tt_mk_` followed by 48 lowercase hex characters) is shown once, so store it in your secret manager right away. You can have up to 10 active keys.

Send it the same way as an ID token:

```text
Authorization: Bearer tt_mk_...
```

A revoked key stops working within about a minute: each server instance caches a key for up to 30 seconds, and each data center for up to 60 seconds. Revoking a management key does not revoke the site API keys it created; the site's key list shows which management key created each one.

### Scopes

| Scope | Allows |
|---|---|
| `read` | `GET` requests: sites, summaries, overview, breakdown, retention, setup checks, goals, email reports, report defaults and the workspace digest, alert rules, key lists, API analytics stats, Search Console stats, AI crawler stats and import jobs |
| `manage` | Changes: create, update and verify sites; create, update and delete goals, email reports and alert rules; update report defaults and the workspace digest; start a Search Console or AI crawler sync; create, rotate and revoke site API keys; revoke other management keys |
| `ingest` | `POST /api/ingest` for any of your sites, named in the `X-Totallytics-Site` header. Websites need to be verified first. |

Scopes don't include each other: a key with only `manage` can create a site but not list your sites. Endpoints that need you signed in are listed under [What a management key cannot do](/docs/authentication#what-a-management-key-cannot-do).

`GET /api/me` works with any scope, and any key can revoke itself with `DELETE /api/management-keys/self`.

### Examples

Read the key into your shell once, and set `EA_HOSTNAME` to one of your sites:

```bash
read -rsp "Clicktag management key: " TT_MANAGEMENT_KEY && export TT_MANAGEMENT_KEY
EA_HOSTNAME='shop.example.com'
```

Then send it with each request:

```curl
# List every site you own
curl -H "Authorization: Bearer $TT_MANAGEMENT_KEY" https://clicktag.io/api/sites
```

```curl
# Create a site
curl -X POST https://clicktag.io/api/sites \
  -H "Authorization: Bearer $TT_MANAGEMENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"hostname":"shop.example.com"}'
```

```curl
# Mint a per-site key (the secret is only in this response)
curl -X POST "https://clicktag.io/api/sites/$EA_HOSTNAME/keys" \
  -H "Authorization: Bearer $TT_MANAGEMENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"label":"Production"}'
```

```curl
# Read the last 7 days
from=$(( $(date +%s) - 7 * 86400 ))
curl -H "Authorization: Bearer $TT_MANAGEMENT_KEY" \
  "https://clicktag.io/api/sites/$EA_HOSTNAME/overview?from=$from"
```

```curl
# Send API analytics for one of your sites
curl -X POST https://clicktag.io/api/ingest \
  -H "Authorization: Bearer $TT_MANAGEMENT_KEY" \
  -H "X-Totallytics-Site: $EA_HOSTNAME" \
  -H "Content-Type: application/json" \
  -d @batch.json
```

The batch format is described in [Send request metrics](/docs/api-requests#send-request-metrics).

### What a management key cannot do

The endpoints below need you signed in. With a key they answer `403` `{"error":"management keys cannot call this endpoint"}`.

- Deleting a site (`DELETE /api/sites/{hostname}`)
- Google connections (everything under `/api/import-oauth/google/`)
- Starting, cancelling and retrying imports (`POST .../imports`, `.../imports/{jobId}/cancel`, `.../imports/{jobId}/retry`)
- Search Console properties, linking and unlinking (`.../gsc/properties`, `.../gsc/link`)
- The Cloudflare connection and AI crawler linking and unlinking (everything under `/api/cloudflare/`, `.../crawlers/link`)
- Test sends (`.../reports/{id}/test`, `.../alerts/{id}/test`, `POST /api/reports/workspace/test`)
- Creating management keys

Report defaults and the workspace digest work with a key: `GET` and `PUT /api/reports/defaults`, `GET` and `PUT /api/reports/workspace`, and `GET /api/reports/workspace/preview`. Live visitor routes (`/api/live/...`) take only an ID token; a key gets `401` `{"error":"sign in required"}`.

Alert rules created with a key must include `recipients`, because a key has no account email to default to.

### Errors and limits

| Status | Body | Meaning |
|---|---|---|
| 401 | `{"error":"invalid or revoked management key"}` | Unknown or revoked key |
| 403 | `{"error":"scope manage required"}` | The key lacks the scope this endpoint needs (`read`, `manage` or `ingest`) |
| 403 | `{"error":"management keys cannot call this endpoint"}` | The endpoint needs you signed in |
| 403 | `{"error":"verify this site before sending API data"}` | Ingest for a website that isn't verified yet |
| 400 | `{"error":"X-Totallytics-Site header required"}` | Ingest with a management key needs the site header |
| 429 | `{"error":"rate limited, retry in 1s"}` | Too many changes from one key: a burst of 60, then one per second, counted on each server instance. `Retry-After` is `1` |

Reads and `POST /api/ingest` are not rate limited. `GET /api/me` returns the key's scopes and your site count; use it to check a credential before a deploy.

## Endpoint authorization rules

Different Clicktag endpoints enforce distinct access rules:

- Public collection (`latest.js`, `simple.gif`, `noscript.gif`, `/r`, `POST /events`, `POST /append`): No token required. Collector CORS allows any origin; storing website traffic requires a registered, verified hostname.
- Public site reads (`GET /api/sites/<hostname>`, `/api/sites/<hostname>/overview`, `/api/sites/<hostname>/overview/summary`, `/api/sites/<hostname>/breakdown`, and `/api/sites/<hostname>/retention`): No token required for a verified site whose owner has enabled public visibility.
- Private site metadata and web statistics: Send the owner's Firebase ID token or a management key with `read`. Missing or invalid tokens, non-owner tokens and unknown sites all return `404 unknown site`. A management key is checked first, on public sites too: an unknown or revoked key returns `401`, a key without `read` returns `403`. An owner can read unverified site metadata, but web statistics return `403` until verification.
- Import source discovery requires sign-in. Imports, goals, Search Console, AI crawlers, email reports, alerts, site API keys and API request reports remain owner-only even when the site is public. Verification requirements depend on the endpoint; [API-only sites](/docs/sites#api-only-sites) can ingest and read request metrics before verification.
- API analytics ingestion (`POST /api/ingest`): Requires a site API key (`tt_...`), not a Firebase ID token. The key can only write request metrics for its own site. A management key with `ingest` can write for any of your sites named in the `X-Totallytics-Site` header.
- Account-wide site lists and summaries require sign-in or a management key and return only the caller's sites.
- Site management (`GET /api/sites`, `POST /api/sites`, `PATCH /api/sites/<hostname>`, `DELETE /api/sites/<hostname>`): Requires a valid ID token belonging to the site owner, or a management key (`read` for `GET`, `manage` for `POST` and `PATCH`). Deleting a site needs the ID token.

### Error responses

Authentication errors return standard JSON payloads accompanied by a `Cache-Control: no-store` header:

```json
{
  "error": "sign in required"
}
```

Expected status codes:

- `401 Unauthorized`: An owner-only or account endpoint needs a valid token or management key. `/api/ingest` instead returns an API-key error when its site key is missing, malformed, invalid or revoked.
- `403 Forbidden`: The caller owns the site, but the operation requires domain verification. Follow the response's `error` message and [verify ownership](/docs/sites#verify-ownership). Management keys also get `403` for a missing scope; see [Errors and limits](/docs/authentication#errors-and-limits).
- `404 Not Found`: The hostname does not exist or the caller cannot access it. Private metadata and web-statistics reads also use this status for missing, expired or malformed tokens.

## Token expiration and retries

Tokens expire after about one hour, and there is no product token-refresh endpoint. For scripts and CI, create a [management key](/docs/authentication#management-keys) instead of refreshing ID tokens. For an existing signed-in session:

1. If a request returns HTTP 401, get a fresh ID token from your signed-in Firebase client session (`user.getIdToken(true)`) or use **Copy access token** again. The Firebase Admin SDK is not a refresh mechanism for a user’s ID token.
2. Re-run safe, idempotent requests (such as `GET` queries) using the new token.
3. Do not retry state-mutating requests (`POST`, `DELETE`) blindly without checking site state first.

## Cross-Origin Resource Sharing (CORS)

Actual administrative and statistics responses (`https://clicktag.io/api/*`) do not include CORS headers. The global OPTIONS handler does answer preflights, but that does not make the API cross-origin readable. Browser applications cannot query the API across origins, even for public sites.

Make all API calls from backend services, serverless functions, or from within the Clicktag web origin.

Review the [Stats API Reference](/docs/stats) for querying overview charts and breakdowns.
