Remediate all P0-S priority findings from cross-platform architecture audit: - Add input validation and authorization checks across handlers - Harden social auth (Apple/Google) token validation - Add document ownership verification and file type validation - Add rate limiting config and CORS origin restrictions - Add subscription tier enforcement in handlers - Add OpenAPI 3.0.3 spec (81 schemas, 104 operations) - Add URL-level contract test (KMP API routes match spec paths) - Add model-level contract test (65 schemas, 464 fields validated) - Add CI workflow for backend tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4524 lines
119 KiB
YAML
4524 lines
119 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: Casera (MyCrib) API
|
|
description: |
|
|
REST API for the Casera 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: Casera Team
|
|
|
|
servers:
|
|
- url: https://mycrib.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/{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 (.casera 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: []
|
|
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'
|
|
|
|
# ===========================================================================
|
|
# 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:
|
|
subscribed_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
expires_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
auto_renew:
|
|
type: boolean
|
|
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
|