Skip to content

API Layer

The REST API is Donna's external surface for the Flutter app, management GUI, CLI, and inter-service communication, protected by a layered authentication stack that federates identity through Immich SSO.

Realizes: spec_v3.md §27 (Admin API & Dashboard), spec_v3.md §28 (Authentication & Access Control)

Overview

The API is a FastAPI application running on port 8200 under src/donna/api/. It wraps the orchestrator's internal capabilities for external consumption by the Flutter client, web dashboard, and homelab services. Every route is mounted through one of five auth-gated router factories, enforcing a deny-by-default posture at the structural level rather than relying on per-endpoint decorators.

Authentication combines three mechanisms: an IP gate with trust durations for network-level access, device tokens with sliding+absolute windows for persistent client sessions, and Immich SSO for identity resolution. The email allowlist syncs from Immich every 15 minutes, and a verification-token flow handles self-service onboarding for new devices or networks.

The application lifecycle (managed via FastAPI's lifespan context) initializes the database, state machine, LLM queue worker, chat engine, calendar client, payload writer, eviction loop, and auth context. CORS is explicitly configured -- wildcard origins are forbidden when auth cookies are in use.

Key Concepts

Concept Description
Router factory Pre-bound auth dependency on every APIRouter; routes declare their auth class by choosing user_router(), admin_router(), service_router(), or public_auth_router().
IP gate Per-IP trust with configurable durations (24h, 7d, 30d, 90d). Actions: allow, challenge, block. Internal CIDRs bypass challenge.
Device tokens Long-lived client tokens hashed with argon2id. Sliding window refreshes on each use, capped by an absolute max from creation.
Immich SSO Identity federation with the homelab Immich photo server. Bearer tokens forwarded to /api/users/me; 60-second LRU cache.
Email allowlist Only Immich-provisioned users can request access. Synced from Immich admin API on startup and every 15 minutes. Empty-response protection prevents lockout.
Verification tokens Single-use magic-link tokens bound to the requesting IP. SHA-256 stored; 15-minute expiry.
Service keys Argon2-hashed shared secrets for inter-service calls (e.g., Ollama container). Requires internal CIDR source and no X-Forwarded-Host.
Trusted proxies Safe X-Forwarded-For parsing. Returns the rightmost non-proxy entry, matching Caddy's append behavior.

Architecture

flowchart TD
    Client["Flutter / Web / CLI"] -->|HTTPS| Caddy["Caddy Reverse Proxy"]
    Service["Homelab Service"] -->|Internal network| API
    Caddy --> API["FastAPI :8200"]

    API --> AuthLayer["Auth Layer"]
    AuthLayer --> IPGate["IP Gate"]
    AuthLayer --> DeviceTokens["Device Tokens"]
    AuthLayer --> ImmichSSO["Immich SSO"]
    AuthLayer --> ServiceKeys["Service Keys"]

    API --> UserRoutes["User Routes"]
    API --> AdminRoutes["Admin Routes"]
    API --> ServiceRoutes["Service Routes"]
    API --> PublicRoutes["Public Routes"]

    UserRoutes --> Tasks & Schedule & Chat & Calendar & Agents
    AdminRoutes --> Dashboard & Logs & Config & Skills & Automations & Claude["Claude Inspector"]
    ServiceRoutes --> LLMGateway["LLM Gateway"]
    PublicRoutes --> AuthFlow["Auth Flow"] & Health

Auth Resolution Flow

flowchart TD
    Request --> TrustedProxy["Resolve client IP via trusted proxies"]
    TrustedProxy --> DeviceCheck{"Device token present?"}
    DeviceCheck -->|Yes| ValidateToken["Validate argon2 hash + sliding window"]
    ValidateToken -->|Valid| Authenticated["Return user_id"]
    ValidateToken -->|Invalid| IPCheck

    DeviceCheck -->|No| IPCheck{"IP in internal CIDR?"}
    IPCheck -->|Yes| DefaultAdmin["Return default admin user"]
    IPCheck -->|No| IPGateCheck{"IP trusted?"}
    IPGateCheck -->|Trusted| ImmichBearer{"Immich bearer present?"}
    IPGateCheck -->|Not trusted| Challenge["403: request_access"]

    ImmichBearer -->|Yes| ResolveImmich["Forward to Immich /api/users/me"]
    ImmichBearer -->|No| Unauthenticated["401: unauthenticated"]
    ResolveImmich -->|Valid| LookupUser["Map immich_user_id to donna_user_id"]
    ResolveImmich -->|Invalid| Unauthenticated
    LookupUser --> Authenticated

Route Surface

The API is organized into four auth tiers:

Tier Prefix Auth Routes
Public /health, /auth/* None Health check, request-access, verify, status, logout
User /tasks, /schedule, /calendar, /chat, /agents Device token or Immich SSO Task CRUD, schedule view, calendar week, chat sessions, agent listing
Admin /admin/* Admin role + IP gate Dashboard KPIs, log search, invocation analytics, Claude Inspector, config, skills, automations, escalations, vault, access management
Service /llm/* Service key + internal CIDR LLM gateway completions (priority queue, streaming)

Admin Route Modules

Module Endpoints Purpose
admin_dashboard /admin/dashboard/* Parse accuracy, agent performance, task throughput, cost analytics
admin_claude /admin/claude/* Claude Inspector: call browser, payload retrieval, insights
admin_logs /admin/logs/* Structured log search across Loki + invocation_log
admin_invocations /admin/invocations/* Per-call analytics with queue wait, chain, caller drill-down
admin_tasks /admin/tasks/* Task management CRUD
admin_agents /admin/agents/* Agent enable/disable/inspect
admin_config /admin/config/* Live config inspection
admin_preferences /admin/preferences/* Preference rule management
admin_health /admin/health/* Deep health check
admin_access /admin/access/* IP trust management
admin_shadow /admin/shadow/* Shadow evaluation inspection
admin_escalations /admin/escalations/* Manual escalation queue
admin_escalation_settings /admin/escalation-settings/* Per-task-type escalation config
admin_vault /admin/vault/* Memory vault inspection
admin_llm /admin/llm/* LLM queue status (read-only)
skills /admin/skills/* Skill registry
skill_candidates /admin/skill-candidates/* Candidate promotion pipeline
skill_drafts /admin/skill-drafts/* Auto-drafter outputs
skill_runs /admin/skill-runs/* Skill execution history
automations /admin/automations/* Automation scheduling
capabilities /admin/capabilities/* Capability registry

Configuration

All auth behavior is driven by config/auth.yaml:

Section Controls
ip_gate Default trust duration, allowed durations, rate limits per IP (request_access: 5/hour, verify: 10/10min)
trusted_proxies CIDR list for X-Forwarded-For parsing (default: 172.20.0.0/16 Docker network)
internal_cidrs Networks that bypass IP gate challenge (same Docker network)
immich Internal/external URLs, admin API key env var, cache TTL (60s), sync interval (900s)
device_tokens Sliding window (90 days), absolute max (365 days), max per user (10)
email From address, subject line, verify URL base, token expiry (15 min)
bootstrap Admin email env var for initial provisioning

Environment variables: DONNA_CORS_ORIGINS (comma-separated, no wildcard), DONNA_DEFAULT_USER_ID, DONNA_LOG_LEVEL, DONNA_DEV_MODE.

See also: Config: auth.yaml

API

Interface Module Description
create_app() donna.api Builds the FastAPI application with all middleware and routes
lifespan() donna.api Async context manager for startup/shutdown of DB, queue, chat, auth, eviction
AuthContext donna.api.auth.dependencies Dataclass bundling DB connection, auth config, and Immich client
CurrentUser donna.api.auth.router_factory FastAPI dependency annotation resolving the authenticated user ID
CurrentAdmin donna.api.auth.router_factory FastAPI dependency annotation requiring admin role
CurrentServiceCaller donna.api.auth.router_factory FastAPI dependency annotation for service-key auth
user_router() donna.api.auth.router_factory Factory for user-authenticated routers
admin_router() donna.api.auth.router_factory Factory for admin-authenticated routers
service_router() donna.api.auth.router_factory Factory for service-key-authenticated routers
check_ip_access() donna.api.auth.ip_gate Core IP gate check returning {action, reason, ip_record}
device_tokens.issue() donna.api.auth.device_tokens Issue a new device token (returns raw token once)
device_tokens.validate() donna.api.auth.device_tokens Validate and refresh a device token's sliding window
ImmichClient.resolve() donna.api.auth.immich Resolve a bearer token to an ImmichUser via Immich API
compute_insights() donna.insights.engine Cost/quality analytics surfaced at /admin/claude/insights

See also: API Reference: donna.api

See Also