- 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>
7.1 KiB
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:
- User clicks "Sign In"
- App redirects to Authentik authorization endpoint
- User authenticates with Authentik
- Authentik redirects back with authorization code
- App exchanges code for tokens
- 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 namedomain(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 textdelta.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:
- Core API: https://api.schweitz.net/docs
- Tatlock API: https://tatlock.schweitz.net/docs