Emit analytics events to Millimetric (track, identify, batch, forget). Use when the user wants to send a custom event, log a signup/purchase/pageview, link a...
---
name: millimetric-track
description: Emit analytics events to Millimetric (track, identify, batch, forget). Use when the user wants to send a custom event, log a signup/purchase/pageview, link an anonymous visitor to a user_id, bulk-import events, or process a GDPR delete request.
metadata: { "openclaw": { "requires": { "env": ["MILLIMETRIC_KEY"], "bins": ["curl"] }, "primaryEnv": "MILLIMETRIC_KEY", "emoji": "📡", "homepage": "https://api.millimetric.ai" } }
---
# Millimetric Track
Send analytics events to Millimetric's API. Server-side keys only (`sk_live_…`). The classifier auto-derives `source`/`medium`/`campaign` from `url` + `referrer` + click IDs — don't try to set those yourself.
## When to Use
- User asks to log/track/emit/send/record an event
- Linking an anonymous visitor to a known user (`identify`)
- Bulk-uploading events from a backfill or webhook
- Processing a "forget me" / GDPR erasure request
- Wiring server-side instrumentation (Express, Fastify, Hono, Next.js route handler, cron job, queue worker)
## When NOT to Use
- Reading data → use `millimetric-query`
- Connecting an AI agent to Millimetric → use `millimetric-mcp-setup`
- Browser instrumentation → tell the user to drop in the `<script src="https://cdn.millimetric.ai/v1/a.js" data-key="pk_live_…">` snippet instead — `pk_*` keys must never appear in shell or server code
## Setup
```bash
export MILLIMETRIC_KEY=sk_live_... # server key, scope: ingest
export MILLIMETRIC_HOST=https://api.millimetric.ai
```
For local dev against the Worker: `MILLIMETRIC_HOST=http://localhost:8787`.
## Quick start
### Track a single event
```bash
curl -X POST "$MILLIMETRIC_HOST/v1/track" \
-H "Authorization: Bearer $MILLIMETRIC_KEY" \
-H "Content-Type: application/json" \
-d '{
"event": "signup",
"anonymous_id": "u_abc",
"user_id": "user_42",
"url": "https://yoursite.com/?utm_source=facebook&utm_medium=cpc&fbclid=abc",
"properties": { "plan": "free" }
}'
```
Response: `202 Accepted`, body `{ "ok": true, "event_id": "..." }`.
### Identify (link anonymous → user)
```bash
curl -X POST "$MILLIMETRIC_HOST/v1/identify" \
-H "Authorization: Bearer $MILLIMETRIC_KEY" \
-H "Content-Type: application/json" \
-d '{
"anonymous_id": "u_abc",
"user_id": "user_42",
"traits": { "email": "matt@example.com", "plan": "pro" }
}'
```
After this, include `user_id` on every subsequent `track` call for that visitor.
### Batch (up to 500 events per request, ≤256 KB body)
```bash
curl -X POST "$MILLIMETRIC_HOST/v1/batch" \
-H "Authorization: Bearer $MILLIMETRIC_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
{ "event": "purchase", "anonymous_id": "u_1", "user_id": "user_1", "properties": { "amount_cents": 4900 } },
{ "event": "purchase", "anonymous_id": "u_2", "user_id": "user_2", "properties": { "amount_cents": 1900 } }
]
}'
```
Each event in the batch supports the same fields as `/v1/track`. Batch is rate-limited at 5/sec, burst 20.
### Forget (GDPR delete)
```bash
curl -X POST "$MILLIMETRIC_HOST/v1/forget" \
-H "Authorization: Bearer $MILLIMETRIC_KEY" \
-H "Content-Type: application/json" \
-d '{ "user_id": "user_42" }'
```
Requires an `sk_*` key — `pk_*` is rejected with `403 forget_requires_secret_key`.
## Field reference (track / batch event)
| Field | Required | Notes |
|-------|----------|-------|
| `event` | yes | 1–128 chars. System events start with `$` (`$pageview`, `$identify`). |
| `event_id` | no | Idempotency key, 1–128 chars. |
| `timestamp` | no | ISO 8601. Defaults to server time. |
| `anonymous_id` | no | Caller-supplied UUID. Server generates one if omitted. |
| `user_id` | no | Stable internal user id. |
| `session_id` | no | Defaults to `${anonymous_id}-${30min_bucket}`. |
| `url` | no | Landing URL with `utm_*`, `fbclid`, `gclid`. **Classifier reads this.** |
| `path` | no | Path only. |
| `referrer` | no | `document.referrer` or server `Referer`. **Classifier reads this.** |
| `properties` | no | Free-form JSON, capped at 8 KB after `JSON.stringify`. |
The Worker enriches every event with `source`, `medium`, `campaign`, `source_confidence`, `source_rule_id`, `country`, `device_type`, `browser`, `os`, `ip_hash`. Don't send these — they're ignored.
## Node SDK alternative
```bash
npm i @millimetric/track-node
```
```ts
import { init, track, identify, flush } from "@millimetric/track-node";
init({ key: process.env.MILLIMETRIC_KEY!, host: "https://api.millimetric.ai" });
track({
event: "purchase",
anonymous_id: req.cookies.aid,
user_id: user.id,
properties: { amount_cents: 4900, currency: "usd" }
});
await flush(); // important in serverless handlers
```
## Common errors
| Status | `error` | Fix |
|--------|---------|-----|
| 400 | `invalid_payload` | `event` missing or `properties` > 8 KB. |
| 401 | `malformed_api_key` | Key doesn't match `(pk\|sk\|rk)_*_*_*`. |
| 401 | `invalid_api_key` | Key not found in DB. |
| 403 | `origin_not_allowed` | Using `pk_*` from a non-allowlisted origin — switch to `sk_*` for server-side. |
| 403 | `forget_requires_secret_key` | `/v1/forget` called with `pk_*`. |
| 429 | `rate_limited` | Back off using the `Retry-After` header. Track: 50/s, burst 200. Batch: 5/s, burst 20. |
## Key hygiene
- `pk_live_…` → browser snippet only. Never paste into shell, server code, or this skill.
- `sk_live_…` → server-side ingest. Use here.
- `rk_live_…` → read-only — for the query skill, not this one.
## See also
- Query/analytics → `millimetric-query`
- AI agent / MCP setup → `millimetric-mcp-setup`
- Attribution rules: https://api.millimetric.ai (docs/concepts/attribution.md)
don't have the plugin yet? install it then click "run inline in claude" again.
formalized intent, inputs, procedure (5 numbered steps with explicit branching), decision points (8 key branches for key type, operation, rate limits, errors), output contract (success/error formats, edge cases), and outcome signal (verification steps); preserved all original api details and curl examples.
Send analytics events to Millimetric's API. server-side keys only (sk_live_…). the classifier auto-derives source/medium/campaign from url + referrer + click IDs, so don't try to set those yourself.
use this skill when the user wants to emit a server-side analytics event to millimetric, link an anonymous visitor to a user id, bulk-upload events from a backfill or webhook, or process a gdpr erasure request. the skill wraps millimetric's /v1/track, /v1/identify, /v1/batch, and /v1/forget endpoints. do not use this for reading analytics data (use millimetric-query), connecting an ai agent (use millimetric-mcp-setup), or browser-side instrumentation (drop the <script> snippet instead).
environment variables:
MILLIMETRIC_KEY (required): server-side api key, format sk_live_*_*_*. scope: ingest. generate at https://api.millimetric.ai.MILLIMETRIC_HOST (optional): api endpoint. defaults to https://api.millimetric.ai. for local dev, set to http://localhost:8787.external connection:
$MILLIMETRIC_HOST (https or http). requires outbound https/http on port 443/80.binaries:
curl (for shell commands). alternatively, use the node sdk: @millimetric/track-node (npm package).caller inputs (per request):
$ (e.g. $pageview)./v1/identify and /v1/forget.${anonymous_id}-${30min_bucket}.step 1: validate api key format
$MILLIMETRIC_KEY env var.(sk|pk|rk)_live_[a-z0-9]+_[a-z0-9]+_[a-z0-9]+.step 2: determine operation type
step 3a (track): emit single event
curl -X POST "$MILLIMETRIC_HOST/v1/track" \
-H "Authorization: Bearer $MILLIMETRIC_KEY" \
-H "Content-Type: application/json" \
-d '{...}'
{ "ok": true, "event_id": "..." }).step 3b (identify): link anonymous to user
curl -X POST "$MILLIMETRIC_HOST/v1/identify" \
-H "Authorization: Bearer $MILLIMETRIC_KEY" \
-H "Content-Type: application/json" \
-d '{"anonymous_id":"...","user_id":"...","traits":{...}}'
{ "ok": true }).step 3c (batch): upload multiple events
curl -X POST "$MILLIMETRIC_HOST/v1/batch" \
-H "Authorization: Bearer $MILLIMETRIC_KEY" \
-H "Content-Type: application/json" \
-d '{"events":[...]}'
{ "ok": true }).step 3d (forget): erase user data
sk_* (secret key, not public or read-only).curl -X POST "$MILLIMETRIC_HOST/v1/forget" \
-H "Authorization: Bearer $MILLIMETRIC_KEY" \
-H "Content-Type: application/json" \
-d '{"user_id":"..."}'
{ "ok": true }).step 4: handle response
step 5: retry on failure (optional)
Retry-After header (if present).Retry-After header and wait that many seconds before retrying.if using browser-side tracking: skip this skill entirely. instead, instruct the user to drop the millimetric script tag into their html: <script src="https://cdn.millimetric.ai/v1/a.js" data-key="pk_live_..."></script>. never paste pk_* keys into shell or server code.
if operation is track or batch: accept optional fields (url, referrer, properties, timestamp, session_id). if url or referrer is provided, millimetric's classifier will auto-derive source/medium/campaign. do not manually set source, medium, campaign, source_confidence, source_rule_id, country, device_type, browser, os, or ip_hash; the server enriches these.
if operation is identify: both anonymous_id and user_id are required. traits (custom user attributes) are optional. after identifying, all subsequent track calls for that visitor must include the user_id.
if operation is batch: enforce limits: max 500 events per request, max 256 kb total body. if more than 500 events, split into multiple batch requests. if any single event.properties exceeds 8 kb, reject that event before sending.
if operation is forget: only sk_* (secret) keys are allowed. if key is pk_* (public) or rk_* (read-only), return error forget_requires_secret_key. user_id is required.
if api key is missing or malformed: fail immediately with "millimetric api key not set or invalid. set env var MILLIMETRIC_KEY to sk_live_*_*_*".
if millimetric host is unreachable (connection timeout, dns failure, or 5xx): retry with exponential backoff. after 3 retries, fail with "millimetric api unavailable after 3 attempts".
if rate limited (429 response): read the Retry-After header (e.g. "60"). wait that many seconds and retry up to 1 time. if still rate limited, return error with advice: "you are hitting millimetric's rate limits. track events are limited to 50/sec (burst 200). batch is limited to 5/sec (burst 20). reduce event frequency or contact support."
if payload is invalid (400 response): check error code. if invalid_payload, likely cause is missing event name or properties > 8 kb. if payload_too_large, body exceeds 256 kb in batch. return error with hints.
if result set is empty (e.g. no events in batch): still send the request; millimetric will accept an empty batch as valid.
on success:
{ "ok": true, "event_id": "evt_..." } (for track/identify/forget) or { "ok": true } (for batch).on error:
{ "error": "error_code", "message": "human readable message" }.edge cases:
event_id collision (sending same idempotency key twice): second request is a no-op; millimetric deduplicates.anonymous_id reused across /v1/identify calls: later identify calls update the same anon visitor's traits. do not assume isolation.user_id reused in /v1/forget: all data for that user_id is erased (cannot be undone). confirm with user before calling forget.the skill worked if:
"ok": true.in real-time: log the response body to stdout or to a dashboard. check millimetric's analytics ui (https://api.millimetric.ai) to see events arriving. if no events appear within 5 minutes, check api key validity, network connectivity, and error logs for 400/401/429 responses.
npm i @millimetric/track-node
import { init, track, identify, flush } from "@millimetric/track-node";
init({ key: process.env.MILLIMETRIC_KEY!, host: "https://api.millimetric.ai" });
track({
event: "purchase",
anonymous_id: req.cookies.aid,
user_id: user.id,
properties: { amount_cents: 4900, currency: "usd" }
});
await flush(); // important in serverless handlers
| status | error | fix |
|---|---|---|
| 400 | invalid_payload |
event missing or properties > 8 kb. |
| 401 | malformed_api_key |
key doesn't match (pk|sk|rk)_*_*_*. |
| 401 | invalid_api_key |
key not found in db. regenerate at https://api.millimetric.ai. |
| 403 | origin_not_allowed |
using pk_* from non-allowlisted origin. switch to sk_* for server-side. |
| 403 | forget_requires_secret_key |
/v1/forget called with pk_*. use sk_* only. |
| 429 | rate_limited |
back off using Retry-After header. track: 50/s burst 200. batch: 5/s burst 20. |
pk_live_… → browser snippet only. never paste into shell, server code, or this skill.sk_live_… → server-side ingest. use here.rk_live_… → read-only. for millimetric-query skill, not this one.millimetric-querymillimetric-mcp-setupcredits: original author soybelli (clawhub). enriched for implexa quality standards.