openapi: 3.1.0
info:
  title: VMaintain API
  version: 1.0.0
  description: |-
    The VMaintain REST API gives you programmatic access to your workspace data —
    sites, tickets, tasks, clients, monitors, and more.

    ## Authentication

    All API endpoints (except `/auth/login`) require a **Bearer token** obtained
    from `POST /api/v1/auth/login`. Pass it in the `Authorization` header:

    ```
    Authorization: Bearer <token>
    ```

    ## Versioning

    The current stable version is **v1** — all paths start with `/api/v1/`.

    ## Rate Limits

    | Tier | Limit |
    |------|-------|
    | Authenticated | 60 req / min |
    | Per API key | 120 req / min |

    Rate limit headers are returned on every response:
    - `X-RateLimit-Limit`
    - `X-RateLimit-Remaining`
    - `X-RateLimit-Reset`
  contact:
    name: VMaintain Developer Support
    url: https://vmaintain.io/docs
  license:
    name: MIT
  x-logo:
    url: /logo.png
    altText: VMaintain
servers:
  - url: https://app-vmaintain.dev.diginnovators.site
    description: Production
  - url: http://localhost:3000
    description: Local development
tags:
  - name: Auth
    description: Authentication and session management
  - name: Workspaces
    description: Multi-tenant workspace management
  - name: Tickets
    description: Support ticket lifecycle
  - name: Sites
    description: WordPress site inventory and health
  - name: Clients
    description: Client account management
  - name: Tasks
    description: Task and project management
  - name: Monitors
    description: Uptime and health monitoring
  - name: Webhooks
    description: Outbound event notifications
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT token obtained from POST /api/v1/auth/login
    cookieAuth:
      type: apiKey
      in: cookie
      name: payload-token
      description: Session cookie set by the web application
  schemas:
    PaginationMeta:
      type: object
      properties:
        total:
          type: number
          example: 142
        page:
          type: number
          example: 1
        limit:
          type: number
          example: 20
        totalPages:
          type: number
          example: 8
        hasNextPage:
          type: boolean
          example: true
        hasPrevPage:
          type: boolean
          example: false
      required:
        - total
        - page
        - limit
        - totalPages
        - hasNextPage
        - hasPrevPage
    ApiError:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              example: NOT_FOUND
            message:
              type: string
              example: Resource not found
            details:
              type: array
              items:
                type: object
                properties:
                  field:
                    type: string
                    example: email
                  message:
                    type: string
                    example: Invalid email address
                required:
                  - field
                  - message
          required:
            - code
            - message
      required:
        - error
    User:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        name:
          type: string
          example: Jane Smith
        email:
          type: string
          format: email
          example: jane@acmecorp.com
        role:
          type: string
          enum:
            - user
            - admin
          example: user
        createdAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
      required:
        - id
        - name
        - email
        - role
        - createdAt
        - updatedAt
    LoginRequest:
      type: object
      properties:
        email:
          type: string
          format: email
          example: jane@acmecorp.com
        password:
          type: string
          minLength: 1
          example: MySecurePass123
        rememberMe:
          type: boolean
          example: false
      required:
        - email
        - password
    LoginResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            user:
              $ref: '#/components/schemas/User'
            workspaces:
              type: array
              items:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                    example: 550e8400-e29b-41d4-a716-446655440000
                  name:
                    type: string
                    example: Acme Corp
                  slug:
                    type: string
                    example: acme-corp
                required:
                  - id
                  - name
                  - slug
          required:
            - user
            - workspaces
      required:
        - data
    MeResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/User'
      required:
        - data
    LogoutResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            message:
              type: string
              example: Logged out successfully
          required:
            - message
      required:
        - data
    Workspace:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        name:
          type: string
          example: Acme Corp
        slug:
          type: string
          pattern: ^[a-z0-9-]+$
          example: acme-corp
        description:
          type:
            - string
            - 'null'
          example: Managed WordPress for Acme
        plan:
          type: string
          enum:
            - free
            - pro
            - business
            - enterprise
          example: pro
        isActive:
          type: boolean
          example: true
        createdAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
      required:
        - id
        - name
        - slug
        - plan
        - isActive
        - createdAt
        - updatedAt
    WorkspaceMember:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        userId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        role:
          type: string
          enum:
            - owner
            - admin
            - member
            - viewer
          example: member
        joinedAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
      required:
        - id
        - userId
        - workspaceId
        - role
        - joinedAt
    CreateWorkspaceRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 100
          example: Acme Corp
        slug:
          type: string
          pattern: ^[a-z0-9-]+$
          example: acme-corp
        description:
          type: string
          maxLength: 500
          example: WordPress maintenance for Acme
      required:
        - name
        - slug
    WorkspaceList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Workspace'
        meta:
          type: object
          properties:
            total:
              type: number
              example: 142
            page:
              type: number
              example: 1
            limit:
              type: number
              example: 20
            totalPages:
              type: number
              example: 8
            hasNextPage:
              type: boolean
              example: true
            hasPrevPage:
              type: boolean
              example: false
          required:
            - total
            - page
            - limit
            - totalPages
            - hasNextPage
            - hasPrevPage
      required:
        - data
        - meta
    Ticket:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        clientId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        siteId:
          type:
            - string
            - 'null'
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        subject:
          type: string
          example: White screen after update
        status:
          type: string
          enum:
            - open
            - in_progress
            - waiting_client
            - resolved
            - closed
          example: open
        priority:
          type: string
          enum:
            - low
            - normal
            - high
            - urgent
          example: high
        assigneeId:
          type:
            - string
            - 'null'
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        reporterId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        reporterEmail:
          type:
            - string
            - 'null'
          format: email
        dueAt:
          type:
            - string
            - 'null'
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        firstResponseAt:
          type:
            - string
            - 'null'
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        resolvedAt:
          type:
            - string
            - 'null'
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        tags:
          type: array
          items:
            type: string
          example:
            - wordpress
            - plugin
        createdAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
      required:
        - id
        - workspaceId
        - clientId
        - subject
        - status
        - priority
        - reporterId
        - createdAt
        - updatedAt
    CreateTicketRequest:
      type: object
      properties:
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        clientId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        siteId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        subject:
          type: string
          minLength: 1
          maxLength: 250
          example: White screen after update
        description:
          type: string
          example: The site shows a blank page after updating WooCommerce.
        priority:
          type: string
          enum:
            - low
            - normal
            - high
            - urgent
          default: normal
        assigneeId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        dueAt:
          type: string
          format: date-time
        tags:
          type: array
          items:
            type: string
      required:
        - workspaceId
        - clientId
        - subject
    UpdateTicketRequest:
      type: object
      properties:
        subject:
          type: string
          minLength: 1
          maxLength: 250
        status:
          type: string
          enum:
            - open
            - in_progress
            - waiting_client
            - resolved
            - closed
        priority:
          type: string
          enum:
            - low
            - normal
            - high
            - urgent
        assigneeId:
          type:
            - string
            - 'null'
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        dueAt:
          type:
            - string
            - 'null'
          format: date-time
        tags:
          type: array
          items:
            type: string
    Site:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        clientId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        name:
          type: string
          example: Acme Main Site
        url:
          type: string
          format: uri
          example: https://acmecorp.com
        status:
          type: string
          enum:
            - active
            - inactive
            - maintenance
          example: active
        phpVersion:
          type:
            - string
            - 'null'
          example: '8.2'
        wpVersion:
          type:
            - string
            - 'null'
          example: 6.5.3
        tags:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
      required:
        - id
        - workspaceId
        - clientId
        - name
        - url
        - status
        - createdAt
        - updatedAt
    CreateSiteRequest:
      type: object
      properties:
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        clientId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        name:
          type: string
          minLength: 1
          maxLength: 200
          example: Acme Main Site
        url:
          type: string
          format: uri
          example: https://acmecorp.com
        phpVersion:
          type: string
        wpVersion:
          type: string
        tags:
          type: array
          items:
            type: string
      required:
        - workspaceId
        - clientId
        - name
        - url
    Client:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        name:
          type: string
          example: Acme Corporation
        email:
          type:
            - string
            - 'null'
          format: email
          example: contact@acmecorp.com
        phone:
          type:
            - string
            - 'null'
          example: +1-555-0100
        website:
          type:
            - string
            - 'null'
          format: uri
          example: https://acmecorp.com
        status:
          type: string
          enum:
            - active
            - inactive
            - prospect
          example: active
        notes:
          type:
            - string
            - 'null'
        createdAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
      required:
        - id
        - workspaceId
        - name
        - status
        - createdAt
        - updatedAt
    CreateClientRequest:
      type: object
      properties:
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        name:
          type: string
          minLength: 1
          maxLength: 200
          example: Acme Corporation
        email:
          type: string
          format: email
        phone:
          type: string
        website:
          type: string
          format: uri
        notes:
          type: string
      required:
        - workspaceId
        - name
    Task:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        clientId:
          type:
            - string
            - 'null'
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        siteId:
          type:
            - string
            - 'null'
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        title:
          type: string
          example: Update WordPress core to 6.5.3
        description:
          type:
            - string
            - 'null'
        status:
          type: string
          enum:
            - todo
            - doing
            - review
            - done
            - cancelled
          example: todo
        priority:
          type: string
          enum:
            - low
            - normal
            - high
            - urgent
          example: high
        assigneeId:
          type:
            - string
            - 'null'
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        dueAt:
          type:
            - string
            - 'null'
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        completedAt:
          type:
            - string
            - 'null'
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        tags:
          type: array
          items:
            type: string
        estimatedMinutes:
          type:
            - integer
            - 'null'
          example: 30
        createdAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
      required:
        - id
        - workspaceId
        - title
        - status
        - priority
        - createdAt
        - updatedAt
    CreateTaskRequest:
      type: object
      properties:
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        clientId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        siteId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        title:
          type: string
          minLength: 1
          maxLength: 250
          example: Update WordPress core to 6.5.3
        description:
          type: string
        priority:
          type: string
          enum:
            - low
            - normal
            - high
            - urgent
          default: normal
        assigneeId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        dueAt:
          type: string
          format: date-time
        tags:
          type: array
          items:
            type: string
        estimatedMinutes:
          type: integer
          minimum: 1
      required:
        - workspaceId
        - title
    Monitor:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        siteId:
          type:
            - string
            - 'null'
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        name:
          type: string
          example: Acme Homepage
        url:
          type: string
          format: uri
          example: https://acmecorp.com
        type:
          type: string
          enum:
            - http
            - ping
            - keyword
          example: http
        status:
          type: string
          enum:
            - up
            - down
            - paused
            - pending
          example: up
        interval:
          type: integer
          description: Check interval in seconds
          example: 300
        uptime:
          type:
            - number
            - 'null'
          minimum: 0
          maximum: 100
          example: 99.97
        responseTime:
          type:
            - number
            - 'null'
          description: Last response time in ms
          example: 245
        lastCheckedAt:
          type:
            - string
            - 'null'
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        createdAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
      required:
        - id
        - workspaceId
        - name
        - url
        - type
        - status
        - interval
        - createdAt
        - updatedAt
    CreateMonitorRequest:
      type: object
      properties:
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        siteId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        name:
          type: string
          minLength: 1
          maxLength: 200
          example: Acme Homepage
        url:
          type: string
          format: uri
          example: https://acmecorp.com
        type:
          type: string
          enum:
            - http
            - ping
            - keyword
          default: http
        interval:
          type: integer
          minimum: 60
          maximum: 3600
          default: 300
        keyword:
          type: string
          description: For keyword monitors — string to search for on the page
      required:
        - workspaceId
        - name
        - url
    Webhook:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        url:
          type: string
          format: uri
          example: https://hooks.example.com/vmaintain
        events:
          type: array
          items:
            type: string
            enum:
              - ticket.created
              - ticket.updated
              - ticket.status_changed
              - ticket.resolved
              - site.created
              - site.updated
              - monitor.down
              - monitor.recovered
              - task.created
              - task.completed
              - client.created
          example:
            - ticket.created
            - monitor.down
        isActive:
          type: boolean
          example: true
        secret:
          type: string
          example: whsec_...
        createdAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        updatedAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
      required:
        - id
        - workspaceId
        - url
        - events
        - isActive
        - secret
        - createdAt
        - updatedAt
    CreateWebhookRequest:
      type: object
      properties:
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        url:
          type: string
          format: uri
          example: https://hooks.example.com/vmaintain
        events:
          type: array
          items:
            type: string
            enum:
              - ticket.created
              - ticket.updated
              - ticket.status_changed
              - ticket.resolved
              - site.created
              - site.updated
              - monitor.down
              - monitor.recovered
              - task.created
              - task.completed
              - client.created
          minItems: 1
      required:
        - workspaceId
        - url
        - events
    WebhookPayload:
      type: object
      properties:
        id:
          type: string
          example: evt_01HXK8...
        event:
          type: string
          enum:
            - ticket.created
            - ticket.updated
            - ticket.status_changed
            - ticket.resolved
            - site.created
            - site.updated
            - monitor.down
            - monitor.recovered
            - task.created
            - task.completed
            - client.created
        workspaceId:
          type: string
          format: uuid
          example: 550e8400-e29b-41d4-a716-446655440000
        createdAt:
          type: string
          format: date-time
          example: '2026-01-15T10:30:00.000Z'
        data:
          type: object
          additionalProperties: {}
          description: Event-specific payload
      required:
        - id
        - event
        - workspaceId
        - createdAt
        - data
  parameters: {}
paths:
  /api/v1/auth/login:
    post:
      tags:
        - Auth
      summary: Authenticate a user
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
      responses:
        '200':
          description: Login successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
        '401':
          description: Invalid credentials
  /api/v1/auth/me:
    get:
      tags:
        - Auth
      summary: Get the authenticated user
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Current user
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MeResponse'
        '401':
          description: Unauthenticated
  /api/v1/auth/logout:
    post:
      tags:
        - Auth
      summary: Log out and clear the session
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Logged out
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LogoutResponse'
  /api/v1/workspaces:
    get:
      tags:
        - Workspaces
      summary: List workspaces for the authenticated user
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: integer
            minimum: 1
            default: 1
            example: 1
          required: false
          name: page
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            example: 20
          required: false
          name: limit
          in: query
      responses:
        '200':
          description: Workspace list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkspaceList'
        '401':
          description: Unauthenticated
    post:
      tags:
        - Workspaces
      summary: Create a new workspace
      security:
        - bearerAuth: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWorkspaceRequest'
      responses:
        '201':
          description: Workspace created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Workspace'
                required:
                  - data
        '400':
          description: Validation error
        '409':
          description: Slug already taken
  /api/v1/workspaces/{workspaceId}:
    get:
      tags:
        - Workspaces
      summary: Get a workspace by ID
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: workspaceId
          in: path
      responses:
        '200':
          description: Workspace
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Workspace'
                required:
                  - data
        '404':
          description: Not found
  /api/v1/workspaces/{workspaceId}/members:
    get:
      tags:
        - Workspaces
      summary: List workspace members
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: workspaceId
          in: path
        - schema:
            type: integer
            minimum: 1
            default: 1
            example: 1
          required: false
          name: page
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            example: 20
          required: false
          name: limit
          in: query
      responses:
        '200':
          description: Member list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WorkspaceMember'
                  meta:
                    type: object
                    properties:
                      total:
                        type: number
                        example: 142
                      page:
                        type: number
                        example: 1
                      limit:
                        type: number
                        example: 20
                      totalPages:
                        type: number
                        example: 8
                      hasNextPage:
                        type: boolean
                        example: true
                      hasPrevPage:
                        type: boolean
                        example: false
                    required:
                      - total
                      - page
                      - limit
                      - totalPages
                      - hasNextPage
                      - hasPrevPage
                required:
                  - data
                  - meta
  /api/v1/tickets:
    get:
      tags:
        - Tickets
      summary: List tickets
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: integer
            minimum: 1
            default: 1
            example: 1
          required: false
          name: page
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            example: 20
          required: false
          name: limit
          in: query
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: workspaceId
          in: query
        - schema:
            type: string
            enum:
              - open
              - in_progress
              - waiting_client
              - resolved
              - closed
          required: false
          name: status
          in: query
        - schema:
            type: string
            enum:
              - low
              - normal
              - high
              - urgent
          required: false
          name: priority
          in: query
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: false
          name: assigneeId
          in: query
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: false
          name: clientId
          in: query
      responses:
        '200':
          description: Ticket list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Ticket'
                  meta:
                    type: object
                    properties:
                      total:
                        type: number
                        example: 142
                      page:
                        type: number
                        example: 1
                      limit:
                        type: number
                        example: 20
                      totalPages:
                        type: number
                        example: 8
                      hasNextPage:
                        type: boolean
                        example: true
                      hasPrevPage:
                        type: boolean
                        example: false
                    required:
                      - total
                      - page
                      - limit
                      - totalPages
                      - hasNextPage
                      - hasPrevPage
                required:
                  - data
                  - meta
    post:
      tags:
        - Tickets
      summary: Create a ticket
      security:
        - bearerAuth: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTicketRequest'
      responses:
        '201':
          description: Ticket created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Ticket'
                required:
                  - data
        '400':
          description: Validation error
        '403':
          description: Forbidden
  /api/v1/tickets/{ticketId}:
    get:
      tags:
        - Tickets
      summary: Get a ticket
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: ticketId
          in: path
      responses:
        '200':
          description: Ticket
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Ticket'
                required:
                  - data
        '404':
          description: Not found
    patch:
      tags:
        - Tickets
      summary: Update a ticket
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: ticketId
          in: path
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateTicketRequest'
      responses:
        '200':
          description: Updated ticket
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Ticket'
                required:
                  - data
        '404':
          description: Not found
    delete:
      tags:
        - Tickets
      summary: Delete a ticket
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: ticketId
          in: path
      responses:
        '204':
          description: Deleted
        '404':
          description: Not found
  /api/v1/sites:
    get:
      tags:
        - Sites
      summary: List sites
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: integer
            minimum: 1
            default: 1
            example: 1
          required: false
          name: page
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            example: 20
          required: false
          name: limit
          in: query
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: workspaceId
          in: query
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: false
          name: clientId
          in: query
        - schema:
            type: string
            enum:
              - active
              - inactive
              - maintenance
          required: false
          name: status
          in: query
      responses:
        '200':
          description: Site list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Site'
                  meta:
                    type: object
                    properties:
                      total:
                        type: number
                        example: 142
                      page:
                        type: number
                        example: 1
                      limit:
                        type: number
                        example: 20
                      totalPages:
                        type: number
                        example: 8
                      hasNextPage:
                        type: boolean
                        example: true
                      hasPrevPage:
                        type: boolean
                        example: false
                    required:
                      - total
                      - page
                      - limit
                      - totalPages
                      - hasNextPage
                      - hasPrevPage
                required:
                  - data
                  - meta
    post:
      tags:
        - Sites
      summary: Add a site
      security:
        - bearerAuth: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSiteRequest'
      responses:
        '201':
          description: Site created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Site'
                required:
                  - data
        '400':
          description: Validation error
  /api/v1/sites/{siteId}:
    get:
      tags:
        - Sites
      summary: Get a site
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: siteId
          in: path
      responses:
        '200':
          description: Site
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Site'
                required:
                  - data
        '404':
          description: Not found
    delete:
      tags:
        - Sites
      summary: Remove a site
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: siteId
          in: path
      responses:
        '204':
          description: Deleted
  /api/v1/clients:
    get:
      tags:
        - Clients
      summary: List clients
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: integer
            minimum: 1
            default: 1
            example: 1
          required: false
          name: page
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            example: 20
          required: false
          name: limit
          in: query
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: workspaceId
          in: query
        - schema:
            type: string
            enum:
              - active
              - inactive
              - prospect
          required: false
          name: status
          in: query
      responses:
        '200':
          description: Client list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Client'
                  meta:
                    type: object
                    properties:
                      total:
                        type: number
                        example: 142
                      page:
                        type: number
                        example: 1
                      limit:
                        type: number
                        example: 20
                      totalPages:
                        type: number
                        example: 8
                      hasNextPage:
                        type: boolean
                        example: true
                      hasPrevPage:
                        type: boolean
                        example: false
                    required:
                      - total
                      - page
                      - limit
                      - totalPages
                      - hasNextPage
                      - hasPrevPage
                required:
                  - data
                  - meta
    post:
      tags:
        - Clients
      summary: Create a client
      security:
        - bearerAuth: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateClientRequest'
      responses:
        '201':
          description: Client created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Client'
                required:
                  - data
  /api/v1/clients/{clientId}:
    get:
      tags:
        - Clients
      summary: Get a client
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: clientId
          in: path
      responses:
        '200':
          description: Client
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Client'
                required:
                  - data
        '404':
          description: Not found
    delete:
      tags:
        - Clients
      summary: Delete a client
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: clientId
          in: path
      responses:
        '204':
          description: Deleted
  /api/v1/tasks:
    get:
      tags:
        - Tasks
      summary: List tasks
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: integer
            minimum: 1
            default: 1
            example: 1
          required: false
          name: page
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            example: 20
          required: false
          name: limit
          in: query
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: workspaceId
          in: query
        - schema:
            type: string
            enum:
              - todo
              - doing
              - review
              - done
              - cancelled
          required: false
          name: status
          in: query
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: false
          name: assigneeId
          in: query
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: false
          name: clientId
          in: query
      responses:
        '200':
          description: Task list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Task'
                  meta:
                    type: object
                    properties:
                      total:
                        type: number
                        example: 142
                      page:
                        type: number
                        example: 1
                      limit:
                        type: number
                        example: 20
                      totalPages:
                        type: number
                        example: 8
                      hasNextPage:
                        type: boolean
                        example: true
                      hasPrevPage:
                        type: boolean
                        example: false
                    required:
                      - total
                      - page
                      - limit
                      - totalPages
                      - hasNextPage
                      - hasPrevPage
                required:
                  - data
                  - meta
    post:
      tags:
        - Tasks
      summary: Create a task
      security:
        - bearerAuth: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateTaskRequest'
      responses:
        '201':
          description: Task created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Task'
                required:
                  - data
  /api/v1/tasks/{taskId}:
    get:
      tags:
        - Tasks
      summary: Get a task
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: taskId
          in: path
      responses:
        '200':
          description: Task
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Task'
                required:
                  - data
        '404':
          description: Not found
    patch:
      tags:
        - Tasks
      summary: Update a task
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: taskId
          in: path
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                clientId:
                  type: string
                  format: uuid
                  example: 550e8400-e29b-41d4-a716-446655440000
                siteId:
                  type: string
                  format: uuid
                  example: 550e8400-e29b-41d4-a716-446655440000
                title:
                  type: string
                  minLength: 1
                  maxLength: 250
                  example: Update WordPress core to 6.5.3
                description:
                  type: string
                priority:
                  type: string
                  enum:
                    - low
                    - normal
                    - high
                    - urgent
                  default: normal
                assigneeId:
                  type: string
                  format: uuid
                  example: 550e8400-e29b-41d4-a716-446655440000
                dueAt:
                  type: string
                  format: date-time
                tags:
                  type: array
                  items:
                    type: string
                estimatedMinutes:
                  type: integer
                  minimum: 1
      responses:
        '200':
          description: Updated task
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Task'
                required:
                  - data
  /api/v1/monitors:
    get:
      tags:
        - Monitors
      summary: List monitors
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: integer
            minimum: 1
            default: 1
            example: 1
          required: false
          name: page
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            example: 20
          required: false
          name: limit
          in: query
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: workspaceId
          in: query
        - schema:
            type: string
            enum:
              - up
              - down
              - paused
              - pending
          required: false
          name: status
          in: query
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: false
          name: siteId
          in: query
      responses:
        '200':
          description: Monitor list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Monitor'
                  meta:
                    type: object
                    properties:
                      total:
                        type: number
                        example: 142
                      page:
                        type: number
                        example: 1
                      limit:
                        type: number
                        example: 20
                      totalPages:
                        type: number
                        example: 8
                      hasNextPage:
                        type: boolean
                        example: true
                      hasPrevPage:
                        type: boolean
                        example: false
                    required:
                      - total
                      - page
                      - limit
                      - totalPages
                      - hasNextPage
                      - hasPrevPage
                required:
                  - data
                  - meta
    post:
      tags:
        - Monitors
      summary: Create a monitor
      security:
        - bearerAuth: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateMonitorRequest'
      responses:
        '201':
          description: Monitor created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Monitor'
                required:
                  - data
  /api/v1/monitors/{monitorId}:
    get:
      tags:
        - Monitors
      summary: Get a monitor
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: monitorId
          in: path
      responses:
        '200':
          description: Monitor
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Monitor'
                required:
                  - data
        '404':
          description: Not found
  /api/v1/webhooks:
    get:
      tags:
        - Webhooks
      summary: List webhooks for a workspace
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: workspaceId
          in: query
      responses:
        '200':
          description: Webhook list
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Webhook'
                required:
                  - data
    post:
      tags:
        - Webhooks
      summary: Register a webhook endpoint
      security:
        - bearerAuth: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookRequest'
      responses:
        '201':
          description: Webhook registered
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Webhook'
                required:
                  - data
  /api/v1/webhooks/{webhookId}:
    delete:
      tags:
        - Webhooks
      summary: Delete a webhook
      security:
        - bearerAuth: []
      parameters:
        - schema:
            type: string
            format: uuid
            example: 550e8400-e29b-41d4-a716-446655440000
          required: true
          name: webhookId
          in: path
      responses:
        '204':
          description: Deleted
webhooks: {}
