openapi: "3.0.3"
info:
  title: backq API
  description: Complete REST API for the backq ticket support system.
  version: "1.0.0"
  contact:
    name: backq
    url: https://lite.backq.io

servers:
  - url: https://lite.backq.io
    description: Production
  - url: http://localhost:3000
    description: Local development

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "API token generated from the profile page. Format: bq_xxxxx"

security:
  - bearerAuth: []

tags:
  - name: Authentication
    description: Register new accounts and manage authentication sessions.
  - name: User Profile
    description: "Manage the authenticated user's own profile, password, language preference, and theme."
  - name: API Tokens
    description: Create and manage personal API tokens for programmatic access. Tokens use the `bq_` prefix.
  - name: Workspaces
    description: Manage workspaces — the top-level organizational unit. Each user gets a personal workspace on registration.
  - name: Projects
    description: Manage projects within workspaces. All project endpoints require workspace context via the `X-Workspace-Id` header.
  - name: Tickets
    description: Core ticketing system. Create, update, search, and manage tickets. All list endpoints require workspace context.
  - name: Files
    description: Download and manage file attachments. The download route provides authenticated, access-checked delivery of attachment bytes.
  - name: Comments
    description: Edit and delete individual comments.
  - name: Timesheet
    description: Track time entries, manage timers, and handle timesheet approval workflows.
  - name: Expenses
    description: Submit, manage, and approve expense reports with multi-currency support and file attachments.
  - name: Notifications
    description: Retrieve and mark notifications for the authenticated user.
  - name: Customer Portal
    description: Endpoints for the customer-facing self-service portal. Customers submit and track their own tickets without a full account.
  - name: Health
    description: Infrastructure health check endpoints. No authentication required.

paths:
  /api/auth/register:
    post:
      summary: Register a new user account
      description: Creates a new user account and automatically creates a personal workspace for the user. Returns the created user object.
      operationId: auth-register
      tags:
        - Authentication
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - email
                - password
              properties:
                name:
                  type: string
                  description: Full name of the user
                  example: Jane Smith
                email:
                  type: string
                  description: Email address (must be unique)
                  example: jane@example.com
                password:
                  type: string
                  description: Password (min 6 characters)
                  example: secret123
                language:
                  type: string
                  description: Preferred language code (en or th)
                  example: en
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Account created successfully
                  user:
                    type: object
                    properties:
                      id:
                        type: string
                        example: cuid
                      name:
                        type: string
                        example: Jane Smith
                      email:
                        type: string
                        example: jane@example.com
                      role:
                        type: string
                        example: USER
                      language:
                        type: string
                        example: en
                      createdAt:
                        type: string
                        example: "2024-01-01T00:00:00.000Z"
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Email already registered
  /api/auth/signin:
    post:
      summary: Sign in (NextAuth credentials)
      description: Sign in using the NextAuth.js credentials provider. Use the standard NextAuth flow or the JSON body approach for API access.
      operationId: auth-signin
      tags:
        - Authentication
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
                - password
              properties:
                email:
                  type: string
                  description: Registered email address
                  example: jane@example.com
                password:
                  type: string
                  description: Account password
                  example: secret123
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  url:
                    type: string
                    example: /
                  ok:
                    type: boolean
                    example: true
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: CredentialsSignin
  /api/user/profile:
    get:
      summary: Get current user profile
      description: "Returns the authenticated user's profile including activity counts."
      operationId: user-profile-get
      tags:
        - User Profile
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  user:
                    type: object
                    properties:
                      id:
                        type: string
                        example: cuid
                      name:
                        type: string
                        example: Jane Smith
                      email:
                        type: string
                        example: jane@example.com
                      role:
                        type: string
                        example: USER
                      avatarUrl:
                        type: string
                        nullable: true
                      createdAt:
                        type: string
                        example: "2024-01-01T00:00:00.000Z"
                      _count:
                        type: object
                        properties:
                          reportedTickets:
                            type: integer
                            example: 5
                          assignedTickets:
                            type: integer
                            example: 3
                          comments:
                            type: integer
                            example: 12
                          projectMembers:
                            type: integer
                            example: 2
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
    put:
      summary: Update user profile
      description: "Updates the authenticated user's name and email."
      operationId: user-profile-put
      tags:
        - User Profile
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - email
              properties:
                name:
                  type: string
                  description: Display name
                  example: Jane Smith
                email:
                  type: string
                  description: Email address
                  example: jane@example.com
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  user:
                    type: object
                    properties:
                      id:
                        type: string
                        example: cuid
                      name:
                        type: string
                        example: Jane Smith
                      email:
                        type: string
                        example: jane@example.com
                      role:
                        type: string
                        example: USER
                      avatarUrl:
                        type: string
                        nullable: true
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
  /api/user/password:
    put:
      summary: Change password
      description: "Changes the authenticated user's password. Requires the current password for verification."
      operationId: user-password
      tags:
        - User Profile
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - currentPassword
                - newPassword
                - confirmPassword
              properties:
                currentPassword:
                  type: string
                  description: Current password
                newPassword:
                  type: string
                  description: New password (min 6 characters)
                confirmPassword:
                  type: string
                  description: Must match newPassword
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Password changed successfully
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Current password is incorrect
  /api/user/language:
    patch:
      summary: Update language preference
      description: Sets the UI language preference for the authenticated user.
      operationId: user-language
      tags:
        - User Profile
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - language
              properties:
                language:
                  type: string
                  enum:
                    - en
                    - th
                  description: Language code
                  example: en
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Language preference updated successfully
                  user:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clxyz123
                      name:
                        type: string
                        example: Jane Smith
                      email:
                        type: string
                        example: jane@example.com
                      language:
                        type: string
                        example: th
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
  /api/user/theme:
    patch:
      summary: Update theme preference
      description: Sets the UI theme (light/dark/system) for the authenticated user.
      operationId: user-theme
      tags:
        - User Profile
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - theme
              properties:
                theme:
                  type: string
                  enum:
                    - light
                    - dark
                    - system
                  description: Theme preference
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Theme preference updated successfully
                  user:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clxyz123
                      name:
                        type: string
                        example: Jane Smith
                      email:
                        type: string
                        example: jane@example.com
                      theme:
                        type: string
                        example: dark
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
  /api/me:
    get:
      summary: Identify the token owner
      description: "Returns the user who owns the credential used for this request. Use it to verify which account an API token belongs to. When authenticated with a `bq_` token, the response also includes that token's metadata (name, prefix, created/last-used/expiry timestamps). Works with both Bearer-token and session-cookie authentication. Raw token values are never returned."
      operationId: me
      tags:
        - API Tokens
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      authMethod:
                        type: string
                        example: api_token
                      user:
                        type: object
                        properties:
                          id:
                            type: string
                            example: cuid
                          name:
                            type: string
                            example: Jane Smith
                          email:
                            type: string
                            example: jane@example.com
                          role:
                            type: string
                            example: USER
                          avatarUrl:
                            type: string
                            nullable: true
                      token:
                        type: object
                        properties:
                          id:
                            type: string
                            example: cuid
                          name:
                            type: string
                            example: CI/CD Pipeline
                          tokenPrefix:
                            type: string
                            example: bq_abc12def
                          createdAt:
                            type: string
                            example: "2024-01-01T00:00:00.000Z"
                          lastUsedAt:
                            type: string
                            example: "2024-03-10T09:00:00.000Z"
                          expiresAt:
                            type: string
                            nullable: true
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Invalid or expired token
  /api/user/tokens:
    get:
      summary: List API tokens
      description: Returns all API tokens belonging to the authenticated user. Token values are not returned after creation.
      operationId: tokens-list
      tags:
        - API Tokens
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: cuid
                        name:
                          type: string
                          example: My Token
                        lastUsedAt:
                          type: string
                          nullable: true
                        expiresAt:
                          type: string
                          nullable: true
                        createdAt:
                          type: string
                          example: "2024-01-01T00:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
    post:
      summary: Create an API token
      description: Creates a new API token. The full token value (prefixed with `bq_`) is returned only once in the response.
      operationId: tokens-create
      tags:
        - API Tokens
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  description: Descriptive token name
                  example: CI/CD Pipeline
                expiresInDays:
                  type: number
                  description: Token expiry in days (1–365). Omit for no expiry.
                  example: 90
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: cuid
                      name:
                        type: string
                        example: CI/CD Pipeline
                      token:
                        type: string
                        example: bq_xxxxxxxxxxxxxxxxxxxx
                      expiresAt:
                        type: string
                        example: "2024-04-01T00:00:00.000Z"
                      createdAt:
                        type: string
                        example: "2024-01-01T00:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
  /api/user/tokens/{id}:
    delete:
      summary: Delete an API token
      description: Revokes and permanently deletes an API token by its ID.
      operationId: tokens-delete
      tags:
        - API Tokens
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Token ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Token deleted
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
  /api/workspaces:
    get:
      summary: List all accessible workspaces
      description: Returns all workspaces the authenticated user can access (via direct membership, team membership, or project membership).
      operationId: workspaces-list
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: cuid
                        name:
                          type: string
                          example: Acme Corp
                        slug:
                          type: string
                          example: acme
                        isPersonal:
                          type: boolean
                          example: false
                        role:
                          type: string
                          example: OWNER
                        memberCount:
                          type: integer
                          example: 5
                        subscription:
                          type: object
                          properties:
                            plan:
                              type: string
                              example: LITE
                        accessVia:
                          type: string
                          example: direct
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
    post:
      summary: Create a workspace
      description: Creates a new workspace. Free plan users are limited to one free workspace.
      operationId: workspaces-create
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - slug
              properties:
                name:
                  type: string
                  description: Workspace display name
                  example: Acme Corp
                slug:
                  type: string
                  description: Unique URL slug (lowercase, hyphens allowed)
                  example: acme-corp
                plan:
                  type: string
                  enum:
                    - FREE
                    - LITE
                  description: Subscription plan
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clws001abc
                      name:
                        type: string
                        example: Acme Corp
                      slug:
                        type: string
                        example: acme-corp
                      isPersonal:
                        type: boolean
                        example: false
                      status:
                        type: string
                        example: ACTIVE
                      createdAt:
                        type: string
                        example: "2024-03-01T08:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Slug already taken
  /api/workspaces/{id}:
    get:
      summary: Get workspace details
      description: Returns full details of a specific workspace including members, settings, and subscription.
      operationId: workspaces-get
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Workspace ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clws001abc
                      name:
                        type: string
                        example: Acme Corp
                      slug:
                        type: string
                        example: acme-corp
                      logoUrl:
                        type: string
                        nullable: true
                      status:
                        type: string
                        example: ACTIVE
                      isPersonal:
                        type: boolean
                        example: false
                      createdAt:
                        type: string
                        example: "2024-03-01T08:00:00.000Z"
                      updatedAt:
                        type: string
                        example: "2024-03-15T10:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Workspace not found or access denied
    patch:
      summary: Update workspace
      description: Updates workspace name, slug, or logo. Requires OWNER or ADMIN role in the workspace.
      operationId: workspaces-update
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Workspace ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: New workspace name
                slug:
                  type: string
                  description: New URL slug
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clws001abc
                      name:
                        type: string
                        example: Acme Corp Updated
                      slug:
                        type: string
                        example: acme-corp
                      logoUrl:
                        type: string
                        nullable: true
                      status:
                        type: string
                        example: ACTIVE
                      isPersonal:
                        type: boolean
                        example: false
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Workspace not found or access denied
  /api/workspaces/current:
    get:
      summary: Get current workspace context
      description: Returns the workspace resolved from the `X-Workspace-Id` header or the `workspace-id` cookie.
      operationId: workspaces-current
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      parameters:
        - name: X-Workspace-Id
          in: header
          required: false
          description: Workspace ID to resolve context for
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clws001abc
                      name:
                        type: string
                        example: Acme Corp
                      slug:
                        type: string
                        example: acme-corp
                      logoUrl:
                        type: string
                        nullable: true
                      status:
                        type: string
                        example: ACTIVE
                      isPersonal:
                        type: boolean
                        example: false
                      role:
                        type: string
                        example: OWNER
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: string
                    nullable: true
                  message:
                    type: string
                    example: No workspace selected
  /api/workspaces/{id}/members:
    get:
      summary: List workspace members
      description: Returns all members of a workspace with their roles.
      operationId: workspaces-members
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Workspace ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      activeMembers:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: clm001
                            userId:
                              type: string
                              example: clxyz123
                            role:
                              type: string
                              example: OWNER
                            joinedAt:
                              type: string
                              example: "2024-03-01T08:00:00.000Z"
                            user:
                              type: object
                              properties:
                                id:
                                  type: string
                                  example: clxyz123
                                name:
                                  type: string
                                  example: Jane Smith
                                email:
                                  type: string
                                  example: jane@example.com
                                avatarUrl:
                                  type: string
                                  nullable: true
                      pendingInvitations:
                        type: array
                        items:
                          type: object
                      seatUsage:
                        type: object
                        properties:
                          used:
                            type: integer
                            example: 1
                          limit:
                            type: integer
                            example: 5
                      userRole:
                        type: string
                        example: OWNER
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Workspace not found or access denied
  /api/workspaces/{id}/leave:
    post:
      summary: Leave a workspace
      description: Removes the authenticated user from the workspace. Owners cannot leave; they must transfer ownership first.
      operationId: workspaces-leave
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Workspace ID
          schema:
            type: string
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Successfully left the workspace
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Owners cannot leave the workspace. Transfer ownership first.
                  code:
                    type: string
                    example: OWNER_CANNOT_LEAVE
  /api/workspaces/{id}/transfer-ownership:
    post:
      summary: Transfer workspace ownership
      description: Transfers ownership of the workspace to another member. Only the current owner can do this.
      operationId: workspaces-transfer
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Workspace ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - newOwnerId
              properties:
                newOwnerId:
                  type: string
                  description: User ID of the new owner
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Ownership transferred successfully
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Only the owner can transfer ownership
  /api/workspaces/{id}/seats:
    get:
      summary: Get seat usage
      description: Returns the current seat count and limit for the workspace subscription.
      operationId: workspaces-seats
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Workspace ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  workspace:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clws001abc
                      name:
                        type: string
                        example: Acme Corp
                  usage:
                    type: object
                    properties:
                      used:
                        type: integer
                        example: 3
                      limit:
                        type: integer
                        example: 5
                      remaining:
                        type: integer
                        example: 2
                      isAtLimit:
                        type: boolean
                        example: false
                  breakdown:
                    type: object
                    properties:
                      directMembers:
                        type: integer
                        example: 3
                      pendingInvitations:
                        type: integer
                        example: 0
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Workspace not found
  /api/workspaces/{id}/subscription:
    get:
      summary: Get subscription details
      description: Returns the subscription plan, status, seat limit, and billing information for a workspace.
      operationId: workspaces-subscription
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Workspace ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  subscription:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clsub001
                      plan:
                        type: string
                        example: LITE
                      status:
                        type: string
                        example: ACTIVE
                      seatLimit:
                        type: integer
                        example: 10
                      currentPeriodStart:
                        type: string
                        example: "2024-03-01T00:00:00.000Z"
                      currentPeriodEnd:
                        type: string
                        example: "2024-04-01T00:00:00.000Z"
                      workspace:
                        type: object
                        properties:
                          id:
                            type: string
                            example: clws001abc
                          name:
                            type: string
                            example: Acme Corp
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Workspace not found or access denied
  /api/workspaces/{id}/invoices:
    get:
      summary: List workspace invoices
      description: Returns the billing invoice history for a workspace.
      operationId: workspaces-invoices
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Workspace ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: clinv001
                        invoiceNumber:
                          type: string
                          example: INV-2024-001
                        status:
                          type: string
                          example: PAID
                        baseFee:
                          type: integer
                          example: 49
                        seatCount:
                          type: integer
                          example: 5
                        extraSeats:
                          type: integer
                          example: 0
                        totalAmount:
                          type: integer
                          example: 49
                        periodStart:
                          type: string
                          example: "2024-03-01T00:00:00.000Z"
                        periodEnd:
                          type: string
                          example: "2024-04-01T00:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Workspace not found or access denied
  /api/workspaces/{id}/sub-statuses:
    get:
      summary: List custom sub-statuses
      description: Returns custom sub-status definitions configured for the workspace.
      operationId: workspaces-sub-statuses
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Workspace ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: clss001
                        key:
                          type: string
                          example: waiting_customer
                        label:
                          type: string
                          example: Waiting on Customer
                        color:
                          type: string
                          example: "#f59e0b"
                        description:
                          type: string
                          nullable: true
                        order:
                          type: integer
                          example: 0
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Internal server error
  /api/workspaces/check-slug:
    get:
      summary: Check slug availability
      description: Checks whether a workspace slug is available for use.
      operationId: workspaces-check-slug
      tags:
        - Workspaces
      security:
        - bearerAuth: []
      parameters:
        - name: slug
          in: query
          required: true
          description: Slug to check
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      available:
                        type: boolean
                        example: true
                      slug:
                        type: string
                        example: my-workspace
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      available:
                        type: boolean
                        example: false
                      slug:
                        type: string
                        example: my-workspace
                      reason:
                        type: string
                        example: TAKEN
                      suggestions:
                        type: array
                        items:
                          type: string
                          example: my-workspace-2
  /api/projects:
    get:
      summary: List projects
      description: Returns all projects in the current workspace. Workspace admins see all projects; members only see projects they belong to.
      operationId: projects-list
      tags:
        - Projects
      security:
        - bearerAuth: []
      parameters:
        - name: X-Workspace-Id
          in: header
          required: true
          description: Target workspace ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  projects:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: clproj001
                        name:
                          type: string
                          example: Backend API
                        key:
                          type: string
                          example: API
                        description:
                          type: string
                          example: Core API project
                        createdAt:
                          type: string
                          example: "2024-03-01T08:00:00.000Z"
                  workspace:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clws001abc
                      role:
                        type: string
                        example: OWNER
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Failed to fetch projects
    post:
      summary: Create a project
      description: Creates a new project in the workspace. Requires at least ADMIN role in the workspace.
      operationId: projects-create
      tags:
        - Projects
      security:
        - bearerAuth: []
      parameters:
        - name: X-Workspace-Id
          in: header
          required: true
          description: Target workspace ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - key
              properties:
                name:
                  type: string
                  description: Project name
                  example: Backend API
                key:
                  type: string
                  description: Short uppercase project key (2–10 chars, e.g. API)
                  example: API
                description:
                  type: string
                  description: Project description
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  project:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clproj001
                      name:
                        type: string
                        example: Backend API
                      key:
                        type: string
                        example: API
                      description:
                        type: string
                        example: Core API project
                      createdAt:
                        type: string
                        example: "2024-03-01T08:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Project key already exists in this workspace
  /api/projects/{id}:
    get:
      summary: Get project details
      description: Returns detailed information about a specific project including member list and settings.
      operationId: projects-get
      tags:
        - Projects
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Project ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  project:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clproj001
                      name:
                        type: string
                        example: Backend API
                      key:
                        type: string
                        example: API
                      description:
                        type: string
                        example: Core API project
                      defaultTicketType:
                        type: string
                        example: TASK
                      createdAt:
                        type: string
                        example: "2024-03-01T08:00:00.000Z"
                      members:
                        type: array
                        items:
                          type: object
                          properties:
                            userId:
                              type: string
                              example: clxyz123
                            role:
                              type: string
                              example: ADMIN
                            user:
                              type: object
                              properties:
                                name:
                                  type: string
                                  example: Jane Smith
                                email:
                                  type: string
                                  example: jane@example.com
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Project not found
    patch:
      summary: Update project
      description: Updates project name, description, or settings.
      operationId: projects-update
      tags:
        - Projects
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Project ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: New project name
                description:
                  type: string
                  description: New description
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  project:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clproj001
                      name:
                        type: string
                        example: Backend API v2
                      key:
                        type: string
                        example: API
                      description:
                        type: string
                        example: Core API project
                      updatedAt:
                        type: string
                        example: "2024-03-15T10:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Project not found
    delete:
      summary: Delete project
      description: Soft-deletes a project (sets deletedAt). All associated tickets become inaccessible.
      operationId: projects-delete
      tags:
        - Projects
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Project ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Project deleted successfully
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Project not found
  /api/projects/{id}/members:
    get:
      summary: List project members
      description: Returns all members of a project with their roles.
      operationId: projects-members
      tags:
        - Projects
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Project ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  members:
                    type: array
                    items:
                      type: object
                      properties:
                        userId:
                          type: string
                          example: clxyz123
                        role:
                          type: string
                          example: ADMIN
                        joinedAt:
                          type: string
                          example: "2024-03-01T08:00:00.000Z"
                        user:
                          type: object
                          properties:
                            id:
                              type: string
                              example: clxyz123
                            name:
                              type: string
                              example: Jane Smith
                            email:
                              type: string
                              example: jane@example.com
                            avatarUrl:
                              type: string
                              nullable: true
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Failed to fetch project members
  /api/projects/{id}/sprints:
    get:
      summary: List project sprints
      description: Returns all sprints for a project ordered by start date.
      operationId: projects-sprints
      tags:
        - Projects
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Project ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: clspr001
                        name:
                          type: string
                          example: Sprint 1
                        goal:
                          type: string
                          example: Fix login flow
                        startDate:
                          type: string
                          example: "2024-03-01T00:00:00.000Z"
                        endDate:
                          type: string
                          example: "2024-03-14T23:59:59.000Z"
                        status:
                          type: string
                          example: ACTIVE
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Failed to fetch sprints
  /api/tickets:
    get:
      summary: List tickets
      description: "Returns paginated tickets in the current workspace with optional filters. Feature 624 — each ticket includes `_count.subTasks`, the count of DIRECT sub-tasks only (archived sub-tasks included; deeper descendants not counted), alongside the existing `_count.comments` / `_count.attachments`. Each ticket also includes `parent` — `{ ticketNum, project: { key } }` when the ticket has a parent, or `null` when it has none."
      operationId: tickets-list
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: projectId
          in: query
          required: false
          description: Filter by project
          schema:
            type: string
        - name: status
          in: query
          required: false
          description: "Filter by status: OPEN, IN_PROGRESS, RESOLVED, CLOSED"
          schema:
            type: string
        - name: priority
          in: query
          required: false
          description: "Filter by priority: LOW, MEDIUM, HIGH, CRITICAL"
          schema:
            type: string
        - name: type
          in: query
          required: false
          description: "Filter by type: BUG, FEATURE_REQUEST, TASK, QUESTION, EPIC"
          schema:
            type: string
        - name: assigneeId
          in: query
          required: false
          description: Filter by assignee user ID
          schema:
            type: string
        - name: reporterId
          in: query
          required: false
          description: Filter by reporter user ID
          schema:
            type: string
        - name: search
          in: query
          required: false
          description: Full-text search on title and description
          schema:
            type: string
        - name: sprintId
          in: query
          required: false
          description: Filter by sprint ID
          schema:
            type: string
        - name: includeArchived
          in: query
          required: false
          description: "Include archived tickets (default: false)"
          schema:
            type: string
        - name: isOverdue
          in: query
          required: false
          description: Filter overdue tickets only
          schema:
            type: string
        - name: dateFrom
          in: query
          required: false
          description: Filter by creation date from (ISO 8601)
          schema:
            type: string
        - name: dateTo
          in: query
          required: false
          description: Filter by creation date to (ISO 8601)
          schema:
            type: string
        - name: X-Workspace-Id
          in: header
          required: true
          description: Target workspace ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  tickets:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: cltck001
                        ticketNum:
                          type: string
                          example: API-42
                        title:
                          type: string
                          example: Login button broken
                        status:
                          type: string
                          example: OPEN
                        priority:
                          type: string
                          example: HIGH
                        type:
                          type: string
                          example: BUG
                        createdAt:
                          type: string
                          example: "2024-03-10T09:00:00.000Z"
                        reporter:
                          type: object
                          properties:
                            id:
                              type: string
                              example: clxyz123
                            name:
                              type: string
                              example: Jane Smith
                        assignee:
                          type: string
                          nullable: true
                        _count:
                          type: object
                          properties:
                            comments:
                              type: integer
                              example: 3
                            attachments:
                              type: integer
                              example: 1
                            subTasks:
                              type: integer
                              example: 2
                        parent:
                          type: object
                          properties:
                            ticketNum:
                              type: integer
                              example: 7
                            project:
                              type: object
                              properties:
                                key:
                                  type: string
                                  example: API
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Failed to fetch tickets
    post:
      summary: Create a ticket
      description: Creates a new ticket in the specified project.
      operationId: tickets-create
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: X-Workspace-Id
          in: header
          required: true
          description: Target workspace ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - projectId
                - title
                - description
              properties:
                projectId:
                  type: string
                  description: Parent project ID
                title:
                  type: string
                  description: Ticket title
                description:
                  type: string
                  description: Ticket description (supports markdown)
                priority:
                  type: string
                  enum:
                    - LOW
                    - MEDIUM
                    - HIGH
                    - CRITICAL
                  description: Priority level
                type:
                  type: string
                  enum:
                    - BUG
                    - TASK
                    - FEATURE_REQUEST
                    - QUESTION
                    - EPIC
                  description: Ticket type
                assigneeId:
                  type: string
                  description: Assigned user ID
                dueDate:
                  type: string
                  description: Due date (ISO 8601)
                storyPoints:
                  type: number
                  description: Story point estimate
                sprintId:
                  type: string
                  description: Sprint ID to assign ticket to
                module:
                  type: string
                  description: Module/component name
                releaseVersion:
                  type: string
                  description: Target release version
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  ticket:
                    type: object
                    properties:
                      id:
                        type: string
                        example: cltck001
                      ticketNum:
                        type: string
                        example: API-42
                      title:
                        type: string
                        example: Login button broken
                      description:
                        type: string
                        example: The login button does not respond on mobile
                      status:
                        type: string
                        example: OPEN
                      priority:
                        type: string
                        example: HIGH
                      type:
                        type: string
                        example: BUG
                      projectId:
                        type: string
                        example: clproj001
                      createdAt:
                        type: string
                        example: "2024-03-10T09:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Project not found
  /api/tickets/{id}:
    get:
      summary: Get ticket details
      description: "Returns full ticket details including comments, attachments, activity log, and SLA status. Feature 624 — the ticket includes `_count.subTasks`, the count of DIRECT sub-tasks only (archived sub-tasks included; deeper descendants not counted), alongside the existing `_count.comments` / `_count.attachments`. It also includes `parent` — `{ ticketNum, project: { key } }` when the ticket has a parent, or `null` when it has none."
      operationId: tickets-get
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  ticket:
                    type: object
                    properties:
                      id:
                        type: string
                        example: cltck001
                      ticketNum:
                        type: string
                        example: API-42
                      title:
                        type: string
                        example: Login button broken
                      description:
                        type: string
                        example: The login button does not respond on mobile
                      status:
                        type: string
                        example: OPEN
                      priority:
                        type: string
                        example: HIGH
                      type:
                        type: string
                        example: BUG
                      createdAt:
                        type: string
                        example: "2024-03-10T09:00:00.000Z"
                      reporter:
                        type: object
                        properties:
                          id:
                            type: string
                            example: clxyz123
                          name:
                            type: string
                            example: Jane Smith
                      assignee:
                        type: string
                        nullable: true
                      comments:
                        type: array
                        items:
                          type: object
                      attachments:
                        type: array
                        items:
                          type: object
                      _count:
                        type: object
                        properties:
                          comments:
                            type: integer
                            example: 3
                          attachments:
                            type: integer
                            example: 1
                          subTasks:
                            type: integer
                            example: 2
                      parent:
                        type: object
                        properties:
                          ticketNum:
                            type: integer
                            example: 7
                          project:
                            type: object
                            properties:
                              key:
                                type: string
                                example: API
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket not found
    patch:
      summary: Update a ticket
      description: Updates ticket fields. All fields are optional — only provided fields are updated. Status changes trigger automatic SLA recalculation and activity log entries.
      operationId: tickets-update
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  description: New title
                description:
                  type: string
                  description: New description
                status:
                  type: string
                  enum:
                    - OPEN
                    - IN_PROGRESS
                    - RESOLVED
                    - CLOSED
                    - ON_HOLD
                  description: New status
                subStatus:
                  type: string
                  description: "Custom sub-status key (e.g. \"waiting_customer\", \"code_review\"). Set to null to clear. Available sub-statuses can be fetched from GET /api/workspaces/[id]/sub-statuses"
                priority:
                  type: string
                  enum:
                    - LOW
                    - MEDIUM
                    - HIGH
                    - CRITICAL
                  description: New priority
                type:
                  type: string
                  enum:
                    - BUG
                    - TASK
                    - FEATURE_REQUEST
                    - QUESTION
                    - EPIC
                  description: Ticket type
                assigneeId:
                  type: string
                  description: New assignee user ID (null to unassign). Ignored when assigneeIds is present.
                assigneeIds:
                  type: string
                  description: Ordered array of assignee user IDs (element 0 = primary/lead). Empty array clears all assignees. When present wins over assigneeId. Feature 251.
                dueDate:
                  type: string
                  description: New due date (ISO 8601, null to clear)
                storyPoints:
                  type: number
                  description: Story point estimate (null to clear)
                originalEstimate:
                  type: integer
                  description: Original time estimate in minutes (null to clear)
                sprintId:
                  type: string
                  description: Sprint ID to assign ticket to (null to remove from sprint)
                module:
                  type: string
                  description: Module/component name (null to clear)
                releaseVersion:
                  type: string
                  description: Target release version (null to clear)
                projectStatusKey:
                  type: string
                  description: "Custom project status key (feature 133). Requires the project to have custom statuses enabled (else 400 project_statuses_not_enabled); validates against the project's active status rows and derives the built-in status enum. null clears it."
                projectPriorityKey:
                  type: string
                  description: "Custom project priority option key (feature 234). Validates against the project's active priority options. null clears it."
                projectTypeKey:
                  type: string
                  description: "Custom project type option key (feature 234). Validates against the project's active type options. null clears it."
                order:
                  type: integer
                  description: Manual display order value (feature 169). Free-form integer, duplicates allowed. null clears it.
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      ticket:
                        type: object
                        properties:
                          id:
                            type: string
                            example: cltck001
                          ticketNum:
                            type: string
                            example: API-42
                          title:
                            type: string
                            example: Login button broken
                          status:
                            type: string
                            example: IN_PROGRESS
                          subStatus:
                            type: string
                            example: code_review
                          priority:
                            type: string
                            example: HIGH
                          type:
                            type: string
                            example: BUG
                          assigneeId:
                            type: string
                            example: clxyz123
                          updatedAt:
                            type: string
                            example: "2024-03-11T10:00:00.000Z"
                      smartTimesheet:
                        type: string
                        nullable: true
                      timerAutoStopped:
                        type: boolean
                        example: false
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket not found
    delete:
      summary: Delete a ticket
      description: Permanently deletes a ticket and all associated data (comments, attachments, activity logs).
      operationId: tickets-delete
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Ticket deleted
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Ticket not found
  /api/tickets/search:
    get:
      summary: Search tickets
      description: Lightweight ticket search for linking/autocomplete. Searches by title or ticket key (e.g. PROJ-42).
      operationId: tickets-search
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: q
          in: query
          required: true
          description: Search query (min 2 characters)
          schema:
            type: string
        - name: excludeId
          in: query
          required: false
          description: Ticket ID to exclude from results
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Max results (default 10, max 20)
          schema:
            type: number
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  tickets:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: cltck001
                        ticketNum:
                          type: string
                          example: API-42
                        title:
                          type: string
                          example: Login button broken
                        status:
                          type: string
                          example: OPEN
                        priority:
                          type: string
                          example: HIGH
                        type:
                          type: string
                          example: BUG
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Internal server error
  /api/tickets/{id}/comments:
    get:
      summary: List ticket comments
      description: "Returns all comments on a ticket ordered by createdAt ascending. CUSTOMER-role callers only see non-internal comments (isInternal: false); WORKER/ADMIN see all. Access gate: ADMIN bypass or project membership — 403/404 semantics match the full-ticket route."
      operationId: tickets-comments
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      comments:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: clcmt001
                            content:
                              type: string
                              example: Working on a fix now.
                            isInternal:
                              type: boolean
                              example: false
                            createdAt:
                              type: string
                              example: "2024-03-10T10:00:00.000Z"
                            user:
                              type: object
                              properties:
                                id:
                                  type: string
                                  example: clxyz123
                                name:
                                  type: string
                                  example: Jane Smith
                                avatarUrl:
                                  type: string
                                  nullable: true
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket not found
    post:
      summary: Add a comment
      description: Adds a comment to a ticket. Internal comments are visible only to workers and admins.
      operationId: tickets-comment-create
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - content
              properties:
                content:
                  type: string
                  description: Comment content (supports markdown)
                isInternal:
                  type: string
                  description: Mark as internal note (workers/admins only)
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  comment:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clcmt001
                      content:
                        type: string
                        example: Working on a fix now.
                      isInternal:
                        type: boolean
                        example: false
                      createdAt:
                        type: string
                        example: "2024-03-10T10:00:00.000Z"
                      author:
                        type: object
                        properties:
                          id:
                            type: string
                            example: clxyz123
                          name:
                            type: string
                            example: Jane Smith
                          avatarUrl:
                            type: string
                            nullable: true
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Access denied
  /api/tickets/{id}/attachments:
    get:
      summary: List attachments
      description: Returns all file attachments on a ticket.
      operationId: tickets-attachments
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      attachments:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: clatt001
                            filename:
                              type: string
                              example: screenshot.png
                            fileUrl:
                              type: string
                              example: /uploads/screenshot.png
                            fileSize:
                              type: integer
                              example: 102400
                            mimeType:
                              type: string
                              example: image/png
                            createdAt:
                              type: string
                              example: "2024-03-10T10:00:00.000Z"
                            downloadUrl:
                              type: string
                              example: /api/attachments/clatt001/download
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket not found
    post:
      summary: Upload attachment
      description: "Uploads a file attachment to a ticket. Supported formats: images, PDFs, documents. Max 10 MB."
      operationId: tickets-attachments-upload
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  description: File to upload (max 10 MB)
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  attachment:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clatt001
                      filename:
                        type: string
                        example: screenshot.png
                      fileUrl:
                        type: string
                        example: /uploads/screenshot.png
                      fileSize:
                        type: integer
                        example: 102400
                      mimeType:
                        type: string
                        example: image/png
                      createdAt:
                        type: string
                        example: "2024-03-10T10:00:00.000Z"
                      downloadUrl:
                        type: string
                        example: /api/attachments/clatt001/download
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: File too large (max 10 MB)
    delete:
      summary: Delete an attachment from a ticket
      description: "Permanently deletes a single ticket attachment from storage and the database. Requires confirmDelete: true in the request body. Permitted for the uploader, project ADMIN/PROJECT_MANAGER, or system ADMIN. Storage-delete failures are non-fatal and reported in warnings[]."
      operationId: tickets-attachments-delete
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
        - name: attachmentId
          in: query
          required: true
          description: ID of the attachment to delete
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - confirmDelete
              properties:
                confirmDelete:
                  type: string
                  description: Must be literal true — defensive guard against accidental deletes
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Attachment deleted
                  warnings:
                    type: array
                    items:
                      type: object
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Attachment not found
    patch:
      summary: Rename an attachment
      description: "Renames a single ticket attachment. `filename` is the ONLY mutable field — the body schema is strict, so a payload carrying mimeType / fileUrl / fileSize / ticketId / commentId is rejected with 400 and nothing is written. The new name is passed through the same sanitizer as upload (path separators and NULs stripped, characters outside [a-zA-Z0-9._-] and Thai collapsed to _, truncated to 255). The file EXTENSION is locked: renaming report.pdf → report.mp4 is a 400, because client-side type detection keys off both filename and mimeType. An extension-less name may only be renamed to another extension-less name. Permitted for the uploader, project ADMIN/PROJECT_MANAGER, or system ADMIN (identical gate to DELETE). The attachment must belong to the ticket in the URL, else 404. No confirmation flag is required — a rename touches no bytes and is replayable from its ATTACHMENT_RENAMED activity-log row; renaming to the current name is a 200 no-op that writes no log row."
      operationId: tickets-attachments-rename
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID that owns the attachment
          schema:
            type: string
        - name: attachmentId
          in: query
          required: true
          description: ID of the attachment to rename
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - filename
              properties:
                filename:
                  type: string
                  description: New filename. Must be non-blank and keep the current extension. Sanitized server-side; unknown body keys are rejected.
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  attachment:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clatt001
                      filename:
                        type: string
                        example: login-error.png
                      fileUrl:
                        type: string
                        example: /uploads/screenshot.png
                      fileSize:
                        type: integer
                        example: 102400
                      mimeType:
                        type: string
                        example: image/png
                      createdAt:
                        type: string
                        example: "2024-03-10T10:00:00.000Z"
                      downloadUrl:
                        type: string
                        example: /api/attachments/clatt001/download
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: "Filename extension cannot be changed (expected \".pdf\", got \".mp4\"). Rename changes the name only, never the file type."
  /api/tickets/{id}/time-entries:
    get:
      summary: List time entries
      description: Returns all time log entries linked to this ticket.
      operationId: tickets-time-entries
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      entries:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: clte001
                            date:
                              type: string
                              example: 2024-03-11
                            hours:
                              type: number
                              example: 2.5
                            description:
                              type: string
                              example: Fixed login validation
                            userId:
                              type: string
                              example: clxyz123
                            userName:
                              type: string
                              example: Jane Smith
                            isBillable:
                              type: boolean
                              example: true
                      summary:
                        type: object
                        properties:
                          totalMinutes:
                            type: integer
                            example: 150
                          byUser:
                            type: array
                            items:
                              type: object
                              properties:
                                userId:
                                  type: string
                                  example: clxyz123
                                name:
                                  type: string
                                  example: Jane Smith
                                totalMinutes:
                                  type: integer
                                  example: 150
                      visibility:
                        type: string
                        example: full
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket not found
  /api/tickets/{id}/checklist:
    get:
      summary: Get checklist items
      description: Returns all checklist items for a ticket, ordered by their display order.
      operationId: tickets-checklist
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                          example: clchk001
                        content:
                          type: string
                          example: Write unit tests
                        completed:
                          type: boolean
                          example: false
                        order:
                          type: integer
                          example: 0
                        assignee:
                          type: string
                          nullable: true
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Failed to fetch checklist items
    post:
      summary: Add a checklist item
      description: "Adds a simple item or a linked-ticket item to a ticket checklist (singular `/checklist` endpoint — the flat legacy surface). Supports bulk creation via `titles[]`. The target checklist can be specified via `checklistId`; if omitted, falls back to the first checklist, auto-creating one named \"Checklist\" if none exists. Requires project worker/admin or ticket reporter."
      operationId: tickets-checklist-add
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  description: Item text for a simple item (mutually exclusive with titles[] and linkedTicketId)
                titles:
                  type: string
                  description: Array of item texts for bulk creation
                linkedTicketId:
                  type: string
                  description: Ticket ID to link as a sub-task item
                checklistId:
                  type: string
                  description: Target checklist ID (defaults to first / auto-created)
                ownerId:
                  type: string
                  description: User ID to assign as item owner (simple items only)
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  item:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clchk002
                      title:
                        type: string
                        example: Write e2e test
                      completed:
                        type: boolean
                        example: false
                      order:
                        type: integer
                        example: 1
                      owner:
                        type: string
                        nullable: true
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Access denied
  /api/tickets/{id}/archive:
    post:
      summary: Archive / unarchive a ticket
      description: Toggles the archived state of a ticket. Archived tickets are hidden from default list views.
      operationId: tickets-archive
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - archived
              properties:
                archived:
                  type: string
                  description: true to archive, false to unarchive
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  ticket:
                    type: object
                    properties:
                      id:
                        type: string
                        example: cltck001
                      ticketNum:
                        type: string
                        example: API-42
                      title:
                        type: string
                        example: Login button broken
                      status:
                        type: string
                        example: OPEN
                      archivedAt:
                        type: string
                        example: "2024-03-15T10:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket not found
    delete:
      summary: Restore (unarchive) a ticket
      description: Restores an archived ticket back to its previous status. Returns 400 if the ticket is not currently archived. Requires ADMIN or project WORKER with access.
      operationId: tickets-unarchive
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  ticket:
                    type: object
                    properties:
                      id:
                        type: string
                        example: cltck001
                      ticketNum:
                        type: string
                        example: API-42
                      title:
                        type: string
                        example: Login button broken
                      status:
                        type: string
                        example: OPEN
                      archivedAt:
                        type: string
                        nullable: true
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket is not archived
  /api/tickets/{id}/watch:
    get:
      summary: Get ticket watchers
      description: Returns the list of watchers for a ticket and whether the authenticated user is currently watching. Requires project access.
      operationId: tickets-watch-status
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      watchers:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: clxyz123
                            name:
                              type: string
                              example: Jane Smith
                            email:
                              type: string
                              example: jane@example.com
                            avatarUrl:
                              type: string
                              nullable: true
                      watcherCount:
                        type: integer
                        example: 1
                      isWatching:
                        type: boolean
                        example: true
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket not found
    post:
      summary: Watch / unwatch a ticket
      description: Subscribes or unsubscribes the authenticated user to notifications for a ticket.
      operationId: tickets-watch
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - watching
              properties:
                watching:
                  type: string
                  description: true to watch, false to unwatch
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      watching:
                        type: boolean
                        example: true
                      watcherCount:
                        type: integer
                        example: 2
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket not found
  /api/tickets/{id}/deadlines:
    patch:
      summary: Update ticket deadlines
      description: Manually sets SLA deadline fields (expectedFirstResponse, expectedResolution) for a TASK, FEATURE_REQUEST or EPIC ticket. BUG and QUESTION tickets have immutable SLA-calculated deadlines and will return 403. Requires ADMIN or project member (worker) role.
      operationId: tickets-deadlines
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                expectedFirstResponse:
                  type: string
                  description: Expected first-response deadline (ISO 8601, null to clear)
                expectedResolution:
                  type: string
                  description: Expected resolution deadline (ISO 8601, null to clear)
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  ticket:
                    type: object
                    properties:
                      id:
                        type: string
                        example: cltck001
                      expectedFirstResponse:
                        type: string
                        example: "2024-03-11T09:00:00.000Z"
                      expectedResolution:
                        type: string
                        example: "2024-03-13T17:00:00.000Z"
                      updatedAt:
                        type: string
                        example: "2024-03-10T09:30:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: SLA deadlines cannot be modified for BUG or QUESTION tickets
  /api/tickets/bulk-move:
    post:
      summary: Bulk-move tickets to a column
      description: "Trello-style \"move all cards in column\": atomically moves a set of tickets to a target status/column. Reversible — same endpoint runs the inverse move. Tickets the actor cannot edit are skipped and reported in skipped[]. Requires confirmMove: true. Feature 202."
      operationId: tickets-bulk-move
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: X-Workspace-Id
          in: header
          required: true
          description: Target workspace ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - ticketIds
                - targetColumnId
                - confirmMove
              properties:
                ticketIds:
                  type: string
                  description: Array of ticket IDs to move
                targetColumnId:
                  type: string
                  description: Target column identifier (status key or project status key)
                confirmMove:
                  type: string
                  description: Must be literal true — defensive guard against accidental bulk moves
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      moved:
                        type: integer
                        example: 5
                      skipped:
                        type: array
                        items:
                          type: object
                      warnings:
                        type: array
                        items:
                          type: object
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Workspace context required
  /api/tickets/bulk-delete:
    post:
      summary: Bulk-delete tickets
      description: "Permanently (hard) deletes up to 100 tickets. Destructive and irreversible — all associated comments, attachments, checklists and activity logs are cascade-deleted. Requires ADMIN/OWNER role and confirmDelete: true. Tickets with live timesheet activity are skipped and reported. Feature 184."
      operationId: tickets-bulk-delete
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: X-Workspace-Id
          in: header
          required: true
          description: Target workspace ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - ticketIds
                - confirmDelete
              properties:
                ticketIds:
                  type: string
                  description: Array of ticket IDs to delete (max 100)
                confirmDelete:
                  type: string
                  description: Must be literal true — defensive guard for destructive operation
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      deleted:
                        type: integer
                        example: 3
                      skipped:
                        type: array
                        items:
                          type: object
                      warnings:
                        type: array
                        items:
                          type: object
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: confirmDelete must be true
  /api/tickets/export:
    post:
      summary: Export tickets as CSV
      description: "Builds and returns a CSV of the requested tickets scoped to the caller's workspace visibility. Pass the ticket IDs from the current list view or a checkbox selection. Max 10,000 IDs. Gaps between requested vs resolved IDs are reported in warnings[]. Feature 180."
      operationId: tickets-export
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: X-Workspace-Id
          in: header
          required: true
          description: Target workspace ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - ticketIds
              properties:
                ticketIds:
                  type: string
                  description: Array of ticket IDs to export (min 1, max 10000)
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      csv:
                        type: string
                        example: "ID,Title,Status,Priority,...\n"
                      filename:
                        type: string
                        example: tickets-export-2024-03-10.csv
                      count:
                        type: integer
                        example: 42
                      warnings:
                        type: array
                        items:
                          type: object
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: No tickets to export
  /api/tickets/bulk-import/template:
    get:
      summary: Download bulk-import CSV template
      description: Returns a CSV template file with the required column headers for ticket bulk import.
      operationId: tickets-bulk-import-template
      tags:
        - Tickets
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      csv:
                        type: string
                        example: "Title,Description,Type,Priority,...\n"
                      filename:
                        type: string
                        example: tickets-import-template.csv
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
  /api/tickets/bulk-import/preview:
    post:
      summary: Preview bulk-import CSV
      description: Parses and validates a CSV file for bulk ticket import without writing any rows. Returns parsed rows with per-row validation results so the user can review before committing.
      operationId: tickets-bulk-import-preview
      tags:
        - Tickets
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
                - projectId
              properties:
                file:
                  type: string
                  description: CSV file to import (max size enforced server-side)
                projectId:
                  type: string
                  description: Target project ID
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      rows:
                        type: array
                        items:
                          type: object
                          properties:
                            rowIndex:
                              type: integer
                              example: 1
                            title:
                              type: string
                              example: Login broken
                            valid:
                              type: boolean
                              example: true
                            errors:
                              type: array
                              items:
                                type: object
                      totalRows:
                        type: integer
                        example: 5
                      validRows:
                        type: integer
                        example: 4
                      invalidRows:
                        type: integer
                        example: 1
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Invalid CSV format
  /api/tickets/bulk-import/commit:
    post:
      summary: Commit bulk-import
      description: Creates tickets from a previously previewed/validated CSV import. Only valid rows are created; invalid rows are skipped and reported in skipped[].
      operationId: tickets-bulk-import-commit
      tags:
        - Tickets
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - rows
                - projectId
              properties:
                rows:
                  type: string
                  description: Parsed rows from the preview step (validated rows only)
                projectId:
                  type: string
                  description: Target project ID
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      created:
                        type: integer
                        example: 4
                      skipped:
                        type: array
                        items:
                          type: object
                          properties:
                            rowIndex:
                              type: integer
                              example: 2
                            reason:
                              type: string
                              example: "Missing required field: title"
                      warnings:
                        type: array
                        items:
                          type: object
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Project not found
  /api/tickets/{id}/checklists:
    get:
      summary: List checklists
      description: Returns all checklists (with their items) for a ticket. Feature 146.
      operationId: tickets-checklists-list
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      checklists:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: clcl001
                            title:
                              type: string
                              example: QA Checklist
                            items:
                              type: array
                              items:
                                type: object
                                properties:
                                  id:
                                    type: string
                                    example: cli001
                                  content:
                                    type: string
                                    example: Write unit tests
                                  completed:
                                    type: boolean
                                    example: false
                                  order:
                                    type: integer
                                    example: 0
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket not found
    post:
      summary: Create checklist
      description: Creates a new empty checklist on a ticket. Requires project worker/admin role or ticket reporter. Feature 146.
      operationId: tickets-checklists-create
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - title
              properties:
                title:
                  type: string
                  description: Checklist title
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      checklist:
                        type: object
                        properties:
                          id:
                            type: string
                            example: clcl001
                          title:
                            type: string
                            example: QA Checklist
                          items:
                            type: array
                            items:
                              type: object
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Access denied
  /api/tickets/{id}/custom-field-values:
    get:
      summary: Get custom field values
      description: Returns all stored custom field values for a ticket, joined to their field definitions (including values for now-inactive fields). Feature 232.
      operationId: tickets-custom-field-values-get
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      values:
                        type: array
                        items:
                          type: object
                          properties:
                            fieldId:
                              type: string
                              example: clfd001
                            fieldLabel:
                              type: string
                              example: Customer Impact
                            value:
                              type: string
                              example: High
                            fieldType:
                              type: string
                              example: TEXT
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket not found
    patch:
      summary: Upsert custom field values
      description: Upserts one or more custom field values for a ticket. Values for missing, inactive, or wrong-project fields are skipped and surfaced in warnings[]. Feature 232.
      operationId: tickets-custom-field-values-patch
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customFieldValues
              properties:
                customFieldValues:
                  type: string
                  description: Array of { fieldId, value } pairs to upsert
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      updated:
                        type: integer
                        example: 2
                      warnings:
                        type: array
                        items:
                          type: object
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Access denied
  /api/tickets/{id}/tags:
    post:
      summary: Attach tag to ticket
      description: "Attaches a tag to a ticket by ID or creates and attaches a new tag by name. All tag IDs must belong to the ticket's project. Feature 168."
      operationId: tickets-tags-attach
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tagId:
                  type: string
                  description: Existing tag ID to attach
                name:
                  type: string
                  description: Tag name for inline create-and-attach
                color:
                  type: string
                  description: Hex color for inline-created tag (optional)
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      tag:
                        type: object
                        properties:
                          id:
                            type: string
                            example: cltag001
                          name:
                            type: string
                            example: urgent
                          color:
                            type: string
                            example: "#ff0000"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: "Tag does not belong to this ticket's project"
  /api/tickets/{id}/clone:
    post:
      summary: Clone a ticket
      description: Creates a Jira-style copy of a ticket. Copies editable fields, parent link and checklist structure (items reset to unchecked). Status resets to default OPEN. Comments, attachments, activity history, watchers and time entries are NOT copied. Feature 174.
      operationId: tickets-clone
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Source ticket ID
          schema:
            type: string
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      ticket:
                        type: object
                        properties:
                          id:
                            type: string
                            example: cltck002
                          ticketNum:
                            type: string
                            example: API-43
                          title:
                            type: string
                            example: "[Clone] Login button broken"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket not found
  /api/tickets/{id}/schedule:
    patch:
      summary: Update ticket planning fields
      description: "Writes only task-planning fields: planStartDate, planEndDate, planDurationDays, progress, isMilestone, isLocked. Never touches SLA deadline fields (use PATCH /api/tickets/[id]/deadlines for those). Locked tasks refuse date/duration/progress changes until unlocked. Feature 236."
      operationId: tickets-schedule-patch
      tags:
        - Tickets
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                planStartDate:
                  type: string
                  description: Plan start date (ISO 8601, null to clear)
                planEndDate:
                  type: string
                  description: Plan end date (ISO 8601, null to clear)
                planDurationDays:
                  type: number
                  description: Planned duration in days (null to clear)
                progress:
                  type: number
                  description: Completion percentage 0–100 (null to clear)
                isMilestone:
                  type: string
                  description: Mark as milestone
                isLocked:
                  type: string
                  description: Lock/unlock the task planning fields
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      ticket:
                        type: object
                        properties:
                          id:
                            type: string
                            example: cltck001
                          planStartDate:
                            type: string
                            example: "2024-03-10T00:00:00.000Z"
                          planEndDate:
                            type: string
                            example: "2024-03-15T00:00:00.000Z"
                          progress:
                            type: integer
                            example: 50
                          isMilestone:
                            type: boolean
                            example: false
                          isLocked:
                            type: boolean
                            example: false
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Cannot modify a locked task — unlock first
  /api/attachments/{id}/download:
    get:
      summary: Download an attachment
      description: "Streams the raw bytes of a ticket attachment. Auth is required (API token or session cookie). Access check: ADMIN bypass; otherwise the caller must be a project member of the attachment's ticket's project — unknown or inaccessible attachments return 404 (minimal disclosure). Supports RFC 7233 single-range requests (Range: bytes=0-1023 → 206 Partial Content). Never exposes storage bucket keys or paths in response headers."
      operationId: attachments-download
      tags:
        - Files
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Attachment ID
          schema:
            type: string
        - name: Range
          in: header
          required: false
          description: Optional RFC 7233 byte range (e.g. bytes=0-1023). Omit for full file.
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: 404 Not Found (unknown or inaccessible attachment — minimal disclosure); 401 Unauthorized; 416 Range Not Satisfiable
  /api/comments/{id}:
    patch:
      summary: Update a comment
      description: "Updates an existing comment. At least one of content / format must be supplied (sending neither is a 400). Only the comment author or an admin can edit, and the caller must still be a member of the comment's project — a direct ProjectMember row or a team OWNER/MEMBER assignment both count (BQ-527 round 3). Ex-members get 403."
      operationId: comment-update
      tags:
        - Comments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Comment ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                content:
                  type: string
                  description: New comment content (non-empty). Optional, but at least one of content / format is required.
                format:
                  type: string
                  description: "Comment body format: PLAIN | MARKDOWN. Optional, but at least one of content / format is required."
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  comment:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clcmt001
                      content:
                        type: string
                        example: Updated comment text
                      isInternal:
                        type: boolean
                        example: false
                      updatedAt:
                        type: string
                        example: "2024-03-10T11:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Comment not found or access denied
    delete:
      summary: Delete a comment
      description: Permanently deletes a comment. Only the comment author or an admin can delete.
      operationId: comment-delete
      tags:
        - Comments
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Comment ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Comment deleted successfully
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Comment not found or access denied
  /api/timesheet/entries:
    get:
      summary: List time entries
      description: "Returns time entries for the authenticated user within the workspace supplied via the `X-Workspace-Id` header. Workers see only their own entries; workspace admins and platform ADMINs see all entries. When `projectId` is provided and the caller has project-level view permission, all entries for that project are returned. Supports `Authorization: Bearer bq_…` bearer-token auth for agent use."
      operationId: timesheet-entries
      tags:
        - Timesheet
      security:
        - bearerAuth: []
      parameters:
        - name: startDate
          in: query
          required: false
          description: Start date filter (ISO 8601 or parseable date)
          schema:
            type: string
        - name: endDate
          in: query
          required: false
          description: End date filter (ISO 8601 or parseable date)
          schema:
            type: string
        - name: projectId
          in: query
          required: false
          description: Filter by project ID (requires view permission on that project)
          schema:
            type: string
        - name: ticketId
          in: query
          required: false
          description: Filter by ticket ID
          schema:
            type: string
        - name: source
          in: query
          required: false
          description: "Filter by entry source: MANUAL | TIMER | GIT_COMMIT | CALENDAR_SYNC"
          schema:
            type: string
        - name: billableOnly
          in: query
          required: false
          description: When true, return only billable entries
          schema:
            type: string
        - name: includeOverlap
          in: query
          required: false
          description: Include overlapping entries (default true)
          schema:
            type: string
        - name: X-Workspace-Id
          in: header
          required: true
          description: Workspace ID (required — entries are workspace-scoped)
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      entries:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: clte001
                            userId:
                              type: string
                              example: clxyz123
                            projectId:
                              type: string
                              example: clproj001
                            ticketId:
                              type: string
                              example: cltck001
                            startTime:
                              type: string
                              example: "2024-01-15T09:00:00.000Z"
                            endTime:
                              type: string
                              example: "2024-01-15T12:30:00.000Z"
                            durationMinutes:
                              type: integer
                              example: 210
                            description:
                              type: string
                              example: Fixed login bug
                            isBillable:
                              type: boolean
                              example: true
                            status:
                              type: string
                              example: DRAFT
                            source:
                              type: string
                              example: MANUAL
                            createdAt:
                              type: string
                              example: "2024-01-15T09:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Workspace context required
    post:
      summary: Log a time entry
      description: "Creates a manual time entry against a project the caller is a member of. The workspace is resolved from the `X-Workspace-Id` header (required for the LITE+ feature gate check). `startTime` and `endTime` must be ISO 8601 datetime strings; the server derives `durationMinutes` automatically. `endTime` must be after `startTime`; maximum span for non-leave projects is 24 hours (enforced after project type is resolved). `status` defaults to DRAFT. Workspace admins may pass `userId` to log on behalf of another project member. Supports `Authorization: Bearer bq_…` bearer-token auth — the canonical path for agent use."
      operationId: timesheet-entries-create
      tags:
        - Timesheet
      security:
        - bearerAuth: []
      parameters:
        - name: X-Workspace-Id
          in: header
          required: true
          description: Workspace ID (required for plan feature gate)
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - projectId
                - startTime
                - endTime
              properties:
                projectId:
                  type: string
                  description: Project ID to log time against (required)
                  example: clproj001
                startTime:
                  type: string
                  description: "Start datetime (ISO 8601, e.g. 2024-01-15T09:00:00.000Z)"
                  example: "2024-01-15T09:00:00.000Z"
                endTime:
                  type: string
                  description: End datetime (ISO 8601). Must be after startTime.
                  example: "2024-01-15T10:30:00.000Z"
                ticketId:
                  type: string
                  description: Primary ticket ID to associate (optional)
                  example: cltck001
                linkedTicketIds:
                  type: string
                  description: Additional ticket IDs to link (optional)
                description:
                  type: string
                  description: Work description (max 50 000 chars)
                  example: Implemented login API endpoint
                isBillable:
                  type: string
                  description: Whether time is billable. Defaults to project billing config.
                status:
                  type: string
                  enum:
                    - DRAFT
                    - SUBMITTED
                    - PENDING
                    - APPROVED
                    - REJECTED
                  description: "Entry status: DRAFT | SUBMITTED | PENDING | APPROVED | REJECTED. Defaults to DRAFT."
                userId:
                  type: string
                  description: Target user ID — workspace admins only; logs time on behalf of another project member.
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clte001
                      userId:
                        type: string
                        example: clxyz123
                      projectId:
                        type: string
                        example: clproj001
                      ticketId:
                        type: string
                        example: cltck001
                      startTime:
                        type: string
                        example: "2024-01-15T09:00:00.000Z"
                      endTime:
                        type: string
                        example: "2024-01-15T10:30:00.000Z"
                      durationMinutes:
                        type: integer
                        example: 90
                      description:
                        type: string
                        example: Implemented login API endpoint
                      isBillable:
                        type: boolean
                        example: false
                      status:
                        type: string
                        example: DRAFT
                      source:
                        type: string
                        example: MANUAL
                      createdAt:
                        type: string
                        example: "2024-01-15T09:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: You cannot record time on this project
  /api/timesheet/entries/{id}:
    get:
      summary: Get a time entry by ID
      description: "Returns a single time entry by its ID. The owner can always view their own entry. Project admins and workspace admins can view entries they manage. Supports `Authorization: Bearer bq_…` bearer-token auth."
      operationId: timesheet-entry-get
      tags:
        - Timesheet
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Time entry CUID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clte001
                      userId:
                        type: string
                        example: clxyz123
                      projectId:
                        type: string
                        example: clproj001
                      ticketId:
                        type: string
                        example: cltck001
                      startTime:
                        type: string
                        example: "2024-01-15T09:00:00.000Z"
                      endTime:
                        type: string
                        example: "2024-01-15T10:30:00.000Z"
                      durationMinutes:
                        type: integer
                        example: 90
                      description:
                        type: string
                        example: Fixed login bug
                      isBillable:
                        type: boolean
                        example: false
                      status:
                        type: string
                        example: DRAFT
                      source:
                        type: string
                        example: MANUAL
                      createdAt:
                        type: string
                        example: "2024-01-15T09:00:00.000Z"
                      updatedAt:
                        type: string
                        example: "2024-01-15T09:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Time entry not found
    patch:
      summary: Update a time entry
      description: "Partially updates a time entry. All fields are optional. If both `startTime` and `endTime` are provided, `endTime` must be after `startTime`. Entries in SUBMITTED, APPROVED, or PROJECT_APPROVED status can only be edited by workspace admins (regular workers get 403 \"cannot edit an entry that has been submitted or approved\"). Setting `status` to SUBMITTED triggers a Level-1 (PM) approval email dispatch (best-effort). Workspace admins may set `userId` to reassign ownership; target must be a project member. Supports `Authorization: Bearer bq_…` bearer-token auth."
      operationId: timesheet-entry-update
      tags:
        - Timesheet
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Time entry CUID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                projectId:
                  type: string
                  description: Change the associated project (optional)
                startTime:
                  type: string
                  description: New start datetime (ISO 8601)
                endTime:
                  type: string
                  description: New end datetime (ISO 8601). Must be after startTime when both supplied.
                description:
                  type: string
                  description: Updated description (max 50 000 chars)
                ticketId:
                  type: string
                  description: Primary ticket ID (nullable)
                linkedTicketIds:
                  type: string
                  description: Updated ticket links
                isBillable:
                  type: string
                  description: Billing flag
                status:
                  type: string
                  enum:
                    - DRAFT
                    - SUBMITTED
                    - PENDING
                    - APPROVED
                    - REJECTED
                  description: "New status: DRAFT | SUBMITTED | PENDING | APPROVED | REJECTED"
                userId:
                  type: string
                  description: Reassign owner (workspace admins only; target must be a project member)
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clte001
                      userId:
                        type: string
                        example: clxyz123
                      projectId:
                        type: string
                        example: clproj001
                      durationMinutes:
                        type: integer
                        example: 120
                      description:
                        type: string
                        example: Updated description
                      status:
                        type: string
                        example: DRAFT
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: cannot edit an entry that has been submitted or approved
    delete:
      summary: Delete a time entry
      description: "Permanently deletes a time entry. The same permission rules as PATCH apply: entries in SUBMITTED, APPROVED, or PROJECT_APPROVED status cannot be deleted by regular workers (403). Platform ADMINs and workspace admins can delete any entry in the workspace. Returns `{ success: true }` on success. Supports `Authorization: Bearer bq_…` bearer-token auth."
      operationId: timesheet-entry-delete
      tags:
        - Timesheet
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Time entry CUID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Time entry not found
  /api/timesheet/timer:
    get:
      summary: Get active timer
      description: Returns the currently running timer for the authenticated user, or null if no timer is active.
      operationId: timesheet-timer
      tags:
        - Timesheet
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: timer_cuid
                      startedAt:
                        type: string
                        example: "2024-01-15T09:00:00.000Z"
                      description:
                        type: string
                        example: Working on login bug
                      ticketId:
                        type: string
                        example: ticket_cuid
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
    post:
      summary: Start / stop timer
      description: Starts a new timer or stops the current one. Only one timer can be active at a time.
      operationId: timesheet-timer-start
      tags:
        - Timesheet
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - action
              properties:
                action:
                  type: string
                  enum:
                    - start
                    - stop
                  description: Action to perform
                ticketId:
                  type: string
                  description: Ticket to track time against (required for start)
                description:
                  type: string
                  description: What you are working on
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: cltmr001
                      startedAt:
                        type: string
                        example: "2024-01-15T09:00:00.000Z"
                      description:
                        type: string
                        example: Fixing login
                      ticketId:
                        type: string
                        example: cltck001
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: A timer is already running
  /api/timesheet/approval:
    get:
      summary: List pending timesheet approvals
      description: Returns timesheets pending approval. Only accessible to managers and admins.
      operationId: timesheet-approval
      tags:
        - Timesheet
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      view:
                        type: string
                        example: consolidated
                      level:
                        type: integer
                        example: 2
                      users:
                        type: array
                        items:
                          type: object
                          properties:
                            userId:
                              type: string
                              example: clxyz123
                            name:
                              type: string
                              example: Jane Smith
                            totalMinutes:
                              type: integer
                              example: 480
                            pendingCount:
                              type: integer
                              example: 3
                      totalUsers:
                        type: integer
                        example: 1
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
  /api/timesheet/periods:
    get:
      summary: List timesheet periods
      description: Returns submitted timesheet periods for the current user.
      operationId: timesheet-periods
      tags:
        - Timesheet
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      periods:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: clper001
                            userId:
                              type: string
                              example: clxyz123
                            userName:
                              type: string
                              example: Jane Smith
                            projectId:
                              type: string
                              example: clproj001
                            projectName:
                              type: string
                              example: Backend API
                            workspaceId:
                              type: string
                              example: clws001abc
                            startDate:
                              type: string
                              example: 2024-01-01
                            endDate:
                              type: string
                              example: 2024-01-31
                            status:
                              type: string
                              example: SUBMITTED
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
  /api/expenses:
    get:
      summary: List expenses
      description: "Returns expense records for the authenticated user. Managers can see their team's expenses."
      operationId: expenses-list
      tags:
        - Expenses
      security:
        - bearerAuth: []
      parameters:
        - name: status
          in: query
          required: false
          description: "Filter by status: DRAFT, SUBMITTED, APPROVED, REJECTED"
          schema:
            type: string
        - name: dateFrom
          in: query
          required: false
          description: Start date filter
          schema:
            type: string
        - name: dateTo
          in: query
          required: false
          description: End date filter
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      expenses:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: clexp001
                            date:
                              type: string
                              example: 2024-01-15
                            amount:
                              type: integer
                              example: 150
                            currency:
                              type: string
                              example: USD
                            description:
                              type: string
                              example: Client dinner
                            status:
                              type: string
                              example: SUBMITTED
                            userId:
                              type: string
                              example: clxyz123
                      pagination:
                        type: object
                        properties:
                          page:
                            type: integer
                            example: 1
                          limit:
                            type: integer
                            example: 20
                          total:
                            type: integer
                            example: 1
                          totalPages:
                            type: integer
                            example: 1
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
    post:
      summary: Create an expense
      description: Creates a new expense record.
      operationId: expenses-create
      tags:
        - Expenses
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - date
                - amount
                - currency
                - description
              properties:
                date:
                  type: string
                  description: Expense date (ISO 8601)
                amount:
                  type: number
                  description: Amount
                currency:
                  type: string
                  description: Currency code (e.g. USD, THB)
                description:
                  type: string
                  description: Expense description
                categoryId:
                  type: string
                  description: Expense category ID
                projectId:
                  type: string
                  description: Associated project ID
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: clexp001
                      date:
                        type: string
                        example: 2024-01-15
                      amount:
                        type: integer
                        example: 150
                      currency:
                        type: string
                        example: USD
                      description:
                        type: string
                        example: Client dinner
                      status:
                        type: string
                        example: DRAFT
                      userId:
                        type: string
                        example: clxyz123
                      createdAt:
                        type: string
                        example: "2024-01-15T09:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Amount must be a positive number
  /api/expenses/approval:
    get:
      summary: List pending expense approvals
      description: Returns expenses pending approval. Accessible to managers and admins.
      operationId: expenses-approval
      tags:
        - Expenses
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      view:
                        type: string
                        example: consolidated
                      level:
                        type: integer
                        example: 2
                      users:
                        type: array
                        items:
                          type: object
                          properties:
                            userId:
                              type: string
                              example: clxyz123
                            name:
                              type: string
                              example: Jane Smith
                            pendingCount:
                              type: integer
                              example: 2
                            totalAmount:
                              type: integer
                              example: 300
                      totalUsers:
                        type: integer
                        example: 1
                      totalAmount:
                        type: integer
                        example: 300
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
  /api/notifications:
    get:
      summary: List notifications
      description: Returns paginated notifications for the authenticated user in the current workspace.
      operationId: notifications-list
      tags:
        - Notifications
      security:
        - bearerAuth: []
      parameters:
        - name: page
          in: query
          required: false
          description: Page number (default 1)
          schema:
            type: number
        - name: limit
          in: query
          required: false
          description: Items per page (default 20)
          schema:
            type: number
        - name: unreadOnly
          in: query
          required: false
          description: Return only unread notifications
          schema:
            type: string
        - name: X-Workspace-Id
          in: header
          required: true
          description: Target workspace ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      notifications:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: clnot001
                            type:
                              type: string
                              example: TICKET_ASSIGNED
                            message:
                              type: string
                              example: You were assigned to API-42
                            isRead:
                              type: boolean
                              example: false
                            createdAt:
                              type: string
                              example: "2024-03-10T09:00:00.000Z"
                            ticketId:
                              type: string
                              example: cltck001
                      totalCount:
                        type: integer
                        example: 1
                      page:
                        type: integer
                        example: 1
                      limit:
                        type: integer
                        example: 20
                      totalPages:
                        type: integer
                        example: 1
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
  /api/notifications/read:
    patch:
      summary: Mark notifications as read
      description: "Marks one or all notifications as read. Pass ids[] for selective marking, or all: true to mark all in the workspace."
      operationId: notifications-read
      tags:
        - Notifications
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                ids:
                  type: string
                  description: "Array of notification IDs to mark read. Omit (or use all: true) to mark all as read."
                all:
                  type: string
                  description: Set true to mark all notifications in the workspace as read.
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      updatedCount:
                        type: integer
                        example: 5
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
  /api/notifications/unread-count:
    get:
      summary: Get unread notification count
      description: Returns the count of unread notifications for the bell badge.
      operationId: notifications-unread-count
      tags:
        - Notifications
      security:
        - bearerAuth: []
      parameters:
        - name: X-Workspace-Id
          in: header
          required: true
          description: Target workspace ID
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      count:
                        type: integer
                        example: 7
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
  /api/portal/info:
    get:
      summary: Get portal configuration
      description: Returns the portal configuration for the current domain/subdomain.
      operationId: portal-info
      tags:
        - Customer Portal
      security: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      projectId:
                        type: string
                        example: clproj001
                      name:
                        type: string
                        example: Acme Support
                      description:
                        type: string
                        example: Submit and track support tickets
                      logoUrl:
                        type: string
                        nullable: true
                      allowAnonymous:
                        type: boolean
                        example: false
                      requireApproval:
                        type: boolean
                        example: true
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Portal not found
  /api/portal/register:
    post:
      summary: Register as portal customer
      description: Registers a customer account for the portal. Account requires approval by workspace staff.
      operationId: portal-register
      tags:
        - Customer Portal
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - email
                - password
                - portalId
              properties:
                name:
                  type: string
                  description: Customer full name
                email:
                  type: string
                  description: Customer email address
                password:
                  type: string
                  description: Account password
                portalId:
                  type: string
                  description: Portal project ID
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      userId:
                        type: string
                        example: clubsr003
                      status:
                        type: string
                        example: PENDING
                      isNewMember:
                        type: boolean
                        example: true
                      requiresApproval:
                        type: boolean
                        example: true
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Email already registered
  /api/portal/tickets:
    get:
      summary: List customer tickets
      description: Returns tickets submitted by the authenticated portal customer.
      operationId: portal-tickets
      tags:
        - Customer Portal
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      tickets:
                        type: array
                        items:
                          type: object
                          properties:
                            id:
                              type: string
                              example: cltck001
                            ticketNum:
                              type: string
                              example: API-42
                            title:
                              type: string
                              example: Cannot export PDF
                            status:
                              type: string
                              example: OPEN
                            priority:
                              type: string
                              example: MEDIUM
                            type:
                              type: string
                              example: BUG
                            createdAt:
                              type: string
                              example: "2024-03-10T09:00:00.000Z"
                      pagination:
                        type: object
                        properties:
                          page:
                            type: integer
                            example: 1
                          limit:
                            type: integer
                            example: 20
                          total:
                            type: integer
                            example: 1
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
    post:
      summary: Submit a ticket
      description: Creates a new support ticket from the customer portal.
      operationId: portal-tickets-create
      tags:
        - Customer Portal
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - title
                - description
              properties:
                title:
                  type: string
                  description: Issue title
                description:
                  type: string
                  description: Detailed description
                type:
                  type: string
                  enum:
                    - BUG
                    - TASK
                    - FEATURE_REQUEST
                    - QUESTION
                  description: Ticket type (defaults to QUESTION)
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: cltck002
                      ticketNum:
                        type: string
                        example: API-43
                      title:
                        type: string
                        example: Cannot export PDF
                      status:
                        type: string
                        example: OPEN
                      type:
                        type: string
                        example: BUG
                      createdAt:
                        type: string
                        example: "2024-03-15T10:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Unauthorized
  /api/portal/tickets/{ticketId}/close:
    post:
      summary: Close a ticket
      description: Allows a portal customer to close their own ticket.
      operationId: portal-ticket-close
      tags:
        - Customer Portal
      security:
        - bearerAuth: []
      parameters:
        - name: ticketId
          in: path
          required: true
          description: Ticket ID
          schema:
            type: string
      responses:
        "201":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                        example: cltck001
                      ticketNum:
                        type: string
                        example: API-42
                      title:
                        type: string
                        example: Cannot export PDF
                      status:
                        type: string
                        example: CLOSED
                      closedAt:
                        type: string
                        example: "2024-03-16T10:00:00.000Z"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  error:
                    type: string
                    example: Unauthorized
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: Ticket not found or access denied
  /api/health:
    get:
      summary: System health check
      description: Returns the overall system health including database and storage backend status.
      operationId: health-main
      tags:
        - Health
      security: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: healthy
                  timestamp:
                    type: string
                    example: "2024-01-01T00:00:00.000Z"
                  database:
                    type: string
                    example: connected
                  storage:
                    type: string
                    example: healthy
                  config:
                    type: object
                    properties:
                      storageBackend:
                        type: string
                        example: s3
  /api/health/storage:
    get:
      summary: Storage health check
      description: Returns detailed storage backend health including S3 or local filesystem status.
      operationId: health-storage
      tags:
        - Health
      security: []
      responses:
        "200":
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        example: healthy
                      backend:
                        type: string
                        example: s3
                      timestamp:
                        type: string
                        example: "2024-03-10T09:00:00.000Z"
                      checks:
                        type: object
                        properties:
                          connectivity:
                            type: object
                            properties:
                              status:
                                type: string
                                example: ok
                              message:
                                type: string
                                example: S3 bucket accessible
                      config:
                        type: object
                        properties:
                          backend:
                            type: string
                            example: s3
                          bucket:
                            type: string
                            example: backq-prod
                          region:
                            type: string
                            example: ap-southeast-7
                          endpoint:
                            type: string
                            example: default
        "400":
          description: Bad request
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: false
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        example: unhealthy
                      backend:
                        type: string
                        example: unknown
                      timestamp:
                        type: string
                        example: "2024-03-10T09:00:00.000Z"
                      checks:
                        type: object
                        properties:
                          connectivity:
                            type: object
                            properties:
                              status:
                                type: string
                                example: error
                              message:
                                type: string
                                example: Connection failed