Version: 1.0
Base URL: https://familytogether-api.up.railway.app (production)
Base URL: http://localhost:5000 (development)
Date: February 10, 2026
All API endpoints (except /api/auth/register and /api/auth/login) require authentication via JWT Bearer token in the Authorization header.
Header Format:
Authorization: Bearer <jwt_token>
Token Structure:
{
"sub": "user-uuid",
"email": "user@example.com",
"family_id": "family-uuid",
"role": "parent",
"iat": 1707559200,
"exp": 1707562800
}Token Expiry: 60 minutes (configurable)
Endpoint: POST /api/auth/register
Authentication: None (public)
Description: Create a new user account and family
Request Body:
{
"email": "john.smith@example.com",
"password": "SecurePassword123!",
"family_name": "The Smith Family",
"user_name": "John Smith"
}Response (201 Created):
{
"success": true,
"data": {
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.smith@example.com",
"created_at": "2026-02-10T10:00:00.000Z"
},
"family": {
"id": "650e8400-e29b-41d4-a716-446655440000",
"name": "The Smith Family",
"created_at": "2026-02-10T10:00:00.000Z"
},
"member": {
"id": "750e8400-e29b-41d4-a716-446655440000",
"name": "John Smith",
"role": "parent",
"points_balance": 0
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}Validation Rules:
- Email: Valid email format, unique
- Password: Minimum 8 characters, at least 1 uppercase, 1 lowercase, 1 number
- Family Name: 3-255 characters
- User Name: 2-255 characters
Errors:
400 Bad Request: Invalid input data409 Conflict: Email already exists
Endpoint: POST /api/auth/login
Authentication: None (public)
Description: Authenticate user and receive JWT token
Request Body:
{
"email": "john.smith@example.com",
"password": "SecurePassword123!"
}Response (200 OK):
{
"success": true,
"data": {
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john.smith@example.com"
},
"family": {
"id": "650e8400-e29b-41d4-a716-446655440000",
"name": "The Smith Family"
},
"member": {
"id": "750e8400-e29b-41d4-a716-446655440000",
"name": "John Smith",
"role": "parent",
"points_balance": 0
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2026-02-10T11:00:00.000Z"
}
}Errors:
400 Bad Request: Missing email or password401 Unauthorized: Invalid credentials
Endpoint: POST /api/auth/refresh
Authentication: Required (valid JWT)
Description: Get a new JWT token before current one expires
Request Body: None
Response (200 OK):
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2026-02-10T12:00:00.000Z"
}
}Errors:
401 Unauthorized: Invalid or expired token
Endpoint: POST /api/auth/logout
Authentication: Required
Description: Invalidate current JWT token
Request Body: None
Response (200 OK):
{
"success": true,
"message": "Successfully logged out"
}Endpoint: GET /api/tasks
Authentication: Required
Description: Get all tasks for the authenticated user's family
Query Parameters:
assigned_to(optional): Filter by member IDstatus(optional): Filter by status (pending, in_progress, awaiting_approval, approved, rejected)include_deleted(optional): Include deleted tasks (default: false)
Example: GET /api/tasks?assigned_to=750e8400-e29b-41d4-a716-446655440000&status=pending
Response (200 OK):
{
"success": true,
"data": {
"tasks": [
{
"id": "850e8400-e29b-41d4-a716-446655440000",
"family_id": "650e8400-e29b-41d4-a716-446655440000",
"title": "Clean your room",
"description": "Vacuum, dust, and organize closet",
"points": 20,
"assigned_to": "750e8400-e29b-41d4-a716-446655440000",
"assigned_to_name": "John Smith",
"created_by": "750e8400-e29b-41d4-a716-446655440000",
"created_by_name": "John Smith",
"status": "pending",
"is_recurring": false,
"recurrence_pattern": null,
"due_date": "2026-02-11T18:00:00.000Z",
"completed_at": null,
"completed_by": null,
"completion_note": null,
"completion_photo_url": null,
"reviewed_at": null,
"reviewed_by": null,
"review_note": null,
"bonus_points": 0,
"created_at": "2026-02-10T10:00:00.000Z",
"updated_at": "2026-02-10T10:00:00.000Z",
"last_modified": 1707559200000
}
],
"total": 1
}
}Endpoint: POST /api/tasks
Authentication: Required
Description: Create a new task
Request Body:
{
"title": "Wash the dishes",
"description": "Load and run the dishwasher",
"points": 15,
"assigned_to": "750e8400-e29b-41d4-a716-446655440000",
"due_date": "2026-02-11T20:00:00.000Z",
"is_recurring": false,
"recurrence_pattern": null,
"recurrence_day": null
}Response (201 Created):
{
"success": true,
"data": {
"task": {
"id": "950e8400-e29b-41d4-a716-446655440000",
"family_id": "650e8400-e29b-41d4-a716-446655440000",
"title": "Wash the dishes",
"description": "Load and run the dishwasher",
"points": 15,
"assigned_to": "750e8400-e29b-41d4-a716-446655440000",
"created_by": "750e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"due_date": "2026-02-11T20:00:00.000Z",
"created_at": "2026-02-10T10:05:00.000Z",
"last_modified": 1707559500000,
"change_id": "a50e8400-e29b-41d4-a716-446655440000",
"sync_version": 1
}
}
}Validation Rules:
- Title: 1-500 characters, required
- Description: 0-5000 characters, optional
- Points: Integer >= 0, required
- Assigned To: Valid member ID in family, required
- Due Date: ISO 8601 datetime, optional
- Recurrence Pattern: 'daily' | 'weekly' | 'monthly' | null
Errors:
400 Bad Request: Invalid input403 Forbidden: Not authorized to create tasks for this family404 Not Found: Assigned member not found
Endpoint: PUT /api/tasks/{taskId}
Authentication: Required
Description: Update an existing task
Request Body (all fields optional):
{
"title": "Clean your room thoroughly",
"description": "Vacuum, dust, organize, and make bed",
"points": 25,
"status": "in_progress",
"due_date": "2026-02-11T19:00:00.000Z"
}Response (200 OK):
{
"success": true,
"data": {
"task": {
"id": "850e8400-e29b-41d4-a716-446655440000",
"title": "Clean your room thoroughly",
"description": "Vacuum, dust, organize, and make bed",
"points": 25,
"status": "in_progress",
"due_date": "2026-02-11T19:00:00.000Z",
"updated_at": "2026-02-10T10:10:00.000Z",
"last_modified": 1707559800000,
"change_id": "b50e8400-e29b-41d4-a716-446655440000",
"sync_version": 2
}
}
}Errors:
400 Bad Request: Invalid input403 Forbidden: Not authorized to update this task404 Not Found: Task not found409 Conflict: Task has been modified by another user (sync conflict)
Endpoint: DELETE /api/tasks/{taskId}
Authentication: Required
Description: Soft delete a task (marks as deleted)
Response (200 OK):
{
"success": true,
"message": "Task deleted successfully",
"data": {
"task_id": "850e8400-e29b-41d4-a716-446655440000",
"deleted_at": "2026-02-10T10:15:00.000Z"
}
}Errors:
403 Forbidden: Not authorized to delete this task404 Not Found: Task not found
Endpoint: POST /api/tasks/{taskId}/complete
Authentication: Required
Description: Mark task as complete (user action)
Request Body:
{
"completion_note": "All done! Took about 30 minutes.",
"completion_photo_url": "https://storage.supabase.co/..."
}Response (200 OK):
{
"success": true,
"data": {
"task": {
"id": "850e8400-e29b-41d4-a716-446655440000",
"status": "awaiting_approval",
"completed_at": "2026-02-10T10:20:00.000Z",
"completed_by": "750e8400-e29b-41d4-a716-446655440000",
"completion_note": "All done! Took about 30 minutes.",
"completion_photo_url": "https://storage.supabase.co/...",
"last_modified": 1707560000000
}
}
}Errors:
400 Bad Request: Task already completed or not assigned to user403 Forbidden: Not authorized404 Not Found: Task not found
Endpoint: POST /api/tasks/{taskId}/approve
Authentication: Required (parent/admin only)
Description: Approve completed task and award points
Request Body:
{
"review_note": "Great job!",
"bonus_points": 5,
"bonus_reason": "Extra effort on organizing closet"
}Response (200 OK):
{
"success": true,
"data": {
"task": {
"id": "850e8400-e29b-41d4-a716-446655440000",
"status": "approved",
"reviewed_at": "2026-02-10T10:25:00.000Z",
"reviewed_by": "750e8400-e29b-41d4-a716-446655440000",
"review_note": "Great job!",
"bonus_points": 5,
"bonus_reason": "Extra effort on organizing closet",
"last_modified": 1707560300000
},
"points_awarded": 25,
"new_balance": 25
}
}Side Effects:
- Creates point transaction for task completion
- Creates bonus point transaction if bonus_points > 0
- Updates member's points_balance
Errors:
400 Bad Request: Task not awaiting approval403 Forbidden: Not authorized (must be parent/admin)404 Not Found: Task not found
Endpoint: POST /api/tasks/{taskId}/reject
Authentication: Required (parent/admin only)
Description: Reject completed task, no points awarded
Request Body:
{
"review_note": "Room still messy. Please try again."
}Response (200 OK):
{
"success": true,
"data": {
"task": {
"id": "850e8400-e29b-41d4-a716-446655440000",
"status": "rejected",
"reviewed_at": "2026-02-10T10:25:00.000Z",
"reviewed_by": "750e8400-e29b-41d4-a716-446655440000",
"review_note": "Room still messy. Please try again.",
"last_modified": 1707560300000
}
}
}Side Effects:
- Task status changes to 'rejected'
- Task can be reattempted by user
Errors:
400 Bad Request: Task not awaiting approval403 Forbidden: Not authorized (must be parent/admin)404 Not Found: Task not found
Endpoint: GET /api/points
Authentication: Required
Description: Get current point balance for authenticated member
Query Parameters:
member_id(optional): Get balance for specific member (parent/admin only)
Response (200 OK):
{
"success": true,
"data": {
"member_id": "750e8400-e29b-41d4-a716-446655440000",
"member_name": "John Smith",
"points_balance": 125,
"last_updated": "2026-02-10T10:25:00.000Z"
}
}Endpoint: GET /api/points/history
Authentication: Required
Description: Get point transaction history
Query Parameters:
member_id(optional): Filter by memberlimit(optional): Number of results (default: 50, max: 200)offset(optional): Pagination offset (default: 0)
Example: GET /api/points/history?member_id=750e8400-e29b-41d4-a716-446655440000&limit=20
Response (200 OK):
{
"success": true,
"data": {
"transactions": [
{
"id": "a50e8400-e29b-41d4-a716-446655440000",
"member_id": "750e8400-e29b-41d4-a716-446655440000",
"member_name": "John Smith",
"amount": 20,
"transaction_type": "task_completion",
"description": "Completed task: Clean your room",
"reference_id": "850e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-02-10T10:25:00.000Z"
},
{
"id": "b50e8400-e29b-41d4-a716-446655440000",
"member_id": "750e8400-e29b-41d4-a716-446655440000",
"member_name": "John Smith",
"amount": 5,
"transaction_type": "bonus",
"description": "Bonus: Extra effort on organizing closet",
"reference_id": "850e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-02-10T10:25:00.000Z"
}
],
"total": 2,
"limit": 20,
"offset": 0
}
}Endpoint: POST /api/points/adjust
Authentication: Required (parent/admin only)
Description: Manually add or remove points
Request Body:
{
"member_id": "750e8400-e29b-41d4-a716-446655440000",
"amount": 10,
"description": "Extra credit for helping with groceries"
}Response (200 OK):
{
"success": true,
"data": {
"transaction": {
"id": "c50e8400-e29b-41d4-a716-446655440000",
"member_id": "750e8400-e29b-41d4-a716-446655440000",
"amount": 10,
"transaction_type": "manual_adjustment",
"description": "Extra credit for helping with groceries",
"created_by": "750e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-02-10T10:30:00.000Z"
},
"new_balance": 135
}
}Validation Rules:
- Amount: Integer (positive or negative), required
- Description: 1-500 characters, required
- Member ID: Valid member in family, required
Errors:
400 Bad Request: Invalid input or insufficient points for negative adjustment403 Forbidden: Not authorized (must be parent/admin)404 Not Found: Member not found
Endpoint: GET /api/rewards
Authentication: Required
Description: Get all rewards for family
Query Parameters:
is_active(optional): Filter by active status (default: true)
Response (200 OK):
{
"success": true,
"data": {
"rewards": [
{
"id": "d50e8400-e29b-41d4-a716-446655440000",
"family_id": "650e8400-e29b-41d4-a716-446655440000",
"title": "Extra screen time (30 min)",
"description": "Get 30 extra minutes of screen time",
"cost": 50,
"icon": "📺",
"is_active": true,
"requires_approval": true,
"created_by": "750e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-02-10T09:00:00.000Z",
"last_modified": 1707555600000
}
],
"total": 1
}
}Endpoint: POST /api/rewards
Authentication: Required (parent/admin only)
Description: Create a new reward
Request Body:
{
"title": "Movie night choice",
"description": "Get to pick the movie for family movie night",
"cost": 100,
"icon": "🎬",
"requires_approval": true
}Response (201 Created):
{
"success": true,
"data": {
"reward": {
"id": "e50e8400-e29b-41d4-a716-446655440000",
"family_id": "650e8400-e29b-41d4-a716-446655440000",
"title": "Movie night choice",
"description": "Get to pick the movie for family movie night",
"cost": 100,
"icon": "🎬",
"is_active": true,
"requires_approval": true,
"created_by": "750e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-02-10T10:35:00.000Z",
"last_modified": 1707560700000
}
}
}Validation Rules:
- Title: 1-255 characters, required
- Description: 0-1000 characters, optional
- Cost: Integer > 0, required
- Icon: Single emoji character, optional
- Requires Approval: Boolean, default true
Endpoint: PUT /api/rewards/{rewardId}
Authentication: Required (parent/admin only)
Description: Update an existing reward
Request Body (all fields optional):
{
"title": "Movie night choice (updated)",
"cost": 75,
"is_active": true
}Response (200 OK):
{
"success": true,
"data": {
"reward": {
"id": "e50e8400-e29b-41d4-a716-446655440000",
"title": "Movie night choice (updated)",
"cost": 75,
"is_active": true,
"updated_at": "2026-02-10T10:40:00.000Z",
"last_modified": 1707561000000
}
}
}Endpoint: DELETE /api/rewards/{rewardId}
Authentication: Required (parent/admin only)
Description: Soft delete a reward
Response (200 OK):
{
"success": true,
"message": "Reward deleted successfully",
"data": {
"reward_id": "e50e8400-e29b-41d4-a716-446655440000",
"deleted_at": "2026-02-10T10:45:00.000Z"
}
}Endpoint: POST /api/rewards/{rewardId}/redeem
Authentication: Required
Description: Redeem a reward using points
Request Body: None
Response (200 OK):
{
"success": true,
"data": {
"redemption": {
"id": "f50e8400-e29b-41d4-a716-446655440000",
"reward_id": "d50e8400-e29b-41d4-a716-446655440000",
"reward_title": "Extra screen time (30 min)",
"member_id": "750e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"points_spent": 50,
"redeemed_at": "2026-02-10T10:50:00.000Z"
},
"new_balance": 85
}
}Side Effects:
- Deducts points from member's balance
- Creates point transaction (negative amount)
- Creates reward redemption record
Errors:
400 Bad Request: Insufficient points403 Forbidden: Not authorized404 Not Found: Reward not found or inactive
Endpoint: POST /api/sync
Authentication: Required
Description: Synchronize local changes with server
Request Body:
{
"client_type": "spa",
"client_version": "1.0.0",
"last_sync_timestamp": 1707559200000,
"changes": [
{
"table_name": "tasks",
"operation": "update",
"record_id": "850e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "850e8400-e29b-41d4-a716-446655440000",
"status": "in_progress",
"last_modified": 1707559800000,
"change_id": "g50e8400-e29b-41d4-a716-446655440000",
"sync_version": 2
}
}
]
}Response (200 OK):
{
"success": true,
"data": {
"sync_timestamp": 1707561600000,
"accepted_changes": [
{
"record_id": "850e8400-e29b-41d4-a716-446655440000",
"status": "accepted"
}
],
"rejected_changes": [],
"server_changes": [
{
"table_name": "rewards",
"operation": "create",
"record_id": "h50e8400-e29b-41d4-a716-446655440000",
"data": {
"id": "h50e8400-e29b-41d4-a716-446655440000",
"family_id": "650e8400-e29b-41d4-a716-446655440000",
"title": "New reward",
"cost": 100,
"last_modified": 1707561000000,
"change_id": "i50e8400-e29b-41d4-a716-446655440000",
"sync_version": 1
}
}
],
"conflicts_resolved": 0
}
}Conflict Response Example:
{
"success": true,
"data": {
"sync_timestamp": 1707561600000,
"accepted_changes": [],
"rejected_changes": [
{
"record_id": "850e8400-e29b-41d4-a716-446655440000",
"status": "conflict",
"reason": "server version newer (Last-Write-Wins)",
"server_data": {
"id": "850e8400-e29b-41d4-a716-446655440000",
"status": "approved",
"last_modified": 1707561000000,
"change_id": "j50e8400-e29b-41d4-a716-446655440000",
"sync_version": 3
}
}
],
"server_changes": [],
"conflicts_resolved": 1
}
}Validation Rules:
- Changes: Array, max 100 items per sync
- Table Name: Must be valid table (families, members, tasks, etc.)
- Operation: 'create' | 'update' | 'delete'
- Record ID: Valid UUID
Errors:
400 Bad Request: Invalid sync payload401 Unauthorized: Invalid token413 Payload Too Large: More than 100 changes
Endpoint: GET /api/sync/status
Authentication: Required
Description: Get last sync information
Response (200 OK):
{
"success": true,
"data": {
"last_sync": "2026-02-10T10:50:00.000Z",
"last_sync_timestamp": 1707561600000,
"pending_changes": 0,
"sync_version": 15
}
}Endpoint: GET /api/family/members
Authentication: Required
Description: Get all members in family
Response (200 OK):
{
"success": true,
"data": {
"family": {
"id": "650e8400-e29b-41d4-a716-446655440000",
"name": "The Smith Family"
},
"members": [
{
"id": "750e8400-e29b-41d4-a716-446655440000",
"name": "John Smith",
"role": "parent",
"avatar": "👨",
"points_balance": 0,
"is_active": true
},
{
"id": "850e8400-e29b-41d4-a716-446655440000",
"name": "Jane Smith",
"role": "child",
"avatar": "👧",
"points_balance": 135,
"is_active": true,
"requires_approval": true,
"parent_id": "750e8400-e29b-41d4-a716-446655440000"
}
],
"total": 2
}
}Endpoint: POST /api/family/members
Authentication: Required (parent/admin only)
Description: Add a new family member (typically a child)
Request Body:
{
"name": "Bobby Smith",
"role": "child",
"avatar": "👦",
"requires_approval": true,
"parent_id": "750e8400-e29b-41d4-a716-446655440000"
}Response (201 Created):
{
"success": true,
"data": {
"member": {
"id": "950e8400-e29b-41d4-a716-446655440000",
"family_id": "650e8400-e29b-41d4-a716-446655440000",
"name": "Bobby Smith",
"role": "child",
"avatar": "👦",
"points_balance": 0,
"is_active": true,
"requires_approval": true,
"parent_id": "750e8400-e29b-41d4-a716-446655440000",
"created_at": "2026-02-10T11:00:00.000Z",
"last_modified": 1707562200000
}
}
}Validation Rules:
- Name: 2-255 characters, required
- Role: 'parent' | 'child' | 'admin', required
- Avatar: Single emoji, optional
- Requires Approval: Boolean, default true for children
Endpoint: DELETE /api/family/members/{memberId}
Authentication: Required (parent/admin only)
Description: Soft delete a family member
Response (200 OK):
{
"success": true,
"message": "Family member removed successfully",
"data": {
"member_id": "950e8400-e29b-41d4-a716-446655440000",
"deleted_at": "2026-02-10T11:05:00.000Z"
}
}Errors:
400 Bad Request: Cannot remove last parent403 Forbidden: Not authorized404 Not Found: Member not found
All API responses follow this structure:
{
"success": true|false,
"data": { ... }, // Present on success
"error": { ... }, // Present on error
"message": "...", // Optional message
"timestamp": "2026-02-10T10:00:00.000Z"
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{
"field": "email",
"message": "Email is required"
},
{
"field": "password",
"message": "Password must be at least 8 characters"
}
]
},
"timestamp": "2026-02-10T10:00:00.000Z"
}| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR |
400 | Input validation failed |
UNAUTHORIZED |
401 | Missing or invalid JWT token |
FORBIDDEN |
403 | User lacks permission for action |
NOT_FOUND |
404 | Resource not found |
CONFLICT |
409 | Resource conflict (e.g., email exists, sync conflict) |
RATE_LIMIT_EXCEEDED |
429 | Too many requests |
INTERNAL_ERROR |
500 | Server error |
- 200 OK: Successful GET, PUT, PATCH requests
- 201 Created: Successful POST requests creating resources
- 400 Bad Request: Invalid input data
- 401 Unauthorized: Missing or invalid authentication
- 403 Forbidden: Insufficient permissions
- 404 Not Found: Resource doesn't exist
- 409 Conflict: Resource conflict (email exists, sync conflict)
- 413 Payload Too Large: Request body too large
- 429 Too Many Requests: Rate limit exceeded
- 500 Internal Server Error: Server error
- 503 Service Unavailable: Server temporarily unavailable
For 5xx errors and 429 (rate limit):
- Wait 1 second, retry
- Wait 2 seconds, retry
- Wait 4 seconds, retry
- Give up, show error to user
For 409 (sync conflict):
- Apply server data to local storage
- Mark local change as synced
- Continue with next change
- General: 100 requests per minute per IP
- Auth endpoints: 5 requests per minute per IP
- Sync endpoint: 2 requests per minute per user
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1707559260
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please try again in 30 seconds.",
"retry_after": 30
},
"timestamp": "2026-02-10T10:00:00.000Z"
}Current version: v1
- Version included in base URL:
/api/v1/...(future) - Currently:
/api/...(no version, defaults to v1) - Breaking changes will increment major version (v2, v3)
- Non-breaking changes maintain version number
- 6 months notice before deprecation
- Old versions supported for 12 months after deprecation notice
- Deprecation warnings in response headers
1. POST /api/auth/register
→ Creates user, family, parent member
→ Returns JWT token
2. Store token in localStorage (SPA) or secure storage (WPF)
3. Use token for all subsequent requests
1. Child: POST /api/tasks/{taskId}/complete
→ Task status: pending → awaiting_approval
→ Local: Save to IndexedDB/SQLite
→ Sync: Add to sync queue
2. Background sync: POST /api/sync
→ Upload completed task to server
→ Download any new tasks
3. Parent: POST /api/tasks/{taskId}/approve
→ Task status: awaiting_approval → approved
→ Points added to child's balance
→ Point transaction created
4. Background sync: POST /api/sync
→ Child receives task approval
→ Child receives updated point balance
1. User goes offline
→ Local changes saved to IndexedDB/SQLite
→ Changes added to sync queue
2. User makes multiple changes while offline
→ All changes queued locally
→ UI shows "pending sync" indicator
3. User comes back online
→ Auto-sync triggered (or manual sync button)
→ POST /api/sync with all queued changes
4. Server processes changes
→ Resolves conflicts (Last-Write-Wins)
→ Returns accepted/rejected changes
5. Client applies server response
→ Update local storage with server data
→ Clear sync queue for accepted changes
→ Show notification to user
END OF API SPECIFICATION DOCUMENT