Docs

API Reference

Everything the SDKs do under the hood. Base URL: https://api.softechlog.com — all endpoints are JSON over HTTPS.

Authentication

Three credentials, each with a deliberately narrow job:

CredentialPrefixWhereCan do
Secret keystl_sk_…Server only, Authorization: Bearer headerIngest, read events/actors/sessions/stats, export, mint feed tokens
Public keystl_pk_…Browser, in the batch JSON bodyPOST /v1/events/batch only — cannot read anything (403)
Feed tokenstl_ft_…Browser, Authorization: Bearer headerRead one actor's events and sessions; cannot ingest, export, or read stats
HeadersBearer auth
# Server-side — secret key (ingest + read + export + stats + mint feed tokens)
Authorization: Bearer stl_sk_xxxxxxxxxxxx

# Browser — feed token (read-only, pinned to one actor; minted with a secret key)
Authorization: Bearer stl_ft_xxxxxxxxxxxx

# Browser — public key (auto-capture ONLY; travels in the batch JSON body, not a header)
{ "public_key": "stl_pk_xxxxxxxxxxxx", "events": [ … ] }

Customer sites can call the batch endpoint and read with feed tokens from any origin (CORS Allow-Origin: *, no credentials); everything else is restricted to the dashboard's origins.

Events

EndpointAuthDescription
POST/v1/eventssecretIngest one event. Returns 201 with the event's UUID.
POST/v1/events/batchpublic (in body)Ingest 1–100 auto-capture events in one call. Returns { accepted, rejected }.
GET/v1/eventssecret · feed tokenList events, newest first, cursor-paginated. See Filtering for all query parameters and the response shape.
GET/v1/events/exportsecret?format=csv|json plus the same filters as the list endpoint. Up to 10,000 rows, Content-Disposition: attachment. Available on all plans.

Ingest an event

POST /v1/eventsrequest / response
POST /v1/events
Content-Type: application/json
Authorization: Bearer stl_sk_xxxxxxxxxxxx

{
  "actor":     { "id": "user_123", "name": "Ari", "email": "ari@acme.co", "avatar_url": null },
  "action":    "member.invited",
  "target":    { "type": "workspace", "id": "ws_abc", "name": "Acme" },
  "metadata":  { "role": "admin" },
  "capture_mode": "manual",
  "session_id": "0f6d5c1e-…",
  "timestamp": "2026-08-17T14:09:31Z",
  "context":   { "ip_address": "203.0.113.4", "user_agent": "Mozilla/5.0 …" }
}

→ 201 { "id": "9b2f0f5e-4c1a-4e7b-9d3f-2a6c8e1b7f10", "queued_at": "2026-08-17T14:09:31.412Z" }
FieldRequiredConstraints
actor.idyes≤ 255 chars; name, email, avatar_url optional
actionyes≤ 200 chars, must match ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$ — otherwise 422
targetnotype, id, name — all optional
metadatanoJSON object ≤ 4 KB
capture_modenomanual (default) or auto
session_idnoAny string ≤ 255 chars (UUID preferred; other strings map to a stable UUID). Creates/extends a session automatically.
timestampnoISO 8601; defaults to now. Stored as occurred_at.
contextnoip_address, user_agent of the end user; otherwise the request's own are recorded

Batch ingest (auto-capture)

POST /v1/events/batchrequest / response
POST /v1/events/batch
Content-Type: text/plain          # no CORS preflight; the body is still JSON

{
  "public_key": "stl_pk_xxxxxxxxxxxx",
  "events": [
    {
      "action": "ui.page_viewed",
      "actor": { "id": "user_123" },
      "metadata": { "url": "https://app.acme.co/settings", "title": "Settings" },
      "capture_mode": "auto",
      "session_id": "0f6d5c1e-…",
      "occurred_at": "2026-08-17T14:09:31Z"
    }
    /* 1 – 100 events */
  ]
}

→ 201 { "accepted": 1, "rejected": 0 }

Each item accepts action (required), actor, target, metadata, capture_mode (default auto), session_id, and occurred_at. Items with an invalid action or oversized metadata are counted in rejected; the rest are stored. The whole batch is refused with 402 if it would exceed your monthly cap.

Feed tokens

EndpointAuthDescription
POST/v1/feed-tokenssecretMint a read-only token pinned to actor_id (and optionally target_type/target_id). ttl_seconds 60–86400, default 3600.
POST /v1/feed-tokensrequest / response
POST /v1/feed-tokens
Authorization: Bearer stl_sk_xxxxxxxxxxxx

{ "actor_id": "user_123", "target_type": null, "target_id": null, "ttl_seconds": 3600 }

→ 201 { "token": "stl_ft_…", "expires_at": "2026-08-17T15:09:31Z", "actor_id": "user_123" }

Actors

EndpointAuthDescription
GET/v1/actorssecret · feed tokenList identified users: { actors: [{ id, external_id, name, email, avatar_url, first_seen_at, last_seen_at, total_events }], has_more, next_cursor }. Supports search (name, email or external id), limit, cursor. Feed tokens see only their own actor.
GET/v1/actors/{external_id}secret · feed tokenFetch one actor by your external ID.

Sessions

Sessions are created automatically from the session_id on any event — auto-capture always sends one.

EndpointAuthDescription
GET/v1/sessionssecret · feed tokenList sessions: { sessions: [{ id, actor, started_at, ended_at, event_count, starting_page }], has_more, next_cursor }. Supports actor_id, from, to, limit, cursor.
GET/v1/sessions/{id}secret · feed tokenFetch one session's summary. For its event timeline call GET /v1/events?session_id={id}.

Stats

EndpointAuthDescription
GET/v1/statssecret?days=14 (1–90). Headline totals, a per-day series, top actions, and current plan usage — the same numbers the dashboard Overview shows.

Errors & limits

Every error is JSON with a detail and a request_id you can quote to support:

Error responseapplication/json
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{ "detail": "Rate limit exceeded: 1000 requests per 60s.", "request_id": "3f9c…" }
StatusMeaning
400Empty request body
401Missing, malformed, revoked, or expired credential
402Monthly plan limit reached — on single and batch ingest alike. Nothing is deleted; reads keep working; ingest resumes on the 1st of next month or when you upgrade.
403Wrong credential type — e.g. a public key on a read endpoint, or a feed token on export/stats
404Unknown actor or session
422Validation error — e.g. malformed action, metadata over 4 KB, more than 100 events in a batch
429Rate limited — 1,000 requests/min per secret key, 200 batch calls/min per public key. Includes a Retry-After header (seconds).
The official SDKs handle retries (network errors, 429, 5xx), timeouts, and error swallowing for you — use the raw API only when an SDK doesn't fit.