API conventions
The rules every GeoMetrikks API endpoint follows for versioning, field casing, errors and operation IDs, and where the code enforces them.
These are the rules for adding or changing an endpoint. tests/test_error_contract.py and the generated OpenAPI document in resources/generated/openapi.json pin them.
The API reference covers what API consumers see, such as authentication, the error codes and paging.
Versioning
Every REST endpoint lives under /api/v1. geometrikks/server/routes.py mounts all API controllers on one Litestar Router with that prefix, and no other file registers a controller. Controllers live in their domain package, in geometrikks/domain/<domain>/controllers.py or a controllers/ package, and declare only their own segment, such as /analytics or /crowdsec; the router adds the version prefix. geometrikks/server/api_version.py holds the version number, which the OpenAPI document and the connection check report.
Routes outside the router are the WebSocket feeds (/ws/live, /ws/logs, /ws/crowdsec), the probes (/health, /health/ready), the OpenAPI document at /schema, the service worker and the web UI.
Renaming or removing a field, path, query parameter or error code breaks API scripts. Mark it with a Breaking: changelog entry that says what callers must change. Every change to a route or model needs the client regenerated, as Generated API types describes.
Field casing
Request and response fields are camelCase on the wire. Python attributes stay snake_case.
- Responses built from SQLAlchemy models use Advanced Alchemy DTOs with
rename_strategy="camel". - Other request and response models are msgspec Structs declared with
rename="camel", in the domain package'sschemas.pyordtos.py, or next to their controller.geometrikks/domain/geo/schemas.pyshows the pattern. - Names with a digit after an underscore pin their wire name with
msgspec.field(name=...). msgspec's camel rename would turnstatus_2xxintostatus2Xx, so the code declaresstatus_2xx: int = msgspec.field(name="status2xx"). - Query parameters declare their camelCase name explicitly with
name=, such asfromTimestamp,startDateoripAddressNotIn. Shared parameters live ingeometrikks/lib/parameters.py. - Path parameters are URL segments, not fields, and stay snake_case:
{location_id},{job_id}. - WebSocket frames are a separate contract. The WebSocket messages guide documents their shape.
Data values keep their own spelling. SettingFieldView.key carries the Python field name of a setting, such as raw_retention_days. Records from /api/v1/logs/tail keep the structlog context keys they were logged with, such as request_id and status_code. Renaming them would misrepresent the log. OpenAPI documents only the stable record fields.
Errors
Error responses keep Litestar's envelope and add a machine-readable code:
{"status_code": 404, "detail": "Not Found", "code": "not_found"}The camelCase rule covers successful responses only. The envelope keys are the framework's, so they stay snake_case.
geometrikks/server/exceptions.py builds the error responses for HTTP exceptions and domain errors. An unhandled exception of any other kind gets Litestar's default 500 body, which has no code.
handle_http_exceptionaddscodeto eachHTTPExceptionon an/api/path. Acodeattribute on the exception class wins, such asapi_key_invalidonInvalidApiKeyError. AValidationExceptiongetsvalidation_failed, with the per-field breakdown inextra. Anything else gets the code for its status from_STATUS_CODES, orhttp_errorwhen the status has none.- An
HTTPExceptionwith status 500 has itsdetailreplaced withInternal Server Error, so exception text never reaches the client. WithAPP_DEBUG=truethe response carries the traceback instead. - Services and shared validation helpers raise
DomainValidationError,DomainNotFoundErrororDomainConflictErrorfromgeometrikks/domain/exceptions.py, never an HTTP exception. They map to 400, 404 and 409 in the same envelope, with the default codesinvalid_request,not_foundandconflict. Passcode=for a more specific one, such asinvalid_range. Controllers may raise HTTP exceptions directly. - CrowdSec and RDAP client errors have their own handlers, with the codes
crowdsec_unreachable,crowdsec_credentials_rejected,crowdsec_unsupportedandrdap_unavailable. handle_not_foundgives every 404 under/api/the JSON envelope, including unknown and unversioned paths, matching the auth boundary. Other 404s, such as a missing static asset, keep an empty body.
A new error code is part of the contract. Name it in the Errors section of the intro text in geometrikks/server/openapi_docs.py, which becomes the intro of the API reference, and cover it in tests/test_error_contract.py.
Operation IDs
OpenAPI operation IDs use Litestar's default, derived from the path and handler function name: GET /api/v1/analytics/summary, handled by get_summary, is ApiV1AnalyticsSummaryGetSummary. The generated TypeScript client names its functions and types after them, so the summary call is apiV1AnalyticsSummaryGetSummary. Moving a route or renaming a handler renames its client function, so keep full paths stable or update every caller in the same change.