This commit is contained in:
+646
@@ -0,0 +1,646 @@
|
||||
# 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",
|
||||
"title": "Setup Expo project",
|
||||
"completed": true,
|
||||
"order": 0,
|
||||
"createdAt": 1699000000000,
|
||||
"updatedAt": 1699000000000
|
||||
}
|
||||
],
|
||||
"timestamp": 1699150000000
|
||||
}
|
||||
```
|
||||
|
||||
**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",
|
||||
"title": "Subtask 1",
|
||||
"completed": false,
|
||||
"order": 0,
|
||||
"createdAt": 1699150000000,
|
||||
"updatedAt": 1699150000000
|
||||
}
|
||||
]
|
||||
},
|
||||
"lastPulledAt": 1699100000000
|
||||
}
|
||||
```
|
||||
|
||||
**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
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
subtasks?: Subtask[];
|
||||
}
|
||||
```
|
||||
|
||||
### Subtask
|
||||
```typescript
|
||||
interface Subtask {
|
||||
id: string;
|
||||
taskId: string;
|
||||
title: string;
|
||||
completed: boolean;
|
||||
order: number;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
}
|
||||
```
|
||||
|
||||
### 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.
|
||||
Reference in New Issue
Block a user