openapi: 3.1.0
info:
  title: Kodo Status Page API
  description: |
    API for managing status pages, incidents, services, and monitoring.

    ## Authentication
    All API requests require authentication via the `X-API-Key` header:
    ```
    X-API-Key: your_api_key_here
    ```

    You can find your API key in the [Dashboard Settings](/dashboard/api).

    ## Rate Limits
    - 100 requests per minute per API key (standard endpoints)
    - 120 requests per minute (heartbeat endpoints)

    ## Errors
    All errors return a JSON object with an `error` field:
    ```json
    {
      "error": "Description of what went wrong"
    }
    ```
  version: 1.0.0
  contact:
    name: Kodo Support
    email: support@kodostatus.com
  license:
    name: MIT

servers:
  - url: https://kodostatus.com/api/v1
    description: Production
  - url: http://localhost:3000/api/v1
    description: Local Development

tags:
  - name: Status Pages
    description: Create and manage status pages
  - name: Status Page Design
    description: Draft, validate, and publish status page designs
  - name: Incidents
    description: Create and manage incidents
  - name: Services
    description: Manage services/components
  - name: Uptime Monitors
    description: HTTP endpoint monitoring
  - name: SSL Monitors
    description: SSL certificate monitoring
  - name: Domain Monitors
    description: Domain expiration monitoring
  - name: Heartbeat
    description: Cron job and worker monitoring
  - name: Metrics
    description: Push internal metrics
  - name: Notification Channels
    description: Configure notification destinations
  - name: Webhooks
    description: Receive real-time events
  - name: Beacon
    description: Client-side error tracking
  - name: Workflows
    description: Manage workflow automation
  - name: Maintenance
    description: Schedule and manage maintenance windows
  - name: Configuration
    description: Organization configuration
  - name: API Keys
    description: Manage API keys
  - name: Audit Log
    description: View audit log entries
  - name: Releases
    description: Track releases
  - name: Source Maps
    description: Upload source maps for error tracking

security:
  - ApiKeyAuth: []

paths:
  # ============================================
  # STATUS PAGES
  # ============================================
  /status-pages:
    get:
      tags: [Status Pages]
      summary: List status pages
      operationId: listStatusPages
      responses:
        '200':
          description: List of status pages
          content:
            application/json:
              schema:
                type: object
                properties:
                  pages:
                    type: array
                    items:
                      $ref: '#/components/schemas/StatusPageSummary'

    post:
      tags: [Status Pages]
      summary: Create status page
      operationId: createStatusPage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateStatusPageRequest'
      responses:
        '201':
          description: Status page created
          content:
            application/json:
              schema:
                type: object
                properties:
                  page:
                    $ref: '#/components/schemas/StatusPage'
        '400':
          description: Invalid request (bad slug, invalid color format, etc.)
        '403':
          description: Plan limit reached or visibility requires higher plan
        '409':
          description: Slug already taken

  /status-pages/{pageId}:
    parameters:
      - name: pageId
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      tags: [Status Pages]
      summary: Get status page
      operationId: getStatusPage
      responses:
        '200':
          description: Status page details
          content:
            application/json:
              schema:
                type: object
                properties:
                  page:
                    $ref: '#/components/schemas/StatusPage'
        '404':
          description: Status page not found

    patch:
      tags: [Status Pages]
      summary: Update status page
      operationId: updateStatusPage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateStatusPageRequest'
      responses:
        '200':
          description: Status page updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  page:
                    $ref: '#/components/schemas/StatusPage'
                  warning:
                    type: string
                    description: Design drift warning if updating Design API-managed fields directly
        '404':
          description: Status page not found

    delete:
      tags: [Status Pages]
      summary: Delete status page
      operationId: deleteStatusPage
      responses:
        '200':
          description: Status page deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: Cannot delete only page or default page
        '404':
          description: Status page not found

  /status-pages/{pageId}/services:
    parameters:
      - name: pageId
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      tags: [Status Pages]
      summary: List assigned services
      operationId: listStatusPageServices
      responses:
        '200':
          description: Assigned services
          content:
            application/json:
              schema:
                type: object
                properties:
                  services:
                    type: array
                    items:
                      $ref: '#/components/schemas/StatusPageServiceAssignment'
        '404':
          description: Status page not found

    put:
      tags: [Status Pages]
      summary: Replace assigned services
      operationId: assignStatusPageServices
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [service_ids]
              properties:
                service_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
      responses:
        '200':
          description: Services assigned
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'
        '400':
          description: Invalid service_ids
        '403':
          description: One or more service_ids do not belong to this organization
        '404':
          description: Status page not found

  # ============================================
  # STATUS PAGE DESIGN
  # ============================================
  /status-pages/{pageId}/design:
    parameters:
      - name: pageId
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      tags: [Status Page Design]
      summary: Get design state
      description: Returns published and draft version pointers plus expanded version records.
      operationId: getDesignState
      responses:
        '200':
          description: Current design state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DesignState'
        '404':
          description: Status page not found

  /status-pages/{pageId}/design/draft:
    parameters:
      - name: pageId
        in: path
        required: true
        schema:
          type: string
          format: uuid

    put:
      tags: [Status Page Design]
      summary: Upsert design draft
      description: Create or update the draft version for a status page.
      operationId: upsertDesignDraft
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DesignDraftPayload'
      responses:
        '200':
          description: Draft saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  draft_version:
                    $ref: '#/components/schemas/DesignVersion'
                  warnings:
                    type: array
                    items:
                      $ref: '#/components/schemas/DesignValidationIssue'
        '400':
          description: Validation or schema error
        '403':
          description: Plan restriction (custom template requires Pro+)
        '404':
          description: Status page not found

  /status-pages/{pageId}/design/draft/validate:
    parameters:
      - name: pageId
        in: path
        required: true
        schema:
          type: string
          format: uuid

    post:
      tags: [Status Page Design]
      summary: Validate design draft
      description: Validate current draft or an optional payload without saving changes.
      operationId: validateDesignDraft
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DesignDraftPayload'
      responses:
        '200':
          description: Validation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DesignValidationResult'
        '400':
          description: Invalid JSON or schema
        '404':
          description: Status page not found

  /status-pages/{pageId}/design/publish:
    parameters:
      - name: pageId
        in: path
        required: true
        schema:
          type: string
          format: uuid

    post:
      tags: [Status Page Design]
      summary: Publish design draft
      description: Publish the draft version if expected_draft_version_id matches server state.
      operationId: publishDesignDraft
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [expected_draft_version_id]
              properties:
                expected_draft_version_id:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Design published
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  status_page_id:
                    type: string
                    format: uuid
                  current_version_id:
                    type: string
                    format: uuid
                  warnings:
                    type: array
                    items:
                      $ref: '#/components/schemas/DesignValidationIssue'
        '400':
          description: Validation or schema error
        '403':
          description: Plan restriction (custom template requires Pro+)
        '404':
          description: Draft or status page not found
        '409':
          description: Draft version mismatch

  /status-pages/{pageId}/design/versions:
    parameters:
      - name: pageId
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      tags: [Status Page Design]
      summary: List design versions
      description: Paginated list of published design versions for a status page.
      operationId: listDesignVersions
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
        - name: cursor
          in: query
          schema:
            type: integer
          description: Cursor for pagination (version_number)
      responses:
        '200':
          description: List of versions
          content:
            application/json:
              schema:
                type: object
                properties:
                  versions:
                    type: array
                    items:
                      $ref: '#/components/schemas/DesignVersionSummary'
                  next_cursor:
                    type: integer
                    nullable: true
                  current_version_id:
                    type: string
                    format: uuid
                    nullable: true
                  draft_version_id:
                    type: string
                    format: uuid
                    nullable: true

  /status-pages/{pageId}/design/versions/{versionId}:
    parameters:
      - name: pageId
        in: path
        required: true
        schema:
          type: string
          format: uuid
      - name: versionId
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      tags: [Status Page Design]
      summary: Get design version
      operationId: getDesignVersion
      responses:
        '200':
          description: Version details
          content:
            application/json:
              schema:
                type: object
                properties:
                  version:
                    $ref: '#/components/schemas/DesignVersion'
                  is_current:
                    type: boolean
                  is_draft:
                    type: boolean
        '404':
          description: Version not found

  # ============================================
  # INCIDENTS
  # ============================================
  /incidents:
    get:
      tags: [Incidents]
      summary: List incidents
      description: Get all incidents for your organization
      operationId: listIncidents
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [investigating, identified, monitoring, resolved]
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 100
      responses:
        '200':
          description: List of incidents
          content:
            application/json:
              schema:
                type: object
                properties:
                  incidents:
                    type: array
                    items:
                      $ref: '#/components/schemas/Incident'

    post:
      tags: [Incidents]
      summary: Create incident
      operationId: createIncident
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateIncidentRequest'
      responses:
        '201':
          description: Incident created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Incident'

  /incidents/{incidentId}:
    parameters:
      - name: incidentId
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      tags: [Incidents]
      summary: Get incident
      operationId: getIncident
      responses:
        '200':
          description: Incident details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Incident'

    patch:
      tags: [Incidents]
      summary: Update incident
      operationId: updateIncident
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateIncidentRequest'
      responses:
        '200':
          description: Incident updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Incident'

    delete:
      tags: [Incidents]
      summary: Delete incident
      operationId: deleteIncident
      responses:
        '200':
          description: Incident deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'

  # ============================================
  # SERVICES
  # ============================================
  /services:
    get:
      tags: [Services]
      summary: List services
      operationId: listServices
      responses:
        '200':
          description: List of services
          content:
            application/json:
              schema:
                type: object
                properties:
                  services:
                    type: array
                    items:
                      $ref: '#/components/schemas/Service'

    post:
      tags: [Services]
      summary: Create service
      operationId: createService
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateServiceRequest'
      responses:
        '201':
          description: Service created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Service'

  /services/{serviceId}:
    parameters:
      - name: serviceId
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      tags: [Services]
      summary: Get service
      operationId: getService
      responses:
        '200':
          description: Service details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Service'

    patch:
      tags: [Services]
      summary: Update service
      operationId: updateService
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateServiceRequest'
      responses:
        '200':
          description: Service updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Service'

    delete:
      tags: [Services]
      summary: Delete service
      operationId: deleteService
      responses:
        '200':
          description: Service deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'

  # ============================================
  # UPTIME MONITORS
  # ============================================
  /uptime-monitors:
    get:
      tags: [Uptime Monitors]
      summary: List uptime monitors
      operationId: listUptimeMonitors
      responses:
        '200':
          description: List of uptime monitors
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitors:
                    type: array
                    items:
                      $ref: '#/components/schemas/UptimeMonitor'

    post:
      tags: [Uptime Monitors]
      summary: Create uptime monitor
      operationId: createUptimeMonitor
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUptimeMonitorRequest'
      responses:
        '201':
          description: Monitor created
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitor:
                    $ref: '#/components/schemas/UptimeMonitor'

  /uptime-monitors/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      tags: [Uptime Monitors]
      summary: Get uptime monitor
      operationId: getUptimeMonitor
      responses:
        '200':
          description: Monitor details
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitor:
                    $ref: '#/components/schemas/UptimeMonitor'

    patch:
      tags: [Uptime Monitors]
      summary: Update uptime monitor
      operationId: updateUptimeMonitor
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateUptimeMonitorRequest'
      responses:
        '200':
          description: Monitor updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitor:
                    $ref: '#/components/schemas/UptimeMonitor'

    delete:
      tags: [Uptime Monitors]
      summary: Delete uptime monitor
      operationId: deleteUptimeMonitor
      responses:
        '200':
          description: Monitor deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'

  # ============================================
  # SSL MONITORS
  # ============================================
  /ssl-monitors:
    get:
      tags: [SSL Monitors]
      summary: List SSL monitors
      operationId: listSslMonitors
      responses:
        '200':
          description: List of SSL monitors
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitors:
                    type: array
                    items:
                      $ref: '#/components/schemas/SslMonitor'

    post:
      tags: [SSL Monitors]
      summary: Create SSL monitor
      operationId: createSslMonitor
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateSslMonitorRequest'
      responses:
        '200':
          description: Monitor created
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitor:
                    $ref: '#/components/schemas/SslMonitor'
                  initial_check:
                    type: object
                    properties:
                      status:
                        type: string
                      days_until_expiry:
                        type: integer
                        nullable: true
                      valid_to:
                        type: string
                        format: date-time
                        nullable: true
                      issuer:
                        type: string
                        nullable: true

  /ssl-monitors/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      tags: [SSL Monitors]
      summary: Get SSL monitor
      operationId: getSslMonitor
      responses:
        '200':
          description: Monitor details
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitor:
                    $ref: '#/components/schemas/SslMonitor'
                  checks:
                    type: array
                    items:
                      type: object

    patch:
      tags: [SSL Monitors]
      summary: Update SSL monitor
      operationId: updateSslMonitor
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateSslMonitorRequest'
      responses:
        '200':
          description: Monitor updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitor:
                    $ref: '#/components/schemas/SslMonitor'

    delete:
      tags: [SSL Monitors]
      summary: Delete SSL monitor
      operationId: deleteSslMonitor
      responses:
        '200':
          description: Monitor deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'

    post:
      tags: [SSL Monitors]
      summary: Run SSL check
      operationId: checkSslMonitor
      responses:
        '200':
          description: Check result
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  days_until_expiry:
                    type: integer
                    nullable: true
                  valid_to:
                    type: string
                    format: date-time
                    nullable: true
                  issuer:
                    type: string
                    nullable: true
                  subject:
                    type: string
                    nullable: true
                  error:
                    type: string
                    nullable: true
                  response_time_ms:
                    type: integer
                    nullable: true

  # ============================================
  # DOMAIN MONITORS
  # ============================================
  /domain-monitors:
    get:
      tags: [Domain Monitors]
      summary: List domain monitors
      operationId: listDomainMonitors
      responses:
        '200':
          description: List of domain monitors
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitors:
                    type: array
                    items:
                      $ref: '#/components/schemas/DomainMonitor'

    post:
      tags: [Domain Monitors]
      summary: Create domain monitor
      operationId: createDomainMonitor
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateDomainMonitorRequest'
      responses:
        '200':
          description: Monitor created
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitor:
                    $ref: '#/components/schemas/DomainMonitor'
                  initial_check:
                    type: object
                    properties:
                      status:
                        type: string
                      days_until_expiry:
                        type: integer
                        nullable: true
                      expires_on:
                        type: string
                        format: date-time
                        nullable: true
                      registrar:
                        type: string
                        nullable: true

  /domain-monitors/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      tags: [Domain Monitors]
      summary: Get domain monitor
      operationId: getDomainMonitor
      responses:
        '200':
          description: Monitor details
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitor:
                    $ref: '#/components/schemas/DomainMonitor'
                  checks:
                    type: array
                    items:
                      type: object

    patch:
      tags: [Domain Monitors]
      summary: Update domain monitor
      operationId: updateDomainMonitor
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateDomainMonitorRequest'
      responses:
        '200':
          description: Monitor updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitor:
                    $ref: '#/components/schemas/DomainMonitor'

    delete:
      tags: [Domain Monitors]
      summary: Delete domain monitor
      operationId: deleteDomainMonitor
      responses:
        '200':
          description: Monitor deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'

    post:
      tags: [Domain Monitors]
      summary: Run domain check
      operationId: checkDomainMonitor
      responses:
        '200':
          description: Check result
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  days_until_expiry:
                    type: integer
                    nullable: true
                  expires_on:
                    type: string
                    format: date-time
                    nullable: true
                  registrar:
                    type: string
                    nullable: true
                  name_servers:
                    type: array
                    items:
                      type: string
                    nullable: true
                  error:
                    type: string
                    nullable: true

  # ============================================
  # NOTIFICATION CHANNELS
  # ============================================
  /notifications/channels:
    get:
      tags: [Notification Channels]
      summary: List notification channels
      operationId: listNotificationChannels
      responses:
        '200':
          description: List of channels
          content:
            application/json:
              schema:
                type: object
                properties:
                  channels:
                    type: array
                    items:
                      $ref: '#/components/schemas/NotificationChannel'

    post:
      tags: [Notification Channels]
      summary: Create notification channel
      operationId: createNotificationChannel
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateNotificationChannelRequest'
      responses:
        '201':
          description: Channel created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotificationChannel'

  /notifications/channels/{id}:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid

    get:
      tags: [Notification Channels]
      summary: Get notification channel
      operationId: getNotificationChannel
      responses:
        '200':
          description: Channel details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotificationChannel'

    patch:
      tags: [Notification Channels]
      summary: Update notification channel
      operationId: updateNotificationChannel
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateNotificationChannelRequest'
      responses:
        '200':
          description: Channel updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotificationChannel'

    delete:
      tags: [Notification Channels]
      summary: Delete notification channel
      operationId: deleteNotificationChannel
      responses:
        '200':
          description: Channel deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'

  /notifications/channels/{id}/test:
    parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
          format: uuid

    post:
      tags: [Notification Channels]
      summary: Test notification channel
      operationId: testNotificationChannel
      responses:
        '200':
          description: Test sent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'

  # ============================================
  # HEARTBEAT
  # Note: These endpoints use /api base, NOT /api/v1.
  # Full URL: https://kodostatus.com/api/heartbeat/{monitorId}
  # ============================================
  /heartbeat/{monitorId}:
    servers:
      - url: https://kodostatus.com/api
        description: Production
      - url: http://localhost:3000/api
        description: Local Development
    parameters:
      - name: monitorId
        in: path
        required: true
        schema:
          type: string
          format: uuid

    post:
      tags: [Heartbeat]
      summary: Send heartbeat
      description: |
        Send a heartbeat ping for cron jobs, workers, or scheduled tasks.
        Requires API key authentication. The monitor must belong to the authenticated organization.
      operationId: sendHeartbeat
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HeartbeatRequest'
      responses:
        '200':
          description: Heartbeat recorded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HeartbeatResponse'
        '401':
          description: API key required or invalid
        '404':
          description: Monitor not found or does not belong to this organization

    get:
      tags: [Heartbeat]
      summary: Get heartbeat status
      operationId: getHeartbeatStatus
      responses:
        '200':
          description: Heartbeat status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HeartbeatStatus'

  # ============================================
  # METRICS
  # Note: Uses /api base, NOT /api/v1.
  # Full URL: https://kodostatus.com/api/metrics/ingest
  # ============================================
  /metrics/ingest:
    servers:
      - url: https://kodostatus.com/api
        description: Production
      - url: http://localhost:3000/api
        description: Local Development
    post:
      tags: [Metrics]
      summary: Push metrics
      description: Push internal metrics for auto-health detection
      operationId: ingestMetrics
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MetricsRequest'
      responses:
        '200':
          description: Metrics received
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MetricsResponse'

  # ============================================
  # BEACON (Error Tracking)
  # Note: Uses /api base, NOT /api/v1.
  # Full URL: https://kodostatus.com/api/beacon
  # ============================================
  /beacon:
    servers:
      - url: https://kodostatus.com/api
        description: Production
      - url: http://localhost:3000/api
        description: Local Development
    post:
      tags: [Beacon]
      summary: Report client error
      description: Report JavaScript errors and performance metrics from frontend
      operationId: reportBeacon
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BeaconRequest'
      responses:
        '200':
          description: Error recorded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessResponse'

  # ============================================
  # WORKFLOWS
  # ============================================
  /workflows/{id}/trigger:
    post:
      tags: [Workflows]
      summary: Trigger a workflow
      operationId: triggerWorkflow
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                payload:
                  type: object
      responses:
        '200':
          description: Workflow triggered

  /incidents/{incidentId}/publish:
    post:
      tags: [Incidents]
      summary: Publish incident to status page
      operationId: publishIncident
      security:
        - ApiKeyAuth: []
      parameters:
        - name: incidentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                status_page_ids:
                  type: array
                  items:
                    type: string
                    format: uuid
      responses:
        '200':
          description: Incident published

  # ============================================
  # MAINTENANCE
  # ============================================
  /maintenance:
    get:
      tags: [Maintenance]
      summary: List maintenance windows
      operationId: listMaintenance
      security:
        - ApiKeyAuth: []
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [scheduled, in_progress, completed]
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
      responses:
        '200':
          description: Maintenance windows list
          content:
            application/json:
              schema:
                type: object
                properties:
                  maintenance:
                    type: array
                    items:
                      $ref: '#/components/schemas/MaintenanceWindow'
    post:
      tags: [Maintenance]
      summary: Create maintenance window
      operationId: createMaintenance
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateMaintenance'
      responses:
        '201':
          description: Maintenance created
  /maintenance/{id}:
    get:
      tags: [Maintenance]
      summary: Get maintenance window
      operationId: getMaintenance
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Maintenance window details
    patch:
      tags: [Maintenance]
      summary: Update maintenance window
      operationId: updateMaintenance
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateMaintenance'
      responses:
        '200':
          description: Maintenance updated
    delete:
      tags: [Maintenance]
      summary: Delete maintenance window
      operationId: deleteMaintenance
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Maintenance deleted

  # ============================================
  # CONFIGURATION
  # ============================================
  /config:
    get:
      tags: [Configuration]
      summary: Get organization configuration
      operationId: getConfig
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: Organization config
    patch:
      tags: [Configuration]
      summary: Update organization configuration
      operationId: updateConfig
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Config updated

  # ============================================
  # RELEASES
  # ============================================
  /releases:
    get:
      tags: [Releases]
      summary: List releases
      operationId: listReleases
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: Releases list
    post:
      tags: [Releases]
      summary: Create release
      operationId: createRelease
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                version:
                  type: string
                service:
                  type: string
      responses:
        '201':
          description: Release created

  # ============================================
  # SOURCE MAPS
  # ============================================
  /sourcemaps:
    post:
      tags: [Source Maps]
      summary: Upload source map
      operationId: uploadSourcemap
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                release:
                  type: string
      responses:
        '200':
          description: Source map uploaded

  # ============================================
  # API KEYS
  # ============================================
  /api-keys:
    get:
      tags: [API Keys]
      summary: List API keys
      operationId: listApiKeys
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: API keys list
          content:
            application/json:
              schema:
                type: object
                properties:
                  api_keys:
                    type: array
                    items:
                      $ref: '#/components/schemas/ApiKey'
    post:
      tags: [API Keys]
      summary: Create API key
      operationId: createApiKey
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApiKeyCreate'
      responses:
        '201':
          description: API key created (plaintext key returned once)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ApiKey'
                  - type: object
                    properties:
                      key:
                        type: string
                        description: Plaintext key (only returned on creation)
  /api-keys/{id}:
    get:
      tags: [API Keys]
      summary: Get API key
      operationId: getApiKey
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: API key details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKey'
    patch:
      tags: [API Keys]
      summary: Update API key
      operationId: updateApiKey
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                scopes:
                  type: array
                  items:
                    type: string
                enabled:
                  type: boolean
                expires_at:
                  type: string
                  format: date-time
                  nullable: true
      responses:
        '200':
          description: API key updated
    delete:
      tags: [API Keys]
      summary: Delete API key
      operationId: deleteApiKey
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: API key deleted

  # ============================================
  # AUDIT LOG
  # ============================================
  /audit-log:
    get:
      tags: [Audit Log]
      summary: List audit log entries
      operationId: listAuditLog
      security:
        - ApiKeyAuth: []
      parameters:
        - name: resource_type
          in: query
          schema:
            type: string
        - name: resource_id
          in: query
          schema:
            type: string
        - name: actor_type
          in: query
          schema:
            type: string
            enum: [user, api_key, system]
        - name: action
          in: query
          schema:
            type: string
        - name: from
          in: query
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          schema:
            type: string
            format: date-time
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Audit log entries
          content:
            application/json:
              schema:
                type: object
                properties:
                  entries:
                    type: array
                    items:
                      $ref: '#/components/schemas/AuditLogEntry'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key

  schemas:
    SuccessResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true

    # ------------------------------------------
    # Status Page schemas
    # ------------------------------------------
    StatusPageSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        custom_domain:
          type: string
          nullable: true
        logo_url:
          type: string
          nullable: true
        primary_color:
          type: string
          nullable: true
        status_page_visibility:
          type: string
          enum: [public, private, internal]
        is_default:
          type: boolean
        service_count:
          type: integer
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    StatusPage:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        slug:
          type: string
        status_page_visibility:
          type: string
          enum: [public, private, internal]
        is_default:
          type: boolean
        primary_color:
          type: string
          nullable: true
        secondary_color:
          type: string
          nullable: true
        tagline:
          type: string
          nullable: true
        logo_url:
          type: string
          nullable: true
        favicon_url:
          type: string
          nullable: true
        theme_mode:
          type: string
          enum: [light, dark, system]
        status_page_theme:
          type: string
          enum: [default, minimal, corporate, neon, ocean, sunset]
        font_family:
          type: string
        header_style:
          type: string
          enum: [centered, left-aligned]
        border_radius:
          type: string
          enum: [rounded, sharp, pill]
        show_subscribe_form:
          type: boolean
        show_incident_history:
          type: boolean
        show_uptime_graph:
          type: boolean
        show_uptime_bars:
          type: boolean
        show_sla_targets:
          type: boolean
        announcement_text:
          type: string
          nullable: true
        announcement_type:
          type: string
          enum: [info, warning, critical, maintenance]
          nullable: true
        announcement_enabled:
          type: boolean
        custom_domain:
          type: string
          nullable: true
        hide_branding:
          type: boolean
        custom_css:
          type: string
          nullable: true
        custom_header_html:
          type: string
          nullable: true
        custom_footer_html:
          type: string
          nullable: true
        custom_template:
          type: string
          nullable: true
        status_page_language:
          type: string
        status_page_password_hint:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    StatusPageServiceRef:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        status:
          type: string
          enum: [operational, degraded, partial_outage, major_outage, maintenance]
      nullable: true

    StatusPageServiceAssignment:
      type: object
      properties:
        id:
          type: string
          format: uuid
        service_id:
          type: string
          format: uuid
        display_order:
          type: integer
        services:
          $ref: '#/components/schemas/StatusPageServiceRef'

    CreateStatusPageRequest:
      type: object
      required: [name, slug]
      properties:
        name:
          type: string
          description: Display name for the status page
        slug:
          type: string
          description: URL slug (lowercase letters, numbers, hyphens)
        status_page_visibility:
          type: string
          enum: [public, private, internal]
          default: public
        service_ids:
          type: array
          items:
            type: string
            format: uuid
          description: Service IDs to assign (displayed in order)
        primary_color:
          type: string
          pattern: '^#[0-9a-fA-F]{6}$'
        secondary_color:
          type: string
          pattern: '^#[0-9a-fA-F]{6}$'
        tagline:
          type: string
        logo_url:
          type: string
        favicon_url:
          type: string
        theme_mode:
          type: string
          enum: [light, dark, system]
        status_page_theme:
          type: string
          enum: [default, minimal, corporate, neon, ocean, sunset]
        font_family:
          type: string
          enum: [inter, dm-sans, space-grotesk, jetbrains-mono, poppins, merriweather, system]
        header_style:
          type: string
          enum: [centered, left-aligned]
        border_radius:
          type: string
          enum: [rounded, sharp, pill]
        show_subscribe_form:
          type: boolean
        show_incident_history:
          type: boolean
        show_uptime_graph:
          type: boolean
        show_uptime_bars:
          type: boolean
        show_sla_targets:
          type: boolean
        announcement_text:
          type: string
        announcement_type:
          type: string
          enum: [info, warning, critical, maintenance]
        announcement_enabled:
          type: boolean

    UpdateStatusPageRequest:
      type: object
      properties:
        name:
          type: string
        slug:
          type: string
        status_page_visibility:
          type: string
          enum: [public, private, internal]
        primary_color:
          type: string
          pattern: '^#[0-9a-fA-F]{6}$'
        secondary_color:
          type: string
          pattern: '^#[0-9a-fA-F]{6}$'
        tagline:
          type: string
        logo_url:
          type: string
        favicon_url:
          type: string
        theme_mode:
          type: string
          enum: [light, dark, system]
        status_page_theme:
          type: string
          enum: [default, minimal, corporate, neon, ocean, sunset]
        font_family:
          type: string
          enum: [inter, dm-sans, space-grotesk, jetbrains-mono, poppins, merriweather, system]
        header_style:
          type: string
          enum: [centered, left-aligned]
        border_radius:
          type: string
          enum: [rounded, sharp, pill]
        show_subscribe_form:
          type: boolean
        show_incident_history:
          type: boolean
        show_uptime_graph:
          type: boolean
        show_uptime_bars:
          type: boolean
        show_sla_targets:
          type: boolean
        announcement_text:
          type: string
        announcement_type:
          type: string
          enum: [info, warning, critical, maintenance]
        announcement_enabled:
          type: boolean
        custom_domain:
          type: string
          nullable: true
        custom_css:
          type: string
          description: Custom CSS (max 64KB, requires Pro+)
        custom_header_html:
          type: string
        custom_footer_html:
          type: string
        custom_template:
          type: string
          nullable: true
          description: Custom HTML template (max 64KB, requires Pro+)
        hide_branding:
          type: boolean
        status_page_password_hint:
          type: string
        status_page_language:
          type: string

    # ------------------------------------------
    # Design API schemas
    # ------------------------------------------
    DesignState:
      type: object
      properties:
        status_page_id:
          type: string
          format: uuid
        current_version_id:
          type: string
          format: uuid
          nullable: true
        draft_version_id:
          type: string
          format: uuid
          nullable: true
        current_version:
          $ref: '#/components/schemas/DesignVersion'
          nullable: true
        draft_version:
          $ref: '#/components/schemas/DesignVersion'
          nullable: true
        effective_design:
          $ref: '#/components/schemas/DesignVersion'
          nullable: true

    DesignDraftPayload:
      type: object
      properties:
        theme_tokens:
          type: object
          additionalProperties: true
        layout:
          type: object
          additionalProperties: true
        template:
          type: string
          nullable: true
        template_mode:
          type: string
          enum: [default, custom]
        summary:
          type: string

    DesignVersion:
      type: object
      properties:
        id:
          type: string
          format: uuid
        status_page_id:
          type: string
          format: uuid
        version_number:
          type: integer
        state:
          type: string
          enum: [draft, published, archived]
        base_version_id:
          type: string
          format: uuid
          nullable: true
        theme_tokens:
          type: object
          additionalProperties: true
        layout:
          type: object
          additionalProperties: true
        template:
          type: string
          nullable: true
        template_mode:
          type: string
          enum: [default, custom]
        summary:
          type: string
          nullable: true
        created_by:
          type: string
        created_at:
          type: string
          format: date-time
        published_at:
          type: string
          format: date-time
          nullable: true

    DesignVersionSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        status_page_id:
          type: string
          format: uuid
        version_number:
          type: integer
        state:
          type: string
          enum: [draft, published, archived]
        base_version_id:
          type: string
          format: uuid
          nullable: true
        template_mode:
          type: string
          enum: [default, custom]
        summary:
          type: string
          nullable: true
        created_by:
          type: string
        created_at:
          type: string
          format: date-time
        published_at:
          type: string
          format: date-time
          nullable: true

    DesignValidationIssue:
      type: object
      properties:
        code:
          type: string
        path:
          type: string
        message:
          type: string
        severity:
          type: string
          enum: [error, warning]

    DesignValidationResult:
      type: object
      properties:
        valid:
          type: boolean
        errors:
          type: array
          items:
            $ref: '#/components/schemas/DesignValidationIssue'
        warnings:
          type: array
          items:
            $ref: '#/components/schemas/DesignValidationIssue'

    Incident:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        status:
          type: string
          enum: [investigating, identified, monitoring, resolved]
        severity:
          type: string
          enum: [minor, major, critical]
        started_at:
          type: string
          format: date-time
        resolved_at:
          type: string
          format: date-time
          nullable: true
        updates:
          type: array
          items:
            $ref: '#/components/schemas/IncidentUpdate'
        services:
          type: array
          items:
            $ref: '#/components/schemas/ServiceRef'

    IncidentUpdate:
      type: object
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
        message:
          type: string
        created_at:
          type: string
          format: date-time

    ServiceRef:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string

    Service:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
          nullable: true
        status:
          type: string
          enum: [operational, degraded, partial_outage, major_outage, maintenance]
        display_order:
          type: integer

    CreateIncidentRequest:
      type: object
      required: [title]
      properties:
        title:
          type: string
        status:
          type: string
          enum: [investigating, identified, monitoring, resolved]
          default: investigating
        severity:
          type: string
          enum: [minor, major, critical]
          default: minor
        message:
          type: string
        services:
          type: array
          items:
            type: string

    UpdateIncidentRequest:
      type: object
      properties:
        status:
          type: string
          enum: [investigating, identified, monitoring, resolved]
        message:
          type: string
        title:
          type: string
        severity:
          type: string
          enum: [minor, major, critical]

    CreateServiceRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        description:
          type: string
        status:
          type: string
          enum: [operational, degraded, partial_outage, major_outage, maintenance]
          default: operational

    UpdateServiceRequest:
      type: object
      properties:
        name:
          type: string
        description:
          type: string
        status:
          type: string
          enum: [operational, degraded, partial_outage, major_outage, maintenance]

    UptimeMonitor:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        url:
          type: string
        type:
          type: string
          enum: [http, heartbeat]
        interval_seconds:
          type: integer
        enabled:
          type: boolean
        last_status:
          type: string
          nullable: true
        last_checked_at:
          type: string
          format: date-time
          nullable: true
        last_response_time_ms:
          type: integer
          nullable: true
        service_id:
          type: string
          format: uuid
          nullable: true
        services:
          type: object
          properties:
            id:
              type: string
              format: uuid
            name:
              type: string
          nullable: true

    CreateUptimeMonitorRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        type:
          type: string
          enum: [http, heartbeat]
          default: http
        url:
          type: string
          description: Required when type is http
        interval_seconds:
          type: integer
          default: 300
        service_id:
          type: string
          format: uuid
        enabled:
          type: boolean

    UpdateUptimeMonitorRequest:
      type: object
      properties:
        name:
          type: string
        url:
          type: string
        interval_seconds:
          type: integer
        enabled:
          type: boolean
        service_id:
          type: string
          format: uuid

    SslMonitor:
      type: object
      properties:
        id:
          type: string
          format: uuid
        hostname:
          type: string
        port:
          type: integer
        enabled:
          type: boolean
        last_status:
          type: string
          nullable: true
        last_checked_at:
          type: string
          format: date-time
          nullable: true
        days_until_expiry:
          type: integer
          nullable: true
        issuer:
          type: string
          nullable: true
        subject:
          type: string
          nullable: true
        valid_from:
          type: string
          format: date-time
          nullable: true
        valid_to:
          type: string
          format: date-time
          nullable: true
        alert_threshold_critical:
          type: integer
        alert_threshold_warning:
          type: integer
        alert_threshold_info:
          type: integer
        service_id:
          type: string
          format: uuid
          nullable: true

    CreateSslMonitorRequest:
      type: object
      required: [hostname]
      properties:
        hostname:
          type: string
        port:
          type: integer
          default: 443
        alert_threshold_critical:
          type: integer
          default: 7
        alert_threshold_warning:
          type: integer
          default: 14
        alert_threshold_info:
          type: integer
          default: 30
        service_id:
          type: string
          format: uuid

    UpdateSslMonitorRequest:
      type: object
      properties:
        enabled:
          type: boolean
        service_id:
          type: string
          format: uuid
        alert_threshold_critical:
          type: integer
        alert_threshold_warning:
          type: integer
        alert_threshold_info:
          type: integer

    DomainMonitor:
      type: object
      properties:
        id:
          type: string
          format: uuid
        domain:
          type: string
        enabled:
          type: boolean
        last_status:
          type: string
          nullable: true
        last_checked_at:
          type: string
          format: date-time
          nullable: true
        days_until_expiry:
          type: integer
          nullable: true
        registrar:
          type: string
          nullable: true
        expires_on:
          type: string
          format: date-time
          nullable: true
        name_servers:
          type: array
          items:
            type: string
          nullable: true
        alert_threshold_critical:
          type: integer
        alert_threshold_warning:
          type: integer
        alert_threshold_info:
          type: integer
        service_id:
          type: string
          format: uuid
          nullable: true

    CreateDomainMonitorRequest:
      type: object
      required: [domain]
      properties:
        domain:
          type: string
        service_id:
          type: string
          format: uuid
        alert_threshold_critical:
          type: integer
          default: 14
        alert_threshold_warning:
          type: integer
          default: 30
        alert_threshold_info:
          type: integer
          default: 60

    UpdateDomainMonitorRequest:
      type: object
      properties:
        enabled:
          type: boolean
        service_id:
          type: string
          format: uuid
        alert_threshold_critical:
          type: integer
        alert_threshold_warning:
          type: integer
        alert_threshold_info:
          type: integer

    NotificationChannel:
      type: object
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum: [email, slack, discord, webhook, pagerduty, opsgenie]
        name:
          type: string
        enabled:
          type: boolean
        config:
          type: object

    CreateNotificationChannelRequest:
      type: object
      required: [type, name, config]
      properties:
        type:
          type: string
          enum: [email, slack, discord, webhook, pagerduty, opsgenie]
        name:
          type: string
        config:
          type: object

    UpdateNotificationChannelRequest:
      type: object
      properties:
        name:
          type: string
        enabled:
          type: boolean
        config:
          type: object

    HeartbeatRequest:
      type: object
      properties:
        status:
          type: string
          enum: [up, down, degraded]
        response_time_ms:
          type: integer
        message:
          type: string

    HeartbeatResponse:
      type: object
      properties:
        success:
          type: boolean
        recorded_at:
          type: string
          format: date-time
        monitor_status:
          type: string

    HeartbeatStatus:
      type: object
      properties:
        status:
          type: string
          enum: [up, down, degraded]
        last_ping:
          type: string
          format: date-time
        expected_interval_seconds:
          type: integer

    MetricsRequest:
      type: object
      required: [api_key, metrics]
      properties:
        api_key:
          type: string
        service:
          type: string
        metrics:
          type: object
          properties:
            error_rate:
              type: number
            response_time_ms:
              type: number
            cpu_percent:
              type: number
            memory_percent:
              type: number

    MetricsResponse:
      type: object
      properties:
        success:
          type: boolean
        received_at:
          type: string
          format: date-time
        health_status:
          type: string
          enum: [operational, degraded, partial_outage, major_outage]
        alerts:
          type: array
          items:
            type: string

    BeaconRequest:
      type: object
      required: [api_key, type]
      properties:
        api_key:
          type: string
        type:
          type: string
          enum: [error, vital, page_load, slow_request]
        message:
          type: string
        stack:
          type: string
        url:
          type: string
        metric:
          type: string
          enum: [LCP, FID, CLS, TTFB, INP]
        value:
          type: number
        environment:
          type: string
          enum: [production, staging, development, test]

    ApiKey:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        key_prefix:
          type: string
          description: First 12 characters of the key for identification
        scopes:
          type: array
          items:
            type: string
          description: "Permission scopes (e.g., incidents:read, services:write)"
        expires_at:
          type: string
          format: date-time
          nullable: true
        last_used_at:
          type: string
          format: date-time
          nullable: true
        enabled:
          type: boolean
        created_at:
          type: string
          format: date-time
    ApiKeyCreate:
      type: object
      required:
        - name
      properties:
        name:
          type: string
        scopes:
          type: array
          items:
            type: string
          default: ["*:*"]
        expires_at:
          type: string
          format: date-time
    AuditLogEntry:
      type: object
      properties:
        id:
          type: string
          format: uuid
        actor_type:
          type: string
          enum: [user, api_key, system]
        actor_id:
          type: string
          nullable: true
        actor_label:
          type: string
          nullable: true
        action:
          type: string
          description: "Action performed (e.g., incident.created)"
        resource_type:
          type: string
        resource_id:
          type: string
          nullable: true
        changes:
          type: object
          nullable: true
          description: "Field-level changes { field: { old, new } }"
        metadata:
          type: object
          nullable: true
        created_at:
          type: string
          format: date-time
    MaintenanceWindow:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        description:
          type: string
        status:
          type: string
          enum: [scheduled, in_progress, completed]
        scheduled_start:
          type: string
          format: date-time
        scheduled_end:
          type: string
          format: date-time
        created_at:
          type: string
          format: date-time
    CreateMaintenance:
      type: object
      required:
        - title
        - scheduled_start
        - scheduled_end
      properties:
        title:
          type: string
        description:
          type: string
        scheduled_start:
          type: string
          format: date-time
        scheduled_end:
          type: string
          format: date-time
        services:
          type: array
          items:
            type: string
    UpdateMaintenance:
      type: object
      properties:
        title:
          type: string
        description:
          type: string
        status:
          type: string
          enum: [scheduled, in_progress, completed]
        scheduled_start:
          type: string
          format: date-time
        scheduled_end:
          type: string
          format: date-time
