API conventions
The wire-format policy for the GeoMetrikks HTTP API, decided for 0.7.0.
tests/test_error_contract.py and the generated OpenAPI document
(resources/generated/openapi.json) pin these rules; change them only with a
coordinated client regeneration and a changelog migration note.
Versioning
Section titled “Versioning”Every REST endpoint lives under /api/v1, mounted as a single Litestar
Router in geometrikks/server/routes.py, which stays the one explicit
registration point. Controllers live in their vertical domain packages
(geometrikks/domain/<domain>/controllers*) and own only their domain
segment (/analytics, /crowdsec, …); the router supplies the version
prefix. Everything under /api/v1 requires the session cookie except
/api/v1/auth/login (with APP_AUTH_DISABLED=true nothing requires a
session, and the auth endpoints stay registered as mode-appropriate no-ops:
/api/v1/auth/me and a valid /api/v1/auth/login return
{"mode": "disabled"}, and /api/v1/auth/logout returns 204). Outside the
router:
/healthand/health/ready: unauthenticated probe endpoints./ws/live,/ws/logs,/ws/crowdsec: WebSocket feeds (geometrikks/domain/realtime/), session-authenticated during the handshake./schema(deliberately unauthenticated),/sw.js, and the SPA shell.
Field casing: camelCase
Section titled “Field casing: camelCase”All request and response JSON fields are camelCase on the wire; Python attributes stay snake_case.
- SQLAlchemy-model responses use Advanced Alchemy DTOs with
rename_strategy="camel". - Bespoke request and response models are msgspec Structs declared with
rename="camel"(seegeometrikks/domain/geo/schemas.pyfor the idiom); they live in each domain package’sschemas.py/dtos.pyor next to their controller. Digit-adjacent names pin their wire form explicitly withmsgspec.field(name=...):status2xx,requestCount24h. - Query parameters carry explicit camelCase
name=declarations (fromTimestamp,startDate,ipAddressNotIn, …); shared aliases live ingeometrikks/lib/parameters.py. - Path parameters are URL segments, not fields, and stay snake_case
(
{location_id},{job_id}). - WebSocket frame payloads are a separate contract and are not covered by this policy (revisited with the realtime refactor).
- Data values are exempt:
SettingFieldView.keydeliberately carries Python settings field names likehome_latitude, and/api/v1/logs/tailrecords pass raw structlog context keys through as recorded (request_id,status_code, …); renaming historical log data would misrepresent it. OpenAPI documents only the stable record fields.
Error envelope: Litestar native
Section titled “Error envelope: Litestar native”Errors use Litestar’s native HTTP-exception envelope, unchanged:
{"status_code": 404, "detail": "Not Found"}extraappears additionally on request-validation errors with the per-field breakdown.- The envelope keys are the framework’s and stay snake_case; the camelCase policy applies to success payloads only.
- Domain exceptions (
DomainValidationError, CrowdSec errors) are translated centrally ingeometrikks/server/exceptions.pyinto the same envelope. - API-path 404s render this envelope too; non-API 404s (static-asset misses)
keep litestar-vite’s empty-body behavior. The 404 handler treats the whole
/api/namespace as API surface, matching the auth boundary: an unknown or unversioned/api/path is an API consumer’s mistake and gets JSON.
Operation IDs
Section titled “Operation IDs”OpenAPI operation IDs use Litestar’s default path-derived naming
(ApiV1AnalyticsSummaryGetSummary). Generated TypeScript client method names
hang off them, so route moves must keep full paths stable or accept a
client-wide rename.