19 KiB
Life Management System — API Design
Overview
REST API at http://localhost:3700/api/* with Socket.IO WebSocket gateways on the same port. All responses follow consistent JSON format. Pagination via query params. Validation via class-validator DTOs.
Base URL: http://localhost:3700/api
Swagger: http://localhost:3700/api-docs
Common Patterns
Pagination
All list endpoints accept:
?page=1&limit=20&sort=createdAt&order=DESC
Response envelope:
{
"data": [...],
"meta": {
"page": 1,
"limit": 20,
"total": 142,
"totalPages": 8
}
}
Error Response
{
"statusCode": 400,
"error": "Bad Request",
"message": "Validation failed",
"details": [
{ "field": "title", "message": "title should not be empty" }
]
}
Filtering
List endpoints support filtering via query params specific to each entity. Common patterns:
?domainId=<uuid>— Filter by domain?status=todo,in_progress— Comma-separated enum values?startDate=2026-01-01&endDate=2026-01-31— Date ranges
Health
GET /health
Health check endpoint (via @lilith/nestjs-health).
Response:
{
"status": "ok",
"info": {
"database": { "status": "up" },
"redis": { "status": "up" }
}
}
Domains Module
GET /api/domains
List all domains.
Query params: ?isActive=true
Response: Domain[]
[
{
"id": "uuid",
"name": "Christine's Startup",
"type": "client_work",
"slug": "christine",
"color": "#4A90D9",
"icon": "briefcase",
"description": "Primary client contract",
"isActive": true,
"sortOrder": 0,
"config": {},
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
]
GET /api/domains/:id
Get domain by ID.
GET /api/domains/slug/:slug
Get domain by slug (for URL-based lookups).
POST /api/domains
Create domain.
Body:
{
"name": "New Domain",
"type": "side_project",
"slug": "new-domain",
"color": "#FF5733",
"icon": "star",
"description": "Optional description",
"config": {}
}
PATCH /api/domains/:id
Update domain (partial).
DELETE /api/domains/:id
Delete domain (hard delete — only if no associated entities).
Tasks Module
GET /api/tasks
List tasks with filtering.
Query params:
?domainId=<uuid>— Filter by domain?goalId=<uuid>— Filter by goal?sprintId=<uuid>— Filter by sprint?parentId=<uuid>— Filter subtasks of a parent?status=todo,in_progress— Status filter (comma-separated)?priority=critical,high— Priority filter?energyLevel=low,medium— Energy level filter?isQuickWin=true— Quick wins only?overdue=true— Overdue tasks only (due_date < today, status not done/cancelled)?scheduledDate=2026-02-24— Scheduled for specific date?dueDateStart=&dueDateEnd=— Due date range?search=<text>— Full-text search on title?tags=urgent,client— Tag filter (comma-separated, AND logic)?includeSubtasks=true— Include subtask tree
Response: Paginated Task[]
{
"data": [
{
"id": "uuid",
"domainId": "uuid",
"domain": { "id": "uuid", "name": "...", "slug": "...", "color": "..." },
"goalId": null,
"sprintId": null,
"parentId": null,
"title": "Design review for Christine",
"description": "Review the latest mockups",
"status": "todo",
"priority": "high",
"energyLevel": "high",
"estimatedMinutes": 60,
"actualMinutes": null,
"dueDate": "2026-02-24",
"scheduledDate": "2026-02-24",
"isQuickWin": false,
"recurrenceRule": null,
"tags": ["design", "client"],
"sortOrder": 0,
"subtaskCount": 3,
"subtaskCompletedCount": 1,
"createdAt": "...",
"updatedAt": "..."
}
],
"meta": { "page": 1, "limit": 20, "total": 45, "totalPages": 3 }
}
GET /api/tasks/:id
Get task by ID with subtasks.
Response: Task with nested subtasks: Task[]
POST /api/tasks
Create task.
Body:
{
"domainId": "uuid",
"goalId": null,
"sprintId": null,
"parentId": null,
"title": "New task",
"description": "Description",
"status": "todo",
"priority": "medium",
"energyLevel": "medium",
"estimatedMinutes": 30,
"dueDate": "2026-03-01",
"scheduledDate": null,
"isQuickWin": false,
"recurrenceRule": null,
"tags": ["tag1"]
}
PATCH /api/tasks/:id
Update task (partial).
PATCH /api/tasks/:id/status
Quick status transition.
Body:
{
"status": "done",
"actualMinutes": 45
}
DELETE /api/tasks/:id
Soft delete (sets deletedAt).
POST /api/tasks/:id/reorder
Reorder task within its list.
Body:
{
"sortOrder": 3
}
Goals Module
GET /api/goals
List goals with filtering.
Query params:
?domainId=<uuid>— Filter by domain?level=yearly,quarterly— Filter by level?status=active— Filter by status?parentId=<uuid>— Children of a specific goal
GET /api/goals/tree
Get hierarchical goal tree.
Query params:
?domainId=<uuid>— Optional domain filter
Response: Nested tree structure:
[
{
"id": "uuid",
"title": "Ship Lilith Platform v1",
"level": "yearly",
"status": "active",
"progressPct": 35,
"children": [
{
"id": "uuid",
"title": "Complete core modules",
"level": "quarterly",
"progressPct": 60,
"children": [
{
"id": "uuid",
"title": "Finish auth system",
"level": "monthly",
"progressPct": 80,
"children": []
}
]
}
]
}
]
GET /api/goals/:id
Get goal with children and linked tasks.
POST /api/goals
Create goal.
Body:
{
"domainId": "uuid",
"parentId": null,
"title": "Goal title",
"description": "Description",
"level": "quarterly",
"targetDate": "2026-06-30"
}
PATCH /api/goals/:id
Update goal.
PATCH /api/goals/:id/progress
Update progress percentage.
Body:
{
"progressPct": 75
}
DELETE /api/goals/:id
Delete goal (cascading to orphan children — re-parents to grandparent or null).
Habits Module
GET /api/habits
List habits.
Query params:
?domainId=<uuid>?isActive=true?frequency=daily?timeOfDay=morning
Response: Habit[] with current streak info.
GET /api/habits/due-today
Get habits due today based on frequency configuration and day of week.
Response: Habit[] with checkedInToday: boolean flag.
GET /api/habits/:id
Get habit with recent check-in history (last 30 days).
POST /api/habits
Create habit.
Body:
{
"domainId": "uuid",
"goalId": null,
"name": "Morning meditation",
"description": "10 minutes mindfulness",
"frequency": "daily",
"frequencyConfig": {},
"timeOfDay": "morning",
"estimatedMinutes": 10
}
PATCH /api/habits/:id
Update habit.
POST /api/habits/:id/check-in
Record check-in for today.
Body:
{
"status": "done",
"qualityRating": 4,
"notes": "Great session today"
}
Response: Updated Habit with new streak values.
GET /api/habits/:id/check-ins
Get check-in history.
Query params:
?startDate=2026-01-01&endDate=2026-02-28
Response: HabitCheckIn[]
GET /api/habits/:id/stats
Get habit statistics.
Response:
{
"currentStreak": 14,
"bestStreak": 30,
"completionRate30d": 0.87,
"completionRate7d": 1.0,
"averageQuality": 3.8,
"totalCheckIns": 120,
"checkInsByStatus": { "done": 105, "skipped": 10, "partial": 5 }
}
Scheduling Module
GET /api/scheduling/time-blocks
List time blocks.
Query params:
?date=2026-02-24— Specific date?startDate=&endDate=— Date range?domainId=<uuid>
POST /api/scheduling/time-blocks
Create time block.
Body:
{
"domainId": "uuid",
"taskId": null,
"habitId": null,
"title": "Christine standup",
"date": "2026-02-24",
"startTime": "09:00",
"endTime": "10:30",
"blockType": "work",
"notes": ""
}
PATCH /api/scheduling/time-blocks/:id
Update time block.
POST /api/scheduling/time-blocks/:id/reorder
Reorder time block (update start/end times).
Body:
{
"startTime": "10:00",
"endTime": "11:30"
}
DELETE /api/scheduling/time-blocks/:id
Delete time block.
GET /api/scheduling/daily-plan/:date
Get or initialize daily plan for a date.
Response:
{
"id": "uuid",
"date": "2026-02-24",
"energyLevel": "medium",
"focusDomains": ["uuid1", "uuid2"],
"morningIntention": "Focus on Christine deliverables",
"eveningReflection": null,
"moodMorning": 3,
"moodEvening": null,
"timeBlocks": [...],
"createdAt": "...",
"updatedAt": "..."
}
PUT /api/scheduling/daily-plan/:date
Create or update daily plan (upsert).
Body:
{
"energyLevel": "medium",
"focusDomains": ["uuid1", "uuid2"],
"morningIntention": "Focus on Christine deliverables",
"moodMorning": 3
}
PATCH /api/scheduling/daily-plan/:date/reflection
Update evening reflection (separate endpoint for end-of-day flow).
Body:
{
"eveningReflection": "Good day, completed all priority tasks",
"moodEvening": 4
}
Today Module (Aggregation)
GET /api/today
Unified daily view — pulls from all modules for today.
Response:
{
"date": "2026-02-24",
"dailyPlan": {
"energyLevel": "medium",
"moodMorning": 3,
"morningIntention": "...",
"focusDomains": [...]
},
"timeBlocks": [...],
"priorityTasks": [...],
"quickWins": [...],
"habitsDueToday": [
{
"habit": { "id": "...", "name": "Morning meditation", ... },
"checkedIn": false,
"checkIn": null
}
],
"domainStats": [
{
"domain": { "id": "...", "name": "Christine's Startup", "color": "#4A90D9" },
"tasksDueToday": 2,
"tasksOverdue": 1,
"incomeThisWeek": "420.00",
"currentStreak": 7,
"sprintProgress": 60
}
],
"overdueTasks": [...],
"completedToday": 5,
"totalToday": 12
}
GET /api/today/next-action
Get suggested next action based on energy, priority, time of day.
Response:
{
"task": {
"id": "uuid",
"title": "Design review for Christine",
"domain": { "name": "Christine's Startup", "color": "#4A90D9" },
"priority": "high",
"energyLevel": "high",
"estimatedMinutes": 60
},
"score": 65,
"reason": "This is overdue and matches your current energy level"
}
Income Module (Encrypted)
GET /api/income
List income entries (decrypted).
Query params:
?domainId=<uuid>?startDate=&endDate=?sourceType=session,tip?currency=GBP
GET /api/income/summary
Income summary with aggregation.
Query params:
?period=month|week|year?domainId=<uuid>— Optional domain filter?startDate=&endDate=— Custom range
Response:
{
"period": "month",
"startDate": "2026-02-01",
"endDate": "2026-02-28",
"total": "3420.00",
"currency": "GBP",
"byDomain": [
{ "domain": { "name": "Escort", "color": "#FF6B6B" }, "total": "2100.00", "count": 8 },
{ "domain": { "name": "OnlyFans", "color": "#00AFF0" }, "total": "820.00", "count": 45 },
{ "domain": { "name": "Christine's Startup", "color": "#4A90D9" }, "total": "500.00", "count": 1 }
],
"bySourceType": [
{ "sourceType": "session", "total": "2100.00", "count": 8 },
{ "sourceType": "subscription", "total": "620.00", "count": 40 },
{ "sourceType": "tip", "total": "200.00", "count": 5 },
{ "sourceType": "invoice", "total": "500.00", "count": 1 }
]
}
POST /api/income
Create income entry (encrypted on write).
Body:
{
"domainId": "uuid",
"date": "2026-02-24",
"amount": "150.00",
"currency": "GBP",
"sourceType": "session",
"description": "Evening session",
"durationMinutes": 120,
"contactId": "uuid"
}
PATCH /api/income/:id
Update income entry.
DELETE /api/income/:id
Delete income entry.
GET /api/income/billable
List billable entries.
Query params:
?domainId=<uuid>?isInvoiced=false?startDate=&endDate=
POST /api/income/billable
Create billable entry.
PATCH /api/income/billable/:id
Update billable entry (mark as invoiced, etc.).
Health Module (Encrypted)
GET /api/health/measurements
List measurements.
Query params:
?domainId=<uuid>?type=weight,waist?startDate=&endDate=
POST /api/health/measurements
Create measurement.
Body:
{
"domainId": "uuid",
"date": "2026-02-24",
"type": "weight",
"value": 62.5,
"unit": "kg",
"notes": "Morning, before breakfast"
}
GET /api/health/medical
List medical entries (decrypted).
Query params:
?domainId=<uuid>?type=hrt_dose,appointment?startDate=&endDate=
POST /api/health/medical
Create medical entry (encrypted on write).
Body:
{
"domainId": "uuid",
"date": "2026-02-24",
"type": "hrt_dose",
"title": "Estradiol injection",
"details": "0.5ml intramuscular, left thigh",
"provider": "Dr. Smith",
"nextDate": "2026-03-10"
}
PATCH /api/health/medical/:id
Update medical entry.
Contacts Module (Encrypted)
GET /api/contacts
List contacts (decrypted).
Query params:
?domainId=<uuid>?contactType=client,agency?isActive=true?search=<name>
GET /api/contacts/:id
Get contact with interaction history.
POST /api/contacts
Create contact (encrypted on write).
Body:
{
"domainId": "uuid",
"name": "James",
"contactType": "client",
"phone": "+447700123456",
"email": "james@example.com",
"notes": "Regular, prefers weekday evenings",
"rating": 4,
"metadata": { "preferences": "..." }
}
PATCH /api/contacts/:id
Update contact.
DELETE /api/contacts/:id
Soft delete contact.
Projects Module
Sprints
GET /api/projects/sprints
Query params: ?domainId=<uuid>&status=active
GET /api/projects/sprints/:id
Get sprint with associated tasks.
POST /api/projects/sprints
Body:
{
"domainId": "uuid",
"name": "Sprint 3",
"startDate": "2026-02-24",
"endDate": "2026-03-07",
"goal": "Complete authentication module"
}
PATCH /api/projects/sprints/:id
Update sprint (including retrospective).
Content Calendar
GET /api/projects/content
Query params:
?domainId=<uuid>?status=idea,planned?platform=onlyfans?startDate=&endDate=
POST /api/projects/content
Body:
{
"domainId": "uuid",
"title": "Lingerie photo set",
"contentType": "photo_set",
"scheduledDate": "2026-03-01",
"platform": "onlyfans",
"status": "planned",
"notes": "Black set, studio lighting"
}
PATCH /api/projects/content/:id
Update content calendar item.
Analytics Module
GET /api/analytics/overview
Cross-domain summary.
Response:
{
"period": "month",
"tasks": {
"completed": 45,
"created": 52,
"overdue": 3,
"byDomain": [...]
},
"habits": {
"adherenceRate": 0.82,
"activeStreaks": 5,
"longestStreak": 30,
"byDomain": [...]
},
"goals": {
"active": 12,
"completed": 3,
"averageProgress": 45
},
"income": {
"total": "3420.00",
"currency": "GBP",
"byDomain": [...]
}
}
GET /api/analytics/domain/:id
Domain-specific analytics.
GET /api/analytics/trends
Time-series data for charts.
Query params:
?metric=income|tasks_completed|habit_adherence|time_tracked?period=day|week|month?domainId=<uuid>— Optional?startDate=&endDate=
Response:
{
"metric": "income",
"period": "week",
"dataPoints": [
{ "date": "2026-02-03", "value": 820 },
{ "date": "2026-02-10", "value": 1050 },
{ "date": "2026-02-17", "value": 750 },
{ "date": "2026-02-24", "value": 800 }
]
}
Chat Module
GET /api/chat/conversations
List conversations (newest first).
Query params:
?archived=false?isVoice=false
Response: Conversation[] with last message preview.
POST /api/chat/conversations
Start new conversation.
Body:
{
"title": "Planning tomorrow",
"model": "qwen3-8b"
}
GET /api/chat/conversations/:id
Get conversation with recent messages.
GET /api/chat/conversations/:id/messages
Get messages for a conversation (paginated, oldest first).
PATCH /api/chat/conversations/:id
Update conversation (rename, archive).
DELETE /api/chat/conversations/:id
Delete conversation and all messages.
WebSocket Events
Chat Gateway (namespace: /chat)
Client → Server:
| Event | Payload | Description |
|---|---|---|
conversation:join |
{ conversationId: string } |
Join conversation room |
conversation:leave |
{ conversationId: string } |
Leave conversation room |
send_message |
{ conversationId: string, content: string, model?: string } |
Send user message |
Server → Client:
| Event | Payload | Description |
|---|---|---|
message_start |
{ messageId: string, conversationId: string } |
New assistant message starting |
message_chunk |
{ messageId: string, chunk: string } |
Streaming text chunk |
message_end |
{ messageId: string, model: string, tokenCount: number, durationMs: number } |
Message complete |
message_error |
{ messageId: string, error: string } |
Generation error |
typing |
{ conversationId: string, isTyping: boolean } |
Typing indicator |
Voice Gateway (namespace: /voice)
Client → Server:
| Event | Payload | Description |
|---|---|---|
voice:start |
{ conversationId: string } |
Start voice session |
voice:audio |
{ audio: string (base64), segmentId: number } |
Audio data from mic |
voice:interrupt |
{} |
User speaking — interrupt TTS |
voice:stop |
{} |
End voice session |
Server → Client:
| Event | Payload | Description |
|---|---|---|
voice:transcription |
{ text: string, segmentId: number, confidence: number } |
STT result |
voice:assistant_text |
{ text: string, messageId: string } |
Assistant response text |
voice:audio_chunk |
Buffer (binary) |
TTS audio chunk (WAV) |
voice:audio_end |
{ messageId: string } |
TTS playback complete |
voice:error |
{ error: string } |
Voice processing error |
voice:status |
{ state: 'listening' | 'processing' | 'speaking' | 'idle' } |
Voice state change |