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:
| Field | Required | Description |
|---|---|---|
actor | yes | Who did it — id (required), 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. |
target | no | What it happened to — type (required if present), optional id and name. |
metadata | no | Any JSON object with extra context (roles, amounts, filenames). |
You can also pass a timestamp to backfill historical events and a session_id to group events into a session.
Node.js / TypeScript
api/members.ts@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',
target: { type: 'workspace', id: workspace.id, name: workspace.name },
metadata: { role: 'admin' },
});
// With await (if you need confirmation or the event ID)
const event = await log.track({ /* … */ });
console.log(event?.id); // evt_abc123Options
| Option | Type | Default | Description |
|---|---|---|---|
secretKey | string | required | Your stl_sk_… secret key |
baseUrl | string | https://api.softechlog.com | Override the API base URL |
timeout | number | 5000 | Request timeout in ms |
silent | boolean | false | Suppress console warnings on errors |
Errors are always swallowed — a logging failure will never crash your app. Skip
await on hot paths for zero added latency.Python
The Python SDK works with FastAPI, Django, and Flask. Use track() in sync code and atrack() in async code:
app/members.pysoftechlog · sync
from app.softechlog import log
log.track(
actor={"id": str(user.id), "name": user.name, "email": user.email},
action="member.invited",
target_type="workspace",
target_id=str(workspace.id),
metadata={"role": "admin"},
)app/files.pysoftechlog · async
@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}FastAPI middleware
Automatically attaches ip_address and user_agent to request state:
app/main.pysoftechlog.fastapi
from softechlog.fastapi import SoftechlogMiddleware app.add_middleware(SoftechlogMiddleware, client=log)
Action naming convention
Stick to resource.verb and your event stream stays queryable as it grows:
Action namesconvention
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.