# AI Agent Access Design ## Overview Add a dedicated API endpoint optimized for AI agents (like Claude) to access dashboard data in a structured, machine-readable format with simple authentication. ## Authentication Strategy ### API Key-Based Auth Simple bearer token authentication that's easy to use from Claude.ai: - Generate a long, random API key for AI access - Store in environment variable: `AI_AGENT_API_KEY` - Use standard `Authorization: Bearer {key}` header - No complex OAuth flows or JWT rotation ### Why This Approach? 1. **Simple for Claude**: Just include auth header in requests 2. **Stateless**: No session management needed 3. **Revocable**: Change key in .env to revoke access 4. **Standard**: Uses common HTTP authorization pattern ## API Endpoints ### GET /api/ai/dashboard Returns complete dashboard data in AI-optimized JSON format. **Request:** ```http GET /api/ai/dashboard HTTP/1.1 Host: localhost:8080 Authorization: Bearer {AI_AGENT_API_KEY} ``` **Response (Success - 200 OK):** ```json { "status": "success", "timestamp": "2026-01-09T14:30:00Z", "data": { "trello_boards": [ { "id": "board123", "name": "Work Projects", "cards": [ { "id": "card1", "name": "Complete project proposal", "list": "In Progress", "due_date": "2026-01-15", "url": "https://trello.com/c/card1" } ] } ], "todoist_tasks": [ { "id": "task1", "content": "Buy groceries", "project": "Personal", "priority": 1, "due_date": "2026-01-10", "completed": false, "labels": ["shopping"], "url": "https://todoist.com/task/1" } ], "obsidian_notes": [ { "filename": "meeting-notes.md", "title": "Team Meeting Notes", "content_preview": "Discussed Q1 goals...", "modified": "2026-01-09T10:00:00Z", "tags": ["meetings", "work"] } ], "plantoeat_meals": [ { "id": "meal1", "recipe": "Spaghetti Carbonara", "date": "2026-01-09", "meal_type": "dinner", "url": "https://plantoeat.com/recipe/123" } ] }, "metadata": { "cache_age_seconds": 45, "sources_refreshed": ["trello", "todoist"], "errors": [] } } ``` **Response (Unauthorized - 401):** ```json { "status": "error", "error": "Invalid or missing API key", "hint": "Include 'Authorization: Bearer {key}' header" } ``` ### GET /api/ai/boards/{board_id}/cards Get cards from a specific Trello board. **Request:** ```http GET /api/ai/boards/board123/cards HTTP/1.1 Authorization: Bearer {AI_AGENT_API_KEY} ``` ### GET /api/ai/tasks?filter={filter} Get filtered tasks (today, week, overdue, completed). **Request:** ```http GET /api/ai/tasks?filter=today HTTP/1.1 Authorization: Bearer {AI_AGENT_API_KEY} ``` ### POST /api/ai/task Create a new Todoist task (Phase 2). **Request:** ```http POST /api/ai/task HTTP/1.1 Authorization: Bearer {AI_AGENT_API_KEY} Content-Type: application/json { "content": "Buy milk", "project": "Personal", "due_date": "2026-01-10", "priority": 1, "labels": ["shopping"] } ``` ## AI-Optimized Response Format ### Design Principles 1. **Flat structure**: Avoid deep nesting where possible 2. **Explicit field names**: Use `due_date` not `dueDate` for clarity 3. **Include metadata**: Provide context about data freshness 4. **Clear errors**: Return actionable error messages 5. **Consistent types**: Always use ISO 8601 for dates ### Example Claude.ai Usage ```markdown User: "What tasks do I have due today?" Claude uses MCP tool or API call: GET /api/ai/tasks?filter=today Authorization: Bearer sk-ai-1234567890abcdef Claude response: "You have 3 tasks due today: 1. Buy groceries (Personal, Priority 1) 2. Complete project proposal (Work, Priority 2) 3. Call dentist (Health, Priority 3)" ``` ## Implementation ### Middleware: Auth Check ```go // internal/middleware/ai_auth.go func AIAuthMiddleware(apiKey string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if !strings.HasPrefix(authHeader, "Bearer ") { respondJSON(w, 401, map[string]string{ "status": "error", "error": "Missing or invalid Authorization header", "hint": "Include 'Authorization: Bearer {key}' header", }) return } token := strings.TrimPrefix(authHeader, "Bearer ") if token != apiKey { respondJSON(w, 401, map[string]string{ "status": "error", "error": "Invalid API key", }) return } next.ServeHTTP(w, r) }) } } ``` ### Handler: AI Dashboard ```go // internal/handlers/ai_handlers.go func (h *Handler) HandleAIDashboard(w http.ResponseWriter, r *http.Request) { ctx := r.Context() // Fetch all data data, err := h.fetchDashboardData(ctx, false) if err != nil { respondJSON(w, 500, map[string]interface{}{ "status": "error", "error": err.Error(), }) return } // Format for AI consumption response := map[string]interface{}{ "status": "success", "timestamp": time.Now().Format(time.RFC3339), "data": map[string]interface{}{ "trello_boards": formatBoardsForAI(data.Boards), "todoist_tasks": formatTasksForAI(data.Tasks), "obsidian_notes": formatNotesForAI(data.Notes), "plantoeat_meals": formatMealsForAI(data.Meals), }, "metadata": map[string]interface{}{ "errors": data.Errors, }, } respondJSON(w, 200, response) } ``` ### Router Setup ```go // cmd/dashboard/main.go func main() { // ... existing setup ... // AI Agent routes (protected by auth middleware) if cfg.AIAgentAPIKey != "" { aiRouter := chi.NewRouter() aiRouter.Use(middleware.AIAuthMiddleware(cfg.AIAgentAPIKey)) r.Route("/api/ai", func(r chi.Router) { r.Use(middleware.AIAuthMiddleware(cfg.AIAgentAPIKey)) r.Get("/dashboard", h.HandleAIDashboard) r.Get("/boards/{boardID}/cards", h.HandleAIBoardCards) r.Get("/tasks", h.HandleAITasks) // Phase 2: Write operations // r.Post("/task", h.HandleAICreateTask) }) } } ``` ## Security Considerations 1. **Key Storage**: Never commit API key to git 2. **Key Rotation**: Easy to change in .env file 3. **Rate Limiting**: Consider adding rate limits per key 4. **HTTPS Only**: Use HTTPS in production 5. **Logging**: Log API key usage (last 4 chars only) ## Configuration ### .env.example ```bash # AI Agent Access AI_AGENT_API_KEY=sk-ai-1234567890abcdef... # Generate with: openssl rand -hex 32 ``` ### config.go ```go type Config struct { // ... existing fields ... AIAgentAPIKey string } func Load() (*Config, error) { cfg := &Config{ // ... existing fields ... AIAgentAPIKey: os.Getenv("AI_AGENT_API_KEY"), } return cfg, nil } ``` ## Usage from Claude.ai ### With MCP (Model Context Protocol) ```json { "mcpServers": { "dashboard": { "command": "curl", "args": [ "-H", "Authorization: Bearer ${AI_AGENT_API_KEY}", "http://localhost:8080/api/ai/dashboard" ] } } } ``` ### Direct API Call Claude can make HTTP requests directly: ``` GET http://localhost:8080/api/ai/dashboard Authorization: Bearer sk-ai-1234567890abcdef ``` ## Future Enhancements 1. **Webhook Support**: POST updates to Claude.ai when data changes 2. **Streaming**: Server-sent events for real-time updates 3. **Scoped Keys**: Different keys for read vs write access 4. **GraphQL**: More flexible querying for AI agents 5. **Natural Language Queries**: `/api/ai/query?q=What's due today?` ## Testing ### Manual Test ```bash # Generate API key export AI_KEY=$(openssl rand -hex 32) # Add to .env echo "AI_AGENT_API_KEY=sk-ai-$AI_KEY" >> .env # Test endpoint curl -H "Authorization: Bearer sk-ai-$AI_KEY" \ http://localhost:8080/api/ai/dashboard | jq . ``` ### Unit Test ```go func TestAIAuthMiddleware(t *testing.T) { // Test valid key // Test invalid key // Test missing header } ```