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:
| Credential | Prefix | Where | Can do |
|---|---|---|---|
| Secret key | stl_sk_… | Server only, Authorization: Bearer header | Ingest, read events/actors/sessions/stats, export, mint feed tokens |
| Public key | stl_pk_… | Browser, in the batch JSON body | POST /v1/events/batch only — cannot read anything (403) |
| Feed token | stl_ft_… | Browser, Authorization: Bearer header | Read one actor's events and sessions; cannot ingest, export, or read stats |
# 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
| Endpoint | Auth | Description |
|---|---|---|
POST/v1/events | secret | Ingest one event. Returns 201 with the event's UUID. |
POST/v1/events/batch | public (in body) | Ingest 1–100 auto-capture events in one call. Returns { accepted, rejected }. |
GET/v1/events | secret · feed token | List events, newest first, cursor-paginated. See Filtering for all query parameters and the response shape. |
GET/v1/events/export | secret | ?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/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" }| Field | Required | Constraints |
|---|---|---|
actor.id | yes | ≤ 255 chars; name, email, avatar_url optional |
action | yes | ≤ 200 chars, must match ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$ — otherwise 422 |
target | no | type, id, name — all optional |
metadata | no | JSON object ≤ 4 KB |
capture_mode | no | manual (default) or auto |
session_id | no | Any string ≤ 255 chars (UUID preferred; other strings map to a stable UUID). Creates/extends a session automatically. |
timestamp | no | ISO 8601; defaults to now. Stored as occurred_at. |
context | no | ip_address, user_agent of the end user; otherwise the request's own are recorded |
Batch ingest (auto-capture)
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
| Endpoint | Auth | Description |
|---|---|---|
POST/v1/feed-tokens | secret | Mint a read-only token pinned to actor_id (and optionally target_type/target_id). ttl_seconds 60–86400, default 3600. |
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
| Endpoint | Auth | Description |
|---|---|---|
GET/v1/actors | secret · feed token | List 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 token | Fetch one actor by your external ID. |
Sessions
Sessions are created automatically from the session_id on any event — auto-capture always sends one.
| Endpoint | Auth | Description |
|---|---|---|
GET/v1/sessions | secret · feed token | List 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 token | Fetch one session's summary. For its event timeline call GET /v1/events?session_id={id}. |
Stats
| Endpoint | Auth | Description |
|---|---|---|
GET/v1/stats | secret | ?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:
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…" }| Status | Meaning |
|---|---|
400 | Empty request body |
401 | Missing, malformed, revoked, or expired credential |
402 | Monthly 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. |
403 | Wrong credential type — e.g. a public key on a read endpoint, or a feed token on export/stats |
404 | Unknown actor or session |
422 | Validation error — e.g. malformed action, metadata over 4 KB, more than 100 events in a batch |
429 | Rate limited — 1,000 requests/min per secret key, 200 batch calls/min per public key. Includes a Retry-After header (seconds). |
429, 5xx), timeouts, and error swallowing for you — use the raw API only when an SDK doesn't fit.