openapi: 3.0.3
info:
  title: UptimeSignal API
  description: |
    Synthetic monitoring API for developers. UptimeSignal pings your APIs and websites on a schedule and alerts you when something breaks.

    ## Authentication
    Most endpoints require a Bearer token obtained via magic link authentication.
    Include the token in the `Authorization` header: `Bearer <token>`

    ## Rate Limiting
    Public tool endpoints (`/tools/*`) are rate-limited to 10 requests per minute per IP.

    ## Plans
    - **Free**: 25 monitors, 5-minute intervals, 7-day history
    - **Pro** ($10/mo billed annually): Unlimited monitors, 1-minute intervals, 90-day history, integrations, custom domains
  version: 1.0.0
  contact:
    name: UptimeSignal
    url: https://uptimesignal.io
servers:
  - url: https://api.uptimesignal.io
    description: Production API

security:
  - BearerAuth: []

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: JWT-style token obtained from /auth/verify

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
        code:
          type: string
          description: Machine-readable error code (present on some errors)

    Success:
      type: object
      properties:
        success:
          type: boolean
          example: true

    User:
      type: object
      properties:
        id:
          type: string
          format: uuid
        email:
          type: string
          format: email
        plan:
          type: string
          enum: [free, pro]
        stripe_customer_id:
          type: string
          nullable: true

    Monitor:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        url:
          type: string
          format: uri
        method:
          type: string
          enum: [GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS]
          default: GET
        headers:
          type: string
          description: JSON-encoded headers object
        body:
          type: string
          nullable: true
        content_type:
          type: string
          default: application/json
        interval_seconds:
          type: integer
          description: Check interval in seconds. Free plan minimum is 300 (5 min), Pro minimum is 60 (1 min).
        expected_status_code:
          type: integer
          default: 200
        status:
          type: string
          enum: [active, paused]
        current_status:
          type: string
          enum: [up, down, pending]
        consecutive_failures:
          type: integer
        last_check_at:
          type: integer
          description: Unix timestamp in milliseconds
          nullable: true
        last_response_time_ms:
          type: integer
          nullable: true
        keyword:
          type: string
          nullable: true
          description: Keyword to search for in response body
        keyword_invert:
          type: integer
          description: "0 = alert if keyword missing, 1 = alert if keyword found"
        ssl_issuer:
          type: string
          nullable: true
        ssl_expires_at:
          type: integer
          nullable: true
        ssl_checked_at:
          type: integer
          nullable: true
        ssl_alert_days:
          type: integer
          default: 14
          description: Days before SSL expiry to send alert
        paused_at:
          type: integer
          nullable: true
        tags:
          type: string
          nullable: true
          description: JSON array of tag strings
        badge_enabled:
          type: integer
          description: "1 = badge enabled, 0 = disabled"
        response_time_threshold:
          type: integer
          nullable: true
          description: Response time threshold in ms for slow alerts (1-60000)
        response_time_alert_enabled:
          type: integer
          description: "1 = slow alerts enabled, 0 = disabled"
        created_at:
          type: integer
          description: Unix timestamp in milliseconds

    Check:
      type: object
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [up, down]
        response_time_ms:
          type: integer
          nullable: true
        status_code:
          type: integer
          nullable: true
        checked_at:
          type: integer
          description: Unix timestamp in milliseconds
        error:
          type: string
          nullable: true

    CheckDetail:
      allOf:
        - $ref: '#/components/schemas/Check'
        - type: object
          properties:
            response_body:
              type: string
              nullable: true
            response_headers:
              type: object
              nullable: true
              additionalProperties:
                type: string

    Alert:
      type: object
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum: [down, up, slow, slow_recovery, ssl]
        message:
          type: string
        sent_at:
          type: integer
          description: Unix timestamp in milliseconds

    MonitorStats:
      type: object
      properties:
        uptime_percent:
          type: number
          nullable: true
        avg_response_time:
          type: integer
          nullable: true
        min_response_time:
          type: integer
          nullable: true
        max_response_time:
          type: integer
          nullable: true
        total_checks:
          type: integer
        successful_checks:
          type: integer
        incident_count:
          type: integer

    UptimeBar:
      type: object
      properties:
        hour:
          type: string
          format: date-time
        status:
          type: string
          enum: [up, down, partial, empty]
        avg_response:
          type: integer
          nullable: true

    MaintenanceWindow:
      type: object
      properties:
        id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        monitor_id:
          type: string
          format: uuid
          nullable: true
          description: Null means applies to all monitors
        name:
          type: string
        start_time:
          type: integer
          description: Unix timestamp in milliseconds
        end_time:
          type: integer
          description: Unix timestamp in milliseconds
        recurring:
          type: string
          enum: [daily, weekly, monthly]
          nullable: true
        recurring_day:
          type: integer
          nullable: true
          description: "Day of week (0-6, Sunday=0) for weekly, day of month (1-31) for monthly"
        recurring_start_minutes:
          type: integer
          nullable: true
          description: Minutes from midnight for recurring window start
        recurring_duration_minutes:
          type: integer
          nullable: true
        timezone:
          type: string
          default: UTC
        monitor_name:
          type: string
          nullable: true
        created_at:
          type: integer

    Integration:
      type: object
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum: [slack, discord, telegram, webhook]
        name:
          type: string
          nullable: true
        enabled:
          type: boolean
        created_at:
          type: integer
        updated_at:
          type: integer
        config:
          type: object
          properties:
            has_webhook_url:
              type: boolean
            has_chat_id:
              type: boolean
            webhook_url_preview:
              type: string
              nullable: true

    IntegrationConfig:
      type: object
      properties:
        webhook_url:
          type: string
          format: uri
          description: Required for slack, discord, and webhook types. Must use HTTPS.
        chat_id:
          type: string
          description: Required for telegram type
        headers:
          type: object
          additionalProperties:
            type: string
          description: Custom headers for webhook type

    StatusPage:
      type: object
      properties:
        id:
          type: string
          format: uuid
        slug:
          type: string
          description: URL-friendly identifier (3-50 lowercase alphanumeric + hyphens)
        name:
          type: string
        logo_url:
          type: string
          format: uri
          nullable: true
        monitors:
          type: string
          description: JSON array of monitor IDs
        is_public:
          type: integer
        custom_domain:
          type: string
          nullable: true
        custom_domain_verified:
          type: integer
        custom_domain_verified_at:
          type: integer
          nullable: true
        accent_color:
          type: string
          nullable: true
          description: Hex color (e.g., #10b981)
        header_text:
          type: string
          nullable: true
          description: Custom header text (max 200 chars)
        show_powered_by:
          type: integer
        created_at:
          type: integer
        updated_at:
          type: integer

    Incident:
      type: object
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
        status:
          type: string
          enum: [investigating, identified, monitoring, resolved]
        impact:
          type: string
          enum: [none, minor, major, critical]
        affected_monitors:
          type: string
          description: JSON array of monitor IDs
        started_at:
          type: integer
        resolved_at:
          type: integer
          nullable: true
        created_at:
          type: integer
        updated_at:
          type: integer

    IncidentUpdate:
      type: object
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum: [investigating, identified, monitoring, resolved]
        message:
          type: string
        created_at:
          type: integer

    ComponentGroup:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
          nullable: true
        display_order:
          type: integer
        collapsed:
          type: integer
        created_at:
          type: integer
        updated_at:
          type: integer

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

    StatusPageWebhook:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        secret_preview:
          type: string
        webhook_url:
          type: string
          format: uri
        enabled:
          type: integer
        last_used_at:
          type: integer
          nullable: true
        created_at:
          type: integer
        updated_at:
          type: integer

    WebhookLog:
      type: object
      properties:
        id:
          type: string
          format: uuid
        action:
          type: string
        success:
          type: integer
        error_message:
          type: string
          nullable: true
        created_at:
          type: integer

    BadgeData:
      type: object
      properties:
        monitor_id:
          type: string
          format: uuid
        name:
          type: string
        status:
          type: string
          enum: [up, down, pending]
        uptime_percent_30d:
          type: number
          nullable: true
        avg_response_ms_24h:
          type: integer
          nullable: true
        generated_at:
          type: string
          format: date-time

    DashboardStats:
      type: object
      properties:
        total:
          type: integer
        up:
          type: integer
        down:
          type: integer
        paused:
          type: integer
        pending:
          type: integer

    Tag:
      type: object
      properties:
        name:
          type: string
        count:
          type: integer

paths:
  # ==================== AUTH ====================
  /auth/magic-link:
    post:
      tags: [Auth]
      summary: Send magic link email
      description: Sends a magic link to the provided email. Creates user if not exists.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email:
                  type: string
                  format: email
                  description: Email address to send magic link to
      responses:
        '200':
          description: Magic link sent
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
        '400':
          description: Invalid email
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /auth/verify:
    post:
      tags: [Auth]
      summary: Verify magic link token
      description: Exchanges a magic link token for a session token (valid 7 days).
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token]
              properties:
                token:
                  type: string
                  description: Token from the magic link URL
      responses:
        '200':
          description: Authenticated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  token:
                    type: string
                    description: Session Bearer token (valid 7 days)
                  user:
                    type: object
                    properties:
                      id:
                        type: string
                      email:
                        type: string
                      plan:
                        type: string
                        enum: [free, pro]
        '401':
          description: Invalid or expired token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /auth/me:
    get:
      tags: [Auth]
      summary: Get current user
      description: Returns the authenticated user's profile including plan and Stripe customer ID.
      responses:
        '200':
          description: Current user info
          content:
            application/json:
              schema:
                type: object
                properties:
                  user:
                    $ref: '#/components/schemas/User'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /auth/logout:
    post:
      tags: [Auth]
      summary: Logout
      description: Acknowledges logout. Token invalidation is handled client-side.
      responses:
        '200':
          description: Logged out
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'

  # ==================== MONITORS ====================
  /monitors:
    get:
      tags: [Monitors]
      summary: List monitors
      description: Returns all monitors for the authenticated user. Optionally filter by tags.
      parameters:
        - name: tags
          in: query
          required: false
          schema:
            type: string
          description: Comma-separated list of tags to filter by. Monitors must have ALL specified tags.
          example: production,api
      responses:
        '200':
          description: List of monitors
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitors:
                    type: array
                    items:
                      $ref: '#/components/schemas/Monitor'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    post:
      tags: [Monitors]
      summary: Create monitor
      description: |
        Creates a new monitor. Free plan limited to 25 monitors with 5-minute minimum interval.
        Pro plan allows unlimited monitors with 1-minute minimum interval.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, url]
              properties:
                name:
                  type: string
                  maxLength: 100
                  description: Display name for the monitor
                url:
                  type: string
                  format: uri
                  maxLength: 2000
                  description: URL to monitor (must start with http:// or https://)
                method:
                  type: string
                  enum: [GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS]
                  default: GET
                headers:
                  type: object
                  additionalProperties:
                    type: string
                  description: Custom HTTP headers to send with checks
                body:
                  type: string
                  nullable: true
                  description: Request body for POST/PUT/PATCH methods
                content_type:
                  type: string
                  default: application/json
                interval_seconds:
                  type: integer
                  minimum: 60
                  default: 300
                  description: Check interval in seconds. Free minimum 300, Pro minimum 60.
                expected_status_code:
                  type: integer
                  minimum: 100
                  maximum: 599
                  default: 200
                keyword:
                  type: string
                  description: Keyword to search for in response body
                keyword_invert:
                  type: boolean
                  default: false
                  description: If true, alert when keyword IS found (instead of when missing)
                ssl_alert_days:
                  type: integer
                  default: 14
                  description: Days before SSL expiry to send an alert
                tags:
                  type: array
                  items:
                    type: string
                  maxItems: 5
                  description: Tags for organizing monitors (max 5, lowercase alphanumeric + hyphens, max 20 chars each)
                response_time_threshold:
                  type: integer
                  minimum: 1
                  maximum: 60000
                  nullable: true
                  description: Response time threshold in ms for slow alerts
                response_time_alert_enabled:
                  type: boolean
                  default: false
      responses:
        '201':
          description: Monitor created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Plan limit reached or interval restricted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /monitors/{id}:
    get:
      tags: [Monitors]
      summary: Get monitor
      description: Returns full details for a single monitor.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Monitor details
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitor:
                    $ref: '#/components/schemas/Monitor'
        '404':
          description: Monitor not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    put:
      tags: [Monitors]
      summary: Update monitor
      description: Updates one or more fields on a monitor. Only provided fields are updated.
      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
                url:
                  type: string
                  format: uri
                method:
                  type: string
                  enum: [GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS]
                headers:
                  type: object
                  additionalProperties:
                    type: string
                body:
                  type: string
                content_type:
                  type: string
                interval_seconds:
                  type: integer
                expected_status_code:
                  type: integer
                status:
                  type: string
                  enum: [active, paused]
                keyword:
                  type: string
                  nullable: true
                keyword_invert:
                  type: boolean
                ssl_alert_days:
                  type: integer
                tags:
                  type: array
                  items:
                    type: string
                  nullable: true
                badge_enabled:
                  type: boolean
                response_time_threshold:
                  type: integer
                  nullable: true
                response_time_alert_enabled:
                  type: boolean
      responses:
        '200':
          description: Monitor updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
        '404':
          description: Monitor not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      tags: [Monitors]
      summary: Delete monitor
      description: Permanently deletes a monitor and all its check history.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Monitor deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
        '404':
          description: Monitor not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /monitors/{id}/pause:
    post:
      tags: [Monitors]
      summary: Pause monitor
      description: Pauses an active monitor. No checks will be performed while paused.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Monitor paused
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
        '400':
          description: Monitor is already paused
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Monitor not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /monitors/{id}/resume:
    post:
      tags: [Monitors]
      summary: Resume monitor
      description: Resumes a paused monitor. Checks will begin again at the configured interval.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Monitor resumed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
        '400':
          description: Monitor is already active
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Monitor not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /monitors/{id}/suppress:
    post:
      tags: [Monitors]
      summary: Suppress alerts
      description: Creates a temporary maintenance window to suppress alerts for a monitor.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [minutes]
              properties:
                minutes:
                  type: integer
                  minimum: 1
                  maximum: 1440
                  description: Duration in minutes to suppress alerts
      responses:
        '200':
          description: Alerts suppressed
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  id:
                    type: string
                    format: uuid
                  message:
                    type: string
                  end_time:
                    type: integer
                    description: Unix timestamp in milliseconds when suppression ends
        '404':
          description: Monitor not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /monitors/{id}/checks:
    get:
      tags: [Monitors]
      summary: List check history
      description: Returns recent check results for a monitor, ordered by most recent first.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 1000
            default: 100
          description: Number of checks to return
      responses:
        '200':
          description: Check history
          content:
            application/json:
              schema:
                type: object
                properties:
                  checks:
                    type: array
                    items:
                      $ref: '#/components/schemas/Check'
        '404':
          description: Monitor not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /monitors/{id}/checks/{checkId}:
    get:
      tags: [Monitors]
      summary: Get check detail
      description: Returns full details for a single check, including response body and headers.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: checkId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Check details
          content:
            application/json:
              schema:
                type: object
                properties:
                  check:
                    $ref: '#/components/schemas/CheckDetail'
        '404':
          description: Monitor or check not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /monitors/{id}/stats:
    get:
      tags: [Monitors]
      summary: Get monitor stats
      description: Returns uptime percentage, response time stats, and incident count for a period.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: period
          in: query
          required: false
          schema:
            type: string
            enum: [24h, 7d, 30d]
            default: 24h
          description: Time period for stats
      responses:
        '200':
          description: Monitor stats
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MonitorStats'
        '404':
          description: Monitor not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /monitors/{id}/uptime-bars:
    get:
      tags: [Monitors]
      summary: Get uptime bars
      description: Returns hourly uptime summary data for rendering uptime bar charts.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: hours
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 168
            default: 24
          description: Number of hours of data (max 168 = 7 days)
      responses:
        '200':
          description: Hourly uptime bars
          content:
            application/json:
              schema:
                type: object
                properties:
                  bars:
                    type: array
                    items:
                      $ref: '#/components/schemas/UptimeBar'
        '404':
          description: Monitor not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /monitors/{id}/alerts:
    get:
      tags: [Monitors]
      summary: List monitor alerts
      description: Returns alert history for a monitor (down, recovery, SSL, slow alerts).
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
      responses:
        '200':
          description: Alert history
          content:
            application/json:
              schema:
                type: object
                properties:
                  alerts:
                    type: array
                    items:
                      $ref: '#/components/schemas/Alert'
        '404':
          description: Monitor not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ==================== STATS & TAGS ====================
  /stats:
    get:
      tags: [Dashboard]
      summary: Get dashboard stats
      description: Returns aggregate counts of monitors by status for the authenticated user.
      responses:
        '200':
          description: Dashboard statistics
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DashboardStats'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /tags:
    get:
      tags: [Dashboard]
      summary: List tags with counts
      description: Returns all tags used by the user's monitors, sorted by usage count.
      responses:
        '200':
          description: Tag list
          content:
            application/json:
              schema:
                type: object
                properties:
                  tags:
                    type: array
                    items:
                      $ref: '#/components/schemas/Tag'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ==================== MAINTENANCE WINDOWS ====================
  /maintenance:
    get:
      tags: [Maintenance Windows]
      summary: List maintenance windows
      description: Returns all maintenance windows for the authenticated user, including monitor name.
      responses:
        '200':
          description: List of maintenance windows
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/MaintenanceWindow'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    post:
      tags: [Maintenance Windows]
      summary: Create maintenance window
      description: |
        Creates a scheduled maintenance window. Can apply to a specific monitor or all monitors (null monitor_id).
        Supports recurring schedules (daily, weekly, monthly).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, start_time, end_time]
              properties:
                name:
                  type: string
                  description: Name for the maintenance window
                monitor_id:
                  type: string
                  format: uuid
                  nullable: true
                  description: Specific monitor ID, or null for all monitors
                start_time:
                  type: integer
                  description: Start time as Unix timestamp in milliseconds
                end_time:
                  type: integer
                  description: End time as Unix timestamp in milliseconds (must be after start_time)
                recurring:
                  type: string
                  enum: [daily, weekly, monthly]
                  nullable: true
                recurring_day:
                  type: integer
                  nullable: true
                  description: "Day of week (0-6, Sunday=0) for weekly, or day of month (1-31) for monthly"
                recurring_start_minutes:
                  type: integer
                  nullable: true
                  description: Minutes from midnight for recurring window start
                recurring_duration_minutes:
                  type: integer
                  nullable: true
                timezone:
                  type: string
                  default: UTC
      responses:
        '201':
          description: Maintenance window created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MaintenanceWindow'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /maintenance/{id}:
    get:
      tags: [Maintenance Windows]
      summary: Get maintenance window
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Maintenance window details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MaintenanceWindow'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    put:
      tags: [Maintenance Windows]
      summary: Update maintenance window
      description: Updates one or more fields. Only provided fields are changed.
      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
                monitor_id:
                  type: string
                  format: uuid
                  nullable: true
                start_time:
                  type: integer
                end_time:
                  type: integer
                recurring:
                  type: string
                  enum: [daily, weekly, monthly]
                  nullable: true
                recurring_day:
                  type: integer
                  nullable: true
                recurring_start_minutes:
                  type: integer
                  nullable: true
                recurring_duration_minutes:
                  type: integer
                  nullable: true
                timezone:
                  type: string
      responses:
        '200':
          description: Updated maintenance window
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MaintenanceWindow'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      tags: [Maintenance Windows]
      summary: Delete maintenance window
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ==================== INTEGRATIONS ====================
  /integrations:
    get:
      tags: [Integrations]
      summary: List integrations
      description: Returns all integrations for the user. Webhook URLs are partially masked for security.
      responses:
        '200':
          description: List of integrations
          content:
            application/json:
              schema:
                type: object
                properties:
                  integrations:
                    type: array
                    items:
                      $ref: '#/components/schemas/Integration'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    post:
      tags: [Integrations]
      summary: Create integration
      description: |
        Creates a new alert integration. Requires Pro plan.
        - Slack: webhook_url must start with https://hooks.slack.com/
        - Discord: webhook_url must start with https://discord.com/api/webhooks/
        - Telegram: use /integrations/telegram/init flow instead
        - Webhook: any HTTPS URL
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type, config]
              properties:
                type:
                  type: string
                  enum: [slack, discord, telegram, webhook]
                name:
                  type: string
                  description: Display name for the integration
                config:
                  $ref: '#/components/schemas/IntegrationConfig'
      responses:
        '201':
          description: Integration created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
        '403':
          description: Pro plan required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /integrations/{id}:
    put:
      tags: [Integrations]
      summary: Update integration
      description: Updates name, config, or enabled status.
      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
                config:
                  $ref: '#/components/schemas/IntegrationConfig'
                enabled:
                  type: boolean
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      tags: [Integrations]
      summary: Delete integration
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /integrations/{id}/test:
    post:
      tags: [Integrations]
      summary: Test integration
      description: Sends a test alert through the integration to verify it works.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Test alert sent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
        '404':
          description: Integration not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Failed to send test alert
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /integrations/telegram/init:
    post:
      tags: [Integrations]
      summary: Initialize Telegram connection
      description: |
        Generates a verification code and Telegram deep link for connecting a Telegram chat.
        Code expires in 10 minutes. User must click the deep link and start the bot.
      responses:
        '200':
          description: Verification code generated
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: string
                    description: Verification code (e.g., USG-ABC123)
                  deepLink:
                    type: string
                    format: uri
                    description: Telegram deep link URL
                  expiresAt:
                    type: integer
                    description: Expiration time as Unix timestamp in milliseconds

  /integrations/telegram/status:
    get:
      tags: [Integrations]
      summary: Check Telegram connection status
      description: Returns whether Telegram is connected, and any pending verification.
      responses:
        '200':
          description: Connection status
          content:
            application/json:
              schema:
                type: object
                properties:
                  connected:
                    type: boolean
                  integration:
                    type: object
                    nullable: true
                    properties:
                      id:
                        type: string
                      name:
                        type: string
                      enabled:
                        type: integer
                  pending:
                    type: object
                    nullable: true
                    properties:
                      code:
                        type: string
                      expiresAt:
                        type: integer

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

    post:
      tags: [Status Pages]
      summary: Create status page
      description: Free plan allows 1 status page, Pro allows unlimited.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [slug, name, monitors]
              properties:
                slug:
                  type: string
                  description: URL slug (3-50 lowercase alphanumeric + hyphens)
                  pattern: "^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$"
                name:
                  type: string
                  minLength: 1
                  maxLength: 100
                logo_url:
                  type: string
                  format: uri
                monitors:
                  type: array
                  items:
                    type: string
                    format: uuid
                  minItems: 1
                  description: Array of monitor IDs to include
      responses:
        '201':
          description: Status page created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  slug:
                    type: string
        '403':
          description: Plan limit reached
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /status-pages/{id}:
    get:
      tags: [Status Pages]
      summary: Get status page
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Status page details
          content:
            application/json:
              schema:
                type: object
                properties:
                  statusPage:
                    $ref: '#/components/schemas/StatusPage'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    put:
      tags: [Status Pages]
      summary: Update status page
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                slug:
                  type: string
                name:
                  type: string
                  minLength: 1
                  maxLength: 100
                logo_url:
                  type: string
                  format: uri
                  nullable: true
                monitors:
                  type: array
                  items:
                    type: string
                    format: uuid
                  minItems: 1
                accent_color:
                  type: string
                  nullable: true
                  description: "Hex color (e.g., #10b981)"
                  pattern: "^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$"
                header_text:
                  type: string
                  nullable: true
                  maxLength: 200
                show_powered_by:
                  type: boolean
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      tags: [Status Pages]
      summary: Delete status page
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ==================== CUSTOM DOMAINS ====================
  /status-pages/{id}/custom-domain:
    post:
      tags: [Status Pages]
      summary: Set custom domain
      description: Configures a custom domain for a status page. Pro plan only. Domain must be a subdomain (e.g., status.example.com).
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [domain]
              properties:
                domain:
                  type: string
                  description: "Custom domain (must be subdomain, e.g., status.example.com)"
      responses:
        '200':
          description: Custom domain configured
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  domain:
                    type: string
                  cname_target:
                    type: string
                    example: status-pages.uptimesignal.io
                  validation_records:
                    type: array
                    nullable: true
                    items:
                      type: object
                      properties:
                        txt_name:
                          type: string
                        txt_value:
                          type: string
                  ssl_status:
                    type: string
                  instructions:
                    type: string
        '403':
          description: Pro plan required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      tags: [Status Pages]
      summary: Remove custom domain
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Custom domain removed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /status-pages/{id}/custom-domain/verify:
    post:
      tags: [Status Pages]
      summary: Verify custom domain
      description: Checks DNS and SSL verification status for the custom domain via Cloudflare.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Verification status
          content:
            application/json:
              schema:
                type: object
                properties:
                  verified:
                    type: boolean
                  domain:
                    type: string
                  hostname_status:
                    type: string
                    description: "pending, active, etc."
                  ssl_status:
                    type: string
                  validation_records:
                    type: array
                    nullable: true
                    items:
                      type: object
                      properties:
                        txt_name:
                          type: string
                        txt_value:
                          type: string
                  status_message:
                    type: string
                  status_step:
                    type: integer
                    description: "1=DNS pending, 2=SSL pending, 3=Done"
                  expected_cname:
                    type: string

  # ==================== INCIDENTS ====================
  /status-pages/{id}/incidents:
    get:
      tags: [Incidents]
      summary: List incidents
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
      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
      description: Creates an incident with an initial status update message.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title, message]
              properties:
                title:
                  type: string
                  minLength: 1
                  maxLength: 200
                status:
                  type: string
                  enum: [investigating, identified, monitoring, resolved]
                  default: investigating
                impact:
                  type: string
                  enum: [none, minor, major, critical]
                  default: minor
                message:
                  type: string
                  minLength: 1
                  maxLength: 5000
                  description: Initial update message
                affected_monitors:
                  type: array
                  items:
                    type: string
                    format: uuid
                  description: Monitor IDs affected (must be in the status page's monitor list)
      responses:
        '201':
          description: Incident created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid

  /status-pages/{id}/incidents/{incidentId}:
    get:
      tags: [Incidents]
      summary: Get incident with updates
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
        - name: incidentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Incident with updates
          content:
            application/json:
              schema:
                type: object
                properties:
                  incident:
                    allOf:
                      - $ref: '#/components/schemas/Incident'
                      - type: object
                        properties:
                          updates:
                            type: array
                            items:
                              $ref: '#/components/schemas/IncidentUpdate'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    delete:
      tags: [Incidents]
      summary: Delete incident
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
        - name: incidentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /status-pages/{id}/incidents/{incidentId}/updates:
    post:
      tags: [Incidents]
      summary: Add incident update
      description: Adds a status update to an active incident. Cannot update resolved incidents.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
        - name: incidentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message]
              properties:
                status:
                  type: string
                  enum: [investigating, identified, monitoring, resolved]
                  description: New status for the incident (defaults to current status)
                message:
                  type: string
                  minLength: 1
                  maxLength: 5000
      responses:
        '201':
          description: Update added
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
        '400':
          description: Cannot update resolved incident
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ==================== COMPONENT GROUPS ====================
  /status-pages/{id}/component-groups:
    get:
      tags: [Components]
      summary: List component groups
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
      responses:
        '200':
          description: List of component groups
          content:
            application/json:
              schema:
                type: object
                properties:
                  groups:
                    type: array
                    items:
                      $ref: '#/components/schemas/ComponentGroup'

    post:
      tags: [Components]
      summary: Create component group
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                description:
                  type: string
                collapsed:
                  type: boolean
                  default: false
      responses:
        '201':
          description: Group created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid

  /status-pages/{id}/component-groups/{groupId}:
    put:
      tags: [Components]
      summary: Update component group
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
        - name: groupId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                display_order:
                  type: integer
                collapsed:
                  type: boolean
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'

    delete:
      tags: [Components]
      summary: Delete component group
      description: Deletes the group. Components in the group become ungrouped.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
        - name: groupId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'

  # ==================== COMPONENTS ====================
  /status-pages/{id}/components:
    get:
      tags: [Components]
      summary: List components
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
      responses:
        '200':
          description: List of components
          content:
            application/json:
              schema:
                type: object
                properties:
                  components:
                    type: array
                    items:
                      $ref: '#/components/schemas/Component'

    post:
      tags: [Components]
      summary: Create component
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                description:
                  type: string
                group_id:
                  type: string
                  format: uuid
                  nullable: true
                status:
                  type: string
                  enum: [operational, degraded, partial_outage, major_outage, maintenance]
                  default: operational
      responses:
        '201':
          description: Component created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid

  /status-pages/{id}/components/{componentId}:
    put:
      tags: [Components]
      summary: Update component
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
        - name: componentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                group_id:
                  type: string
                  format: uuid
                  nullable: true
                status:
                  type: string
                  enum: [operational, degraded, partial_outage, major_outage, maintenance]
                display_order:
                  type: integer
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'

    delete:
      tags: [Components]
      summary: Delete component
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
        - name: componentId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'

  # ==================== STATUS PAGE WEBHOOKS ====================
  /status-pages/{id}/webhooks:
    get:
      tags: [Status Page Webhooks]
      summary: List inbound webhooks
      description: Returns webhooks for programmatic status updates. Secrets are partially masked.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
      responses:
        '200':
          description: List of webhooks
          content:
            application/json:
              schema:
                type: object
                properties:
                  webhooks:
                    type: array
                    items:
                      $ref: '#/components/schemas/StatusPageWebhook'

    post:
      tags: [Status Page Webhooks]
      summary: Create inbound webhook
      description: Creates a webhook with a unique secret URL for programmatic status updates.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
      responses:
        '201':
          description: Webhook created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    format: uuid
                  secret:
                    type: string
                    description: Full webhook secret (only shown once at creation)
                  webhook_url:
                    type: string
                    format: uri

  /status-pages/{id}/webhooks/{webhookId}:
    put:
      tags: [Status Page Webhooks]
      summary: Update webhook
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                enabled:
                  type: boolean
      responses:
        '200':
          description: Updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'

    delete:
      tags: [Status Page Webhooks]
      summary: Delete webhook
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Deleted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Success'

  /status-pages/{id}/webhooks/{webhookId}/regenerate:
    post:
      tags: [Status Page Webhooks]
      summary: Regenerate webhook secret
      description: Generates a new secret, invalidating the old webhook URL.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: New secret generated
          content:
            application/json:
              schema:
                type: object
                properties:
                  secret:
                    type: string
                  webhook_url:
                    type: string
                    format: uri

  /status-pages/{id}/webhooks/{webhookId}/logs:
    get:
      tags: [Status Page Webhooks]
      summary: Get webhook call logs
      description: Returns the last 50 webhook call logs for debugging.
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Status page ID
        - name: webhookId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Webhook logs
          content:
            application/json:
              schema:
                type: object
                properties:
                  logs:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookLog'

  # ==================== PUBLIC WEBHOOK ENDPOINT ====================
  /webhook/{secret}:
    post:
      tags: [Status Page Webhooks]
      summary: Inbound webhook for status updates
      description: |
        Public endpoint for programmatic status page updates. No auth required - uses webhook secret in URL.
        Supports updating components and managing incidents.
      security: []
      parameters:
        - name: secret
          in: path
          required: true
          schema:
            type: string
          description: Webhook secret from webhook creation
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [action]
              properties:
                action:
                  type: string
                  enum: [update_component, create_incident, update_incident, resolve_incident]
                component_name:
                  type: string
                  description: Required for update_component
                component_status:
                  type: string
                  enum: [operational, degraded, partial_outage, major_outage, maintenance]
                  description: Required for update_component
                incident_title:
                  type: string
                  description: Required for create_incident
                incident_message:
                  type: string
                  description: Required for create_incident and update_incident
                incident_impact:
                  type: string
                  enum: [none, minor, major, critical]
                  description: For create_incident (default minor)
                incident_id:
                  type: string
                  format: uuid
                  description: Required for update_incident and resolve_incident
      responses:
        '200':
          description: Action completed
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
        '201':
          description: Incident created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  incident_id:
                    type: string
                    format: uuid
                  message:
                    type: string
        '401':
          description: Invalid webhook secret
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ==================== BADGES ====================
  /badge/{id}/uptime.svg:
    get:
      tags: [Badges]
      summary: Uptime badge SVG
      description: Returns an SVG badge showing 30-day uptime percentage. No auth required. Badge must be enabled on the monitor.
      security: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Monitor ID
      responses:
        '200':
          description: SVG badge image
          content:
            image/svg+xml:
              schema:
                type: string

  /badge/{id}/status.svg:
    get:
      tags: [Badges]
      summary: Status badge SVG
      description: Returns an SVG badge showing current monitor status (operational/down/pending). No auth required.
      security: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Monitor ID
      responses:
        '200':
          description: SVG badge image
          content:
            image/svg+xml:
              schema:
                type: string

  /badge/{id}/response.svg:
    get:
      tags: [Badges]
      summary: Response time badge SVG
      description: Returns an SVG badge showing average response time over last 24 hours. No auth required.
      security: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Monitor ID
      responses:
        '200':
          description: SVG badge image
          content:
            image/svg+xml:
              schema:
                type: string

  /badge/{id}/data.json:
    get:
      tags: [Badges]
      summary: Badge data JSON
      description: Returns monitor status data as JSON for programmatic access. No auth required.
      security: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Monitor ID
      responses:
        '200':
          description: Badge data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BadgeData'
        '403':
          description: Badge disabled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Monitor not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ==================== TOOLS ====================
  /tools/check-url:
    post:
      tags: [Tools]
      summary: Check URL status
      description: |
        Checks if a URL is accessible and returns status code, response time, and up/down status.
        Rate limited to 10 requests per minute per IP. No auth required.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url:
                  type: string
                  format: uri
                  description: URL to check (must start with http:// or https://)
      responses:
        '200':
          description: Check result
          content:
            application/json:
              schema:
                type: object
                properties:
                  url:
                    type: string
                  status:
                    type: integer
                    description: HTTP status code (0 if connection failed)
                  statusText:
                    type: string
                  responseTime:
                    type: integer
                    description: Response time in milliseconds
                  isUp:
                    type: boolean
                    description: True if status 200-399 or 429
                  error:
                    type: string
                    description: Error message if connection failed
                  timestamp:
                    type: string
                    format: date-time
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /tools/ssl-check:
    post:
      tags: [Tools]
      summary: Check SSL certificate
      description: |
        Checks if a domain has a valid SSL certificate. Returns validity status.
        Rate limited to 10 requests per minute per IP. No auth required.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [domain]
              properties:
                domain:
                  type: string
                  description: Domain to check (protocol and path are stripped automatically)
      responses:
        '200':
          description: SSL check result
          content:
            application/json:
              schema:
                type: object
                properties:
                  domain:
                    type: string
                  valid:
                    type: boolean
                  protocol:
                    type: string
                    description: Present when valid (e.g., TLS)
                  message:
                    type: string
                    description: Human-readable result
                  error:
                    type: string
                    description: Error message when invalid
                  checkedAt:
                    type: string
                    format: date-time
        '429':
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ==================== STRIPE ====================
  /stripe/checkout:
    post:
      tags: [Billing]
      summary: Create checkout session
      description: Creates a Stripe Checkout session to upgrade to Pro plan. Returns the checkout URL.
      responses:
        '200':
          description: Checkout session URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  url:
                    type: string
                    format: uri
                    description: Stripe Checkout URL to redirect the user to
        '400':
          description: Already on Pro plan
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /stripe/portal:
    post:
      tags: [Billing]
      summary: Create billing portal session
      description: Creates a Stripe Billing Portal session for managing subscription. Returns the portal URL.
      responses:
        '200':
          description: Portal session URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  url:
                    type: string
                    format: uri
                    description: Stripe Billing Portal URL
        '400':
          description: No subscription found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ==================== PUBLIC STATUS PAGE ====================
  /public/status/{slug}:
    get:
      tags: [Public]
      summary: Get public status page data
      description: Returns the public-facing status page data including monitors, components, and incidents. No auth required.
      security: []
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Public status page data
          content:
            application/json:
              schema:
                type: object
                properties:
                  name:
                    type: string
                  logo_url:
                    type: string
                    nullable: true
                  accent_color:
                    type: string
                    nullable: true
                  header_text:
                    type: string
                    nullable: true
                  show_powered_by:
                    type: integer
                  overall_status:
                    type: string
                    enum: [operational, partial, major]
                  monitors:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        name:
                          type: string
                        status:
                          type: string
                        uptime_30d:
                          type: number
                        uptime_bars:
                          type: array
                          items:
                            type: number
                          description: 30 daily uptime percentages
                        last_checked:
                          type: string
                          format: date-time
                          nullable: true
                        affected_by_incident:
                          type: boolean
                  component_groups:
                    type: array
                    items:
                      $ref: '#/components/schemas/ComponentGroup'
                  components:
                    type: array
                    items:
                      $ref: '#/components/schemas/Component'
                  active_incidents:
                    type: array
                    items:
                      $ref: '#/components/schemas/Incident'
                  past_incidents:
                    type: array
                    items:
                      $ref: '#/components/schemas/Incident'
                  updated_at:
                    type: string
                    format: date-time
        '404':
          description: Status page not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /public/status-by-domain/{domain}:
    get:
      tags: [Public]
      summary: Lookup status page by custom domain
      description: Resolves a custom domain to a status page slug. Used by the status page router.
      security: []
      parameters:
        - name: domain
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Slug resolved
          content:
            application/json:
              schema:
                type: object
                properties:
                  slug:
                    type: string
        '404':
          description: No status page for this domain
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  # ==================== TEST ENDPOINTS ====================
  /test/healthy:
    get:
      tags: [Test]
      summary: Healthy test endpoint
      description: Always returns 200. Use for testing monitor setup.
      security: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  timestamp:
                    type: integer

  /test/unhealthy:
    get:
      tags: [Test]
      summary: Unhealthy test endpoint
      description: Always returns 500. Use for testing down alerts.
      security: []
      responses:
        '500':
          description: Simulated failure
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: error
                  message:
                    type: string

tags:
  - name: Auth
    description: Authentication via magic link emails
  - name: Monitors
    description: CRUD and management of uptime monitors
  - name: Dashboard
    description: Aggregate stats and tags
  - name: Maintenance Windows
    description: Scheduled maintenance windows to suppress alerts
  - name: Integrations
    description: Alert integrations (Slack, Discord, Telegram, Webhook). Requires Pro plan.
  - name: Status Pages
    description: Public status pages with custom domains
  - name: Incidents
    description: Incident management for status pages
  - name: Components
    description: Components and component groups for status pages
  - name: Status Page Webhooks
    description: Inbound webhooks for programmatic status page updates
  - name: Badges
    description: Public embeddable status badges (SVG and JSON)
  - name: Tools
    description: Free public tools (URL check, SSL check). Rate limited.
  - name: Billing
    description: Stripe checkout and billing portal
  - name: Public
    description: Public-facing status page data (no auth)
  - name: Test
    description: Test endpoints for verifying monitor setup
