Internal
Public Access
1171 lines
23 KiB
Markdown
1171 lines
23 KiB
Markdown
# KeepItGoing REST API Documentation
|
|
|
|
Complete API documentation for integrating applications with KeepItGoing Server.
|
|
|
|
**API Version:** 1.0
|
|
**Base URL:** `https://yourdomain.com/api`
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
- [Authentication](#authentication)
|
|
- [Rate Limiting](#rate-limiting)
|
|
- [Error Handling](#error-handling)
|
|
- [User Management](#user-management)
|
|
- [Task Management](#task-management)
|
|
- [Tags](#tags)
|
|
- [Time Tracking](#time-tracking)
|
|
- [Task Sharing](#task-sharing)
|
|
- [Sync (Offline-First)](#sync-offline-first)
|
|
- [Notifications](#notifications)
|
|
- [Common Patterns](#common-patterns)
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
### JWT Token Authentication
|
|
|
|
All authenticated endpoints require the `Authorization` header:
|
|
|
|
```
|
|
Authorization: Bearer {access_token}
|
|
```
|
|
|
|
### Obtain Access Token
|
|
|
|
**POST** `/api/users/token/`
|
|
|
|
Authenticates user and returns JWT tokens. User must have verified email and admin approval.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"username": "user@example.com",
|
|
"password": "password123"
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
}
|
|
```
|
|
|
|
**Rate Limit:** 10 requests/hour
|
|
|
|
### Refresh Access Token
|
|
|
|
**POST** `/api/users/token/refresh/`
|
|
|
|
Refreshes an expired access token using a refresh token.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"refresh": "{refresh_token}"
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"access": "new_access_token_here"
|
|
}
|
|
```
|
|
|
|
**Token Lifetimes:**
|
|
- Access token: 60 minutes
|
|
- Refresh token: 7 days
|
|
- Refresh tokens rotate on each use (old token blacklisted)
|
|
|
|
---
|
|
|
|
## Rate Limiting
|
|
|
|
All endpoints are rate-limited per user:
|
|
|
|
| User Type | Limit | Scope |
|
|
|-----------|-------|-------|
|
|
| Anonymous | 100/hour | General API access |
|
|
| Authenticated | 1000/hour | General API access |
|
|
| Login | 10/hour | Token endpoint |
|
|
| Registration | 5/hour | Registration endpoint |
|
|
| Sync | 100/hour | Sync endpoint |
|
|
|
|
**Rate Limit Headers:**
|
|
```
|
|
X-RateLimit-Limit: 1000
|
|
X-RateLimit-Remaining: 999
|
|
X-RateLimit-Reset: 1234567890
|
|
```
|
|
|
|
**Rate Limit Exceeded (429):**
|
|
```json
|
|
{
|
|
"detail": "Request was throttled. Expected available in 120 seconds."
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### HTTP Status Codes
|
|
|
|
| Code | Meaning |
|
|
|------|---------|
|
|
| 200 | Successful request |
|
|
| 201 | Resource created |
|
|
| 204 | Successful deletion (no content) |
|
|
| 400 | Invalid request data |
|
|
| 401 | Authentication required or invalid |
|
|
| 403 | Forbidden (insufficient permissions) |
|
|
| 404 | Resource not found |
|
|
| 429 | Rate limit exceeded |
|
|
| 500 | Server error |
|
|
|
|
### Error Response Format
|
|
|
|
```json
|
|
{
|
|
"detail": "Error message",
|
|
"field_name": ["Field-specific error message"]
|
|
}
|
|
```
|
|
|
|
**Examples:**
|
|
|
|
**Validation Error (400):**
|
|
```json
|
|
{
|
|
"email": ["Enter a valid email address."],
|
|
"password": ["This field is required."]
|
|
}
|
|
```
|
|
|
|
**Authentication Error (401):**
|
|
```json
|
|
{
|
|
"detail": "Given token not valid for any token type"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## User Management
|
|
|
|
### Register New User
|
|
|
|
**POST** `/api/users/register/` (Public)
|
|
|
|
Creates a new user account and sends a verification email when self-registration is enabled.
|
|
|
|
**Availability:**
|
|
- Controlled by `ALLOW_SELF_REGISTRATION`
|
|
- Disabled by default
|
|
- Returns `403 Forbidden` when self-registration is off
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"email": "user@example.com",
|
|
"username": "username",
|
|
"password": "SecurePassword123!",
|
|
"password_confirm": "SecurePassword123!",
|
|
"first_name": "John",
|
|
"last_name": "Doe",
|
|
"timezone": "America/New_York"
|
|
}
|
|
```
|
|
|
|
**Response (201 Created):**
|
|
```json
|
|
{
|
|
"message": "Registration successful! Please check your email to verify your account.",
|
|
"email": "user@example.com",
|
|
"email_verified": false,
|
|
"is_approved": false
|
|
}
|
|
```
|
|
|
|
**Rate Limit:** 5 requests/hour
|
|
|
|
### Verify Email
|
|
|
|
**POST** `/api/users/verify-email/` (Public)
|
|
|
|
Verifies user email with token from verification email.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"token": "uuid-token-from-email"
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"message": "Email verified successfully.",
|
|
"email_verified": true,
|
|
"is_approved": false
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
- Tokens expire after 24 hours (configurable)
|
|
- Tokens are single-use
|
|
- Admin approval still required before login
|
|
|
|
### Resend Verification Email
|
|
|
|
**POST** `/api/users/resend-verification/` (Public)
|
|
|
|
Resends verification email if original was lost.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"email": "user@example.com"
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"message": "Verification email sent."
|
|
}
|
|
```
|
|
|
|
### Get User Profile
|
|
|
|
**GET** `/api/users/profile/` (Authenticated)
|
|
|
|
Returns current user's profile information.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "uuid-string",
|
|
"email": "user@example.com",
|
|
"username": "username",
|
|
"first_name": "John",
|
|
"last_name": "Doe",
|
|
"timezone": "America/New_York",
|
|
"default_reminder_minutes": 30,
|
|
"email_notifications": true,
|
|
"push_notifications": true,
|
|
"created_at": "2024-01-15T10:30:00Z",
|
|
"updated_at": "2024-01-20T14:45:00Z"
|
|
}
|
|
```
|
|
|
|
### Update User Profile
|
|
|
|
**PATCH** `/api/users/profile/` (Authenticated)
|
|
|
|
Updates current user's profile. All fields optional.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"first_name": "John",
|
|
"last_name": "Doe",
|
|
"timezone": "America/Los_Angeles",
|
|
"default_reminder_minutes": 15,
|
|
"email_notifications": true,
|
|
"push_notifications": false
|
|
}
|
|
```
|
|
|
|
**Response:** Updated user object
|
|
|
|
### Change Password
|
|
|
|
**POST** `/api/users/change-password/` (Authenticated)
|
|
|
|
Changes user's password.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"old_password": "CurrentPassword123!",
|
|
"new_password": "NewPassword123!"
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"message": "Password changed successfully."
|
|
}
|
|
```
|
|
|
|
### Device Token Management
|
|
|
|
#### Register Device
|
|
|
|
**POST** `/api/users/devices/` (Authenticated)
|
|
|
|
Registers device for push notifications.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"platform": "android",
|
|
"token": "firebase_device_token",
|
|
"device_name": "John's Phone"
|
|
}
|
|
```
|
|
|
|
**Platform Options:** `android`, `web`, `desktop`
|
|
|
|
**Response (201 Created):**
|
|
```json
|
|
{
|
|
"id": "uuid-string",
|
|
"platform": "android",
|
|
"token": "firebase_device_token",
|
|
"device_name": "John's Phone",
|
|
"is_active": true,
|
|
"created_at": "2024-01-15T10:30:00Z"
|
|
}
|
|
```
|
|
|
|
#### List Devices
|
|
|
|
**GET** `/api/users/devices/` (Authenticated)
|
|
|
|
Returns all registered devices for current user.
|
|
|
|
#### Delete Device
|
|
|
|
**DELETE** `/api/users/devices/{device_id}/` (Authenticated)
|
|
|
|
Removes device token.
|
|
|
|
**Response (204 No Content)**
|
|
|
|
---
|
|
|
|
## Task Management
|
|
|
|
### List Tasks
|
|
|
|
**GET** `/api/tasks/` (Authenticated)
|
|
|
|
Returns paginated list of tasks owned by or shared with user.
|
|
|
|
**Query Parameters:**
|
|
- `status` - Filter: `pending`, `in_progress`, `completed`, `cancelled`
|
|
- `priority` - Filter: `low`, `medium`, `high`, `urgent`
|
|
- `parent` - Filter by parent task ID (shows only subtasks)
|
|
- `search` - Search in title and description
|
|
- `ordering` - Sort by: `due_date`, `-due_date`, `priority`, `created_at`, `sort_order`
|
|
- `page` - Page number (default: 1)
|
|
|
|
**Example:**
|
|
```
|
|
GET /api/tasks/?status=pending&priority=high&ordering=-due_date
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"count": 45,
|
|
"next": "http://domain/api/tasks/?page=2",
|
|
"previous": null,
|
|
"results": [
|
|
{
|
|
"id": "uuid-string",
|
|
"title": "Complete project proposal",
|
|
"status": "in_progress",
|
|
"priority": "high",
|
|
"due_date": "2024-02-15",
|
|
"due_time": "17:00:00",
|
|
"tags": [...],
|
|
"subtask_count": 2,
|
|
"is_overdue": false,
|
|
"sync_id": "uuid-string",
|
|
"updated_at": "2024-01-20T14:00:00Z"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Create Task
|
|
|
|
**POST** `/api/tasks/` (Authenticated)
|
|
|
|
Creates a new task.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"title": "Complete project proposal",
|
|
"description": "Detailed description",
|
|
"status": "pending",
|
|
"priority": "high",
|
|
"due_date": "2024-02-15",
|
|
"due_time": "17:00:00",
|
|
"reminder_at": "2024-02-15T16:00:00Z",
|
|
"recurrence": "none",
|
|
"recurrence_end_date": null,
|
|
"tag_ids": ["uuid-1", "uuid-2"],
|
|
"parent": null,
|
|
"sort_order": 0
|
|
}
|
|
```
|
|
|
|
**Field Reference:**
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| title | string | Yes | Task title (max 500 chars) |
|
|
| description | string | No | Detailed description |
|
|
| status | enum | No | `pending`, `in_progress`, `completed`, `cancelled` |
|
|
| priority | enum | No | `low`, `medium`, `high`, `urgent` |
|
|
| due_date | date | No | YYYY-MM-DD |
|
|
| due_time | time | No | HH:MM:SS |
|
|
| reminder_at | datetime | No | ISO 8601 format |
|
|
| recurrence | enum | No | `none`, `daily`, `weekly`, `biweekly`, `monthly`, `yearly`, `custom` |
|
|
| recurrence_rule | string | No | RRULE string for custom recurrence |
|
|
| recurrence_end_date | date | No | End date for recurring tasks |
|
|
| tag_ids | array | No | Array of tag UUIDs |
|
|
| parent | uuid | No | Parent task ID (for subtasks) |
|
|
| sort_order | integer | No | Display order |
|
|
|
|
**Response (201 Created):**
|
|
```json
|
|
{
|
|
"id": "uuid-string",
|
|
"sync_id": "uuid-string",
|
|
"title": "Complete project proposal",
|
|
"description": "Detailed description",
|
|
"status": "pending",
|
|
"priority": "high",
|
|
"due_date": "2024-02-15",
|
|
"due_time": "17:00:00",
|
|
"reminder_at": "2024-02-15T16:00:00Z",
|
|
"completed_at": null,
|
|
"recurrence": "none",
|
|
"recurrence_rule": "",
|
|
"recurrence_end_date": null,
|
|
"tags": [...],
|
|
"subtasks": [],
|
|
"is_overdue": false,
|
|
"total_time_spent": 0,
|
|
"created_at": "2024-01-20T14:00:00Z",
|
|
"updated_at": "2024-01-20T14:00:00Z"
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
- When a recurring task is completed, the next instance is automatically created
|
|
- `completed_at` is automatically set when `status` changes to `completed`
|
|
- `is_overdue` is calculated based on user's timezone
|
|
|
|
### Get Task Details
|
|
|
|
**GET** `/api/tasks/{task_id}/` (Authenticated)
|
|
|
|
Returns full task details including subtasks.
|
|
|
|
**Response:** Task object with nested subtasks array
|
|
|
|
### Update Task
|
|
|
|
**PATCH** `/api/tasks/{task_id}/` (Authenticated)
|
|
|
|
Updates task fields. All fields optional.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"title": "Updated title",
|
|
"status": "completed",
|
|
"priority": "medium"
|
|
}
|
|
```
|
|
|
|
**Response:** Updated task object
|
|
|
|
### Delete Task
|
|
|
|
**DELETE** `/api/tasks/{task_id}/` (Authenticated)
|
|
|
|
Soft-deletes task (marks as deleted, preserves in database for sync).
|
|
|
|
**Response (204 No Content)**
|
|
|
|
---
|
|
|
|
## Tags
|
|
|
|
### List Tags
|
|
|
|
**GET** `/api/tasks/tags/` (Authenticated)
|
|
|
|
Returns all tags for current user.
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"id": "uuid-string",
|
|
"name": "work",
|
|
"description": "Work-related tasks",
|
|
"color": "#3B82F6",
|
|
"icon": "briefcase",
|
|
"sort_order": 0,
|
|
"is_archived": false,
|
|
"task_count": 12,
|
|
"sync_id": "uuid-string",
|
|
"created_at": "2024-01-10T10:00:00Z",
|
|
"updated_at": "2024-01-20T14:00:00Z"
|
|
}
|
|
]
|
|
```
|
|
|
|
### Create Tag
|
|
|
|
**POST** `/api/tasks/tags/` (Authenticated)
|
|
|
|
Creates a new tag.
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"name": "work",
|
|
"description": "Work-related tasks",
|
|
"color": "#3B82F6",
|
|
"icon": "briefcase",
|
|
"sort_order": 0,
|
|
"is_archived": false
|
|
}
|
|
```
|
|
|
|
**Response (201 Created):** Tag object
|
|
|
|
### Update Tag
|
|
|
|
**PATCH** `/api/tasks/tags/{tag_id}/` (Authenticated)
|
|
|
|
Updates tag fields.
|
|
|
|
### Delete Tag
|
|
|
|
**DELETE** `/api/tasks/tags/{tag_id}/` (Authenticated)
|
|
|
|
Soft-deletes tag.
|
|
|
|
**Response (204 No Content)**
|
|
|
|
---
|
|
|
|
## Time Tracking
|
|
|
|
### List Time Entries
|
|
|
|
**GET** `/api/tasks/time-entries/` (Authenticated)
|
|
|
|
Returns time entries for user's tasks.
|
|
|
|
**Query Parameters:**
|
|
- `task` - Filter by task UUID
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"id": "uuid-string",
|
|
"task": "task-uuid",
|
|
"started_at": "2024-01-20T09:00:00Z",
|
|
"ended_at": "2024-01-20T10:30:00Z",
|
|
"duration_seconds": 5400,
|
|
"notes": "Worked on section 2",
|
|
"is_running": false,
|
|
"sync_id": "uuid-string"
|
|
}
|
|
]
|
|
```
|
|
|
|
### Create Time Entry
|
|
|
|
**POST** `/api/tasks/time-entries/` (Authenticated)
|
|
|
|
Creates a time entry (manual entry or closed timer).
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"task": "task-uuid",
|
|
"started_at": "2024-01-20T09:00:00Z",
|
|
"ended_at": "2024-01-20T10:30:00Z",
|
|
"notes": "Worked on section 2"
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
- If `ended_at` is omitted, timer is considered running
|
|
- `duration_seconds` is automatically calculated from start/end times
|
|
|
|
**Response (201 Created):** Time entry object
|
|
|
|
### Start Timer (Convenience)
|
|
|
|
**POST** `/api/tasks/{task_id}/start-timer/` (Authenticated)
|
|
|
|
Starts a timer for specified task. Only one timer can run at a time.
|
|
|
|
**Request:** Empty body
|
|
|
|
**Response (201 Created):**
|
|
```json
|
|
{
|
|
"id": "uuid-string",
|
|
"task": "task-uuid",
|
|
"started_at": "2024-01-20T14:30:00Z",
|
|
"ended_at": null,
|
|
"is_running": true,
|
|
"sync_id": "uuid-string"
|
|
}
|
|
```
|
|
|
|
**Error (400):**
|
|
```json
|
|
{
|
|
"error": "You already have a running timer. Stop it first."
|
|
}
|
|
```
|
|
|
|
### Stop Timer
|
|
|
|
**POST** `/api/tasks/time-entries/{entry_id}/stop/` (Authenticated)
|
|
|
|
Stops a running timer.
|
|
|
|
**Request:** Empty body
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "uuid-string",
|
|
"task": "task-uuid",
|
|
"started_at": "2024-01-20T14:30:00Z",
|
|
"ended_at": "2024-01-20T14:45:00Z",
|
|
"duration_seconds": 900,
|
|
"is_running": false
|
|
}
|
|
```
|
|
|
|
### Update Time Entry
|
|
|
|
**PATCH** `/api/tasks/time-entries/{entry_id}/` (Authenticated)
|
|
|
|
Updates time entry fields.
|
|
|
|
### Delete Time Entry
|
|
|
|
**DELETE** `/api/tasks/time-entries/{entry_id}/` (Authenticated)
|
|
|
|
Soft-deletes time entry.
|
|
|
|
**Response (204 No Content)**
|
|
|
|
---
|
|
|
|
## Task Sharing
|
|
|
|
### List Shares
|
|
|
|
**GET** `/api/tasks/shares/` (Authenticated)
|
|
|
|
Returns shares where user is owner or recipient.
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"id": "uuid-string",
|
|
"task": "task-uuid",
|
|
"tag": null,
|
|
"shared_with_email": "colleague@example.com",
|
|
"permission": "viewer",
|
|
"sync_id": "uuid-string",
|
|
"created_at": "2024-01-20T14:00:00Z"
|
|
}
|
|
]
|
|
```
|
|
|
|
### Create Share
|
|
|
|
**POST** `/api/tasks/shares/` (Authenticated)
|
|
|
|
Shares a task or tag with another user.
|
|
|
|
**Request (share task):**
|
|
```json
|
|
{
|
|
"task": "task-uuid",
|
|
"shared_with_email": "colleague@example.com",
|
|
"permission": "viewer"
|
|
}
|
|
```
|
|
|
|
**Request (share tag):**
|
|
```json
|
|
{
|
|
"tag": "tag-uuid",
|
|
"shared_with_email": "colleague@example.com",
|
|
"permission": "editor"
|
|
}
|
|
```
|
|
|
|
**Permission Levels:**
|
|
- `viewer` - Read-only access
|
|
- `editor` - Can modify (not yet fully implemented)
|
|
|
|
**Validation:**
|
|
- Cannot share both task and tag in one request
|
|
- Cannot share with yourself
|
|
- Recipient email must exist in system
|
|
- Must own the task/tag being shared
|
|
|
|
**Response (201 Created):** Share object
|
|
|
|
### Delete Share
|
|
|
|
**DELETE** `/api/tasks/shares/{share_id}/` (Authenticated)
|
|
|
|
Removes a share. Owner only.
|
|
|
|
**Response (204 No Content)**
|
|
|
|
---
|
|
|
|
## Sync (Offline-First)
|
|
|
|
The sync system enables offline-first mobile/desktop clients through bidirectional synchronization.
|
|
|
|
### Main Sync Endpoint
|
|
|
|
**POST** `/api/sync/` (Authenticated)
|
|
|
|
Performs bidirectional sync: accepts client changes, returns server changes.
|
|
|
|
**Rate Limit:** 100 requests/hour
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"device_id": "unique-device-identifier",
|
|
"last_sync_token": "previous-token-or-null",
|
|
"changes": {
|
|
"tasks": [
|
|
{
|
|
"sync_id": "uuid-string",
|
|
"title": "Task title",
|
|
"status": "completed",
|
|
"priority": "high",
|
|
"due_date": "2024-02-15",
|
|
"due_time": "17:00:00",
|
|
"description": "Description",
|
|
"tag_sync_ids": ["tag-sync-id-1"],
|
|
"parent_sync_id": null,
|
|
"sort_order": 0,
|
|
"is_deleted": false
|
|
}
|
|
],
|
|
"tags": [
|
|
{
|
|
"sync_id": "uuid-string",
|
|
"name": "work",
|
|
"description": "Work tasks",
|
|
"color": "#3B82F6",
|
|
"icon": "briefcase",
|
|
"is_deleted": false
|
|
}
|
|
],
|
|
"time_entries": [
|
|
{
|
|
"sync_id": "uuid-string",
|
|
"task_sync_id": "task-sync-id",
|
|
"started_at": "2024-01-20T09:00:00Z",
|
|
"ended_at": "2024-01-20T10:30:00Z",
|
|
"notes": "Work notes",
|
|
"is_deleted": false
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"sync_token": "new-uuid-token",
|
|
"server_time": "2024-01-20T14:30:00Z",
|
|
"server_changes": {
|
|
"tasks": [...],
|
|
"tags": [...],
|
|
"time_entries": [...]
|
|
},
|
|
"conflicts": [
|
|
{
|
|
"id": "conflict-uuid",
|
|
"entity_type": "task",
|
|
"entity_id": "task-uuid",
|
|
"local_data": {...},
|
|
"server_data": {...},
|
|
"status": "pending"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Sync Process:**
|
|
1. Client submits changes with `device_id` and optional `last_sync_token`
|
|
2. Server processes client changes and detects conflicts
|
|
3. Server returns new `sync_token`, server changes since last sync, and any conflicts
|
|
4. Client applies server changes and handles conflicts
|
|
5. Next sync uses returned `sync_token` as `last_sync_token`
|
|
|
|
**Full vs Incremental Sync:**
|
|
- **Full Sync:** If `last_sync_token` is null/invalid, server returns all data
|
|
- **Incremental Sync:** If `last_sync_token` is valid, server returns only changes since that sync
|
|
|
|
**Key Sync Concepts:**
|
|
- Uses `sync_id` (separate from database `id`) for cross-device consistency
|
|
- Includes `is_deleted` flag to propagate deletions
|
|
- Soft-delete architecture maintains data for sync
|
|
- Timestamp-based conflict detection
|
|
|
|
### Get Sync Conflicts
|
|
|
|
**GET** `/api/sync/conflicts/` (Authenticated)
|
|
|
|
Lists pending conflicts requiring resolution.
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"id": "conflict-uuid",
|
|
"entity_type": "task",
|
|
"entity_id": "task-uuid",
|
|
"local_data": {
|
|
"title": "Client version",
|
|
"status": "completed"
|
|
},
|
|
"server_data": {
|
|
"title": "Server version",
|
|
"status": "in_progress"
|
|
},
|
|
"status": "pending",
|
|
"created_at": "2024-01-20T14:30:00Z"
|
|
}
|
|
]
|
|
```
|
|
|
|
### Resolve Sync Conflict
|
|
|
|
**POST** `/api/sync/conflicts/{conflict_id}/resolve/` (Authenticated)
|
|
|
|
Resolves a conflict using one of three strategies.
|
|
|
|
**Request (use local/client version):**
|
|
```json
|
|
{
|
|
"resolution": "local"
|
|
}
|
|
```
|
|
|
|
**Request (use server version):**
|
|
```json
|
|
{
|
|
"resolution": "server"
|
|
}
|
|
```
|
|
|
|
**Request (use merged version):**
|
|
```json
|
|
{
|
|
"resolution": "merged",
|
|
"merged_data": {
|
|
"title": "Merged title",
|
|
"status": "completed"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"status": "resolved"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Notifications
|
|
|
|
### List Notifications
|
|
|
|
**GET** `/api/notifications/` (Authenticated)
|
|
|
|
Returns notifications for current user.
|
|
|
|
**Query Parameters:**
|
|
- `is_read` - Filter: `true` or `false`
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"id": "notification-uuid",
|
|
"notification_type": "reminder",
|
|
"title": "Task reminder",
|
|
"message": "Complete project proposal is due soon",
|
|
"task": "task-uuid",
|
|
"task_title": "Complete project proposal",
|
|
"is_read": false,
|
|
"read_at": null,
|
|
"created_at": "2024-01-20T14:00:00Z"
|
|
}
|
|
]
|
|
```
|
|
|
|
**Notification Types:**
|
|
- `reminder` - Task reminder
|
|
- `due_soon` - Task due soon
|
|
- `overdue` - Task overdue
|
|
- `shared` - Task/tag shared with user
|
|
- `daily_email` - Daily email digest sent
|
|
|
|
### Mark Notification as Read
|
|
|
|
**POST** `/api/notifications/{notification_id}/read/` (Authenticated)
|
|
|
|
Marks single notification as read.
|
|
|
|
**Response:** Updated notification object with `is_read: true`
|
|
|
|
### Mark All as Read
|
|
|
|
**POST** `/api/notifications/read-all/` (Authenticated)
|
|
|
|
Marks all unread notifications as read.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"status": "All notifications marked as read"
|
|
}
|
|
```
|
|
|
|
### Get Unread Count
|
|
|
|
**GET** `/api/notifications/unread-count/` (Authenticated)
|
|
|
|
Returns count of unread notifications.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"unread_count": 5
|
|
}
|
|
```
|
|
|
|
### Delete Notification
|
|
|
|
**DELETE** `/api/notifications/{notification_id}/` (Authenticated)
|
|
|
|
Deletes notification.
|
|
|
|
**Response (204 No Content)**
|
|
|
|
---
|
|
|
|
## Common Patterns
|
|
|
|
### Pagination
|
|
|
|
List endpoints return paginated results (50 items per page):
|
|
|
|
```json
|
|
{
|
|
"count": 100,
|
|
"next": "https://domain/api/tasks/?page=2",
|
|
"previous": null,
|
|
"results": [...]
|
|
}
|
|
```
|
|
|
|
Use `page` query parameter to navigate: `?page=2`
|
|
|
|
### Filtering & Search
|
|
|
|
**Available Filters:**
|
|
- Tasks: `status`, `priority`, `parent`
|
|
- Notifications: `is_read`
|
|
- Time Entries: `task`
|
|
|
|
**Search:**
|
|
- Tasks: Searches `title` and `description` fields
|
|
- Use `search` query parameter: `?search=project`
|
|
|
|
**Ordering:**
|
|
- Use `ordering` parameter with field name
|
|
- Prefix with `-` for descending: `?ordering=-due_date`
|
|
- Available fields vary by endpoint
|
|
|
|
### Timezone Handling
|
|
|
|
**User Timezone:**
|
|
- Stored as IANA timezone string (e.g., "America/New_York")
|
|
- Used for due date calculations and email timing
|
|
|
|
**API Timestamps:**
|
|
- All timestamps in ISO 8601 format with UTC: `2024-01-20T19:00:00Z`
|
|
- Clients should convert to user's timezone for display
|
|
|
|
### Recurring Tasks
|
|
|
|
**Recurrence Types:**
|
|
- `none` - No recurrence
|
|
- `daily` - Every day
|
|
- `weekly` - Every week (same day)
|
|
- `biweekly` - Every 2 weeks
|
|
- `monthly` - Every month (same date)
|
|
- `yearly` - Every year (same date)
|
|
- `custom` - Custom RRULE pattern
|
|
|
|
**Behavior:**
|
|
- When recurring task marked as completed, next instance automatically created
|
|
- New instance has `status: pending` and calculated `due_date`
|
|
- Recurrence stops at `recurrence_end_date` if specified
|
|
|
|
**Custom RRULE Example:**
|
|
```json
|
|
{
|
|
"recurrence": "custom",
|
|
"recurrence_rule": "FREQ=WEEKLY;BYDAY=MO,WE,FR",
|
|
"recurrence_end_date": "2024-12-31"
|
|
}
|
|
```
|
|
|
|
### Complete Workflow Example
|
|
|
|
**1. Register:** `ALLOW_SELF_REGISTRATION=True` only
|
|
```bash
|
|
POST /api/users/register/
|
|
{
|
|
"email": "user@example.com",
|
|
"password": "SecurePass123!",
|
|
"password_confirm": "SecurePass123!",
|
|
"timezone": "America/New_York"
|
|
}
|
|
```
|
|
|
|
**If self-registration is disabled:** create the user through Django admin or another internal provisioning flow, then continue with verification/approval as needed.
|
|
|
|
**2. Verify Email:**
|
|
```bash
|
|
POST /api/users/verify-email/
|
|
{
|
|
"token": "token-from-email"
|
|
}
|
|
```
|
|
|
|
**3. Wait for Admin Approval** (admin sets `is_approved = true`)
|
|
|
|
**4. Login:**
|
|
```bash
|
|
POST /api/users/token/
|
|
{
|
|
"username": "user@example.com",
|
|
"password": "SecurePass123!"
|
|
}
|
|
```
|
|
|
|
**5. Create Task:**
|
|
```bash
|
|
POST /api/tasks/
|
|
Authorization: Bearer {access_token}
|
|
{
|
|
"title": "Complete project",
|
|
"priority": "high",
|
|
"due_date": "2024-02-15"
|
|
}
|
|
```
|
|
|
|
**6. Start Timer:**
|
|
```bash
|
|
POST /api/tasks/{task_id}/start-timer/
|
|
Authorization: Bearer {access_token}
|
|
```
|
|
|
|
**7. Stop Timer:**
|
|
```bash
|
|
POST /api/tasks/time-entries/{entry_id}/stop/
|
|
Authorization: Bearer {access_token}
|
|
```
|
|
|
|
**8. Sync (Mobile App):**
|
|
```bash
|
|
POST /api/sync/
|
|
Authorization: Bearer {access_token}
|
|
{
|
|
"device_id": "device-123",
|
|
"last_sync_token": null,
|
|
"changes": { ... }
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Additional Information
|
|
|
|
### Security
|
|
|
|
- JWT authentication with Bearer tokens
|
|
- Access tokens expire after 60 minutes
|
|
- Refresh tokens last 7 days and rotate on use
|
|
- Email verification required
|
|
- Admin approval required for new accounts
|
|
- Users can only access their own data (except shared items)
|
|
|
|
### Data Limits
|
|
|
|
- Max file upload: 5 MB
|
|
- Max request body: 5 MB
|
|
- Task title: 500 characters max
|
|
- Page size: 50 items
|
|
|
|
### CORS
|
|
|
|
Cross-Origin Resource Sharing (CORS) is configured. Contact admin for allowed origins.
|
|
|
|
### API Versioning
|
|
|
|
Current version: 1.0 (no version prefix in URLs)
|
|
|
|
---
|
|
|
|
## Support
|
|
|
|
For API support or questions:
|
|
- GitHub Issues: Create an issue in the repository
|
|
- Email: keith@firebugit.com
|
|
|
|
---
|
|
|
|
**Built by Firebug IT**
|
|
**Documentation Version: 1.0**
|
|
**Last Updated: 2025-01-10**
|