Skip to main content

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/health
  • configure_sessions() - Session endpoints (auth handled internally via system API key)
  • configure_authenticated_routes() - All v1 API route modules
  • configure_graphql() - GraphQL endpoints (/gql, /graphiql, /schema)
  • configure_oauth() - OAuth authorization/token endpoints

Route Modules (configured by configure_authenticated_routes):

  • auth::configure - Authentication endpoints
  • api_keys::configure - API key management
  • roles::configure - Role management
  • permissions::configure - Permission management
  • users::configure - User management
  • gps::configure - GPS data endpoints
  • devices::configure - Device management
  • trips::configure - Trip management
  • trip_categories::configure - Trip category management
  • trip_templates::configure - Trip template management
  • locations::configure - Location management
  • geofences::configure - Geofence management
  • integrations::configure - Platform integrations (Twitch, etc.)
  • platforms::configure - Authentication platform management
  • two_factor::configure - Two-factor authentication
  • storage::configure - File storage
  • settings::configure - Settings management
  • system_config::configure - System configuration
  • pegel::configure - Pegelonline water levels
  • stats::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 /v1
  • GET /health - Health check
  • GET /v1 - API info
  • GET /v1/health - Health check
  • GET /v1/settings/public - Public settings
  • GET /v1/stats/public - Public statistics
  • POST /v1/auth/signin - Sign in
  • POST /v1/auth/validate - Validate token
  • GET /v1/graphiql - GraphiQL IDE (development only)
  • GET /v1/schema - GraphQL schema

Optionally Authenticated (auth checked per-resolver)

  • POST /v1/gql - GraphQL endpoint
  • GET /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:read or self)
  • PATCH /v1/users/{id} - Update user (users:write or self)
  • DELETE /v1/users/{id} - Delete user (users:delete)
  • DELETE /v1/discord/guilds/{id} - Delete Discord guild (discord:delete)

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:

  1. CORS (heimdall-api) - Environment-aware (permissive in dev, strict in prod)
  2. Logger - HTTP request logging
  3. TracingLogger (heimdall-telemetry) - Structured logging with tracing
  4. AuthMiddleware (heimdall-rest::middleware) - Bearer token authentication. Wraps the entire /v1 scope (not per-route). Public routes tolerate a missing AuthContext; authenticated routes require it.
  5. RequireRole (heimdall-rest::middleware) - Role-based access control (per-route)
  6. 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