API and Examples

API Reference

Practical reference for the current Logga backend route surface.

This page is a practical guide to the route surface currently wired in logga-backend/src/index.ts.

Base Concepts

Auth modes

Mode Typical caller Notes
Dashboard auth Web dashboard or native user flows Human user JWT-based access.
API key auth Backend or service integration Used for ingest and other workspace-scoped automation.
Hybrid auth Either of the above Route decides from the auth context which workspace and environment are active.
MCP token An AI assistant A lg_mcp_ key on the /mcp endpoint. See AI Connectors.

Scopes

An API key carries an explicit list of scopes. A request whose key lacks the scope its route needs is answered 403 insufficient_scope, naming the scope that was missing. Session tokens are not scoped — their authorisation is workspace membership.

Keys created before scopes existed carry an empty list, which means unrestricted.

Scope Grants
events:read / events:write GET / everything else on /v1/events
channels:read / channels:write /v1/channels
sessions:read / sessions:write /v1/sessions
rules:read / rules:write /v1/rules
analytics:read /v1/stats
notifications:read /v1/notifications
users:read tracked-user data through MCP
devices:write /v1/device-tokens

Common route families

Route family Auth
/health none
/auth/* mixed, depending on route
/v1/dashboard/* dashboard auth
/v1/events hybrid auth
/v1/channels hybrid auth
/v1/sessions hybrid auth
/v1/stats hybrid auth
/v1/rules hybrid auth
/v1/notifications hybrid auth
/v1/device-tokens hybrid auth
/v1/reports dashboard auth
/mcp MCP token

Health

GET /health

Returns a basic liveness payload.

Example response:

json
{
  "status": "ok",
  "timestamp": "2026-03-19T12:00:00.000Z"
}

Authentication

POST /auth/register

Creates a user, workspace, workspace membership, and a default project for the production environment.

Example body:

json
{
  "email": "[email protected]",
  "password": "very-secret-password",
  "name": "Alex",
  "workspaceName": "Acme"
}

POST /auth/login

Returns a user token plus a basic user payload.

GET /auth/me

Returns the authenticated user plus workspace memberships and environment summaries.

Events

POST /v1/events

Create an event in the authenticated workspace and environment.

Headers:

text
Authorization: Bearer lg_sk_xxxx
Content-Type: application/json

Example body:

json
{
  "channel": "payments",
  "event": "invoice_sent",
  "level": "success",
  "tags": ["stripe", "pro"],
  "metadata": {
    "amount": 49,
    "currency": "EUR"
  },
  "actor": {
    "id": "user_123",
    "name": "Francesco",
    "email": "[email protected]",
    "role": "admin",
    "properties": {
      "plan": "pro",
      "seats": 5
    }
  },
  "idempotencyKey": "invoice_abc123"
}

Notable validation behavior:

  • channel is required
  • event is required
  • level defaults to info
  • metadata is capped at 10KB
  • idempotencyKey is optional but globally unique when provided
  • actor.id is preferred over userId when both are sent
  • actor.properties values must be strings, numbers, booleans, or null; keys ≤64 chars, string values ≤1024 chars, max 50 keys per user
  • events upsert a row in the tracked users table (TrackedUser), keyed on (projectId, actor.id), and merge any identity fields carried in the event
  • actor.properties is a patch: sent keys are written, missing keys are kept, null removes a key. Properties that break the limits, or would take the user past 50 keys, are not stored; the event still is, and the 201 body carries warnings: ["actor.properties ignored: ..."]

GET /v1/events

Supported query filters include:

  • channel
  • level
  • userId
  • sessionId
  • from
  • to
  • limit
  • cursor
  • tags
  • projectId

GET /v1/events/:id

Fetch a single event scoped to the authenticated workspace and environment.

DELETE /v1/events/:id

Delete one event.

DELETE /v1/events

Bulk delete up to 200 event ids in one request.

Sessions

POST /v1/sessions

Creates a new session.

Example body:

json
{
  "channel": "system",
  "name": "nightly_build",
  "metadata": {
    "branch": "main",
    "commit": "abc123"
  },
  "ttlSeconds": 1800
}

PATCH /v1/sessions/:id

Updates session progress, current step, or metadata.

POST /v1/sessions/:id/complete

Marks the session complete and may include a summary.

POST /v1/sessions/:id/fail

Marks the session failed and may include a reason.

POST /v1/sessions/:id/close

Closes the session without marking it successful.

Rules

GET /v1/rules

Lists alert rules for the authenticated workspace and environment.

Optional filters:

  • channelId
  • isActive
  • cursor pagination fields

POST /v1/rules

Creates an alert rule.

Example body:

json
{
  "name": "Alert on payment failures",
  "channelId": "channel_uuid",
  "condition": {
    "type": "on_event",
    "level": "error"
  }
}

PATCH /v1/rules/:id

Updates name, target channel, condition, or isActive.

Reports

Reports are dashboard-auth-only today.

GET /v1/reports

Query parameters:

  • workspaceId
  • optional environmentId
  • pagination fields

POST /v1/reports

Creates a scheduled report.

Example body:

json
{
  "workspaceId": "workspace_uuid",
  "environmentId": "workspace_uuid_production",
  "name": "Daily payment volume",
  "schedule": "daily",
  "metric": "sum",
  "aggregateField": "amount",
  "filters": {
    "eventType": "payment_succeeded",
    "tags": ["payments"]
  },
  "groupBy": null,
  "channelIds": ["channel_uuid"]
}

PATCH /v1/reports/:id

Updates report configuration. Aggregate metrics require aggregateField.

DELETE /v1/reports/:id

Deletes a report config.

Dashboard Project Users

Project users are dashboard-auth-only and scoped to a single project inside the selected environment.

GET /v1/dashboard/projects/:projectId/users

Returns a paginated tracked-user list plus page summary.

Query parameters:

  • workspaceId
  • optional environmentId
  • limit
  • optional opaque cursor
  • optional query
  • optional range: 24h, 7d, 30d, all
  • optional sort: currently last_seen_desc

Response shape:

json
{
  "summary": {
    "trackedUsers": 42,
    "activeUsersInRange": 17,
    "eventsInRange": 382,
    "unattributedEventsInRange": 21
  },
  "data": [
    {
      "userId": "user_123",
      "name": "Francesco",
      "email": "[email protected]",
      "avatarUrl": null,
      "role": "admin",
      "label": null,
      "properties": {
        "plan": "pro",
        "seats": 5
      },
      "lastSeenAt": "2026-04-09T09:15:00.000Z",
      "eventCount": 18,
      "sessionCount": 4,
      "channelCount": 3,
      "lastEventType": "signed_in"
    }
  ],
  "nextCursor": "opaque_cursor"
}

GET /v1/dashboard/projects/:projectId/users/detail

Returns one tracked user detail payload.

Query parameters:

  • workspaceId
  • optional environmentId
  • required userId
  • optional range: 24h, 7d, 30d, all
  • optional eventsLimit
  • optional eventsCursor

The detail response includes:

  • stable user identity, including properties (custom key-value metadata)
  • all-time firstSeenAt
  • all-time lastSeenAt
  • range-scoped totals
  • top channels
  • top event types
  • recent events with event-style pagination

PATCH /v1/dashboard/projects/:projectId/users

Updates identity fields and custom properties for a tracked user. All fields except workspaceId and userId are optional; only the fields you pass are updated.

Body:

json
{
  "workspaceId": "workspace_uuid",
  "environmentId": "environment_id",
  "userId": "user_123",
  "name": "Francesco",
  "email": "[email protected]",
  "avatarUrl": "https://…",
  "role": "admin",
  "label": "Pro tier",
  "properties": {
    "plan": "pro",
    "seats": 5,
    "trial": false
  }
}

Response mirrors the request's identity fields and the full stored properties object.

Validation:

  • name ≤255, email must parse as email, avatarUrl must parse as URL, role ≤128, label ≤255
  • properties keys ≤64 chars, values must be strings/numbers/booleans/null, string values ≤1024 chars, max 50 keys
  • empty strings are normalized to null so the editor can clear a field
  • properties here is a full replacement: send the complete object. Events work the other way, patching only the keys they carry

Note: subsequent events carrying actor.name, actor.email, etc. will overwrite what you save here. See Tracking Users In The Dashboard for how to reason about the source of truth.

Analytics

Dashboard auth. Every body carries workspaceId (and optionally environmentId); the project must belong to that workspace and environment or the call returns 404. Queries time out after 10 seconds.

POST /v1/analytics/query

Aggregates over events: count, count_distinct, sum, avg, min, max, p50, p95, p99, grouped by channel, eventType, level, userId, actor, metadata.<path>, time:<bucket> or user.<property>.

userFilters keeps only events whose user matches, using the same operators as metadata rules (exists, not_exists, equals, not_equals, contains, gt, gte, lt, lte):

json
{
  "workspaceId": "…",
  "projectId": "…",
  "aggregations": [{ "op": "count", "alias": "reports" }],
  "filters": { "eventType": "report_sent" },
  "userFilters": [{ "field": "mrr", "operator": "gt", "value": 100 }],
  "groupBy": ["user.plan", "time:week"]
}

User properties are read as they are now, not as they were when the event happened.

POST /v1/analytics/funnel

How many users did a sequence of events, in order. A user enters at their first step-1 event between from and to; each later step must happen strictly after the previous one and within conversionWindowSeconds of step 1 (default 7 days, max 90). Users are followed by the event's user id, so events without one are ignored.

json
{
  "workspaceId": "…",
  "projectId": "…",
  "steps": [
    { "eventType": "user_signed_up" },
    { "eventType": "account_added" },
    { "eventType": "report_created", "metadataRules": [{ "source": "metadata", "field": "type", "operator": "equals", "value": "weekly" }] }
  ],
  "from": "2026-08-01T00:00:00Z",
  "to": "2026-09-01T00:00:00Z",
  "conversionWindowSeconds": 604800,
  "userFilters": [{ "field": "is_agency", "operator": "equals", "value": true }]
}

Response: one entry per step with users, conversionFromStart, conversionFromPrevious and medianSecondsFromPrevious.

POST /v1/analytics/retention

Do users come back. Each user joins the cohort of the day, week or month in which they first did startEvent, ever; cohorts are the ones whose first occurrence falls between from and to (at most 60). For each later period it counts the users who did returnEvent; omit returnEvent to count any event.

json
{
  "workspaceId": "…",
  "projectId": "…",
  "startEvent": { "eventType": "user_signed_up" },
  "returnEvent": { "eventType": "dashboard_viewed" },
  "period": "week",
  "from": "2026-07-01T00:00:00Z",
  "to": "2026-09-01T00:00:00Z",
  "periods": 8
}

Response: cohorts[] with cohortStart, size, retained[] and rates[], where index 0 is the cohort itself, plus average[] weighted by cohort size. A null means that period has not started yet; it is not zero retention.

The same three questions are available to AI agents over MCP as run_analytics_query, run_funnel_query and run_retention_query.

Device Tokens

POST /v1/device-tokens

Registers or updates an iOS device token for the active workspace and environment.

Example body:

json
{
  "token": "apns_device_token_hex",
  "workspaceId": "workspace_uuid",
  "environmentId": "workspace_uuid_production",
  "platform": "ios"
}

Important behavior:

  • workspaceId must match the authenticated workspace
  • if environmentId is supplied, it must match the authenticated environment
  • platform is currently ios only

API Keys

GET /v1/dashboard/api-keys

Lists the keys for a workspace and environment. Returns keyPrefix, never the key: the server stores a SHA-256 digest and cannot produce the original.

POST /v1/dashboard/api-keys

Creates a key. The response is the only place the raw value ever appears.

Example body:

json
{
  "workspaceId": "workspace_uuid",
  "environmentId": "workspace_uuid_production",
  "type": "mcp",
  "name": "Claude Desktop",
  "scopes": ["events:read", "analytics:read"],
  "expiresInDays": 90
}
Field Notes
environmentId Optional. Defaults to the workspace's default environment.
projectId Optional. Confines the key to one project. Omitted means every project in the environment, which is what every key created before this field is.
type secret (default) for an ingest key, mcp for an AI connector. Decides the key's prefix.
name Human label. label is accepted as the older name for this field.
scopes Optional. Omitted means events:write/sessions:write/devices:write when projectId is set, everything for a secret without one, and read plus events:write/sessions:write/rules:write for mcp. Unknown scopes are dropped rather than rejected.
expiresInDays Optional hard expiry. An expired key is rejected exactly like a revoked one.

Keys for a distributed client

A key compiled into a mobile or desktop app is handed to everyone who downloads it, so treat it as public. Give it a projectId and let the scopes default: it can then write its own project's events and read nothing at all.

Scopes alone are not enough. They say what a key may do and never whose data it may do it to: before projectId existed, a key made for one app could read every other project in the workspace, which is how a key created for one iOS app returned another app's events from /v1/stats on 2026-08-31.

A bound key behaves like this:

  • an unqualified request resolves to its project, not to the environment's default;
  • ?projectId= naming any other project answers 403 project_forbidden;
  • over MCP it sees its project alone, and list_projects returns only that one.

DELETE /v1/dashboard/api-keys/:id

Soft revocation. The key stops authenticating immediately; the row survives so lastUsedAt and the creation date stay available after a rotation.

MCP

POST /mcp

JSON-RPC 2.0 over HTTP, stateless. Authenticated with a lg_mcp_ token as a bearer, or at POST /mcp/k/:token for clients that accept only a URL. Full detail in AI Connectors (MCP).

Example Integration Flow

bash
curl -X POST https://api.logga.sh/v1/events \
  -H "Authorization: Bearer lg_sk_test_1234567890" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "auth",
    "event": "login_failed",
    "level": "warning",
    "actor": {
      "id": "user_123",
      "email": "[email protected]"
    },
    "metadata": {
      "reason": "invalid_password"
    }
  }'