# API Specification

## Base URL
```
Development: http://localhost:3000/api
Production:  https://api.carryyourlive.com/api
```

## Authentication
All endpoints require authentication via Bearer token:
```
Authorization: Bearer <jwt_token>
```

## Endpoints

### Sync

#### Pull Changes
```
GET /sync
```

**Query Parameters**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| since | integer | Yes | Unix timestamp (ms) of last successful sync |

**Response** (200 OK)
```json
{
  "categories": [
    {
      "id": "cat_1",
      "name": "Work",
      "color": "#E53935",
      "order": 0,
      "createdAt": 1699000000000,
      "updatedAt": 1699000000000
    }
  ],
  "tasks": [
    {
      "id": "task_1",
      "title": "Finish project",
      "description": "Complete the mobile app",
      "categoryId": "cat_1",
      "priority": "high",
      "completed": false,
      "dueDate": 1699100000000,
      "dueTime": "17:00",
      "createdAt": 1699000000000,
      "updatedAt": 1699000000000
    }
  ],
  "subtasks": [
    {
      "id": "sub_1",
      "taskId": "task_1",
      "parentSubtaskId": null,
      "title": "Setup Expo project",
      "completed": true,
      "order": 0,
      "createdAt": 1699000000000,
      "updatedAt": 1699000000000
    }
  ],
  "repeatProfiles": [],
  "friendships": [],
  "deleted": [
    {
      "entity": "tasks",
      "id": "task_5",
      "updatedAt": 1699150000000
    }
  ],
  "timestamp": 1699150000000
}
```

`deleted` contains every row tombstoned (deleted) since `since` — one entry per entity type/id. Clients must remove the row locally (and any related rows: a task deletion implies its subtasks, a category deletion implies its tasks and their subtasks, a parent subtask deletion implies its children).

**Error Responses**
| Status | Code | Message |
|--------|------|---------|
| 400 | INVALID_TIMESTAMP | Invalid or missing `since` parameter |
| 401 | UNAUTHORIZED | Missing or invalid token |
| 500 | SERVER_ERROR | Internal server error |

---

#### Push Changes
```
POST /sync/push
```

**Request Body**
```json
{
  "changes": {
    "categories": [
      {
        "id": "cat_new",
        "name": "New Category",
        "color": "#1E88E5",
        "order": 6,
        "createdAt": 1699150000000,
        "updatedAt": 1699150000000
      }
    ],
    "tasks": [
      {
        "id": "task_new",
        "title": "New Task",
        "description": "Task description",
        "categoryId": "cat_1",
        "priority": "medium",
        "completed": false,
        "dueDate": 1699200000000,
        "dueTime": "10:00",
        "createdAt": 1699150000000,
        "updatedAt": 1699150000000
      }
    ],
    "subtasks": [
      {
        "id": "sub_new",
        "taskId": "task_new",
        "parentSubtaskId": null,
        "title": "Subtask 1",
        "completed": false,
        "order": 0,
        "createdAt": 1699150000000,
        "updatedAt": 1699150000000
      }
    ]
  },
  "deleted": [
    {
      "entity": "categories",
      "id": "cat_gone",
      "updatedAt": 1699150000000
    }
  ],
  "lastPulledAt": 1699100000000
}
```

`deleted` is a list of tombstones the client recorded locally since its last successful sync. Deletions are resolved by last-writer-wins: if the server row's `updatedAt` is newer than the tombstone's `updatedAt`, the deletion is rejected and reported as a `server_wins` conflict (with the server row as `serverVersion`) so the client re-pulls it. Accepted deletions remove the rows server-side; cascaded rows (subtasks of a deleted task, tasks/subtasks of a deleted category, children of a deleted parent subtask) are tombstoned automatically so every device removes them.

**Response** (200 OK)
```json
{
  "success": true,
  "timestamp": 1699150000000,
  "conflicts": [
    {
      "entity": "tasks",
      "id": "task_1",
      "serverVersion": { ... },
      "clientVersion": { ... },
      "resolution": "server_wins"
    }
  ]
}
```

**Error Responses**
| Status | Code | Message |
|--------|------|---------|
| 400 | INVALID_PAYLOAD | Malformed request body |
| 401 | UNAUTHORIZED | Missing or invalid token |
| 409 | CONFLICT | Unresolvable conflicts (if not using auto-resolution) |
| 500 | SERVER_ERROR | Internal server error |

---

### Categories

#### List Categories
```
GET /categories
```

**Response** (200 OK)
```json
{
  "categories": [
    {
      "id": "cat_1",
      "name": "Work",
      "color": "#E53935",
      "order": 0,
      "createdAt": 1699000000000,
      "updatedAt": 1699000000000
    }
  ]
}
```

#### Create Category
```
POST /categories
```

**Request Body**
```json
{
  "name": "New Category",
  "color": "#1E88E5",
  "order": 6
}
```

**Response** (201 Created)
```json
{
  "id": "cat_new",
  "name": "New Category",
  "color": "#1E88E5",
  "order": 6,
  "createdAt": 1699150000000,
  "updatedAt": 1699150000000
}
```

#### Update Category
```
PATCH /categories/{id}
```

**Request Body**
```json
{
  "name": "Updated Name",
  "color": "#FB8C00",
  "order": 1
}
```

**Response** (200 OK)
```json
{
  "id": "cat_1",
  "name": "Updated Name",
  "color": "#FB8C00",
  "order": 1,
  "createdAt": 1699000000000,
  "updatedAt": 1699150000000
}
```

#### Delete Category
```
DELETE /categories/{id}
```

**Response** (204 No Content)

---

### Tasks

#### List Tasks
```
GET /tasks
```

**Query Parameters**
| Parameter | Type | Description |
|-----------|------|-------------|
| categoryId | string | Filter by category |
| completed | boolean | Filter by completion status |
| dueBefore | integer | Due date before timestamp (ms) |
| dueAfter | integer | Due date after timestamp (ms) |
| priority | string | Filter by priority |
| sortBy | string | Sort field: dueDate, priority, createdAt, title |
| sortOrder | string | asc or desc |
| limit | integer | Pagination limit (default 50) |
| offset | integer | Pagination offset |

**Response** (200 OK)
```json
{
  "tasks": [
    {
      "id": "task_1",
      "title": "Finish project",
      "description": "Complete the mobile app",
      "categoryId": "cat_1",
      "priority": "high",
      "completed": false,
      "dueDate": 1699100000000,
      "dueTime": "17:00",
      "createdAt": 1699000000000,
      "updatedAt": 1699000000000,
      "subtasks": [
        {
          "id": "sub_1",
          "title": "Setup Expo project",
          "completed": true,
          "order": 0
        }
      ]
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}
```

#### Get Task
```
GET /tasks/{id}
```

**Response** (200 OK)
```json
{
  "id": "task_1",
  "title": "Finish project",
  "description": "Complete the mobile app",
  "categoryId": "cat_1",
  "priority": "high",
  "completed": false,
  "dueDate": 1699100000000,
  "dueTime": "17:00",
  "createdAt": 1699000000000,
  "updatedAt": 1699000000000,
  "subtasks": [...]
}
```

#### Create Task
```
POST /tasks
```

**Request Body**
```json
{
  "title": "New Task",
  "description": "Task description",
  "categoryId": "cat_1",
  "priority": "medium",
  "dueDate": 1699200000000,
  "dueTime": "10:00",
  "subtasks": [
    { "title": "Subtask 1" },
    { "title": "Subtask 2" }
  ]
}
```

**Response** (201 Created)
```json
{
  "id": "task_new",
  "title": "New Task",
  "description": "Task description",
  "categoryId": "cat_1",
  "priority": "medium",
  "completed": false,
  "dueDate": 1699200000000,
  "dueTime": "10:00",
  "createdAt": 1699150000000,
  "updatedAt": 1699150000000,
  "subtasks": [
    { "id": "sub_1", "title": "Subtask 1", "completed": false, "order": 0 },
    { "id": "sub_2", "title": "Subtask 2", "completed": false, "order": 1 }
  ]
}
```

#### Update Task
```
PATCH /tasks/{id}
```

**Request Body** (all fields optional)
```json
{
  "title": "Updated Title",
  "description": "Updated description",
  "categoryId": "cat_2",
  "priority": "high",
  "completed": true,
  "dueDate": 1699300000000,
  "dueTime": "12:00"
}
```

**Response** (200 OK)
```json
{
  "id": "task_1",
  "title": "Updated Title",
  "description": "Updated description",
  "categoryId": "cat_2",
  "priority": "high",
  "completed": true,
  "dueDate": 1699300000000,
  "dueTime": "12:00",
  "createdAt": 1699000000000,
  "updatedAt": 1699150000000,
  "subtasks": [...]
}
```

#### Delete Task
```
DELETE /tasks/{id}
```

**Response** (204 No Content)

#### Batch Update Tasks
```
POST /tasks/batch
```

**Request Body**
```json
{
  "operations": [
    { "type": "update", "id": "task_1", "data": { "completed": true } },
    { "type": "delete", "id": "task_2" },
    { "type": "create", "data": { "title": "New Task", "categoryId": "cat_1" } }
  ]
}
```

**Response** (200 OK)
```json
{
  "results": [
    { "id": "task_1", "success": true },
    { "id": "task_2", "success": true },
    { "id": "task_new", "success": true, "data": { ... } }
  ]
}
```

---

### Subtasks

#### List Subtasks
```
GET /tasks/{taskId}/subtasks
```

**Response** (200 OK)
```json
{
  "subtasks": [
    {
      "id": "sub_1",
      "taskId": "task_1",
      "title": "Subtask 1",
      "completed": false,
      "order": 0,
      "createdAt": 1699000000000,
      "updatedAt": 1699000000000
    }
  ]
}
```

#### Create Subtask
```
POST /tasks/{taskId}/subtasks
```

**Request Body**
```json
{
  "title": "New Subtask",
  "order": 2
}
```

**Response** (201 Created)
```json
{
  "id": "sub_new",
  "taskId": "task_1",
  "title": "New Subtask",
  "completed": false,
  "order": 2,
  "createdAt": 1699150000000,
  "updatedAt": 1699150000000
}
```

#### Update Subtask
```
PATCH /subtasks/{id}
```

**Request Body**
```json
{
  "title": "Updated Subtask",
  "completed": true,
  "order": 1
}
```

**Response** (200 OK)

#### Delete Subtask
```
DELETE /subtasks/{id}
```

**Response** (204 No Content)

---

### Users

#### Get Current User
```
GET /users/me
```

**Response** (200 OK)
```json
{
  "id": "user_1",
  "email": "user@example.com",
  "name": "John Doe",
  "settings": {
    "darkMode": false,
    "notifications": true,
    "reminderTime": "09:00",
    "defaultCategory": "cat_1",
    "sortBy": "dueDate",
    "sortOrder": "asc"
  },
  "createdAt": 1699000000000
}
```

#### Update Settings
```
PATCH /users/me/settings
```

**Request Body**
```json
{
  "darkMode": true,
  "notifications": false,
  "reminderTime": "08:00",
  "defaultCategory": "cat_2",
  "sortBy": "priority",
  "sortOrder": "desc"
}
```

**Response** (200 OK)

---

## Data Types

### Category
```typescript
interface Category {
  id: string;
  name: string;
  color: string;      // HEX format
  order: number;
  createdAt: number;  // Unix timestamp (ms)
  updatedAt: number;  // Unix timestamp (ms)
}
```

### Task
```typescript
interface Task {
  id: string;
  title: string;
  description: string;
  categoryId: string;
  priority: 'none' | 'low' | 'medium' | 'high' | 'critical';
  completed: boolean;
  dueDate: number;    // Unix timestamp (ms), 0 if not set
  dueTime: string;    // HH:MM format, empty if not set
  endTime: string;    // HH:MM format, empty if not set
  allDay: boolean;
  repeat: 'none' | 'daily' | 'weekly' | 'monthly' | 'custom';
  repeatInterval: number;
  repeatDays: string; // comma separated weekday numbers, e.g. "0,2,4"
  seriesId: string;   // repeating-series id, empty if not part of a series
  reminder: 'none' | 'at_time' | '15' | '30' | '60' | '120' | '1440';
  reminders: string;  // comma separated reminder values, empty if none
  assigneeId: string | null;
  completedAt: number | null; // Unix timestamp (ms) when completed
  createdAt: number;
  updatedAt: number;
  subtasks?: Subtask[];
}
```

### Subtask
```typescript
interface Subtask {
  id: string;
  taskId: string;
  parentSubtaskId: string | null; // id of the parent subtask, null for top-level
  title: string;
  description: string;
  priority: 'none' | 'low' | 'medium' | 'high' | 'critical';
  completed: boolean;
  dueDate: number;
  dueTime: string;
  endTime: string;
  allDay: boolean;
  repeat: 'none' | 'daily' | 'weekly' | 'monthly' | 'custom';
  repeatInterval: number;
  repeatDays: string;
  seriesId: string;
  reminder: 'none' | 'at_time' | '15' | '30' | '60' | '120' | '1440';
  reminders: string;
  assigneeId: string | null;
  order: number;
  createdAt: number;
  updatedAt: number;
}
```

### Tombstone
```typescript
interface Tombstone {
  entity: 'categories' | 'tasks' | 'subtasks' | 'repeatProfiles' | 'friendships';
  id: string;        // id of the deleted row
  updatedAt: number; // Unix timestamp (ms) of the deletion
}
```

### UserSettings
```typescript
interface UserSettings {
  darkMode: boolean;
  notifications: boolean;
  reminderTime: string;      // HH:MM
  defaultCategory: string;   // Category ID
  sortBy: 'dueDate' | 'priority' | 'title' | 'createdAt';
  sortOrder: 'asc' | 'desc';
}
```

## Error Format

All error responses follow this format:
```json
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human readable message",
    "details": {}
  }
}
```

Common error codes:
- `INVALID_PAYLOAD` - Request validation failed
- `UNAUTHORIZED` - Authentication required
- `FORBIDDEN` - Insufficient permissions
- `NOT_FOUND` - Resource not found
- `CONFLICT` - Version conflict
- `RATE_LIMITED` - Too many requests
- `SERVER_ERROR` - Internal server error

## Rate Limiting

- 100 requests/minute per user for sync endpoints
- 500 requests/minute per user for other endpoints
- Returns 429 with `Retry-After` header

## Webhooks (Optional)

Server can notify clients of remote changes:
```
POST /webhooks/sync
```

Payload:
```json
{
  "event": "data_changed",
  "entities": ["tasks", "categories"],
  "timestamp": 1699150000000
}
```

Client should trigger sync on receipt.