TidyQuest/API.md
root a7cd81fe81 Initial release: TidyQuest v0.1.0
TidyQuest is a self-hosted web application that gamifies household chores
using RPG mechanics. Complete tasks, earn coins, unlock achievements, and
compete with your family on the leaderboard.

Features:
- 🎯 Health-based task tracking with visual decay over time
- 💰 Coin system with customizable rewards
- 🔥 Daily/weekly streak tracking
- 🏆 Family leaderboard (week/month/quarter/year)
- 🎖️ Automatic achievement unlocking
- 📅 Calendar view for upcoming tasks
- 🌍 Multilingual support (EN/FR/DE/ES)
- 📱 Optional Telegram notifications
- 🏖️ Vacation mode (pause task decay)
- 🐳 Docker deployment (single container)

Tech Stack:
- Frontend: React 19 + Vite + TypeScript
- Backend: Node.js + Express + TypeScript
- Database: SQLite3 with WAL mode
- Auth: JWT + bcrypt
- Deployment: Docker Compose

Includes:
- 8 room types with 60+ predefined tasks
- 10 preset rewards
- 12 achievement types
- Complete API documentation
- Security best practices guide
- Comprehensive README with examples

License: GNU Affero General Public License v3.0 (AGPL-3.0)
This ensures TidyQuest remains free and open-source forever, preventing
proprietary SaaS forks. Any modified hosted version must share source code.
2026-02-17 18:40:09 +01:00

1053 lines
16 KiB
Markdown

# 🌐 TidyQuest API Documentation
Complete reference for all TidyQuest REST API endpoints.
**Base URL**: `http://localhost:3020/api`
**Authentication**: All endpoints (except `/auth/*`) require a JWT token in the `Authorization` header:
```http
Authorization: Bearer <your-jwt-token>
```
---
## 📑 Table of Contents
- [Authentication](#authentication)
- [Rooms](#rooms)
- [Tasks](#tasks)
- [Dashboard](#dashboard)
- [Leaderboard](#leaderboard)
- [History](#history)
- [Users](#users)
- [Rewards](#rewards)
- [Achievements](#achievements)
- [Data Export/Import](#data-exportimport)
- [Error Responses](#error-responses)
---
## 🔐 Authentication
### Register
Create a new user account.
**Endpoint**: `POST /api/auth/register`
**Request Body**:
```json
{
"username": "john",
"password": "securepass123",
"displayName": "John Doe",
"avatarColor": "#F97316",
"language": "en"
}
```
**Response** (201):
```json
{
"user": {
"id": 1,
"username": "john",
"displayName": "John Doe",
"role": "admin",
"avatarColor": "#F97316",
"avatarType": "letter",
"coins": 0,
"currentStreak": 0,
"language": "en",
"createdAt": "2026-02-17T10:30:00.000Z"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```
**cURL Example**:
```bash
curl -X POST http://localhost:3020/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"username": "john",
"password": "securepass123",
"displayName": "John Doe",
"avatarColor": "#F97316",
"language": "en"
}'
```
---
### Login
Authenticate and receive a JWT token.
**Endpoint**: `POST /api/auth/login`
**Request Body**:
```json
{
"username": "john",
"password": "securepass123"
}
```
**Response** (200):
```json
{
"user": { ... },
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```
**cURL Example**:
```bash
curl -X POST http://localhost:3020/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "john", "password": "securepass123"}'
```
---
### Get Current User
Get authenticated user's profile.
**Endpoint**: `GET /api/auth/me`
**Headers**: `Authorization: Bearer <token>`
**Response** (200):
```json
{
"id": 1,
"username": "john",
"displayName": "John Doe",
"role": "admin",
"avatarColor": "#F97316",
"avatarType": "letter",
"avatarPreset": null,
"avatarPhotoUrl": null,
"coins": 150,
"currentStreak": 7,
"goalCoins": 500,
"goalStartAt": "2026-02-01T00:00:00.000Z",
"goalEndAt": "2026-02-28T23:59:59.000Z",
"isVacationMode": false,
"language": "en",
"createdAt": "2026-01-15T08:00:00.000Z"
}
```
**cURL Example**:
```bash
curl -X GET http://localhost:3020/api/auth/me \
-H "Authorization: Bearer <your-token>"
```
---
## 🏠 Rooms
### List All Rooms
**Endpoint**: `GET /api/rooms`
**Response** (200):
```json
[
{
"id": 1,
"name": "Kitchen",
"roomType": "kitchen",
"color": "#FFE4C4",
"accentColor": "#FFA500",
"photoUrl": null,
"sortOrder": 1,
"health": 75,
"createdAt": "2026-02-10T12:00:00.000Z"
}
]
```
**cURL Example**:
```bash
curl -X GET http://localhost:3020/api/rooms \
-H "Authorization: Bearer <your-token>"
```
---
### Get Room by ID
**Endpoint**: `GET /api/rooms/:id`
**Response** (200):
```json
{
"id": 1,
"name": "Kitchen",
"roomType": "kitchen",
"color": "#FFE4C4",
"accentColor": "#FFA500",
"photoUrl": null,
"sortOrder": 1,
"createdAt": "2026-02-10T12:00:00.000Z"
}
```
---
### Create Room
**Endpoint**: `POST /api/rooms` (Admin only)
**Request Body**:
```json
{
"name": "Master Bedroom",
"roomType": "bedroom",
"color": "#E6F3FF",
"accentColor": "#4A90E2"
}
```
**Response** (201):
```json
{
"id": 5,
"name": "Master Bedroom",
"roomType": "bedroom",
...
}
```
**cURL Example**:
```bash
curl -X POST http://localhost:3020/api/rooms \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Master Bedroom",
"roomType": "bedroom",
"color": "#E6F3FF",
"accentColor": "#4A90E2"
}'
```
---
### Update Room
**Endpoint**: `PUT /api/rooms/:id`
**Request Body**:
```json
{
"name": "Main Kitchen",
"sortOrder": 2
}
```
**Response** (200):
```json
{
"id": 1,
"name": "Main Kitchen",
"sortOrder": 2,
...
}
```
---
### Delete Room
**Endpoint**: `DELETE /api/rooms/:id` (Admin only)
**Response** (200):
```json
{
"success": true
}
```
---
### Get Default Tasks for Room Type
**Endpoint**: `GET /api/rooms/defaults/:roomType`
Available room types: `kitchen`, `bedroom`, `bathroom`, `living`, `office`, `garage`, `laundry`, `garden`
**Response** (200):
```json
[
{
"name": "Wash Dishes",
"frequencyDays": 1,
"effort": 2,
"translationKey": "kitchen.wash_dishes",
"iconKey": "wash_dishes"
},
{
"name": "Clean Fridge",
"frequencyDays": 14,
"effort": 3,
"translationKey": "kitchen.clean_fridge",
"iconKey": "fridge"
}
]
```
---
## ✅ Tasks
### List Tasks for a Room
**Endpoint**: `GET /api/rooms/:roomId/tasks`
**Response** (200):
```json
[
{
"id": 1,
"roomId": 1,
"name": "Wash Dishes",
"notes": "Use dishwasher for big loads",
"frequencyDays": 1,
"effort": 2,
"health": 85,
"lastCompletedAt": "2026-02-17T08:00:00.000Z",
"translationKey": "kitchen.wash_dishes",
"iconKey": "wash_dishes",
"createdAt": "2026-02-10T12:00:00.000Z"
}
]
```
**cURL Example**:
```bash
curl -X GET http://localhost:3020/api/rooms/1/tasks \
-H "Authorization: Bearer <your-token>"
```
---
### Create Task
**Endpoint**: `POST /api/rooms/:roomId/tasks` (Admin only)
**Request Body**:
```json
{
"name": "Deep Clean Oven",
"notes": "Use oven cleaner spray",
"frequencyDays": 30,
"effort": 5,
"translationKey": "kitchen.deep_clean_oven",
"iconKey": "oven"
}
```
**Response** (201):
```json
{
"id": 15,
"roomId": 1,
"name": "Deep Clean Oven",
...
}
```
---
### Update Task
**Endpoint**: `PUT /api/tasks/:id` (Admin only)
**Request Body**:
```json
{
"frequencyDays": 7,
"effort": 3
}
```
**Response** (200):
```json
{
"id": 15,
"frequencyDays": 7,
"effort": 3,
...
}
```
---
### Delete Task
**Endpoint**: `DELETE /api/tasks/:id` (Admin only)
**Response** (200):
```json
{
"success": true
}
```
---
### Complete Task
Mark a task as completed, earn coins, update streak.
**Endpoint**: `POST /api/tasks/:id/complete`
**Response** (200):
```json
{
"completion": {
"id": 123,
"taskId": 1,
"userId": 1,
"completedAt": "2026-02-17T14:30:00.000Z",
"coinsEarned": 10
},
"user": {
"id": 1,
"coins": 160,
"currentStreak": 8,
"lastActiveDate": "2026-02-17",
...
},
"newAchievements": [
{
"id": "streak_7",
"name": "On Fire",
"description": "Complete tasks for 7 days in a row",
"icon": "🔥"
}
]
}
```
**cURL Example**:
```bash
curl -X POST http://localhost:3020/api/tasks/1/complete \
-H "Authorization: Bearer <your-token>"
```
---
## 📊 Dashboard
Get aggregated view: house health, today's quests, upcoming tasks, recent activity, goals, pending rewards.
**Endpoint**: `GET /api/dashboard`
**Response** (200):
```json
{
"houseHealth": 78,
"todayQuests": {
"completed": 5,
"total": 12
},
"nextTasks": [
{
"id": 3,
"name": "Vacuum Living Room",
"roomName": "Living Room",
"health": 45,
"effort": 3,
"dueIn": "2 hours"
}
],
"recentActivity": [
{
"id": 120,
"taskName": "Wash Dishes",
"userName": "John Doe",
"completedAt": "2026-02-17T14:00:00.000Z",
"coinsEarned": 10
}
],
"myGoals": [
{
"id": 1,
"title": "February Goal",
"goalCoins": 500,
"currentCoins": 160,
"progress": 32,
"startAt": "2026-02-01T00:00:00.000Z",
"endAt": "2026-02-28T23:59:59.000Z"
}
],
"pendingRewardRequests": 2
}
```
**cURL Example**:
```bash
curl -X GET http://localhost:3020/api/dashboard \
-H "Authorization: Bearer <your-token>"
```
---
## 🏆 Leaderboard
Get family rankings by period.
**Endpoint**: `GET /api/leaderboard?period=<period>`
**Query Params**:
- `period`: `week`, `month`, `quarter`, or `year`
**Response** (200):
```json
{
"period": "week",
"leaderboard": [
{
"userId": 1,
"displayName": "John Doe",
"avatarColor": "#F97316",
"avatarType": "letter",
"avatarPreset": null,
"avatarPhotoUrl": null,
"coinsEarned": 150,
"tasksCompleted": 25,
"rank": 1
},
{
"userId": 2,
"displayName": "Jane Doe",
"coinsEarned": 120,
"tasksCompleted": 20,
"rank": 2
}
]
}
```
**cURL Example**:
```bash
curl -X GET "http://localhost:3020/api/leaderboard?period=week" \
-H "Authorization: Bearer <your-token>"
```
---
## 📜 History
Get paginated activity log of all task completions.
**Endpoint**: `GET /api/history?limit=<limit>&offset=<offset>`
**Query Params**:
- `limit`: Number of records (default: 20)
- `offset`: Skip N records (default: 0)
**Response** (200):
```json
{
"history": [
{
"id": 120,
"taskName": "Wash Dishes",
"roomName": "Kitchen",
"userName": "John Doe",
"completedAt": "2026-02-17T14:00:00.000Z",
"coinsEarned": 10
}
],
"total": 500,
"limit": 20,
"offset": 0
}
```
**cURL Example**:
```bash
curl -X GET "http://localhost:3020/api/history?limit=50&offset=100" \
-H "Authorization: Bearer <your-token>"
```
---
## 👥 Users
### List All Users
**Endpoint**: `GET /api/users` (Admin only)
**Response** (200):
```json
[
{
"id": 1,
"username": "john",
"displayName": "John Doe",
"role": "admin",
"coins": 160,
"currentStreak": 8,
...
}
]
```
---
### Create User
**Endpoint**: `POST /api/users` (Admin only)
**Request Body**:
```json
{
"username": "alice",
"password": "alicepass",
"displayName": "Alice",
"role": "child",
"avatarColor": "#FFB6C1",
"language": "en"
}
```
**Response** (201):
```json
{
"id": 3,
"username": "alice",
"displayName": "Alice",
"role": "child",
...
}
```
---
### Update User Profile
**Endpoint**: `PUT /api/users/:id/profile`
**Request Body**:
```json
{
"displayName": "John Smith",
"avatarType": "character",
"avatarPreset": "cat",
"language": "fr"
}
```
**Response** (200):
```json
{
"id": 1,
"displayName": "John Smith",
"avatarType": "character",
"avatarPreset": "cat",
...
}
```
---
### Upload Avatar Photo
**Endpoint**: `POST /api/users/:id/avatar-upload`
**Headers**:
- `Content-Type: multipart/form-data`
**Request Body**:
```
FormData:
- avatar: <file> (image/*, max 2MB)
```
**Response** (200):
```json
{
"id": 1,
"avatarType": "photo",
"avatarPhotoUrl": "/api/avatars/user-1-1708176000000.jpg",
...
}
```
**cURL Example**:
```bash
curl -X POST http://localhost:3020/api/users/1/avatar-upload \
-H "Authorization: Bearer <your-token>" \
-F "avatar=@/path/to/photo.jpg"
```
---
### Update User Settings
**Endpoint**: `PUT /api/users/:id/settings` (Admin only, self only)
**Request Body**:
```json
{
"isVacationMode": true,
"language": "es"
}
```
**Response** (200):
```json
{
"id": 1,
"isVacationMode": true,
"vacationStartDate": "2026-02-17T15:00:00.000Z",
"language": "es",
...
}
```
---
### Get Coins Configuration
**Endpoint**: `GET /api/users/coins-config`
**Response** (200):
```json
{
"coinsByEffort": {
"1": 5,
"2": 10,
"3": 15,
"4": 20,
"5": 25
}
}
```
---
### Update Coins Configuration
**Endpoint**: `PUT /api/users/coins-config` (Admin only)
**Request Body**:
```json
{
"coinsByEffort": {
"1": 10,
"2": 20,
"3": 30,
"4": 40,
"5": 50
}
}
```
Or reset to defaults:
```json
{
"useDefault": true
}
```
**Response** (200):
```json
{
"coinsByEffort": { ... }
}
```
---
## 🎁 Rewards
### List Rewards
**Endpoint**: `GET /api/rewards`
**Response** (200):
```json
{
"rewards": [
{
"id": 1,
"title": "Movie Night Pick",
"description": "Choose the family movie",
"costCoins": 40,
"isActive": true,
"isPreset": true,
"createdBy": null,
"createdAt": "2026-02-10T00:00:00.000Z"
}
],
"myRedemptions": [
{
"id": 10,
"rewardId": 1,
"rewardTitle": "Movie Night Pick",
"costCoins": 40,
"status": "requested",
"redeemedAt": "2026-02-17T18:00:00.000Z"
}
]
}
```
---
### Create Reward
**Endpoint**: `POST /api/rewards` (Admin only)
**Request Body**:
```json
{
"title": "Extra Dessert",
"description": "Second serving at dinner",
"costCoins": 25
}
```
**Response** (201):
```json
{
"id": 8,
"title": "Extra Dessert",
"costCoins": 25,
"isActive": true,
"createdBy": 1,
...
}
```
---
### Redeem Reward
**Endpoint**: `POST /api/rewards/:id/redeem`
**Response** (200):
```json
{
"redemption": {
"id": 12,
"rewardId": 1,
"userId": 2,
"costCoins": 40,
"status": "requested",
"redeemedAt": "2026-02-17T19:00:00.000Z"
},
"newCoinsBalance": 110
}
```
**Errors**:
- `400`: Insufficient coins
- `404`: Reward not found or inactive
---
### Approve/Reject Redemption
**Endpoint**: `PUT /api/rewards/redemptions/:id` (Admin only)
**Request Body**:
```json
{
"status": "approved"
}
```
Status options: `approved`, `rejected`
**Response** (200):
```json
{
"id": 12,
"status": "approved",
...
}
```
**Note**: If rejected, coins are refunded to user.
---
### Cancel Redemption
**Endpoint**: `POST /api/rewards/redemptions/:id/cancel` (Self only)
**Response** (200):
```json
{
"id": 12,
"status": "cancelled",
...
}
```
**Note**: Coins are refunded.
---
## 🎖️ Achievements
### Get Achievements
**Endpoint**: `GET /api/achievements`
**Response** (200):
```json
{
"myAchievements": [
{
"id": "tasks_10",
"name": "Cleaner",
"description": "Complete 10 tasks",
"icon": "✨",
"unlockedAt": "2026-02-15T10:00:00.000Z"
}
],
"allAchievements": [
{
"id": "tasks_1",
"name": "Starter",
"description": "Complete your first task",
"icon": "🌟",
"unlocked": true
},
{
"id": "streak_30",
"name": "Legend",
"description": "Maintain a 30-day streak",
"icon": "👑",
"unlocked": false
}
]
}
```
**Note**: `allAchievements` only returned for admins.
**cURL Example**:
```bash
curl -X GET http://localhost:3020/api/achievements \
-H "Authorization: Bearer <your-token>"
```
---
## 📤 Data Export/Import
### Export Data
**Endpoint**: `GET /api/data/export` (Admin only)
**Response** (200):
```json
{
"version": "1.0",
"exportedAt": "2026-02-17T20:00:00.000Z",
"users": [...],
"rooms": [...],
"tasks": [...],
"taskCompletions": [...],
"rewards": [...],
"rewardRedemptions": [...],
"appSettings": [...]
}
```
**cURL Example**:
```bash
curl -X GET http://localhost:3020/api/data/export \
-H "Authorization: Bearer <admin-token>" \
-o backup.json
```
---
### Import Data
**Endpoint**: `POST /api/data/import` (Admin only)
**Request Body**: Full JSON export (same structure as export)
**Response** (200):
```json
{
"success": true,
"imported": {
"users": 3,
"rooms": 5,
"tasks": 42,
"taskCompletions": 250,
"rewards": 10,
"rewardRedemptions": 15
}
}
```
**Warning**: This will CLEAR and REPLACE all data.
**cURL Example**:
```bash
curl -X POST http://localhost:3020/api/data/import \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d @backup.json
```
---
## ❌ Error Responses
All errors follow this format:
```json
{
"error": "Descriptive error message"
}
```
**Common HTTP Status Codes**:
| Code | Meaning | Example |
|------|---------|---------|
| 400 | Bad Request | Missing required field |
| 401 | Unauthorized | Invalid or missing JWT token |
| 403 | Forbidden | Not admin or not owner |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Username already exists |
| 500 | Server Error | Database or internal error |
---
## 🔔 Rate Limiting
Currently **no rate limiting** is implemented. For production deployments, consider using a reverse proxy (Nginx, Caddy) with rate limiting configured.
---
## 📝 Notes
- All timestamps are in ISO 8601 format (UTC)
- JWT tokens expire after **30 days**
- Pagination uses `limit` and `offset` query parameters
- File uploads (avatars) limited to **2MB**
- Accepted image types: `image/*` (MIME type check)
---
**Need help?** Open an issue on [GitHub Issues](https://github.com/mellow-fox/TidyQuest/issues)