Files
Todo-App-Backend/openapi/openapi.yaml
2026-05-20 16:45:40 +02:00

2187 lines
64 KiB
YAML

openapi: "3.0.3"
info:
title: Todo App API
description: |
RESTful API for the Todo App backend. Provides user management, todo
tracking with categories, projects, recurring tasks, activity logging,
a theme marketplace, and JWT/ApiKey authentication.
Base URL: `http://localhost:8080/api/v1`
version: "1.0.0"
contact:
name: Todo App Team
license:
name: MIT
servers:
- url: http://localhost:8080/api/v1
description: Local development server
paths:
# ──────────────────────────────────────────────────────────────────────────
# AUTHENTICATION
# ──────────────────────────────────────────────────────────────────────────
/auth/register:
post:
tags: [Authentication]
summary: Register a new user
description: >
Creates a new user account and returns the user data along with a
newly generated API key. Store the API key securely — it will not
be shown again.
operationId: registerUser
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email, password, name]
properties:
email:
type: string
format: email
description: Must be unique in the system
password:
type: string
format: password
minLength: 8
description: Plain-text password (hashed with bcrypt on storage)
name:
type: string
maxLength: 255
avatar_url:
type: string
format: uri
nullable: true
settings:
type: object
description: JSON-serialized user preferences
default: { theme: light }
properties:
theme:
type: string
example: dark
language:
type: string
example: en
example:
email: user@example.com
password: securepass123
name: John Doe
avatar_url: https://example.com/avatar.jpg
settings:
theme: dark
language: en
responses:
"201":
description: User registered successfully
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: User registered successfully
data:
type: object
properties:
user:
$ref: "#/components/schemas/User"
api_key:
type: string
description: Full API key (shown once only)
key_prefix:
type: string
example: todo_abc1
"422":
$ref: "#/components/responses/ValidationError"
"409":
description: Email already registered
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorBody"
/auth/login:
post:
tags: [Authentication]
summary: Login with email and password
description: >
Authenticates a user and returns an API key. If the user already has
an active API key, only the key prefix is returned (full key was
shown on first creation).
operationId: loginUser
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email, password]
properties:
email:
type: string
format: email
password:
type: string
format: password
example:
email: user@example.com
password: securepass123
responses:
"200":
description: Login successful
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: Login successful
data:
type: object
properties:
user:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
name:
type: string
api_key:
type: string
description: Full API key (if new key was created)
api_key_prefix:
type: string
description: Key prefix when using an existing key
"401":
description: Invalid email or password
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorBody"
"422":
$ref: "#/components/responses/ValidationError"
/auth/api-key:
post:
tags: [Authentication]
summary: Create an additional API key (legacy)
description: >
Creates a new API key for an app or integration. Requires email and
password for re-authentication.
operationId: createApiKeyAuth
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email, password]
properties:
email:
type: string
format: email
password:
type: string
format: password
minLength: 6
name:
type: string
description: User-friendly label for the key
scopes:
type: array
items:
type: string
enum: [read, write]
description: >
Permission scopes. If omitted, grants full access.
example: [read, write]
expires_at:
type: string
format: date-time
nullable: true
description: ISO-8601 expiration date
example:
email: user@example.com
password: securepass123
name: My App Key
scopes: [read, write]
responses:
"200":
description: API key created
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: API key created successfully
data:
$ref: "#/components/schemas/ApiKeyCreated"
"401":
description: Invalid credentials
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorBody"
/auth/jwt/register:
post:
tags: [Authentication]
summary: Register and receive JWT + API key
description: >
Registers a new user and returns both a JWT token (for bearer auth)
and an API key.
operationId: jwtRegister
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/requestBodies/UserRegistration"
responses:
"201":
description: Registration successful with JWT
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
example: User registered successfully
data:
type: object
properties:
user:
$ref: "#/components/schemas/User"
token:
type: string
description: JWT bearer token (valid 1 hour)
api_key:
type: string
description: Full API key value
/auth/jwt/login:
post:
tags: [Authentication]
summary: Login and receive JWT
description: >
Authenticates with email/password and returns a JWT bearer token.
operationId: jwtLogin
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email, password]
properties:
email:
type: string
format: email
password:
type: string
format: password
responses:
"200":
description: Login successful with JWT
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
type: object
properties:
user:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
name:
type: string
token:
type: string
description: JWT bearer token (valid 1 hour)
/auth/jwt/refresh:
post:
tags: [Authentication]
summary: Refresh JWT token
description: >
Obtains a new JWT token using a valid (not yet expired) bearer token.
operationId: jwtRefresh
security:
- bearerAuth: []
responses:
"200":
description: Token refreshed
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
type: object
properties:
token:
type: string
"401":
description: Invalid or expired token
# ──────────────────────────────────────────────────────────────────────────
# MARKETPLACE (public)
# ──────────────────────────────────────────────────────────────────────────
/marketplace/themes:
get:
tags: [Marketplace]
summary: List all published marketplace themes
description: Public endpoint — no authentication required.
operationId: listMarketplaceThemes
responses:
"200":
description: List of published themes
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
type: array
items:
$ref: "#/components/schemas/MarketplaceTheme"
/marketplace/themes/{id}:
get:
tags: [Marketplace]
summary: Get a single marketplace theme
operationId: getMarketplaceTheme
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Theme details
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/MarketplaceTheme"
"404":
$ref: "#/components/responses/NotFound"
# ──────────────────────────────────────────────────────────────────────────
# USER PROFILE
# ──────────────────────────────────────────────────────────────────────────
/user/profile:
get:
tags: [User]
summary: Get authenticated user's profile
description: Returns profile data for the user identified by the API key.
operationId: getUserProfile
security:
- apiKeyAuth: []
responses:
"200":
description: User profile
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/User"
"401":
$ref: "#/components/responses/Unauthorized"
put:
tags: [User]
summary: Update the authenticated user's profile
operationId: updateUserProfile
security:
- apiKeyAuth: []
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
maxLength: 255
avatar_url:
type: string
format: uri
nullable: true
settings:
type: object
description: User preferences (JSON)
responses:
"200":
description: Profile updated
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/User"
/user/api-keys:
get:
tags: [User]
summary: List all API keys for the authenticated user
description: >
Returns API key metadata (prefix, name, scopes, active status).
The full key hash is never exposed.
operationId: listApiKeys
security:
- apiKeyAuth: []
responses:
"200":
description: List of API keys
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
type: array
items:
$ref: "#/components/schemas/ApiKeyMetadata"
post:
tags: [User]
summary: Create a new API key
operationId: createApiKey
security:
- apiKeyAuth: []
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: User-friendly label
default: API Key
scopes:
type: array
items:
type: string
enum: [read, write]
default: [read, write]
expires_at:
type: string
format: date-time
nullable: true
responses:
"200":
description: API key created
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/ApiKeyCreated"
/user/api-keys/{id}:
delete:
tags: [User]
summary: Revoke an API key
description: Permanently deactivates an API key.
operationId: revokeApiKey
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Key revoked
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
type: null
example: null
"404":
$ref: "#/components/responses/NotFound"
# ──────────────────────────────────────────────────────────────────────────
# CATEGORIES
# ──────────────────────────────────────────────────────────────────────────
/categories:
get:
tags: [Categories]
summary: List all categories for the authenticated user
operationId: listCategories
security:
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/page"
- $ref: "#/components/parameters/per_page"
- $ref: "#/components/parameters/sort"
- name: favorite
in: query
schema:
type: boolean
description: Filter by favorite status
responses:
"200":
description: Paginated list of categories
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/SuccessBody"
- $ref: "#/components/schemas/PaginatedResponse"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/Category"
post:
tags: [Categories]
summary: Create a new category
operationId: createCategory
security:
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name, color]
properties:
name:
type: string
maxLength: 255
color:
type: string
pattern: "^#[0-9a-fA-F]{6}$"
description: Hex colour code for UI
example: "#3B82F6"
favorite:
type: boolean
default: false
responses:
"201":
description: Category created
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/Category"
"409":
description: A category with this name already exists for this user
"422":
$ref: "#/components/responses/ValidationError"
/categories/{id}:
get:
tags: [Categories]
summary: Get a single category
operationId: getCategory
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Category details
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/Category"
"404":
$ref: "#/components/responses/NotFound"
put:
tags: [Categories]
summary: Update a category
operationId: updateCategory
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
maxLength: 255
color:
type: string
pattern: "^#[0-9a-fA-F]{6}$"
favorite:
type: boolean
responses:
"200":
description: Category updated
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/Category"
"404":
$ref: "#/components/responses/NotFound"
delete:
tags: [Categories]
summary: Delete a category
operationId: deleteCategory
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Category deleted
"404":
$ref: "#/components/responses/NotFound"
# ──────────────────────────────────────────────────────────────────────────
# PROJECTS
# ──────────────────────────────────────────────────────────────────────────
/projects:
get:
tags: [Projects]
summary: List all projects for the authenticated user
operationId: listProjects
security:
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/page"
- $ref: "#/components/parameters/per_page"
- $ref: "#/components/parameters/sort"
responses:
"200":
description: Paginated list of projects
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/SuccessBody"
- $ref: "#/components/schemas/PaginatedResponse"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/Project"
post:
tags: [Projects]
summary: Create a new project
operationId: createProject
security:
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name:
type: string
maxLength: 255
description:
type: string
nullable: true
color:
type: string
pattern: "^#[0-9a-fA-F]{6}$"
default: "#8B5CF6"
example: "#8B5CF6"
responses:
"201":
description: Project created
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/Project"
"422":
$ref: "#/components/responses/ValidationError"
/projects/{id}:
get:
tags: [Projects]
summary: Get a single project
operationId: getProject
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Project details
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/Project"
"404":
$ref: "#/components/responses/NotFound"
put:
tags: [Projects]
summary: Update a project
operationId: updateProject
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
maxLength: 255
description:
type: string
nullable: true
color:
type: string
pattern: "^#[0-9a-fA-F]{6}$"
responses:
"200":
description: Project updated
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/Project"
"404":
$ref: "#/components/responses/NotFound"
delete:
tags: [Projects]
summary: Delete a project
operationId: deleteProject
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Project deleted
"404":
$ref: "#/components/responses/NotFound"
# ──────────────────────────────────────────────────────────────────────────
# TODOS
# ──────────────────────────────────────────────────────────────────────────
/todos:
get:
tags: [Todos]
summary: List todos with pagination, sorting, and filtering
operationId: listTodos
security:
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/page"
- $ref: "#/components/parameters/per_page"
- $ref: "#/components/parameters/sort"
- name: status
in: query
schema:
type: string
enum: [open, in_progress, completed, archived]
description: Filter by status
- name: project_id
in: query
schema:
type: string
format: uuid
- name: sync_enabled
in: query
schema:
type: boolean
- name: reminder_enabled
in: query
schema:
type: boolean
- name: recurring_enabled
in: query
schema:
type: boolean
responses:
"200":
description: Paginated list of todos (with linked categories)
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/SuccessBody"
- $ref: "#/components/schemas/PaginatedResponse"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/Todo"
post:
tags: [Todos]
summary: Create a new todo
operationId: createTodo
security:
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [title]
properties:
title:
type: string
maxLength: 255
description:
type: string
nullable: true
status:
type: string
enum: [open, in_progress, completed, archived]
default: open
due_date:
type: string
format: date
nullable: true
example: "2025-01-15"
due_time:
type: string
format: partial-time
nullable: true
example: "10:30:00"
sync_enabled:
type: boolean
default: true
reminder_enabled:
type: boolean
default: false
recurring_enabled:
type: boolean
default: false
project_id:
type: string
format: uuid
nullable: true
category_id:
type: string
format: uuid
nullable: true
description: >
Optional — link an existing category on creation
responses:
"201":
description: Todo created
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/Todo"
"422":
$ref: "#/components/responses/ValidationError"
/todos/{id}:
get:
tags: [Todos]
summary: Get a single todo
operationId: getTodo
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Todo details (with linked categories)
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/Todo"
"404":
$ref: "#/components/responses/NotFound"
put:
tags: [Todos]
summary: Update a todo
operationId: updateTodo
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
type: object
properties:
title:
type: string
maxLength: 255
description:
type: string
nullable: true
status:
type: string
enum: [open, in_progress, completed, archived]
due_date:
type: string
format: date
nullable: true
due_time:
type: string
format: partial-time
nullable: true
sync_enabled:
type: boolean
reminder_enabled:
type: boolean
recurring_enabled:
type: boolean
project_id:
type: string
format: uuid
nullable: true
category_id:
type: string
format: uuid
nullable: true
description: >
Replaces all category links with the given category
responses:
"200":
description: Todo updated
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/Todo"
"404":
$ref: "#/components/responses/NotFound"
delete:
tags: [Todos]
summary: Delete a todo
operationId: deleteTodo
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Todo deleted
"404":
$ref: "#/components/responses/NotFound"
/todos/{id}/categories:
post:
tags: [Todos]
summary: Link a category to a todo
operationId: addTodoCategory
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
description: Todo UUID
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [category_id]
properties:
category_id:
type: string
format: uuid
responses:
"201":
description: Category linked to todo
"409":
description: Category already linked
"404":
$ref: "#/components/responses/NotFound"
/todos/{id}/categories/{categoryId}:
delete:
tags: [Todos]
summary: Remove a category link from a todo
operationId: removeTodoCategory
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
description: Todo UUID
- name: categoryId
in: path
required: true
schema:
type: string
format: uuid
description: Category UUID
responses:
"200":
description: Category removed from todo
"404":
$ref: "#/components/responses/NotFound"
# ──────────────────────────────────────────────────────────────────────────
# RECURRING TASKS
# ──────────────────────────────────────────────────────────────────────────
/recurring-tasks:
get:
tags: [Recurring Tasks]
summary: List all recurring tasks
operationId: listRecurringTasks
security:
- apiKeyAuth: []
parameters:
- $ref: "#/components/parameters/page"
- $ref: "#/components/parameters/per_page"
- $ref: "#/components/parameters/sort"
- name: schedule
in: query
schema:
type: string
enum: [daily, weekly, monthly, custom]
- name: favorite
in: query
schema:
type: boolean
responses:
"200":
description: Paginated list of recurring tasks
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/SuccessBody"
- $ref: "#/components/schemas/PaginatedResponse"
- type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/RecurringTask"
post:
tags: [Recurring Tasks]
summary: Create a recurring task
operationId: createRecurringTask
security:
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [title]
properties:
title:
type: string
maxLength: 255
description:
type: string
nullable: true
schedule:
type: string
enum: [daily, weekly, monthly, custom]
default: weekly
custom_days:
type: array
items:
type: string
enum: [mon, tue, wed, thu, fri, sat, sun]
description: Active only when schedule=custom
example: [mon, wed, fri]
favorite:
type: boolean
default: false
category_id:
type: string
format: uuid
nullable: true
responses:
"201":
description: Recurring task created
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/RecurringTask"
"422":
$ref: "#/components/responses/ValidationError"
/recurring-tasks/{id}:
get:
tags: [Recurring Tasks]
summary: Get a single recurring task
operationId: getRecurringTask
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Recurring task details
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/RecurringTask"
"404":
$ref: "#/components/responses/NotFound"
put:
tags: [Recurring Tasks]
summary: Update a recurring task
operationId: updateRecurringTask
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
type: object
properties:
title:
type: string
maxLength: 255
description:
type: string
nullable: true
schedule:
type: string
enum: [daily, weekly, monthly, custom]
custom_days:
type: array
items:
type: string
enum: [mon, tue, wed, thu, fri, sat, sun]
favorite:
type: boolean
category_id:
type: string
format: uuid
nullable: true
description: Replaces all category links
responses:
"200":
description: Recurring task updated
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/RecurringTask"
"404":
$ref: "#/components/responses/NotFound"
delete:
tags: [Recurring Tasks]
summary: Delete a recurring task
operationId: deleteRecurringTask
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Recurring task deleted
"404":
$ref: "#/components/responses/NotFound"
/recurring-tasks/{id}/categories:
post:
tags: [Recurring Tasks]
summary: Link a category to a recurring task
operationId: addRecurringTaskCategory
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [category_id]
properties:
category_id:
type: string
format: uuid
responses:
"201":
description: Category linked
"409":
description: Already linked
"404":
$ref: "#/components/responses/NotFound"
/recurring-tasks/{id}/categories/{categoryId}:
delete:
tags: [Recurring Tasks]
summary: Remove a category link from a recurring task
operationId: removeRecurringTaskCategory
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: categoryId
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Category removed
"404":
$ref: "#/components/responses/NotFound"
# ──────────────────────────────────────────────────────────────────────────
# ACTIVITY LOGS
# ──────────────────────────────────────────────────────────────────────────
/activity-logs:
get:
tags: [Activity Logs]
summary: List recent activity logs for the authenticated user
operationId: listActivityLogs
security:
- apiKeyAuth: []
parameters:
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 200
default: 50
description: Maximum number of log entries to return
responses:
"200":
description: List of activity log entries
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
type: array
items:
$ref: "#/components/schemas/ActivityLog"
/activity-logs/{id}:
get:
tags: [Activity Logs]
summary: Get a single activity log entry
operationId: getActivityLog
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Activity log entry
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/ActivityLog"
"404":
$ref: "#/components/responses/NotFound"
# ──────────────────────────────────────────────────────────────────────────
# USER THEMES
# ──────────────────────────────────────────────────────────────────────────
/user/themes:
get:
tags: [User Themes]
summary: List themes installed by the authenticated user
operationId: listUserThemes
security:
- apiKeyAuth: []
responses:
"200":
description: List of user themes
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
type: array
items:
$ref: "#/components/schemas/UserTheme"
post:
tags: [User Themes]
summary: Install a theme for the user
operationId: createUserTheme
security:
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [theme_id]
properties:
theme_id:
type: string
format: uuid
is_active:
type: boolean
default: false
custom_settings:
type: object
description: Theme variable overrides
properties:
primary_color:
type: string
example: "#3B82F6"
font_size:
type: string
example: medium
responses:
"201":
description: Theme installed
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
message:
type: string
data:
$ref: "#/components/schemas/UserTheme"
/user/themes/{id}:
put:
tags: [User Themes]
summary: Update a user's theme settings
operationId: updateUserTheme
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
type: object
properties:
is_active:
type: boolean
custom_settings:
type: object
responses:
"200":
description: Theme updated
"404":
$ref: "#/components/responses/NotFound"
delete:
tags: [User Themes]
summary: Uninstall a user theme
operationId: deleteUserTheme
security:
- apiKeyAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: Theme uninstalled
"404":
$ref: "#/components/responses/NotFound"
# ──────────────────────────────────────────────────────────────────────────
# CORS PREFLIGHT
# ──────────────────────────────────────────────────────────────────────────
/{any}:
options:
tags: [CORS]
summary: CORS preflight handler
description: Handles OPTIONS preflight requests for all API routes.
parameters:
- name: any
in: path
required: true
schema:
type: string
responses:
"200":
description: CORS headers set, no body
# ============================================================================
# COMPONENTS
# ============================================================================
components:
securitySchemes:
apiKeyAuth:
type: apiKey
in: header
name: X-API-Key
description: >
API key obtained from `/auth/register`, `/auth/login`, or
`/user/api-keys`. Include it as `X-API-Key: todo_xxxxx`.
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: >
JWT token obtained from `/auth/jwt/login` or `/auth/jwt/register`.
Include it as `Authorization: Bearer <token>`.
parameters:
page:
name: page
in: query
schema:
type: integer
minimum: 1
default: 1
description: Page number for pagination
per_page:
name: per_page
in: query
schema:
type: integer
minimum: 1
maximum: 200
default: 50
description: Items per page
sort:
name: sort
in: query
schema:
type: string
description: >
Sort fields with optional `-` prefix for descending order.
Comma-separated: `sort=title,-created_at`
requestBodies:
UserRegistration:
description: User registration payload
required: true
content:
application/json:
schema:
type: object
required: [email, password, name]
properties:
email:
type: string
format: email
password:
type: string
format: password
minLength: 8
name:
type: string
maxLength: 255
avatar_url:
type: string
format: uri
nullable: true
settings:
type: object
properties:
theme:
type: string
example: dark
language:
type: string
example: en
responses:
ValidationError:
description: Validation failed
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
enum: [false]
message:
type: string
example: Validation failed
errors:
type: object
additionalProperties:
type: string
example:
email: The Email field must contain a unique value.
Unauthorized:
description: Missing or invalid API key
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: Unauthorized
message:
type: string
example: Invalid or expired API key
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorBody"
schemas:
# ── Base envelope ──────────────────────────────────────────────────────
SuccessBody:
type: object
properties:
success:
type: boolean
example: true
message:
type: string
ErrorBody:
type: object
properties:
success:
type: boolean
example: false
message:
type: string
errors:
type: object
additionalProperties:
type: string
PaginatedResponse:
type: object
properties:
pagination:
type: object
properties:
page:
type: integer
per_page:
type: integer
total:
type: integer
last_page:
type: integer
has_more:
type: boolean
# ── User ────────────────────────────────────────────────────────────────
User:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
avatar_url:
type: string
format: uri
nullable: true
settings:
type: object
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
example:
id: "550e8400-e29b-41d4-a716-446655440000"
email: user@example.com
name: John Doe
avatar_url: null
settings:
theme: dark
language: en
created_at: "2025-01-01 00:00:00"
updated_at: "2025-01-01 00:00:00"
# ── API Keys ───────────────────────────────────────────────────────────
ApiKeyCreated:
type: object
properties:
id:
type: string
format: uuid
key:
type: string
description: Full API key value (only shown once)
example: "todo_abc123def456..."
prefix:
type: string
example: "todo_abc1"
name:
type: string
example: "My App Key"
scopes:
type: array
items:
type: string
enum: [read, write]
expires_at:
type: string
format: date-time
nullable: true
ApiKeyMetadata:
type: object
properties:
id:
type: string
format: uuid
key_prefix:
type: string
example: "todo_abc1"
name:
type: string
scopes:
type: array
items:
type: string
is_active:
type: boolean
last_used_at:
type: string
format: date-time
nullable: true
created_at:
type: string
format: date-time
# ── Category ────────────────────────────────────────────────────────────
Category:
type: object
properties:
id:
type: string
format: uuid
user_id:
type: string
format: uuid
name:
type: string
color:
type: string
pattern: "^#[0-9a-fA-F]{6}$"
example: "#3B82F6"
favorite:
type: boolean
created_at:
type: string
format: date-time
# ── Project ────────────────────────────────────────────────────────────
Project:
type: object
properties:
id:
type: string
format: uuid
user_id:
type: string
format: uuid
name:
type: string
description:
type: string
nullable: true
color:
type: string
pattern: "^#[0-9a-fA-F]{6}$"
example: "#8B5CF6"
created_at:
type: string
format: date-time
# ── Todo ────────────────────────────────────────────────────────────────
Todo:
type: object
properties:
id:
type: string
format: uuid
user_id:
type: string
format: uuid
title:
type: string
description:
type: string
nullable: true
status:
type: string
enum: [open, in_progress, completed, archived]
due_date:
type: string
format: date
nullable: true
due_time:
type: string
format: partial-time
nullable: true
sync_enabled:
type: boolean
reminder_enabled:
type: boolean
recurring_enabled:
type: boolean
project_id:
type: string
format: uuid
nullable: true
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
categories:
type: array
items:
$ref: "#/components/schemas/LinkedCategory"
example:
id: "660e8400-e29b-41d4-a716-446655440001"
user_id: "550e8400-e29b-41d4-a716-446655440000"
title: "Complete task"
description: "Task description"
status: open
due_date: "2025-01-15"
due_time: "10:30:00"
sync_enabled: true
reminder_enabled: false
recurring_enabled: false
project_id: "770e8400-e29b-41d4-a716-446655440002"
created_at: "2025-01-01 00:00:00"
updated_at: "2025-01-01 00:00:00"
categories:
- id: "880e8400-e29b-41d4-a716-446655440003"
name: "Work"
color: "#3B82F6"
LinkedCategory:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
color:
type: string
pattern: "^#[0-9a-fA-F]{6}$"
# ── Recurring Task ──────────────────────────────────────────────────────
RecurringTask:
type: object
properties:
id:
type: string
format: uuid
user_id:
type: string
format: uuid
title:
type: string
description:
type: string
nullable: true
schedule:
type: string
enum: [daily, weekly, monthly, custom]
custom_days:
type: array
items:
type: string
favorite:
type: boolean
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
categories:
type: array
items:
$ref: "#/components/schemas/LinkedCategory"
# ── Activity Log ───────────────────────────────────────────────────────
ActivityLog:
type: object
properties:
id:
type: string
format: uuid
user_id:
type: string
format: uuid
nullable: true
action:
type: string
example: "todo_created"
entity_type:
type: string
example: "todo"
entity_id:
type: string
format: uuid
nullable: true
details:
type: object
nullable: true
example:
title: "New Task"
ip_address:
type: string
example: "127.0.0.1"
user_agent:
type: string
created_at:
type: string
format: date-time
# ── Marketplace Theme ──────────────────────────────────────────────────
MarketplaceTheme:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
display_name:
type: string
description:
type: string
nullable: true
author:
type: string
nullable: true
version:
type: string
thumbnail_url:
type: string
format: uri
nullable: true
price:
type: number
format: decimal
example: 0
is_free:
type: boolean
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
# ── User Theme ─────────────────────────────────────────────────────────
UserTheme:
type: object
properties:
id:
type: string
format: uuid
user_id:
type: string
format: uuid
theme_id:
type: string
format: uuid
is_active:
type: boolean
custom_settings:
type: object
nullable: true
created_at:
type: string
format: date-time