> For the complete documentation index, see [llms.txt](https://docs.millimetric.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.millimetric.ai/api-reference/track.md).

# POST /v1/track

Emit a single analytics event. The most common write endpoint.

## Auth

|                |                                                                     |
| -------------- | ------------------------------------------------------------------- |
| Required scope | `ingest`                                                            |
| Key kinds      | `pk_*` (browser, origin-checked) · `sk_*` (server, no origin check) |

## Request

```http
POST /v1/track
Authorization: Bearer {key}
Origin: {your-origin}                ← only for pk_* keys
Content-Type: application/json
```

```json
{
  "event": "signup",
  "event_id": "evt_abc123",
  "timestamp": "2026-05-16T19:00:00.000Z",
  "anonymous_id": "u_abc",
  "user_id": "user_42",
  "session_id": "sess_xyz",

  "url": "https://yoursite.com/?utm_source=facebook&utm_medium=cpc&fbclid=abc",
  "path": "/",
  "referrer": "https://l.facebook.com/",

  "properties": { "plan": "free", "team_size": 4 }
}
```

### Field reference

| Field          | Required | Notes                                                                                                                   |
| -------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
| `event`        | **yes**  | Event name. 1–128 chars. By convention, system events start with `$` (`$pageview`, `$identify`).                        |
| `event_id`     | no       | Idempotency key, 1–128 chars.                                                                                           |
| `timestamp`    | no       | ISO 8601. Defaults to server time when the request lands.                                                               |
| `anonymous_id` | no       | Caller-supplied UUID. If omitted, the Worker generates one — but you usually want to control this from the browser SDK. |
| `user_id`      | no       | If you've called `/v1/identify`, set this on subsequent events to link them to a person.                                |
| `session_id`   | no       | If omitted, derived as `${anonymous_id}-${30min_bucket}`. Override for custom session boundaries.                       |
| `url`          | no       | Landing URL — including any `utm_*`, `fbclid`, `gclid` query params. **The classifier reads this.**                     |
| `path`         | no       | URL path. The browser SDK sets this automatically.                                                                      |
| `referrer`     | no       | `document.referrer` value (or `Referer` header server-side). **The classifier reads this.**                             |
| `properties`   | no       | Free-form JSON, capped at 8 KB after `JSON.stringify`.                                                                  |

## Response

```http
HTTP/1.1 202 Accepted
Content-Type: application/json
```

```json
{ "ok": true, "event_id": "evt_abc123" }
```

The 202 means the event has been written to ClickHouse synchronously. Materialised views (`daily_rollup`, `sessions`) update within seconds.

## What the server adds

The Worker enriches every event before insert:

* **Source classification** — `source`, `medium`, `campaign`, `source_confidence`, `source_rule_id` (see [Attribution](/core-concepts/attribution.md)).
* **Geo & device** — `country` (geo-IP, country code only), `device_type`, `browser`, `os` (parsed from User-Agent).
* **Hashed IP** — `ip_hash = HMAC(ip, IP_SALT || UTC_date)`. Raw IPs are never persisted.

You don't need to send any of these; if you do, they're ignored.

## Errors

See [overview](/api-reference/overview.md#errors). Common ones for `track`:

* `400 invalid_payload` — `event` missing or `properties` exceeds 8 KB.
* `403 origin_not_allowed` — `pk_*` key, but `Origin` isn't in the project's allowlist.
* `429 rate_limited` — burst over 200 requests; back off and retry.

## Examples

### curl (server-side)

```bash
curl -X POST https://api.millimetric.ai/v1/track \
  -H "Authorization: Bearer $SK_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "purchase",
    "anonymous_id": "u_abc",
    "user_id": "user_42",
    "properties": { "amount_cents": 4900, "currency": "usd" }
  }'
```

### Browser SDK

```js
import { track } from "@millimetric/track";
track("purchase", { amount_cents: 4900, currency: "usd" });
```

### Node SDK

```js
import { track } from "@millimetric/track-node";
track({
  event: "purchase",
  anonymous_id: req.cookies.aid,
  user_id: user.id,
  properties: { amount_cents: 4900 }
});
```

### MCP

```json
{ "name": "track_event",
  "arguments": { "event": "purchase", "anonymous_id": "u_abc",
                 "properties": { "amount_cents": 4900 } } }
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.millimetric.ai/api-reference/track.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
