Routes Structure
The Heimdall API is organized into a clean, hierarchical route structure with versioned endpoints.
Route Hierarchy
/ [Root - redirects to /v1]
/health [Health check - root level]
/v1 [API v1 info endpoint]
/v1/health [Health check - also available under v1]
/v1/auth/signin [POST - Sign in with API token]
/v1/auth/validate [POST - Validate API token]
/v1/gps [GET - List GPS data (paginated, requires gps:read)]
/v1/gps [POST - Create GPS data (requires gps:write, rate-limited)]
/v1/gps/current [GET - Get current GPS (requires gps:read)]
/v1/gps/current/devices [GET - Latest GPS per device (requires gps:read)]
/v1/gps/{id} [GET - Get GPS by ID (requires gps:read)]
/v1/gps/{id} [DELETE - Delete GPS data (requires gps:delete, 409 if trip-linked)]
/v1/settings [GET - Get all settings (settings:read)]
/v1/settings/public [GET - Get public settings (no auth)]
/v1/settings/{key} [GET - Get setting by key (settings:read)]
/v1/settings [POST - Create setting (settings:write)]
/v1/settings/{key} [PUT - Update setting (settings:write)]
/v1/settings/{key} [DELETE - Delete setting (settings:delete)]
/v1/stats/public [GET - Get public stats (no auth)]
/v1/ws [WS - WebSocket connection]
/v1/gql [POST/GET - GraphQL endpoint (authenticated)]
/v1/graphiql [GET - GraphiQL IDE (dev only, no auth)]
/v1/schema [GET - GraphQL schema SDL (no auth)]
/v1/api-keys [GET - List API keys (api_keys:read)]
/v1/api-keys [POST - Create API key (api_keys:write)]
/v1/api-keys/{id} [GET - Get API key by ID (api_keys:read)]
/v1/api-keys/{id} [PATCH - Update API key (api_keys:write)]
/v1/api-keys/{id} [DELETE - Delete API key (api_keys:delete)]
/v1/api-keys/{id}/regenerate [POST - Regenerate API key (api_keys:write)]
/v1/roles [GET - List roles (roles:read)]
/v1/roles [POST - Create role (roles:write)]
/v1/roles/{id} [GET - Get role by ID (roles:read)]
/v1/roles/{id} [PATCH - Update role (roles:write)]
/v1/roles/{id} [DELETE - Delete role (roles:delete)]
/v1/permissions [GET - List permissions (permissions:read)]
/v1/permissions [POST - Create permission (permissions:write)]
/v1/permissions/{id} [GET - Get permission by ID (permissions:read)]
/v1/permissions/{id} [PATCH - Update permission (permissions:write)]
/v1/permissions/{id} [DELETE - Delete permission (permissions:delete)]
/v1/users [GET - List users (users:read)]
/v1/users/{id} [GET - Get user by ID (users:read or self)]
/v1/users/{id} [PATCH - Update user (users:write or self)]
/v1/users/{id} [DELETE - Delete user (users:delete)]
/v1/swagger-ui/ [GET - Swagger UI (development builds only)]
/v1/openapi.json [GET - OpenAPI JSON specification (development builds only)]
Route Configuration Flow
Routes are defined in the heimdall-rest crate (crates/heimdall-rest/src/routes/). The platform/api/src/main.rs binary wires them into Actix-web.
main.rs (platform/api)
App::new()
.configure(heimdall_rest::routes::root_routes) // "/" and "/health"
.service({
// Build the /v1 scope
let scope = web::scope("/v1")
.configure(heimdall_rest::routes::configure_public_routes) // Public routes under /v1
// SwaggerUi added here only when is_development
.configure(heimdall_rest::routes::configure_sessions)
.configure(heimdall_rest::routes::configure_graphql) // GraphQL routes
.configure(heimdall_rest::routes::configure_oauth)
.configure(heimdall_rest::routes::configure_authenticated_routes); // All v1 API routes
// The ENTIRE /v1 scope is wrapped with AuthMiddleware.
// Public routes handle a missing AuthContext gracefully; authenticated
// routes require it to be present.
scope.wrap(api::middleware::AuthMiddleware)
})
heimdall-rest::routes
Public Functions (called from main.rs):
root_routes()- Root endpoint (redirects to /v1)configure_public_routes()- Public routes including /health, /v1, /v1/healthconfigure_sessions()- Session endpoints (auth handled internally via system API key)configure_authenticated_routes()- All v1 API route modulesconfigure_graphql()- GraphQL endpoints (/gql, /graphiql, /schema)configure_oauth()- OAuth authorization/token endpoints
Route Modules (configured by configure_authenticated_routes):
auth::configure- Authentication endpointsapi_keys::configure- API key managementroles::configure- Role managementpermissions::configure- Permission managementusers::configure- User managementgps::configure- GPS data endpointsdevices::configure- Device managementtrips::configure- Trip managementtrip_categories::configure- Trip category managementtrip_templates::configure- Trip template managementlocations::configure- Location managementgeofences::configure- Geofence managementintegrations::configure- Platform integrations (Twitch, etc.)platforms::configure- Authentication platform managementtwo_factor::configure- Two-factor authenticationstorage::configure- File storagesettings::configure- Settings managementsystem_config::configure- System configurationpegel::configure- Pegelonline water levelsstats::configure- Statistics endpoints
The WebSocket endpoint (websocket::configure) is registered under configure_public_routes, not the authenticated modules — connections are public at the route level (auth is performed during the WebSocket handshake via token).
Authentication & Authorization
No Authentication Required
GET /- Root redirect to /v1GET /health- Health checkGET /v1- API infoGET /v1/health- Health checkGET /v1/settings/public- Public settingsGET /v1/stats/public- Public statisticsPOST /v1/auth/signin- Sign inPOST /v1/auth/validate- Validate tokenGET /v1/graphiql- GraphiQL IDE (development only)GET /v1/schema- GraphQL schema
Optionally Authenticated (auth checked per-resolver)
POST /v1/gql- GraphQL endpointGET /v1/gql- GraphQL endpoint
Permission-Gated
Each handler calls require_permission(resource, action). Admin/super_admin hold these via the *:* wildcard; other roles need the specific permission.
GET /v1/gps- List GPS data (gps:read)GET /v1/gps/current- Current GPS (gps:read)GET /v1/gps/current/devices- Latest GPS per device (gps:read)GET /v1/gps/{id}- GPS by ID (gps:read)POST /v1/gps- Create GPS (gps:write, rate-limited: 30 req/60s)DELETE /v1/gps/{id}- Delete GPS (gps:delete, 409 if trip-linked)GET /v1/settings- All settings (settings:read)GET /v1/settings/{key}- Setting by key (settings:read)POST /v1/settings- Create setting (settings:write)PUT /v1/settings/{key}- Update setting (settings:write)DELETE /v1/settings/{key}- Delete setting (settings:delete)GET /v1/api-keys- List API keys (api_keys:read)POST /v1/api-keys- Create API key (api_keys:write)GET /v1/api-keys/{id}- Get API key (api_keys:read)PATCH /v1/api-keys/{id}- Update API key (api_keys:write)DELETE /v1/api-keys/{id}- Delete API key (api_keys:delete)GET /v1/roles- List roles (roles:read)POST /v1/roles- Create role (roles:write)GET /v1/roles/{id}- Get role (roles:read)PATCH /v1/roles/{id}- Update role (roles:write)DELETE /v1/roles/{id}- Delete role (roles:delete)GET /v1/permissions- List permissions (permissions:read)POST /v1/permissions- Create permission (permissions:write)GET /v1/permissions/{id}- Get permission (permissions:read)PATCH /v1/permissions/{id}- Update permission (permissions:write)DELETE /v1/permissions/{id}- Delete permission (permissions:delete)GET /v1/users- List users (users:read)GET /v1/users/{id}- Get user (users:reador self)PATCH /v1/users/{id}- Update user (users:writeor self)DELETE /v1/users/{id}- Delete user (users:delete)DELETE /v1/discord/guilds/{id}- Delete Discord guild (discord:delete)
HATEOAS Links
All responses include _links with self-referencing URLs:
Single Resource:
{
"data": { ... },
"_links": {
"self": "https://api.example.com/v1/gps/550e8400-e29b-41d4-a716-446655440000"
}
}
Paginated Resource:
{
"data": [...],
"pagination": { ... },
"_links": {
"self": "https://api.example.com/v1/gps?page=2&limit=10",
"first": "https://api.example.com/v1/gps?page=1&limit=10",
"prev": "https://api.example.com/v1/gps?page=1&limit=10",
"next": "https://api.example.com/v1/gps?page=3&limit=10",
"last": "https://api.example.com/v1/gps?page=10&limit=10"
}
}
Middleware Stack
Middleware is defined in heimdall-rest::middleware and heimdall-api:
- CORS (
heimdall-api) - Environment-aware (permissive in dev, strict in prod) - Logger - HTTP request logging
- TracingLogger (
heimdall-telemetry) - Structured logging with tracing - AuthMiddleware (
heimdall-rest::middleware) - Bearer token authentication. Wraps the entire/v1scope (not per-route). Public routes tolerate a missingAuthContext; authenticated routes require it. - RequireRole (
heimdall-rest::middleware) - Role-based access control (per-route) - RateLimitMiddleware (
heimdall-rest::middleware) - Rate limiting (per-route)
Benefits of This Structure
✅ Clear Separation: Root-level vs versioned API routes
✅ Scalable: Easy to add /v2 scope in the future
✅ Flexible Auth: Per-route authentication and authorization
✅ DRY: Reusable route configuration functions
✅ Type-Safe: Compile-time route verification
✅ HATEOAS: Self-documenting API with links