Files
honeyDueAPI/docs/openapi.yaml
Trey t 4976eafc6c Rebrand from Casera/MyCrib to honeyDue
Total rebrand across all Go API source files:
- Go module path: casera-api -> honeydue-api
- All imports updated (130+ files)
- Docker: containers, images, networks renamed
- Email templates: support email, noreply, icon URL
- Domains: casera.app/mycrib.treytartt.com -> honeyDue.treytartt.com
- Bundle IDs: com.tt.casera -> com.tt.honeyDue
- IAP product IDs updated
- Landing page, admin panel, config defaults
- Seeds, CI workflows, Makefile, docs
- Database table names preserved (no migration needed)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 06:33:38 -06:00

4720 lines
125 KiB
YAML

openapi: 3.0.3
info:
title: honeyDue API
description: |
REST API for the honeyDue property management platform.
Consumed by iOS (SwiftUI) and Android (Compose) mobile clients via Kotlin Multiplatform.
## Authentication
Token-based authentication. After login/register, include the token in subsequent requests:
```
Authorization: Token <token>
```
`Bearer <token>` is also accepted.
## Common Headers
| Header | Description | Required |
|--------|-------------|----------|
| `Authorization` | `Token <token>` or `Bearer <token>` | Protected routes |
| `X-Timezone` | IANA timezone (e.g. `America/Chicago`) for overdue calculations | Optional |
| `Accept-Language` | Locale for i18n error messages (e.g. `en`, `es`) | Optional |
| `If-None-Match` | ETag for conditional requests on `/api/static_data/` | Optional |
## Error Responses
All errors follow a consistent JSON shape. Validation errors include a `details` map.
version: 2.0.0
contact:
name: honeyDue Team
servers:
- url: https://honeyDue.treytartt.com/api
description: Production
- url: http://127.0.0.1:8000/api
description: Local development (iOS simulator)
- url: http://10.0.2.2:8000/api
description: Local development (Android emulator)
tags:
- name: Auth (Public)
description: Authentication endpoints that do not require a token
- name: Auth (Protected)
description: Authentication endpoints that require a token
- name: Static Data
description: Lookup/reference data (public, cached with ETag)
- name: Residences
description: Property management (protected)
- name: Tasks
description: Task management and kanban board (protected)
- name: Task Completions
description: Task completion records (protected)
- name: Contractors
description: Contractor management (protected)
- name: Documents
description: Document and warranty management (protected)
- name: Notifications
description: Push notifications and preferences (protected)
- name: Subscriptions
description: In-app subscription management
- name: Uploads
description: File upload endpoints (protected)
- name: Media
description: Authenticated media serving (protected)
paths:
# ===========================================================================
# Auth (Public)
# ===========================================================================
/auth/login/:
post:
tags: [Auth (Public)]
operationId: login
summary: Login with email/password
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoginRequest'
responses:
'200':
description: Successful login
content:
application/json:
schema:
$ref: '#/components/schemas/LoginResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Error'
/auth/register/:
post:
tags: [Auth (Public)]
operationId: register
summary: Register a new user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterRequest'
responses:
'201':
description: Registration successful
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterResponse'
'400':
$ref: '#/components/responses/ValidationError'
'409':
$ref: '#/components/responses/Error'
/auth/forgot-password/:
post:
tags: [Auth (Public)]
operationId: forgotPassword
summary: Send password reset code to email
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ForgotPasswordRequest'
responses:
'200':
description: Reset code sent
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'400':
$ref: '#/components/responses/ValidationError'
/auth/verify-reset-code/:
post:
tags: [Auth (Public)]
operationId: verifyResetCode
summary: Verify password reset code
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/VerifyResetCodeRequest'
responses:
'200':
description: Code verified, returns reset token
content:
application/json:
schema:
$ref: '#/components/schemas/VerifyResetCodeResponse'
'400':
$ref: '#/components/responses/Error'
/auth/reset-password/:
post:
tags: [Auth (Public)]
operationId: resetPassword
summary: Reset password using reset token
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ResetPasswordRequest'
responses:
'200':
description: Password reset successful
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'400':
$ref: '#/components/responses/Error'
/auth/apple-sign-in/:
post:
tags: [Auth (Public)]
operationId: appleSignIn
summary: Sign in with Apple ID token
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/AppleSignInRequest'
responses:
'200':
description: Successful Apple sign-in
content:
application/json:
schema:
$ref: '#/components/schemas/SocialSignInResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Error'
/auth/google-sign-in/:
post:
tags: [Auth (Public)]
operationId: googleSignIn
summary: Sign in with Google ID token
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/GoogleSignInRequest'
responses:
'200':
description: Successful Google sign-in
content:
application/json:
schema:
$ref: '#/components/schemas/SocialSignInResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Error'
# ===========================================================================
# Auth (Protected)
# ===========================================================================
/auth/logout/:
post:
tags: [Auth (Protected)]
operationId: logout
summary: Logout (invalidate token)
security:
- tokenAuth: []
responses:
'200':
description: Logged out
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/auth/me/:
get:
tags: [Auth (Protected)]
operationId: getCurrentUser
summary: Get current authenticated user with profile
security:
- tokenAuth: []
responses:
'200':
description: Current user
content:
application/json:
schema:
$ref: '#/components/schemas/CurrentUserResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/auth/profile/:
put:
tags: [Auth (Protected)]
operationId: updateProfile
summary: Update user profile
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateProfileRequest'
responses:
'200':
description: Profile updated
content:
application/json:
schema:
$ref: '#/components/schemas/CurrentUserResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
patch:
tags: [Auth (Protected)]
operationId: patchProfile
summary: Partial update user profile
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateProfileRequest'
responses:
'200':
description: Profile updated
content:
application/json:
schema:
$ref: '#/components/schemas/CurrentUserResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
/auth/verify-email/:
post:
tags: [Auth (Protected)]
operationId: verifyEmail
summary: Verify email with 6-digit code
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/VerifyEmailRequest'
responses:
'200':
description: Email verified
content:
application/json:
schema:
$ref: '#/components/schemas/VerifyEmailResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/auth/resend-verification/:
post:
tags: [Auth (Protected)]
operationId: resendVerification
summary: Resend email verification code
security:
- tokenAuth: []
responses:
'200':
description: Verification email sent
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
# ===========================================================================
# Static Data (Public)
# ===========================================================================
/static_data/:
get:
tags: [Static Data]
operationId: getStaticData
summary: Get all lookup/reference data (ETag support)
description: |
Returns all seeded lookup data in a single payload. Supports conditional
requests via `If-None-Match` header -- returns 304 when data has not changed.
parameters:
- in: header
name: If-None-Match
schema:
type: string
description: ETag from a previous response
responses:
'200':
description: All lookup data
headers:
ETag:
schema:
type: string
description: Content hash for conditional requests
Cache-Control:
schema:
type: string
description: "private, max-age=3600"
content:
application/json:
schema:
$ref: '#/components/schemas/SeededDataResponse'
'304':
description: Not Modified (client ETag matches)
/residences/types/:
get:
tags: [Static Data]
operationId: getResidenceTypes
summary: List residence types
responses:
'200':
description: Residence types
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ResidenceTypeResponse'
/tasks/categories/:
get:
tags: [Static Data]
operationId: getTaskCategories
summary: List task categories
responses:
'200':
description: Task categories
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TaskCategoryResponse'
/tasks/priorities/:
get:
tags: [Static Data]
operationId: getTaskPriorities
summary: List task priorities
responses:
'200':
description: Task priorities
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TaskPriorityResponse'
/tasks/frequencies/:
get:
tags: [Static Data]
operationId: getTaskFrequencies
summary: List task frequencies
responses:
'200':
description: Task frequencies
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TaskFrequencyResponse'
/contractors/specialties/:
get:
tags: [Static Data]
operationId: getContractorSpecialties
summary: List contractor specialties
responses:
'200':
description: Contractor specialties
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ContractorSpecialtyResponse'
/tasks/templates/:
get:
tags: [Static Data]
operationId: getTaskTemplates
summary: List all task templates
responses:
'200':
description: Task templates
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TaskTemplateResponse'
/tasks/templates/grouped/:
get:
tags: [Static Data]
operationId: getTaskTemplatesGrouped
summary: Get task templates grouped by category
responses:
'200':
description: Templates grouped by category
content:
application/json:
schema:
$ref: '#/components/schemas/TaskTemplatesGroupedResponse'
/tasks/templates/search/:
get:
tags: [Static Data]
operationId: searchTaskTemplates
summary: Search task templates by query
parameters:
- in: query
name: q
schema:
type: string
description: Search query string
responses:
'200':
description: Matching templates
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TaskTemplateResponse'
/tasks/templates/by-category/{category_id}/:
get:
tags: [Static Data]
operationId: getTaskTemplatesByCategory
summary: Get templates for a specific category
parameters:
- $ref: '#/components/parameters/CategoryIdParam'
responses:
'200':
description: Templates for category
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TaskTemplateResponse'
/tasks/templates/by-region/:
get:
tags: [Static Data]
operationId: getTaskTemplatesByRegion
summary: Get task templates for a climate region by state or ZIP code
description: Returns templates matching the climate zone for a given US state abbreviation or ZIP code. At least one parameter is required. If both are provided, state takes priority.
parameters:
- name: state
in: query
required: false
schema:
type: string
example: MA
description: US state abbreviation (e.g., MA, FL, TX)
- name: zip
in: query
required: false
schema:
type: string
example: "02101"
description: US ZIP code (resolved to state on the server)
responses:
'200':
description: Regional templates for the climate zone
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TaskTemplateResponse'
'400':
$ref: '#/components/responses/BadRequest'
/tasks/templates/{id}/:
get:
tags: [Static Data]
operationId: getTaskTemplate
summary: Get a single task template
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Task template
content:
application/json:
schema:
$ref: '#/components/schemas/TaskTemplateResponse'
'404':
$ref: '#/components/responses/NotFound'
# ===========================================================================
# Residences
# ===========================================================================
/residences/:
get:
tags: [Residences]
operationId: listResidences
summary: List all residences the user has access to
security:
- tokenAuth: []
responses:
'200':
description: List of residences
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ResidenceResponse'
'401':
$ref: '#/components/responses/Unauthorized'
post:
tags: [Residences]
operationId: createResidence
summary: Create a new residence
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateResidenceRequest'
responses:
'201':
description: Residence created
content:
application/json:
schema:
$ref: '#/components/schemas/ResidenceWithSummaryResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/residences/my-residences/:
get:
tags: [Residences]
operationId: getMyResidences
summary: Get user residences (optimized for main screen)
security:
- tokenAuth: []
responses:
'200':
description: Residences with metadata
content:
application/json:
schema:
$ref: '#/components/schemas/MyResidencesResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/residences/summary/:
get:
tags: [Residences]
operationId: getResidenceSummary
summary: Get summary counts across all residences
security:
- tokenAuth: []
responses:
'200':
description: Summary statistics
content:
application/json:
schema:
$ref: '#/components/schemas/TotalSummary'
'401':
$ref: '#/components/responses/Unauthorized'
/residences/join-with-code/:
post:
tags: [Residences]
operationId: joinWithCode
summary: Join a residence using a share code
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/JoinWithCodeRequest'
responses:
'200':
description: Joined residence
content:
application/json:
schema:
$ref: '#/components/schemas/JoinResidenceResponse'
'400':
$ref: '#/components/responses/Error'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Error'
/residences/{id}/:
get:
tags: [Residences]
operationId: getResidence
summary: Get a single residence
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Residence detail
content:
application/json:
schema:
$ref: '#/components/schemas/ResidenceResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
put:
tags: [Residences]
operationId: updateResidence
summary: Update a residence
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateResidenceRequest'
responses:
'200':
description: Residence updated
content:
application/json:
schema:
$ref: '#/components/schemas/ResidenceWithSummaryResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
patch:
tags: [Residences]
operationId: patchResidence
summary: Partial update a residence
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateResidenceRequest'
responses:
'200':
description: Residence updated
content:
application/json:
schema:
$ref: '#/components/schemas/ResidenceWithSummaryResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
delete:
tags: [Residences]
operationId: deleteResidence
summary: Delete a residence (owner only)
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Residence deleted
content:
application/json:
schema:
$ref: '#/components/schemas/ResidenceDeleteWithSummaryResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/residences/{id}/generate-share-code/:
post:
tags: [Residences]
operationId: generateShareCode
summary: Generate a share code for a residence
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateShareCodeRequest'
responses:
'201':
description: Share code generated
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateShareCodeResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/residences/{id}/generate-share-package/:
post:
tags: [Residences]
operationId: generateSharePackage
summary: Generate a share package (.honeydue file metadata)
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Share package data
content:
application/json:
schema:
$ref: '#/components/schemas/SharePackageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/residences/{id}/share-code/:
get:
tags: [Residences]
operationId: getShareCode
summary: Get active share code for a residence
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Active share code
content:
application/json:
schema:
$ref: '#/components/schemas/ShareCodeResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/residences/{id}/users/:
get:
tags: [Residences]
operationId: getResidenceUsers
summary: List users who have access to a residence
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: List of residence users
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ResidenceUserResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/residences/{id}/users/{user_id}/:
delete:
tags: [Residences]
operationId: removeResidenceUser
summary: Remove a user from a residence (owner only)
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
- in: path
name: user_id
required: true
schema:
type: integer
format: uint
description: ID of the user to remove
responses:
'200':
description: User removed
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/residences/{id}/generate-tasks-report/:
post:
tags: [Residences]
operationId: generateTasksReport
summary: Generate a PDF tasks report for a residence
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: PDF report
content:
application/pdf:
schema:
type: string
format: binary
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
# ===========================================================================
# Tasks
# ===========================================================================
/tasks/:
get:
tags: [Tasks]
operationId: listTasks
summary: Get kanban board with all user tasks
description: Returns tasks organized into kanban columns (overdue, due_soon, upcoming, completed, in_progress, archived).
security:
- tokenAuth: []
responses:
'200':
description: Kanban board
content:
application/json:
schema:
$ref: '#/components/schemas/KanbanBoardResponse'
'401':
$ref: '#/components/responses/Unauthorized'
post:
tags: [Tasks]
operationId: createTask
summary: Create a new task
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateTaskRequest'
responses:
'201':
description: Task created
content:
application/json:
schema:
$ref: '#/components/schemas/TaskWithSummaryResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/tasks/by-residence/{residence_id}/:
get:
tags: [Tasks]
operationId: getTasksByResidence
summary: Get kanban board for a specific residence
security:
- tokenAuth: []
parameters:
- in: path
name: residence_id
required: true
schema:
type: integer
format: uint
description: Residence ID
responses:
'200':
description: Kanban board for residence
content:
application/json:
schema:
$ref: '#/components/schemas/KanbanBoardResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/tasks/{id}/:
get:
tags: [Tasks]
operationId: getTask
summary: Get task detail
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Task detail
content:
application/json:
schema:
$ref: '#/components/schemas/TaskResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
put:
tags: [Tasks]
operationId: updateTask
summary: Update a task
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateTaskRequest'
responses:
'200':
description: Task updated
content:
application/json:
schema:
$ref: '#/components/schemas/TaskWithSummaryResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
patch:
tags: [Tasks]
operationId: patchTask
summary: Partial update a task
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateTaskRequest'
responses:
'200':
description: Task updated
content:
application/json:
schema:
$ref: '#/components/schemas/TaskWithSummaryResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
delete:
tags: [Tasks]
operationId: deleteTask
summary: Delete a task
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Task deleted
content:
application/json:
schema:
$ref: '#/components/schemas/DeleteWithSummaryResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/tasks/{id}/mark-in-progress/:
post:
tags: [Tasks]
operationId: markTaskInProgress
summary: Mark a task as in progress
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Task marked in progress
content:
application/json:
schema:
$ref: '#/components/schemas/TaskWithSummaryResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/tasks/{id}/cancel/:
post:
tags: [Tasks]
operationId: cancelTask
summary: Cancel a task
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Task cancelled
content:
application/json:
schema:
$ref: '#/components/schemas/TaskWithSummaryResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/tasks/{id}/uncancel/:
post:
tags: [Tasks]
operationId: uncancelTask
summary: Uncancel a previously cancelled task
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Task uncancelled
content:
application/json:
schema:
$ref: '#/components/schemas/TaskWithSummaryResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/tasks/{id}/archive/:
post:
tags: [Tasks]
operationId: archiveTask
summary: Archive a task
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Task archived
content:
application/json:
schema:
$ref: '#/components/schemas/TaskWithSummaryResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/tasks/{id}/unarchive/:
post:
tags: [Tasks]
operationId: unarchiveTask
summary: Unarchive a previously archived task
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Task unarchived
content:
application/json:
schema:
$ref: '#/components/schemas/TaskWithSummaryResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/tasks/{id}/quick-complete/:
post:
tags: [Tasks]
operationId: quickCompleteTask
summary: Quick complete a task (from widget, no notes/images)
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Task completed
content:
application/json:
schema:
$ref: '#/components/schemas/TaskCompletionWithSummaryResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/tasks/{id}/completions/:
get:
tags: [Tasks]
operationId: getTaskCompletions
summary: List completions for a specific task
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Task completions
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TaskCompletionResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
# ===========================================================================
# Task Completions
# ===========================================================================
/task-completions/:
get:
tags: [Task Completions]
operationId: listCompletions
summary: List all task completions for the user
security:
- tokenAuth: []
responses:
'200':
description: List of completions
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TaskCompletionResponse'
'401':
$ref: '#/components/responses/Unauthorized'
post:
tags: [Task Completions]
operationId: createCompletion
summary: Create a task completion (with optional images)
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateTaskCompletionRequest'
responses:
'201':
description: Completion created (includes updated task)
content:
application/json:
schema:
$ref: '#/components/schemas/TaskCompletionWithSummaryResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/task-completions/{id}/:
get:
tags: [Task Completions]
operationId: getCompletion
summary: Get a single task completion
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Completion detail
content:
application/json:
schema:
$ref: '#/components/schemas/TaskCompletionResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
put:
tags: [Task Completions]
operationId: updateCompletion
summary: Update a task completion
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateTaskCompletionRequest'
responses:
'200':
description: Completion updated
content:
application/json:
schema:
$ref: '#/components/schemas/TaskCompletionResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
delete:
tags: [Task Completions]
operationId: deleteCompletion
summary: Delete a task completion
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Completion deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
# ===========================================================================
# Contractors
# ===========================================================================
/contractors/:
get:
tags: [Contractors]
operationId: listContractors
summary: List all contractors accessible to the user
security:
- tokenAuth: []
responses:
'200':
description: List of contractors
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ContractorResponse'
'401':
$ref: '#/components/responses/Unauthorized'
post:
tags: [Contractors]
operationId: createContractor
summary: Create a new contractor
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateContractorRequest'
responses:
'201':
description: Contractor created
content:
application/json:
schema:
$ref: '#/components/schemas/ContractorResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/contractors/by-residence/{residence_id}/:
get:
tags: [Contractors]
operationId: listContractorsByResidence
summary: List contractors for a specific residence
security:
- tokenAuth: []
parameters:
- in: path
name: residence_id
required: true
schema:
type: integer
format: uint
description: Residence ID
responses:
'200':
description: Contractors for residence
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ContractorResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/contractors/{id}/:
get:
tags: [Contractors]
operationId: getContractor
summary: Get a single contractor
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Contractor detail
content:
application/json:
schema:
$ref: '#/components/schemas/ContractorResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
put:
tags: [Contractors]
operationId: updateContractor
summary: Update a contractor
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateContractorRequest'
responses:
'200':
description: Contractor updated
content:
application/json:
schema:
$ref: '#/components/schemas/ContractorResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
patch:
tags: [Contractors]
operationId: patchContractor
summary: Partial update a contractor
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateContractorRequest'
responses:
'200':
description: Contractor updated
content:
application/json:
schema:
$ref: '#/components/schemas/ContractorResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
delete:
tags: [Contractors]
operationId: deleteContractor
summary: Delete a contractor
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Contractor deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/contractors/{id}/toggle-favorite/:
post:
tags: [Contractors]
operationId: toggleContractorFavorite
summary: Toggle favorite status of a contractor
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Favorite toggled
content:
application/json:
schema:
$ref: '#/components/schemas/ToggleFavoriteResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/contractors/{id}/tasks/:
get:
tags: [Contractors]
operationId: getContractorTasks
summary: Get tasks associated with a contractor
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Contractor tasks
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/TaskResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
# ===========================================================================
# Documents
# ===========================================================================
/documents/:
get:
tags: [Documents]
operationId: listDocuments
summary: List all documents accessible to the user
security:
- tokenAuth: []
parameters:
- in: query
name: residence
description: Filter by residence ID
schema:
type: integer
format: uint
- in: query
name: document_type
description: Filter by document type
schema:
type: string
- in: query
name: is_active
description: Filter by active/inactive status (default true)
schema:
type: boolean
- in: query
name: expiring_soon
description: Return warranties expiring in N days
schema:
type: integer
minimum: 1
- in: query
name: search
description: Case-insensitive search over title/description
schema:
type: string
responses:
'200':
description: List of documents
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/DocumentResponse'
'401':
$ref: '#/components/responses/Unauthorized'
post:
tags: [Documents]
operationId: createDocument
summary: Create a new document
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateDocumentRequest'
responses:
'201':
description: Document created
content:
application/json:
schema:
$ref: '#/components/schemas/DocumentResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/documents/warranties/:
get:
tags: [Documents]
operationId: listWarranties
summary: List warranty documents
security:
- tokenAuth: []
responses:
'200':
description: Warranty documents
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/DocumentResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/documents/{id}/:
get:
tags: [Documents]
operationId: getDocument
summary: Get a single document
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Document detail
content:
application/json:
schema:
$ref: '#/components/schemas/DocumentResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
put:
tags: [Documents]
operationId: updateDocument
summary: Update a document
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateDocumentRequest'
responses:
'200':
description: Document updated
content:
application/json:
schema:
$ref: '#/components/schemas/DocumentResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
patch:
tags: [Documents]
operationId: patchDocument
summary: Partial update a document
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateDocumentRequest'
responses:
'200':
description: Document updated
content:
application/json:
schema:
$ref: '#/components/schemas/DocumentResponse'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
delete:
tags: [Documents]
operationId: deleteDocument
summary: Delete a document
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Document deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/documents/{id}/activate/:
post:
tags: [Documents]
operationId: activateDocument
summary: Activate a document
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Document activated
content:
application/json:
schema:
type: object
properties:
message:
type: string
document:
$ref: '#/components/schemas/DocumentResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/documents/{id}/deactivate/:
post:
tags: [Documents]
operationId: deactivateDocument
summary: Deactivate a document
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Document deactivated
content:
application/json:
schema:
type: object
properties:
message:
type: string
document:
$ref: '#/components/schemas/DocumentResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/documents/{id}/images/:
post:
tags: [Documents]
operationId: uploadDocumentImage
summary: Upload an image to a document
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- image
properties:
image:
type: string
format: binary
description: Image file to upload
caption:
type: string
description: Optional caption for the image
responses:
'201':
description: Image uploaded successfully
content:
application/json:
schema:
$ref: '#/components/schemas/DocumentResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/documents/{id}/images/{imageId}/:
delete:
tags: [Documents]
operationId: deleteDocumentImage
summary: Delete an image from a document
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
- in: path
name: imageId
required: true
schema:
type: integer
description: Image ID
responses:
'200':
description: Image deleted, returns updated document
content:
application/json:
schema:
$ref: '#/components/schemas/DocumentResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
# ===========================================================================
# Notifications
# ===========================================================================
/notifications/:
get:
tags: [Notifications]
operationId: listNotifications
summary: List user notifications
security:
- tokenAuth: []
parameters:
- in: query
name: limit
schema:
type: integer
default: 50
description: Max number of notifications to return
- in: query
name: offset
schema:
type: integer
default: 0
description: Offset for pagination
responses:
'200':
description: Notifications list
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationListResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/notifications/unread-count/:
get:
tags: [Notifications]
operationId: getUnreadCount
summary: Get count of unread notifications
security:
- tokenAuth: []
responses:
'200':
description: Unread count
content:
application/json:
schema:
type: object
required: [unread_count]
properties:
unread_count:
type: integer
'401':
$ref: '#/components/responses/Unauthorized'
/notifications/{id}/read/:
post:
tags: [Notifications]
operationId: markNotificationRead
summary: Mark a single notification as read
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Marked as read
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/notifications/mark-all-read/:
post:
tags: [Notifications]
operationId: markAllNotificationsRead
summary: Mark all notifications as read
security:
- tokenAuth: []
responses:
'200':
description: All marked as read
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/notifications/devices/:
post:
tags: [Notifications]
operationId: registerDevice
summary: Register a push notification device
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterDeviceRequest'
responses:
'201':
description: Device registered
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
get:
tags: [Notifications]
operationId: listDevices
summary: List registered push devices
security:
- tokenAuth: []
responses:
'200':
description: List of devices
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/DeviceResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/notifications/devices/register/:
post:
tags: [Notifications]
operationId: registerDeviceAlias
summary: Register a push notification device (alias)
description: Alias for POST /notifications/devices/ for mobile client compatibility.
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RegisterDeviceRequest'
responses:
'201':
description: Device registered
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/notifications/devices/unregister/:
post:
tags: [Notifications]
operationId: unregisterDevice
summary: Unregister a push device by registration ID
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UnregisterDeviceRequest'
responses:
'200':
description: Device unregistered
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/notifications/devices/{id}/:
delete:
tags: [Notifications]
operationId: deleteDevice
summary: Delete a registered push device
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
- in: query
name: platform
schema:
type: string
enum: [ios, android]
default: ios
description: Device platform
responses:
'200':
description: Device removed
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/notifications/preferences/:
get:
tags: [Notifications]
operationId: getNotificationPreferences
summary: Get notification preferences
security:
- tokenAuth: []
responses:
'200':
description: Notification preferences
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationPreference'
'401':
$ref: '#/components/responses/Unauthorized'
put:
tags: [Notifications]
operationId: updateNotificationPreferences
summary: Update notification preferences
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdatePreferencesRequest'
responses:
'200':
description: Preferences updated
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationPreference'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
patch:
tags: [Notifications]
operationId: patchNotificationPreferences
summary: Partial update notification preferences
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdatePreferencesRequest'
responses:
'200':
description: Preferences updated
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationPreference'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
# ===========================================================================
# Subscriptions
# ===========================================================================
/subscription/:
get:
tags: [Subscriptions]
operationId: getSubscription
summary: Get current subscription
security:
- tokenAuth: []
responses:
'200':
description: Subscription details
content:
application/json:
schema:
$ref: '#/components/schemas/SubscriptionResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/subscription/status/:
get:
tags: [Subscriptions]
operationId: getSubscriptionStatus
summary: Get subscription status with limits and usage
security:
- tokenAuth: []
responses:
'200':
description: Subscription status
content:
application/json:
schema:
$ref: '#/components/schemas/SubscriptionStatusResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/subscription/features/:
get:
tags: [Subscriptions]
operationId: getFeatureBenefits
summary: List feature benefits (free vs pro comparison)
security:
- tokenAuth: []
responses:
'200':
description: Feature benefits list
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/FeatureBenefit'
'401':
$ref: '#/components/responses/Unauthorized'
/subscription/promotions/:
get:
tags: [Subscriptions]
operationId: getPromotions
summary: List active promotions for the user
security:
- tokenAuth: []
responses:
'200':
description: Active promotions
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Promotion'
'401':
$ref: '#/components/responses/Unauthorized'
/subscription/upgrade-triggers/:
get:
tags: [Subscriptions]
operationId: getAllUpgradeTriggers
summary: Get all upgrade triggers (public, no auth required)
description: Available without authentication so the app can display upgrade prompts before login.
responses:
'200':
description: All upgrade triggers
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/UpgradeTriggerResponse'
/subscription/upgrade-trigger/{key}/:
get:
tags: [Subscriptions]
operationId: getUpgradeTrigger
summary: Get a single upgrade trigger by key
security:
- tokenAuth: []
parameters:
- in: path
name: key
required: true
schema:
type: string
description: Trigger key (e.g. "properties_limit", "tasks_limit")
responses:
'200':
description: Upgrade trigger
content:
application/json:
schema:
$ref: '#/components/schemas/UpgradeTriggerResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/subscription/purchase/:
post:
tags: [Subscriptions]
operationId: processPurchase
summary: Process an in-app purchase (iOS or Android)
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProcessPurchaseRequest'
responses:
'200':
description: Purchase processed
content:
application/json:
schema:
type: object
properties:
message:
type: string
subscription:
$ref: '#/components/schemas/SubscriptionResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/subscription/cancel/:
post:
tags: [Subscriptions]
operationId: cancelSubscription
summary: Cancel subscription
security:
- tokenAuth: []
responses:
'200':
description: Subscription cancelled
content:
application/json:
schema:
type: object
properties:
message:
type: string
subscription:
$ref: '#/components/schemas/SubscriptionResponse'
'401':
$ref: '#/components/responses/Unauthorized'
/subscription/restore/:
post:
tags: [Subscriptions]
operationId: restoreSubscription
summary: Restore a previous subscription
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ProcessPurchaseRequest'
responses:
'200':
description: Subscription restored
content:
application/json:
schema:
type: object
properties:
message:
type: string
subscription:
$ref: '#/components/schemas/SubscriptionResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/subscription/checkout/:
post:
tags: [Subscriptions]
operationId: createCheckoutSession
summary: Create a Stripe Checkout session for web subscription purchase
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [price_id, success_url, cancel_url]
properties:
price_id:
type: string
description: Stripe Price ID
success_url:
type: string
format: uri
cancel_url:
type: string
format: uri
responses:
'200':
description: Checkout session created
content:
application/json:
schema:
type: object
properties:
checkout_url:
type: string
format: uri
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'409':
description: Already subscribed on another platform
content:
application/json:
schema:
type: object
properties:
error:
type: string
existing_platform:
type: string
message:
type: string
/subscription/portal/:
post:
tags: [Subscriptions]
operationId: createPortalSession
summary: Create a Stripe Customer Portal session for managing web subscriptions
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [return_url]
properties:
return_url:
type: string
format: uri
responses:
'200':
description: Portal session created
content:
application/json:
schema:
type: object
properties:
portal_url:
type: string
format: uri
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/subscription/webhook/stripe/:
post:
tags: [Subscriptions]
operationId: handleStripeWebhook
summary: Handle Stripe webhook events (server-to-server)
description: |
Receives Stripe webhook events for subscription lifecycle management.
Verifies the webhook signature using the configured signing secret.
No auth token required — uses Stripe signature verification.
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
'200':
description: Webhook processed successfully
content:
application/json:
schema:
type: object
properties:
received:
type: boolean
'400':
$ref: '#/components/responses/Error'
# ===========================================================================
# Uploads
# ===========================================================================
/uploads/image/:
post:
tags: [Uploads]
operationId: uploadImage
summary: Upload an image file
security:
- tokenAuth: []
parameters:
- in: query
name: category
schema:
type: string
default: images
description: Storage category/folder
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
responses:
'200':
description: Upload result
content:
application/json:
schema:
$ref: '#/components/schemas/UploadResult'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/uploads/document/:
post:
tags: [Uploads]
operationId: uploadDocument
summary: Upload a document file
security:
- tokenAuth: []
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
responses:
'200':
description: Upload result
content:
application/json:
schema:
$ref: '#/components/schemas/UploadResult'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/uploads/completion/:
post:
tags: [Uploads]
operationId: uploadCompletion
summary: Upload a task completion image
security:
- tokenAuth: []
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
responses:
'200':
description: Upload result
content:
application/json:
schema:
$ref: '#/components/schemas/UploadResult'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/uploads/:
delete:
tags: [Uploads]
operationId: deleteUploadedFile
summary: Delete an uploaded file
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [url]
properties:
url:
type: string
description: URL of the file to delete
responses:
'200':
description: File deleted
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
# ===========================================================================
# Media (Authenticated serving)
# ===========================================================================
/media/document/{id}:
get:
tags: [Media]
operationId: serveDocument
summary: Serve a document file (authenticated)
description: Proxies the document file from S3 after verifying the user has access to the residence.
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: File content
content:
application/octet-stream:
schema:
type: string
format: binary
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/media/document-image/{id}:
get:
tags: [Media]
operationId: serveDocumentImage
summary: Serve a document image (authenticated)
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Image content
content:
image/*:
schema:
type: string
format: binary
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/media/completion-image/{id}:
get:
tags: [Media]
operationId: serveCompletionImage
summary: Serve a task completion image (authenticated)
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/IdParam'
responses:
'200':
description: Image content
content:
image/*:
schema:
type: string
format: binary
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
# =============================================================================
# Components
# =============================================================================
components:
securitySchemes:
tokenAuth:
type: apiKey
in: header
name: Authorization
description: 'Token-based auth. Format: `Token <token>` or `Bearer <token>`'
parameters:
IdParam:
in: path
name: id
required: true
schema:
type: integer
format: uint
description: Resource ID
CategoryIdParam:
in: path
name: category_id
required: true
schema:
type: integer
format: uint
description: Category ID
responses:
Error:
description: Error response
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
ValidationError:
description: Validation error with field details
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Unauthorized:
description: Authentication required or token invalid
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Forbidden:
description: Access denied
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
schemas:
# =========================================================================
# Common
# =========================================================================
ErrorResponse:
type: object
required: [error]
properties:
error:
type: string
description: Human-readable error message (may be localized)
details:
type: object
additionalProperties:
type: string
description: Field-level validation errors (field name -> message)
MessageResponse:
type: object
required: [message]
properties:
message:
type: string
# =========================================================================
# Auth Requests
# =========================================================================
LoginRequest:
type: object
properties:
username:
type: string
description: Username (required if email not provided)
email:
type: string
format: email
description: Email (required if username not provided)
password:
type: string
minLength: 1
required: [password]
RegisterRequest:
type: object
required: [username, email, password]
properties:
username:
type: string
minLength: 3
maxLength: 150
email:
type: string
format: email
maxLength: 254
password:
type: string
minLength: 8
first_name:
type: string
maxLength: 150
last_name:
type: string
maxLength: 150
ForgotPasswordRequest:
type: object
required: [email]
properties:
email:
type: string
format: email
VerifyResetCodeRequest:
type: object
required: [email, code]
properties:
email:
type: string
format: email
code:
type: string
minLength: 6
maxLength: 6
ResetPasswordRequest:
type: object
required: [reset_token, new_password]
properties:
reset_token:
type: string
new_password:
type: string
minLength: 8
UpdateProfileRequest:
type: object
properties:
email:
type: string
format: email
maxLength: 254
nullable: true
first_name:
type: string
maxLength: 150
nullable: true
last_name:
type: string
maxLength: 150
nullable: true
VerifyEmailRequest:
type: object
required: [code]
properties:
code:
type: string
minLength: 6
maxLength: 6
AppleSignInRequest:
type: object
required: [id_token, user_id]
properties:
id_token:
type: string
description: Apple identity token (JWT)
user_id:
type: string
description: Apple user ID (sub claim)
email:
type: string
format: email
nullable: true
description: May be nil or private relay address
first_name:
type: string
nullable: true
last_name:
type: string
nullable: true
GoogleSignInRequest:
type: object
required: [id_token]
properties:
id_token:
type: string
description: Google ID token from Credential Manager
# =========================================================================
# Auth Responses
# =========================================================================
UserResponse:
type: object
properties:
id:
type: integer
format: uint
username:
type: string
email:
type: string
format: email
first_name:
type: string
last_name:
type: string
is_active:
type: boolean
verified:
type: boolean
date_joined:
type: string
format: date-time
last_login:
type: string
format: date-time
nullable: true
UserProfileResponse:
type: object
properties:
id:
type: integer
format: uint
user_id:
type: integer
format: uint
verified:
type: boolean
bio:
type: string
phone_number:
type: string
date_of_birth:
type: string
format: date-time
nullable: true
profile_picture:
type: string
LoginResponse:
type: object
required: [token, user]
properties:
token:
type: string
user:
$ref: '#/components/schemas/UserResponse'
RegisterResponse:
type: object
required: [token, user, message]
properties:
token:
type: string
user:
$ref: '#/components/schemas/UserResponse'
message:
type: string
SocialSignInResponse:
type: object
required: [token, user, is_new_user]
properties:
token:
type: string
user:
$ref: '#/components/schemas/UserResponse'
is_new_user:
type: boolean
CurrentUserResponse:
type: object
properties:
id:
type: integer
format: uint
username:
type: string
email:
type: string
format: email
first_name:
type: string
last_name:
type: string
is_active:
type: boolean
date_joined:
type: string
format: date-time
last_login:
type: string
format: date-time
nullable: true
profile:
$ref: '#/components/schemas/UserProfileResponse'
VerifyEmailResponse:
type: object
properties:
message:
type: string
verified:
type: boolean
VerifyResetCodeResponse:
type: object
properties:
message:
type: string
reset_token:
type: string
# =========================================================================
# Static Data / Lookups
# =========================================================================
SeededDataResponse:
type: object
properties:
residence_types:
type: array
items:
$ref: '#/components/schemas/ResidenceTypeResponse'
task_categories:
type: array
items:
$ref: '#/components/schemas/TaskCategoryResponse'
task_priorities:
type: array
items:
$ref: '#/components/schemas/TaskPriorityResponse'
task_frequencies:
type: array
items:
$ref: '#/components/schemas/TaskFrequencyResponse'
contractor_specialties:
type: array
items:
$ref: '#/components/schemas/ContractorSpecialtyResponse'
task_templates:
$ref: '#/components/schemas/TaskTemplatesGroupedResponse'
ResidenceTypeResponse:
type: object
properties:
id:
type: integer
format: uint
name:
type: string
TaskCategoryResponse:
type: object
properties:
id:
type: integer
format: uint
name:
type: string
description:
type: string
icon:
type: string
color:
type: string
display_order:
type: integer
TaskPriorityResponse:
type: object
properties:
id:
type: integer
format: uint
name:
type: string
level:
type: integer
color:
type: string
display_order:
type: integer
TaskFrequencyResponse:
type: object
properties:
id:
type: integer
format: uint
name:
type: string
days:
type: integer
nullable: true
description: Number of days between occurrences (null for one-time)
display_order:
type: integer
ContractorSpecialtyResponse:
type: object
properties:
id:
type: integer
format: uint
name:
type: string
description:
type: string
icon:
type: string
display_order:
type: integer
TaskTemplateResponse:
type: object
properties:
id:
type: integer
format: uint
title:
type: string
description:
type: string
category_id:
type: integer
format: uint
nullable: true
category:
$ref: '#/components/schemas/TaskCategoryResponse'
frequency_id:
type: integer
format: uint
nullable: true
frequency:
$ref: '#/components/schemas/TaskFrequencyResponse'
icon_ios:
type: string
icon_android:
type: string
tags:
type: array
items:
type: string
display_order:
type: integer
is_active:
type: boolean
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
TaskTemplateCategoryGroup:
type: object
properties:
category_name:
type: string
category_id:
type: integer
format: uint
nullable: true
templates:
type: array
items:
$ref: '#/components/schemas/TaskTemplateResponse'
count:
type: integer
TaskTemplatesGroupedResponse:
type: object
properties:
categories:
type: array
items:
$ref: '#/components/schemas/TaskTemplateCategoryGroup'
total_count:
type: integer
# =========================================================================
# Residence
# =========================================================================
CreateResidenceRequest:
type: object
required: [name]
properties:
name:
type: string
minLength: 1
maxLength: 200
property_type_id:
type: integer
format: uint
nullable: true
street_address:
type: string
maxLength: 255
apartment_unit:
type: string
maxLength: 50
city:
type: string
maxLength: 100
state_province:
type: string
maxLength: 100
postal_code:
type: string
maxLength: 20
country:
type: string
maxLength: 100
bedrooms:
type: integer
nullable: true
bathrooms:
type: string
description: Decimal value (e.g. "2.5")
nullable: true
square_footage:
type: integer
nullable: true
lot_size:
type: string
description: Decimal value
nullable: true
year_built:
type: integer
nullable: true
description:
type: string
purchase_date:
type: string
format: date-time
nullable: true
purchase_price:
type: string
description: Decimal value (e.g. "350000.00")
nullable: true
is_primary:
type: boolean
nullable: true
UpdateResidenceRequest:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 200
nullable: true
property_type_id:
type: integer
format: uint
nullable: true
street_address:
type: string
maxLength: 255
nullable: true
apartment_unit:
type: string
maxLength: 50
nullable: true
city:
type: string
maxLength: 100
nullable: true
state_province:
type: string
maxLength: 100
nullable: true
postal_code:
type: string
maxLength: 20
nullable: true
country:
type: string
maxLength: 100
nullable: true
bedrooms:
type: integer
nullable: true
bathrooms:
type: string
nullable: true
square_footage:
type: integer
nullable: true
lot_size:
type: string
nullable: true
year_built:
type: integer
nullable: true
description:
type: string
nullable: true
purchase_date:
type: string
format: date-time
nullable: true
purchase_price:
type: string
nullable: true
is_primary:
type: boolean
nullable: true
JoinWithCodeRequest:
type: object
required: [code]
properties:
code:
type: string
minLength: 6
maxLength: 6
GenerateShareCodeRequest:
type: object
properties:
expires_in_hours:
type: integer
description: Hours until code expires (default 24)
ResidenceUserResponse:
type: object
properties:
id:
type: integer
format: uint
username:
type: string
email:
type: string
format: email
first_name:
type: string
last_name:
type: string
ResidenceResponse:
type: object
properties:
id:
type: integer
format: uint
owner_id:
type: integer
format: uint
owner:
$ref: '#/components/schemas/ResidenceUserResponse'
users:
type: array
items:
$ref: '#/components/schemas/ResidenceUserResponse'
name:
type: string
property_type_id:
type: integer
format: uint
nullable: true
property_type:
$ref: '#/components/schemas/ResidenceTypeResponse'
street_address:
type: string
apartment_unit:
type: string
city:
type: string
state_province:
type: string
postal_code:
type: string
country:
type: string
bedrooms:
type: integer
nullable: true
bathrooms:
type: string
nullable: true
description: Decimal
square_footage:
type: integer
nullable: true
lot_size:
type: string
nullable: true
description: Decimal
year_built:
type: integer
nullable: true
description:
type: string
purchase_date:
type: string
format: date-time
nullable: true
purchase_price:
type: string
nullable: true
description: Decimal
is_primary:
type: boolean
is_active:
type: boolean
overdue_count:
type: integer
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
TotalSummary:
type: object
properties:
total_residences:
type: integer
total_tasks:
type: integer
total_pending:
type: integer
total_overdue:
type: integer
tasks_due_next_week:
type: integer
tasks_due_next_month:
type: integer
MyResidencesResponse:
type: object
properties:
residences:
type: array
items:
$ref: '#/components/schemas/ResidenceResponse'
ResidenceWithSummaryResponse:
type: object
properties:
data:
$ref: '#/components/schemas/ResidenceResponse'
summary:
$ref: '#/components/schemas/TotalSummary'
ResidenceDeleteWithSummaryResponse:
type: object
properties:
data:
type: string
summary:
$ref: '#/components/schemas/TotalSummary'
ShareCodeResponse:
type: object
properties:
id:
type: integer
format: uint
code:
type: string
residence_id:
type: integer
format: uint
created_by_id:
type: integer
format: uint
is_active:
type: boolean
expires_at:
type: string
format: date-time
nullable: true
created_at:
type: string
format: date-time
JoinResidenceResponse:
type: object
properties:
message:
type: string
residence:
$ref: '#/components/schemas/ResidenceResponse'
summary:
$ref: '#/components/schemas/TotalSummary'
GenerateShareCodeResponse:
type: object
properties:
message:
type: string
share_code:
$ref: '#/components/schemas/ShareCodeResponse'
SharePackageResponse:
type: object
properties:
share_code:
type: string
residence_name:
type: string
shared_by:
type: string
expires_at:
type: string
format: date-time
nullable: true
# =========================================================================
# Task
# =========================================================================
CreateTaskRequest:
type: object
required: [residence_id, title]
properties:
residence_id:
type: integer
format: uint
title:
type: string
minLength: 1
maxLength: 200
description:
type: string
category_id:
type: integer
format: uint
nullable: true
priority_id:
type: integer
format: uint
nullable: true
frequency_id:
type: integer
format: uint
nullable: true
custom_interval_days:
type: integer
nullable: true
description: For "Custom" frequency, user-specified interval in days
in_progress:
type: boolean
default: false
assigned_to_id:
type: integer
format: uint
nullable: true
due_date:
type: string
description: Accepts "2025-11-27" or "2025-11-27T00:00:00Z"
nullable: true
estimated_cost:
type: string
description: Decimal value (e.g. "150.00")
nullable: true
contractor_id:
type: integer
format: uint
nullable: true
UpdateTaskRequest:
type: object
properties:
title:
type: string
minLength: 1
maxLength: 200
nullable: true
description:
type: string
nullable: true
category_id:
type: integer
format: uint
nullable: true
priority_id:
type: integer
format: uint
nullable: true
frequency_id:
type: integer
format: uint
nullable: true
custom_interval_days:
type: integer
nullable: true
in_progress:
type: boolean
nullable: true
assigned_to_id:
type: integer
format: uint
nullable: true
due_date:
type: string
nullable: true
estimated_cost:
type: string
nullable: true
actual_cost:
type: string
nullable: true
contractor_id:
type: integer
format: uint
nullable: true
TaskUserResponse:
type: object
properties:
id:
type: integer
format: uint
username:
type: string
email:
type: string
format: email
first_name:
type: string
last_name:
type: string
TaskResponse:
type: object
properties:
id:
type: integer
format: uint
residence_id:
type: integer
format: uint
created_by_id:
type: integer
format: uint
created_by:
$ref: '#/components/schemas/TaskUserResponse'
assigned_to_id:
type: integer
format: uint
nullable: true
assigned_to:
$ref: '#/components/schemas/TaskUserResponse'
title:
type: string
description:
type: string
category_id:
type: integer
format: uint
nullable: true
category:
$ref: '#/components/schemas/TaskCategoryResponse'
priority_id:
type: integer
format: uint
nullable: true
priority:
$ref: '#/components/schemas/TaskPriorityResponse'
frequency_id:
type: integer
format: uint
nullable: true
frequency:
$ref: '#/components/schemas/TaskFrequencyResponse'
in_progress:
type: boolean
due_date:
type: string
format: date
nullable: true
next_due_date:
type: string
format: date
nullable: true
description: For recurring tasks, updated after each completion
estimated_cost:
type: string
nullable: true
description: Decimal
actual_cost:
type: string
nullable: true
description: Decimal
contractor_id:
type: integer
format: uint
nullable: true
is_cancelled:
type: boolean
is_archived:
type: boolean
parent_task_id:
type: integer
format: uint
nullable: true
completion_count:
type: integer
kanban_column:
type: string
description: "Computed column name: overdue, due_soon, upcoming, completed, in_progress, archived, cancelled"
enum: [overdue, due_soon, upcoming, completed, in_progress, archived, cancelled]
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
KanbanColumnResponse:
type: object
properties:
name:
type: string
description: Column identifier
display_name:
type: string
description: Human-readable column name
button_types:
type: array
items:
type: string
description: Available action button types for tasks in this column
icons:
type: object
additionalProperties:
type: string
description: Platform-specific icon names (ios, android)
color:
type: string
description: Hex color for the column
tasks:
type: array
items:
$ref: '#/components/schemas/TaskResponse'
count:
type: integer
KanbanBoardResponse:
type: object
properties:
columns:
type: array
items:
$ref: '#/components/schemas/KanbanColumnResponse'
days_threshold:
type: integer
description: Number of days used for "due soon" calculation (default 30)
residence_id:
type: string
description: '"all" or the specific residence ID'
TaskWithSummaryResponse:
type: object
properties:
data:
$ref: '#/components/schemas/TaskResponse'
summary:
$ref: '#/components/schemas/TotalSummary'
DeleteWithSummaryResponse:
type: object
properties:
data:
type: string
summary:
$ref: '#/components/schemas/TotalSummary'
# =========================================================================
# Task Completion
# =========================================================================
CreateTaskCompletionRequest:
type: object
required: [task_id]
properties:
task_id:
type: integer
format: uint
completed_at:
type: string
format: date-time
nullable: true
description: Defaults to now if not provided
notes:
type: string
actual_cost:
type: string
nullable: true
description: Decimal
rating:
type: integer
minimum: 1
maximum: 5
nullable: true
image_urls:
type: array
items:
type: string
description: URLs of uploaded images to attach
UpdateTaskCompletionRequest:
type: object
properties:
notes:
type: string
nullable: true
actual_cost:
type: string
nullable: true
rating:
type: integer
minimum: 1
maximum: 5
nullable: true
image_urls:
type: array
items:
type: string
description: Replaces all existing image associations
TaskCompletionImageResponse:
type: object
properties:
id:
type: integer
format: uint
image_url:
type: string
description: Direct S3 URL (may require auth)
media_url:
type: string
description: "Authenticated proxy endpoint: /api/media/completion-image/{id}"
caption:
type: string
TaskCompletionResponse:
type: object
properties:
id:
type: integer
format: uint
task_id:
type: integer
format: uint
completed_by:
$ref: '#/components/schemas/TaskUserResponse'
completed_at:
type: string
format: date-time
notes:
type: string
actual_cost:
type: string
nullable: true
description: Decimal
rating:
type: integer
nullable: true
images:
type: array
items:
$ref: '#/components/schemas/TaskCompletionImageResponse'
created_at:
type: string
format: date-time
task:
$ref: '#/components/schemas/TaskResponse'
description: Included after create/quick-complete with updated task state
TaskCompletionWithSummaryResponse:
type: object
properties:
data:
$ref: '#/components/schemas/TaskCompletionResponse'
summary:
$ref: '#/components/schemas/TotalSummary'
# =========================================================================
# Contractor
# =========================================================================
CreateContractorRequest:
type: object
required: [name]
properties:
residence_id:
type: integer
format: uint
nullable: true
name:
type: string
minLength: 1
maxLength: 200
company:
type: string
maxLength: 200
phone:
type: string
maxLength: 20
email:
type: string
format: email
maxLength: 254
website:
type: string
maxLength: 200
notes:
type: string
street_address:
type: string
maxLength: 255
city:
type: string
maxLength: 100
state_province:
type: string
maxLength: 100
postal_code:
type: string
maxLength: 20
specialty_ids:
type: array
items:
type: integer
format: uint
rating:
type: number
format: double
nullable: true
is_favorite:
type: boolean
nullable: true
UpdateContractorRequest:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 200
nullable: true
company:
type: string
maxLength: 200
nullable: true
phone:
type: string
maxLength: 20
nullable: true
email:
type: string
format: email
maxLength: 254
nullable: true
website:
type: string
maxLength: 200
nullable: true
notes:
type: string
nullable: true
street_address:
type: string
maxLength: 255
nullable: true
city:
type: string
maxLength: 100
nullable: true
state_province:
type: string
maxLength: 100
nullable: true
postal_code:
type: string
maxLength: 20
nullable: true
specialty_ids:
type: array
items:
type: integer
format: uint
rating:
type: number
format: double
nullable: true
is_favorite:
type: boolean
nullable: true
residence_id:
type: integer
format: uint
nullable: true
ContractorResponse:
type: object
properties:
id:
type: integer
format: uint
residence_id:
type: integer
format: uint
nullable: true
created_by_id:
type: integer
format: uint
added_by:
type: integer
format: uint
description: Alias for created_by_id (KMM compatibility)
created_by:
type: object
properties:
id:
type: integer
format: uint
username:
type: string
first_name:
type: string
last_name:
type: string
name:
type: string
company:
type: string
phone:
type: string
email:
type: string
website:
type: string
notes:
type: string
street_address:
type: string
city:
type: string
state_province:
type: string
postal_code:
type: string
specialties:
type: array
items:
$ref: '#/components/schemas/ContractorSpecialtyResponse'
rating:
type: number
format: double
nullable: true
is_favorite:
type: boolean
is_active:
type: boolean
task_count:
type: integer
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
ToggleFavoriteResponse:
type: object
properties:
message:
type: string
is_favorite:
type: boolean
# =========================================================================
# Document
# =========================================================================
DocumentType:
type: string
enum: [general, warranty, receipt, contract, insurance, manual]
CreateDocumentRequest:
type: object
required: [residence_id, title]
properties:
residence_id:
type: integer
format: uint
title:
type: string
minLength: 1
maxLength: 200
description:
type: string
document_type:
$ref: '#/components/schemas/DocumentType'
file_url:
type: string
maxLength: 500
file_name:
type: string
maxLength: 255
file_size:
type: integer
format: int64
nullable: true
mime_type:
type: string
maxLength: 100
purchase_date:
type: string
format: date-time
nullable: true
expiry_date:
type: string
format: date-time
nullable: true
purchase_price:
type: string
nullable: true
description: Decimal
vendor:
type: string
maxLength: 200
serial_number:
type: string
maxLength: 100
model_number:
type: string
maxLength: 100
task_id:
type: integer
format: uint
nullable: true
image_urls:
type: array
items:
type: string
description: URLs of uploaded images to attach
UpdateDocumentRequest:
type: object
properties:
title:
type: string
minLength: 1
maxLength: 200
nullable: true
description:
type: string
nullable: true
document_type:
$ref: '#/components/schemas/DocumentType'
file_url:
type: string
maxLength: 500
nullable: true
file_name:
type: string
maxLength: 255
nullable: true
file_size:
type: integer
format: int64
nullable: true
mime_type:
type: string
maxLength: 100
nullable: true
purchase_date:
type: string
format: date-time
nullable: true
expiry_date:
type: string
format: date-time
nullable: true
purchase_price:
type: string
nullable: true
vendor:
type: string
maxLength: 200
nullable: true
serial_number:
type: string
maxLength: 100
nullable: true
model_number:
type: string
maxLength: 100
nullable: true
task_id:
type: integer
format: uint
nullable: true
DocumentImageResponse:
type: object
properties:
id:
type: integer
format: uint
image_url:
type: string
media_url:
type: string
description: "Authenticated proxy endpoint: /api/media/document-image/{id}"
caption:
type: string
DocumentResponse:
type: object
properties:
id:
type: integer
format: uint
residence_id:
type: integer
format: uint
residence:
type: integer
format: uint
description: Alias for residence_id (KMM compatibility)
created_by_id:
type: integer
format: uint
created_by:
type: object
properties:
id:
type: integer
format: uint
username:
type: string
first_name:
type: string
last_name:
type: string
title:
type: string
description:
type: string
document_type:
$ref: '#/components/schemas/DocumentType'
file_url:
type: string
media_url:
type: string
description: "Authenticated proxy endpoint: /api/media/document/{id}"
file_name:
type: string
file_size:
type: integer
format: int64
nullable: true
mime_type:
type: string
purchase_date:
type: string
format: date-time
nullable: true
expiry_date:
type: string
format: date-time
nullable: true
purchase_price:
type: string
nullable: true
description: Decimal
vendor:
type: string
serial_number:
type: string
model_number:
type: string
task_id:
type: integer
format: uint
nullable: true
is_active:
type: boolean
images:
type: array
items:
$ref: '#/components/schemas/DocumentImageResponse'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
# =========================================================================
# Notifications
# =========================================================================
NotificationType:
type: string
enum:
- task_due_soon
- task_overdue
- task_completed
- task_assigned
- residence_shared
- warranty_expiring
Notification:
type: object
properties:
id:
type: integer
format: uint
user_id:
type: integer
format: uint
notification_type:
$ref: '#/components/schemas/NotificationType'
title:
type: string
body:
type: string
task_id:
type: integer
format: uint
nullable: true
data:
type: string
description: JSON string with additional data
sent:
type: boolean
sent_at:
type: string
format: date-time
nullable: true
read:
type: boolean
read_at:
type: string
format: date-time
nullable: true
error_message:
type: string
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
NotificationListResponse:
type: object
properties:
count:
type: integer
results:
type: array
items:
$ref: '#/components/schemas/Notification'
NotificationPreference:
type: object
properties:
id:
type: integer
format: uint
user_id:
type: integer
format: uint
task_due_soon:
type: boolean
task_overdue:
type: boolean
task_completed:
type: boolean
task_assigned:
type: boolean
residence_shared:
type: boolean
warranty_expiring:
type: boolean
daily_digest:
type: boolean
email_task_completed:
type: boolean
task_due_soon_hour:
type: integer
nullable: true
description: UTC hour (0-23) for task due soon notifications
task_overdue_hour:
type: integer
nullable: true
description: UTC hour (0-23) for task overdue notifications
warranty_expiring_hour:
type: integer
nullable: true
daily_digest_hour:
type: integer
nullable: true
UpdatePreferencesRequest:
type: object
properties:
task_due_soon:
type: boolean
nullable: true
task_overdue:
type: boolean
nullable: true
task_completed:
type: boolean
nullable: true
task_assigned:
type: boolean
nullable: true
residence_shared:
type: boolean
nullable: true
warranty_expiring:
type: boolean
nullable: true
daily_digest:
type: boolean
nullable: true
email_task_completed:
type: boolean
nullable: true
task_due_soon_hour:
type: integer
nullable: true
task_overdue_hour:
type: integer
nullable: true
warranty_expiring_hour:
type: integer
nullable: true
daily_digest_hour:
type: integer
nullable: true
RegisterDeviceRequest:
type: object
required: [device_id, registration_id, platform]
properties:
name:
type: string
device_id:
type: string
registration_id:
type: string
description: APNs device token or FCM registration token
platform:
type: string
enum: [ios, android]
UnregisterDeviceRequest:
type: object
required: [registration_id]
properties:
registration_id:
type: string
platform:
type: string
enum: [ios, android]
default: ios
DeviceResponse:
type: object
properties:
id:
type: integer
format: uint
name:
type: string
device_id:
type: string
registration_id:
type: string
platform:
type: string
enum: [ios, android]
active:
type: boolean
date_created:
type: string
format: date-time
# =========================================================================
# Subscriptions
# =========================================================================
SubscriptionResponse:
type: object
properties:
tier:
type: string
enum: [free, pro]
subscribed_at:
type: string
format: date-time
nullable: true
expires_at:
type: string
format: date-time
nullable: true
auto_renew:
type: boolean
cancelled_at:
type: string
format: date-time
nullable: true
platform:
type: string
description: "ios or android"
is_active:
type: boolean
is_pro:
type: boolean
UsageResponse:
type: object
properties:
properties_count:
type: integer
format: int64
tasks_count:
type: integer
format: int64
contractors_count:
type: integer
format: int64
documents_count:
type: integer
format: int64
TierLimitsClientResponse:
type: object
properties:
properties:
type: integer
nullable: true
description: "null means unlimited"
tasks:
type: integer
nullable: true
contractors:
type: integer
nullable: true
documents:
type: integer
nullable: true
SubscriptionStatusResponse:
type: object
properties:
tier:
type: string
description: 'Subscription tier (free or pro)'
is_active:
type: boolean
description: Whether the subscription is currently active
subscribed_at:
type: string
format: date-time
nullable: true
expires_at:
type: string
format: date-time
nullable: true
auto_renew:
type: boolean
trial_start:
type: string
format: date-time
nullable: true
trial_end:
type: string
format: date-time
nullable: true
trial_active:
type: boolean
subscription_source:
type: string
nullable: true
description: 'Platform source of the active subscription (ios, android, stripe, or null)'
usage:
$ref: '#/components/schemas/UsageResponse'
limits:
type: object
additionalProperties:
$ref: '#/components/schemas/TierLimitsClientResponse'
description: 'Map of tier name -> limits (e.g. {"free": {...}, "pro": {...}})'
limitations_enabled:
type: boolean
description: Whether subscription limitations are currently enforced
UpgradeTriggerResponse:
type: object
properties:
trigger_key:
type: string
title:
type: string
message:
type: string
promo_html:
type: string
button_text:
type: string
FeatureBenefit:
type: object
properties:
id:
type: integer
format: uint
feature_name:
type: string
free_tier_text:
type: string
pro_tier_text:
type: string
display_order:
type: integer
is_active:
type: boolean
Promotion:
type: object
properties:
id:
type: integer
format: uint
promotion_id:
type: string
title:
type: string
message:
type: string
link:
type: string
nullable: true
start_date:
type: string
format: date-time
end_date:
type: string
format: date-time
target_tier:
type: string
enum: [free, pro]
is_active:
type: boolean
ProcessPurchaseRequest:
type: object
required: [platform]
properties:
receipt_data:
type: string
description: iOS StoreKit 1 receipt or StoreKit 2 JWS
transaction_id:
type: string
description: iOS StoreKit 2 transaction ID
purchase_token:
type: string
description: Android Google Play purchase token
product_id:
type: string
description: Android product ID (helps identify subscription)
platform:
type: string
enum: [ios, android]
# =========================================================================
# Uploads
# =========================================================================
UploadResult:
type: object
properties:
url:
type: string
description: URL of the uploaded file
file_name:
type: string
file_size:
type: integer
format: int64
mime_type:
type: string