# 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 ``` ## 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.