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

425 lines
7.1 KiB
Markdown

# 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
```dart
// 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:
```json
{
"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:
```json
[
{
"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:
```json
{
"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:
```json
{
"success": true,
"message": "Container restarted"
}
```
#### Get Container Resources
```
GET /infrastructure/resources/containers
```
Response:
```json
[
{
"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:
```json
{
"groups": [
{
"name": "Infrastructure",
"services": [
{
"name": "Portainer",
"url": "https://portainer.schweitz.net",
"icon": "portainer",
"status": "up"
}
]
}
]
}
```
#### Health Check
```
GET /health
```
Response:
```json
{
"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:
```json
[
{
"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:
```json
[
{
"id": "living_room",
"name": "Living Room",
"device_count": 5
}
]
```
#### List Scenes
```
GET /housekeeping/scenes
```
Response:
```json
[
{
"entity_id": "scene.movie_time",
"friendly_name": "Movie Time",
"area": "Living Room"
}
]
```
#### Control Device
```
POST /housekeeping/devices/{entity_id}/control
```
Request:
```json
{
"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:
```json
{
"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:
```json
{
"data": [
{
"id": "tatlock",
"object": "model",
"owned_by": "local"
}
]
}
```
---
## SSE Streaming Implementation
For chat completions, use Server-Sent Events:
```dart
// 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
```json
{
"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
```dart
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