Skip to content

API

The JSON API lives under /api and is written with huma, code first. The server publishes:

  • /api/openapi (OpenAPI 3.1 spec, also targetlog openapi on the command line),
  • /api/docs (interactive documentation).

The frontend uses openapi-fetch with types generated from the spec: after changing an operation, run make api and commit web/openapi.json and web/src/lib/api.gen.ts.

Authentication

Client How
App session cookie targetlog_session from POST /api/auth/login
Kiosk screen Authorization: Bearer <kiosk token> on /api/kiosk/*, ?token= on the WebSocket
Print agent Authorization: Bearer <agent token> on /api/agent/*
Scripts of a shooter personal API token tl_… as Authorization: Bearer or X-API-Key on /api/my/*

Personal API tokens

Created in My training → Settings (POST /api/my/tokens) with a name, scopes and an optional expiry of 30, 90 or 365 days. Only the sha256 hash is stored; the secret (tl_ + 40 hex characters) is returned once. Scopes:

Scope Allows
training:read GET on the paths below
training:write POST, PUT and DELETE on the paths below

A token reaches /api/my and /api/my/{trainings,stats,disciplines,weapons,goals,competitions,export}. Everything else answers 403: profile, email, account, the token management itself; a token on any other path is ignored (401). Unknown, revoked and expired tokens get 401 with a distinct detail; a missing scope gets 403. Each use updates last used.

curl -H "Authorization: Bearer $TOKEN" https://app.targetlog.ch/api/my/trainings?limit=10

Conventions

  • Tenant-scoped paths: /api/tenants/{tenantId}/…; non-members get 404, staff get 403 on admin operations.
  • Errors are RFC 9457 problem details (application/problem+json) with an English detail.
  • A change to an archived event answers 409.
  • WebSockets: /api/tenants/{tenantId}/events/{eventId}/live and /api/kiosk/live send {topic} messages; clients refetch what the topic covers.