Files
tatlock-ui/docs/API_INTEGRATION.md
T
jpmschweitzerandClaude Opus 4.5 0c3239f4b0 docs: add Phase 0 documentation
- docs/ARCHITECTURE.md - Clean Architecture patterns and conventions
- docs/API_INTEGRATION.md - Core API and Tatlock API endpoints
- docs/DEPLOYMENT.md - Docker, NPM, Portainer setup
- docs/DATAGRID.md - DataGrid component specification
- docs/THEMING.md - Material 3 theming guide

Also update seed color to teal for consistency.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 16:26:44 +01:00

7.1 KiB

API Integration Guide

This document describes the backend APIs that Tatlock UI integrates with.

Backend Services

Service URL Purpose
Core API https://api.schweitz.net Infrastructure, widgets, housekeeping
Tatlock API https://tatlock.schweitz.net LLM chat completions, streaming

Both APIs are behind Authentik SSO - requests must include a valid Bearer token.

Authentication

Authentik OIDC Flow

Tatlock UI uses the Authorization Code flow with PKCE:

  1. User clicks "Sign In"
  2. App redirects to Authentik authorization endpoint
  3. User authenticates with Authentik
  4. Authentik redirects back with authorization code
  5. App exchanges code for tokens
  6. Access token used for API requests, refresh token for renewal

Configuration

Setting Value
Provider Authentik
Client ID tatlock-ui
Client Type Public (PKCE)
Scopes openid profile email
Discovery URL https://auth.schweitz.net/application/o/tatlock-ui/.well-known/openid-configuration

Token Usage

// Include in all API requests
headers: {
  'Authorization': 'Bearer $accessToken',
  'Content-Type': 'application/json',
}

Core API Endpoints

Base URL: https://api.schweitz.net

Infrastructure

Get System Metrics

GET /infrastructure/resources/system

Response:

{
  "cpu": {
    "percent": 23.5,
    "cores": 8
  },
  "memory": {
    "percent": 45.2,
    "total_gb": 32.0,
    "used_gb": 14.5
  },
  "disk": {
    "percent": 67.8,
    "total_gb": 500.0,
    "used_gb": 339.0
  }
}

List Containers

GET /infrastructure/containers

Query params:

  • status (optional): Filter by status (running, stopped, paused)
  • search (optional): Search by name

Response:

[
  {
    "id": "abc123...",
    "name": "tatlock-api",
    "status": "running",
    "image": "ghcr.io/jpmschweitzer/tatlock:latest",
    "created": "2024-12-01T10:00:00Z",
    "ports": ["8000:8000"]
  }
]

Get Container Details

GET /infrastructure/containers/{id}

Get Container Logs

GET /infrastructure/containers/{id}/logs

Query params:

  • tail (optional): Number of lines (default: 100)
  • since (optional): ISO timestamp

Response:

{
  "logs": "2024-12-30 10:00:00 INFO Starting server...\n..."
}

Container Actions

POST /infrastructure/containers/{id}/{action}

Actions: start, stop, restart, pause, unpause

Response:

{
  "success": true,
  "message": "Container restarted"
}

Get Container Resources

GET /infrastructure/resources/containers

Response:

[
  {
    "id": "abc123...",
    "name": "tatlock-api",
    "cpu_percent": 2.5,
    "memory_mb": 256,
    "memory_limit_mb": 1024
  }
]

Dashboard

Get Widget Data

GET /infrastructure/widget-data

Response:

{
  "groups": [
    {
      "name": "Infrastructure",
      "services": [
        {
          "name": "Portainer",
          "url": "https://portainer.schweitz.net",
          "icon": "portainer",
          "status": "up"
        }
      ]
    }
  ]
}

Health Check

GET /health

Response:

{
  "status": "healthy",
  "timestamp": "2024-12-30T10:00:00Z"
}

Housekeeping (Home Assistant)

List Devices

GET /housekeeping/devices

Query params:

  • area (optional): Filter by area name
  • domain (optional): Filter by domain (light, switch, climate, etc.)

Response:

[
  {
    "entity_id": "light.living_room",
    "friendly_name": "Living Room Light",
    "domain": "light",
    "state": "on",
    "area": "Living Room",
    "attributes": {
      "brightness": 255,
      "color_temp": 370
    }
  }
]

List Areas

GET /housekeeping/areas

Response:

[
  {
    "id": "living_room",
    "name": "Living Room",
    "device_count": 5
  }
]

List Scenes

GET /housekeeping/scenes

Response:

[
  {
    "entity_id": "scene.movie_time",
    "friendly_name": "Movie Time",
    "area": "Living Room"
  }
]

Control Device

POST /housekeeping/devices/{entity_id}/control

Request:

{
  "action": "turn_on",
  "attributes": {
    "brightness": 200
  }
}

Activate Scene

POST /housekeeping/scenes/{scene_id}/activate

Tatlock API Endpoints

Base URL: https://tatlock.schweitz.net

Chat Completions (Streaming)

POST /v1/chat/completions

Request:

{
  "model": "tatlock",
  "messages": [
    {"role": "system", "content": "You are Tatlock, a helpful butler."},
    {"role": "user", "content": "What's the weather like?"}
  ],
  "stream": true
}

Response (SSE stream):

data: {"id":"chatcmpl-123","choices":[{"delta":{"content":"The"}}]}

data: {"id":"chatcmpl-123","choices":[{"delta":{"content":" weather"}}]}

data: {"id":"chatcmpl-123","choices":[{"delta":{"reasoning_content":"Checking weather API..."}}]}

data: [DONE]

Important fields:

  • delta.content - Main response text
  • delta.reasoning_content - Thinking/reasoning (show in collapsible block)

List Models

GET /v1/models

Response:

{
  "data": [
    {
      "id": "tatlock",
      "object": "model",
      "owned_by": "local"
    }
  ]
}

SSE Streaming Implementation

For chat completions, use Server-Sent Events:

// Platform-aware SSE client
class SseClient {
  Stream<ChatCompletionChunk> streamCompletion(ChatCompletionRequest request) async* {
    final response = await _client.post(
      '/v1/chat/completions',
      data: request.toJson(),
      options: Options(
        responseType: ResponseType.stream,
        headers: {'Accept': 'text/event-stream'},
      ),
    );

    await for (final chunk in response.data.stream) {
      final lines = utf8.decode(chunk).split('\n');
      for (final line in lines) {
        if (line.startsWith('data: ') && line != 'data: [DONE]') {
          final json = jsonDecode(line.substring(6));
          yield ChatCompletionChunk.fromJson(json);
        }
      }
    }
  }
}

Error Handling

Standard Error Response

{
  "error": {
    "code": "CONTAINER_NOT_FOUND",
    "message": "Container with ID 'xyz' not found",
    "details": {}
  }
}

HTTP Status Codes

Code Meaning
200 Success
400 Bad Request - Invalid parameters
401 Unauthorized - Token expired or invalid
403 Forbidden - Insufficient permissions
404 Not Found - Resource doesn't exist
500 Server Error - Backend issue

Handling in App

class ApiException implements Exception {
  ApiException({required this.code, required this.message});

  final String code;
  final String message;
}

// In interceptor
if (response.statusCode == 401) {
  // Trigger token refresh or re-auth
  throw AuthException();
}

Rate Limiting

Currently no rate limiting on internal APIs. For LLM endpoints, be mindful of:

  • Concurrent requests (limit to 1 active chat stream)
  • Token consumption (context window limits)

API Documentation

Interactive API docs available at: