Docs
Manual Tracking
Emit events from your backend wherever meaningful actions happen — invites, uploads, plan changes, deletions. One consistent shape, two SDKs, zero risk to your app.
The event shape
Every event answers four questions, plus a few optional extras:
| Field | Required | Description |
|---|---|---|
actor | yes | Who did it — id (required, ≤ 255 chars), plus optional name, email, avatar_url. Actors are upserted automatically; the same id always maps to the same user record. |
action | yes | What happened, in resource.verb form (e.g. member.invited). Lowercase letters, digits, and underscores, with at least one dot; ≤ 200 chars. |
target | no | What it happened to — type, id, name (all optional). |
metadata | no | Any JSON object with extra context (roles, amounts, filenames). ≤ 4 KB. |
timestamp | no | ISO 8601 time the action happened — defaults to now. Use it to backfill history. |
session_id | no | Groups events into a session (any stable string ≤ 255 chars — a UUID is preferred; other strings are mapped to a stable UUID). Sessions are created automatically. |
context | no | The end user's ip_address and user_agent. Without it the API can only see your server's address. |
A successful call returns { id, queued_at } — event IDs are UUIDs.
Node.js / TypeScript
Requires Node 18+ (uses the global fetch). Install with npm install @softechlog/node.
import { log } from '@/lib/softechlog';
// Fire-and-forget (non-blocking — recommended)
log.track({
actor: { id: req.user.id, name: req.user.name, email: req.user.email },
action: 'member.invited', // resource.verb
target: { type: 'workspace', id: workspace.id, name: workspace.name },
metadata: { role: 'admin' }, // any JSON, ≤ 4 KB
sessionId: req.session.id, // optional: groups events into a session
context: { ip_address: req.ip, user_agent: req.get('user-agent') }, // optional: the *end user's* context
});
// With await (if you need confirmation or the event ID)
const event = await log.track({ /* … */ });
console.log(event?.id); // "9b2f0f5e-…" (UUID) or null if delivery failedOptions
| Option | Type | Default | Description |
|---|---|---|---|
secretKey | string | required | Your stl_sk_… secret key. The constructor throws if it is missing or malformed. |
baseUrl | string | https://api.softechlog.com | Override the API base URL |
timeout | number | 5000 | Per-request timeout in ms |
retries | number | 2 | Retries on network errors, 429 and 5xx (exponential backoff, honours Retry-After) |
silent | boolean | false | Suppress console warnings on errors |
fetch | function | global fetch | Custom fetch implementation (tests, polyfills) |
track() never throws — network failures, timeouts and API errors resolve to null (and log a warning unless silent). Invalid actions and oversized metadata are rejected locally without a round-trip. Skip await on hot paths for zero added latency.Serverless
Await log.flush() before returning so background track() calls finish:
export async function handler(event) {
log.track({ /* … */ });
// …
await log.flush(); // wait for background track() calls before the runtime freezes
}The client also exposes feedToken({ actorId, targetType?, targetId?, ttlSeconds? }), which returns { token, expires_at, actor_id } for the feed component. Unlike track(), it throws on failure — a missing token is a rendering bug you want to see.
Python
Install with pip install softechlog. Works with FastAPI, Django, Flask, or plain scripts; fully typed. Use track() in sync code and atrack() in async code:
from app.softechlog import log
log.track(
actor={"id": str(user.id), "name": user.name, "email": user.email},
action="member.invited", # resource.verb
target_type="workspace",
target_id=str(workspace.id),
target_name=workspace.name,
metadata={"role": "admin"}, # any JSON, ≤ 4 KB
session_id=request.session.session_key, # optional: groups events into a session
)@router.delete("/files/{file_id}")
async def delete_file(file_id: str, current_user: User = Depends(get_current_user)):
file = await File.get(file_id)
await file.delete()
await log.atrack(
actor={"id": str(current_user.id), "name": current_user.name},
action="file.deleted",
target_type="file",
target_id=file_id,
target_name=file.name,
)
return {"deleted": True}Options
| Option | Type | Default | Description |
|---|---|---|---|
secret_key | str | required | Your stl_sk_… secret key |
base_url | str | https://api.softechlog.com | Override the API base URL |
timeout | float | 5.0 | Request timeout in seconds |
retries | int | 2 | Retries on network errors, 429, 5xx (backoff, honours Retry-After) |
silent | bool | False | Suppress warnings on errors |
track() / atrack() accept actor, action, target_type, target_id, target_name, metadata, timestamp, session_id and context, never raise, and return None on failure. feed_token() / afeed_token() raise SoftechlogError. The client keeps persistent HTTP connections — call log.close() / await log.aclose() on shutdown, or use it as a context manager.
FastAPI middleware
Records the end user's IP and User-Agent. Every track()/atrack() call made while handling a request automatically carries context={"ip_address": …, "user_agent": …}; without it the API only sees your server's address.
from softechlog.fastapi import SoftechlogMiddleware # honours X-Forwarded-For; pass trust_proxy_headers=False if not behind a proxy app.add_middleware(SoftechlogMiddleware)
Limits enforced by the API
action≤ 200 chars,actor.id≤ 255 chars,metadata≤ 4 KB,session_id≤ 255 chars.- 1,000 requests per minute per secret key (
429with aRetry-Afterheader — the SDKs retry automatically). - Monthly plan caps: 10,000 (Free), 250,000 (Growth), 2,000,000 (Scale) events. Over the cap the API returns
402until the 1st of next month; nothing is deleted and reads keep working. See Pricing.
Action naming convention
Stick to resource.verb and your event stream stays queryable as it grows — you can later filter with a prefix like action=member.*:
member.invited member.removed member.role_changed file.uploaded file.deleted file.downloaded project.created project.archived project.deleted payment.succeeded payment.failed plan.upgraded plan.downgraded
Start with the workflows that matter for support and audit — invites, billing, security actions — and expand coverage incrementally.