Theme
Analytics + Beacon
Lightweight, privacy-respecting metrics: which Stories get how many mounts, segmented by language and audience.
Beacon (anonymous mount tracking)
http
POST /v1/cdn/{space}/_beacon
Content-Type: application/jsonBody (all fields optional):
json
{
"story_uuid": "abc-...",
"segment_key": "eu-visitors",
"lang": "de-AT",
"referrer": "https://blog.acme.com/post-1",
"device": "desktop"
}Response: 204 No Content always. Beacon failures NEVER block the page — fire-and-forget.
How the SDK uses this
meta.beacon_url on every CDN-story response carries the absolute URL of this endpoint. The SDK auto-pings it after mount with the resolved story UUID, segment, and locale. The Astro/Next/Nuxt SDKs are opt-in — set beacon: true in createClient().
Rate-limited at 600/min/IP. No auth. Data retained 90 days then aggregated into rollups.
Reading aggregated data
http
GET /v1/spaces/{slug}/analytics/stories?from=2026-05-01&to=2026-05-27&granularity=dayAuth: auth:sanctum + space.scope middleware (role: viewer or higher).
Response:
json
{
"data": [
{
"story_uuid": "abc-...",
"slug": "welcome",
"mounts": 4291,
"by_lang": { "en": 3120, "de-AT": 980, "es-ES": 191 },
"by_segment": { "default": 3812, "eu-visitors": 479 },
"by_day": [
{ "date": "2026-05-26", "mounts": 174 },
{ "date": "2026-05-27", "mounts": 203 }
]
}
],
"meta": { "from": "2026-05-01", "to": "2026-05-27", "total_stories": 12 }
}Per-story timeline
http
GET /v1/spaces/{slug}/stories/{uuid}/analytics?days=30Returns daily mounts + by_lang + by_segment + by_device + a sparkline prepared for the admin UI.
AI insights (optional)
http
POST /v1/spaces/{slug}/analytics/ai-insightsRuns a one-shot LLM analysis over the last 30 days of mount data and returns plain-text observations:
- Stories trending up/down vs prior period
- Locales over-/under-served
- Segments not seeing variants (opportunity)
- Outliers (sudden traffic spikes)
Rate-limited: 10/hour per Space. Costs apply (LLM token cost passed through).
Privacy + GDPR
- Beacon stores no PII. No cookies set. No fingerprinting. No IP retained past the rate-limit window (10 min).
referreris truncated to scheme + host (no path, no query) before storage.deviceis the literal stringdesktop/mobile/tablet— no UA strings, no model numbers.- Operators can disable the beacon entirely by setting
PEACOCK_BEACON_ENABLED=falsein the API env. CDN responses then omitmeta.beacon_url.
See also
/api/audiences— what segment_key values mean/guide/personalisation— how segments shape what's measured- Security audit 2026-05-21 — beacon Host-poisoning fix (Medium #5)