life-docs/api.md
2026-03-20 09:32:20 -07:00

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.

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