openapi: 3.0.3
info:
  title: Geutebruck Video Surveillance API
  description: |
    Complete RESTful API for Geutebruck GeViScope/GeViSoft video surveillance system control.

    **Features:**
    - JWT-based authentication with refresh tokens
    - Live video streaming from surveillance cameras
    - PTZ (Pan-Tilt-Zoom) camera control
    - Real-time event notifications via WebSocket
    - Video recording management
    - Video analytics configuration (VMD, NPR, OBTRACK)
    - Multi-camera management
    - System health monitoring

    **Authentication:**
    All endpoints except `/health` and `/docs` require Bearer token authentication.
    Obtain tokens via `/api/v1/auth/login` endpoint.

    **Rate Limiting:**
    - Authentication endpoints: 5 requests/minute per IP
    - All other endpoints: 500 requests/minute per user

    **Support:**
    - Documentation: https://docs.example.com
    - GitHub: https://github.com/example/geutebruck-api
  version: 1.0.0
  contact:
    name: API Support
    email: api-support@example.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: https://api.example.com
    description: Production server
  - url: https://staging-api.example.com
    description: Staging server
  - url: http://localhost:8000
    description: Development server

tags:
  - name: Authentication
    description: User authentication and session management
  - name: Cameras
    description: Camera management and live streaming
  - name: Events
    description: Real-time event subscriptions and history
  - name: Recordings
    description: Video recording management
  - name: Analytics
    description: Video analytics configuration
  - name: System
    description: System health and status

paths:
  # ============================================================================
  # AUTHENTICATION ENDPOINTS
  # ============================================================================

  /api/v1/auth/login:
    post:
      tags: [Authentication]
      summary: Authenticate user and obtain JWT tokens
      description: |
        Validates user credentials and returns access + refresh JWT tokens.
        Access tokens expire in 1 hour, refresh tokens in 7 days.
      operationId: login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
            examples:
              operator_login:
                summary: Operator login
                value:
                  username: operator1
                  password: SecurePass123!
      responses:
        '200':
          description: Authentication successful
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenPair'
              examples:
                success:
                  value:
                    access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                    refresh_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
                    token_type: bearer
                    expires_in: 3600
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'

  /api/v1/auth/refresh:
    post:
      tags: [Authentication]
      summary: Refresh access token
      description: Obtain a new access token using a valid refresh token
      operationId: refreshToken
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefreshTokenRequest'
      responses:
        '200':
          description: New access token issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccessToken'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/auth/logout:
    post:
      tags: [Authentication]
      summary: Logout and invalidate tokens
      description: Invalidates current session and revokes all tokens
      operationId: logout
      security:
        - BearerAuth: []
      responses:
        '204':
          description: Logout successful
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ============================================================================
  # CAMERA ENDPOINTS
  # ============================================================================

  /api/v1/cameras:
    get:
      tags: [Cameras]
      summary: List all cameras
      description: |
        Returns all cameras the authenticated user has permission to view.
        Results include camera status, capabilities, and current recording state.
      operationId: listCameras
      security:
        - BearerAuth: []
      parameters:
        - name: status
          in: query
          description: Filter by camera status
          schema:
            $ref: '#/components/schemas/CameraStatus'
        - name: location
          in: query
          description: Filter by location
          schema:
            type: string
        - name: has_ptz
          in: query
          description: Filter cameras with PTZ capability
          schema:
            type: boolean
        - name: limit
          in: query
          description: Maximum number of results
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 1000
        - name: offset
          in: query
          description: Pagination offset
          schema:
            type: integer
            default: 0
            minimum: 0
      responses:
        '200':
          description: List of cameras
          content:
            application/json:
              schema:
                type: object
                properties:
                  cameras:
                    type: array
                    items:
                      $ref: '#/components/schemas/Camera'
                  total:
                    type: integer
                    description: Total number of cameras (before pagination)
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/cameras/{camera_id}:
    get:
      tags: [Cameras]
      summary: Get camera details
      description: Returns detailed information about a specific camera
      operationId: getCamera
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/CameraId'
      responses:
        '200':
          description: Camera details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Camera'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/cameras/{camera_id}/stream:
    post:
      tags: [Cameras]
      summary: Request live video stream
      description: |
        Returns an authenticated URL for accessing the live video stream.
        The URL includes a time-limited JWT token and expires in 1 hour.
        Clients connect directly to the GeViScope stream URL (no proxy).
      operationId: requestStream
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/CameraId'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StreamRequest'
            examples:
              default:
                summary: Default stream (H.264, max resolution)
                value:
                  format: h264
                  quality: 90
              low_bandwidth:
                summary: Low bandwidth stream
                value:
                  format: mjpeg
                  resolution: "640x480"
                  fps: 15
                  quality: 60
      responses:
        '200':
          description: Stream URL created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StreamResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '503':
          description: Camera offline or unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /api/v1/cameras/{camera_id}/ptz:
    post:
      tags: [Cameras]
      summary: Send PTZ control command
      description: |
        Controls pan, tilt, and zoom operations on PTZ-capable cameras.
        Commands execute with <500ms latency from request to camera movement.
      operationId: controlPTZ
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/CameraId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PTZCommand'
            examples:
              pan_left:
                summary: Pan camera left
                value:
                  action: pan_left
                  speed: 50
              goto_preset:
                summary: Move to preset position
                value:
                  action: goto_preset
                  preset_id: 3
      responses:
        '200':
          description: PTZ command accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: PTZ command executed
                  camera_id:
                    type: integer
                  action:
                    type: string
        '400':
          description: Invalid command or camera lacks PTZ
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/cameras/{camera_id}/presets:
    get:
      tags: [Cameras]
      summary: List PTZ presets
      description: Returns all saved PTZ preset positions for this camera
      operationId: listPresets
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/CameraId'
      responses:
        '200':
          description: List of presets
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/PTZPreset'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

    post:
      tags: [Cameras]
      summary: Save PTZ preset
      description: Saves current camera position as a named preset
      operationId: savePreset
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/CameraId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
              properties:
                name:
                  type: string
                  minLength: 1
                  maxLength: 50
                  example: "Main Entrance View"
      responses:
        '201':
          description: Preset created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PTZPreset'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  # ============================================================================
  # EVENT ENDPOINTS
  # ============================================================================

  /api/v1/events/stream:
    get:
      tags: [Events]
      summary: WebSocket event stream
      description: |
        **WebSocket endpoint** for real-time event notifications.

        **Connection Flow:**
        1. Client upgrades HTTP to WebSocket connection
        2. Client sends subscription message with filters
        3. Server sends matching events as they occur
        4. Client sends heartbeat (ping) every 30s
        5. Server responds with pong

        **Message Format:**
        ```json
        // Subscribe
        {
          "action": "subscribe",
          "filters": {
            "event_types": ["motion_detected", "alarm_triggered"],
            "camera_ids": [1, 2, 3],
            "severity": "warning"
          }
        }

        // Event notification
        {
          "subscription_id": "uuid",
          "event": { ...event object... },
          "sequence_number": 42
        }

        // Heartbeat
        {"action": "ping"}
        {"action": "pong"}
        ```

        **Reconnection:**
        - Client implements exponential backoff
        - Server buffers critical events for 5 minutes
        - Reconnected clients receive missed critical events
      operationId: streamEvents
      security:
        - BearerAuth: []
      parameters:
        - name: Connection
          in: header
          required: true
          schema:
            type: string
            enum: [Upgrade]
        - name: Upgrade
          in: header
          required: true
          schema:
            type: string
            enum: [websocket]
      responses:
        '101':
          description: Switching Protocols to WebSocket
        '401':
          $ref: '#/components/responses/Unauthorized'
        '426':
          description: Upgrade Required
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /api/v1/events:
    get:
      tags: [Events]
      summary: Query event history
      description: Retrieve historical events with filtering and pagination
      operationId: queryEvents
      security:
        - BearerAuth: []
      parameters:
        - name: event_type
          in: query
          schema:
            $ref: '#/components/schemas/EventType'
        - name: camera_id
          in: query
          schema:
            type: integer
        - name: start_time
          in: query
          description: Filter events after this time (ISO 8601)
          schema:
            type: string
            format: date-time
        - name: end_time
          in: query
          description: Filter events before this time (ISO 8601)
          schema:
            type: string
            format: date-time
        - name: severity
          in: query
          schema:
            $ref: '#/components/schemas/EventSeverity'
        - name: acknowledged
          in: query
          description: Filter by acknowledgment status
          schema:
            type: boolean
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 1000
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
            minimum: 0
      responses:
        '200':
          description: Event list
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: array
                    items:
                      $ref: '#/components/schemas/Event'
                  total:
                    type: integer
                  limit:
                    type: integer
                  offset:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/events/{event_id}:
    get:
      tags: [Events]
      summary: Get event details
      description: Retrieve detailed information about a specific event
      operationId: getEvent
      security:
        - BearerAuth: []
      parameters:
        - name: event_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Event details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Event'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

    patch:
      tags: [Events]
      summary: Acknowledge event
      description: Mark an event as acknowledged by current user
      operationId: acknowledgeEvent
      security:
        - BearerAuth: []
      parameters:
        - name: event_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Event acknowledged
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Event'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  # ============================================================================
  # RECORDING ENDPOINTS
  # ============================================================================

  /api/v1/recordings:
    get:
      tags: [Recordings]
      summary: Query recordings
      description: Search for recorded video segments by time range and camera
      operationId: queryRecordings
      security:
        - BearerAuth: []
      parameters:
        - name: camera_id
          in: query
          schema:
            type: integer
        - name: start_time
          in: query
          required: true
          schema:
            type: string
            format: date-time
        - name: end_time
          in: query
          required: true
          schema:
            type: string
            format: date-time
        - name: trigger
          in: query
          schema:
            $ref: '#/components/schemas/RecordingTrigger'
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Recording list
          content:
            application/json:
              schema:
                type: object
                properties:
                  recordings:
                    type: array
                    items:
                      $ref: '#/components/schemas/Recording'
                  total:
                    type: integer
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'

  /api/v1/recordings/{recording_id}:
    get:
      tags: [Recordings]
      summary: Get recording details
      operationId: getRecording
      security:
        - BearerAuth: []
      parameters:
        - name: recording_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Recording details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Recording'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/recordings/{recording_id}/export:
    post:
      tags: [Recordings]
      summary: Export recording
      description: |
        Request video export for a recording segment.
        Returns job ID for tracking export progress.
        Completed exports available for download via provided URL.
      operationId: exportRecording
      security:
        - BearerAuth: []
      parameters:
        - name: recording_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                format:
                  type: string
                  enum: [mp4, avi]
                  default: mp4
                include_metadata:
                  type: boolean
                  default: true
      responses:
        '202':
          description: Export job created
          content:
            application/json:
              schema:
                type: object
                properties:
                  job_id:
                    type: string
                    format: uuid
                  status:
                    type: string
                    enum: [pending, processing]
                  estimated_completion:
                    type: string
                    format: date-time
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /api/v1/recordings/capacity:
    get:
      tags: [Recordings]
      summary: Get recording capacity
      description: Returns storage capacity metrics for the ring buffer
      operationId: getRecordingCapacity
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Capacity metrics
          content:
            application/json:
              schema:
                type: object
                properties:
                  total_capacity_gb:
                    type: number
                    format: float
                  used_capacity_gb:
                    type: number
                    format: float
                  free_capacity_gb:
                    type: number
                    format: float
                  percent_used:
                    type: number
                    format: float
                  recording_depth_hours:
                    type: number
                    format: float
                  oldest_recording:
                    type: string
                    format: date-time
                  warnings:
                    type: array
                    items:
                      type: string
        '401':
          $ref: '#/components/responses/Unauthorized'

  # ============================================================================
  # ANALYTICS ENDPOINTS
  # ============================================================================

  /api/v1/analytics/{camera_id}:
    get:
      tags: [Analytics]
      summary: Get analytics configuration
      description: Returns current analytics configuration for a camera
      operationId: getAnalyticsConfig
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/CameraId'
      responses:
        '200':
          description: Analytics configurations
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AnalyticsConfig'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

    put:
      tags: [Analytics]
      summary: Update analytics configuration
      description: Configure video analytics for a camera
      operationId: updateAnalyticsConfig
      security:
        - BearerAuth: []
      parameters:
        - $ref: '#/components/parameters/CameraId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AnalyticsConfig'
      responses:
        '200':
          description: Configuration updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnalyticsConfig'
        '400':
          description: Invalid configuration or unsupported analytics type
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'

  # ============================================================================
  # SYSTEM ENDPOINTS
  # ============================================================================

  /api/v1/health:
    get:
      tags: [System]
      summary: Health check
      description: |
        Returns API health status. No authentication required.
        Used by load balancers and monitoring systems.
      operationId: healthCheck
      responses:
        '200':
          description: System healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [healthy, degraded, unhealthy]
                  checks:
                    type: object
                    properties:
                      api:
                        type: string
                      sdk_bridge:
                        type: string
                      redis:
                        type: string
                      geviserver:
                        type: string
                  timestamp:
                    type: string
                    format: date-time
        '503':
          description: System unhealthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum: [unhealthy]
                  checks:
                    type: object
                  timestamp:
                    type: string

  /api/v1/status:
    get:
      tags: [System]
      summary: System status
      description: Detailed system status and metrics (requires authentication)
      operationId: getStatus
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Detailed status
          content:
            application/json:
              schema:
                type: object
                properties:
                  uptime_seconds:
                    type: integer
                  active_streams:
                    type: integer
                  active_websocket_connections:
                    type: integer
                  cameras_online:
                    type: integer
                  cameras_total:
                    type: integer
                  sdk_version:
                    type: string
                  api_version:
                    type: string
        '401':
          $ref: '#/components/responses/Unauthorized'

# ==============================================================================
# COMPONENTS
# ==============================================================================

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT token obtained from /auth/login endpoint

  parameters:
    CameraId:
      name: camera_id
      in: path
      required: true
      description: Camera channel ID
      schema:
        type: integer
        minimum: 1

  responses:
    BadRequest:
      description: Bad request - invalid parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

    Unauthorized:
      description: Unauthorized - missing or invalid authentication
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error_code: UNAUTHORIZED
            message: Missing or invalid authentication token
            details: null
            timestamp: "2025-12-08T14:30:00Z"

    Forbidden:
      description: Forbidden - insufficient permissions
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error_code: FORBIDDEN
            message: Insufficient permissions for this resource
            details: null
            timestamp: "2025-12-08T14:30:00Z"

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error_code: NOT_FOUND
            message: Resource not found
            details: null
            timestamp: "2025-12-08T14:30:00Z"

    TooManyRequests:
      description: Too many requests - rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error_code: RATE_LIMIT_EXCEEDED
            message: Too many requests, please try again later
            details:
              retry_after_seconds: 60
            timestamp: "2025-12-08T14:30:00Z"
      headers:
        Retry-After:
          description: Seconds until rate limit resets
          schema:
            type: integer

  schemas:
    # Authentication Schemas
    LoginRequest:
      type: object
      required:
        - username
        - password
      properties:
        username:
          type: string
          minLength: 3
          maxLength: 50
          example: operator1
        password:
          type: string
          format: password
          minLength: 8
          example: SecurePass123!

    TokenPair:
      type: object
      properties:
        access_token:
          type: string
          description: JWT access token (1 hour expiration)
        refresh_token:
          type: string
          description: JWT refresh token (7 days expiration)
        token_type:
          type: string
          enum: [bearer]
        expires_in:
          type: integer
          description: Seconds until access token expires
          example: 3600

    RefreshTokenRequest:
      type: object
      required:
        - refresh_token
      properties:
        refresh_token:
          type: string

    AccessToken:
      type: object
      properties:
        access_token:
          type: string
        token_type:
          type: string
          enum: [bearer]
        expires_in:
          type: integer

    # Camera Schemas
    Camera:
      type: object
      properties:
        id:
          type: integer
          description: Camera channel ID
          example: 5
        global_id:
          type: string
          format: uuid
          description: GeViScope global identifier
        name:
          type: string
          example: "Entrance Camera"
        description:
          type: string
          nullable: true
          example: "Main entrance monitoring"
        location:
          type: string
          nullable: true
          example: "Building A - Main Entrance"
        status:
          $ref: '#/components/schemas/CameraStatus'
        capabilities:
          $ref: '#/components/schemas/CameraCapabilities'
        recording_status:
          type: object
          properties:
            is_recording:
              type: boolean
            mode:
              $ref: '#/components/schemas/RecordingTrigger'
            start_time:
              type: string
              format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    CameraStatus:
      type: string
      enum: [online, offline, error, maintenance]

    CameraCapabilities:
      type: object
      properties:
        has_ptz:
          type: boolean
        has_video_sensor:
          type: boolean
        has_contrast_detection:
          type: boolean
        has_sync_detection:
          type: boolean
        supported_analytics:
          type: array
          items:
            type: string
            enum: [vmd, npr, obtrack, gtect, cpa]
        supported_resolutions:
          type: array
          items:
            type: string
            example: "1920x1080"
        supported_formats:
          type: array
          items:
            type: string
            enum: [h264, mjpeg]

    # Stream Schemas
    StreamRequest:
      type: object
      properties:
        format:
          type: string
          enum: [h264, mjpeg]
          default: h264
        resolution:
          type: string
          nullable: true
          example: "1920x1080"
        fps:
          type: integer
          minimum: 1
          maximum: 60
          nullable: true
        quality:
          type: integer
          minimum: 1
          maximum: 100
          default: 90

    StreamResponse:
      type: object
      properties:
        stream_id:
          type: string
          format: uuid
        camera_id:
          type: integer
        stream_url:
          type: string
          format: uri
          description: Token-authenticated stream URL
          example: "http://localhost:7703/stream?channel=5&token=eyJhbGc..."
        format:
          type: string
        resolution:
          type: string
        fps:
          type: integer
        expires_at:
          type: string
          format: date-time

    # PTZ Schemas
    PTZCommand:
      type: object
      required:
        - action
      properties:
        action:
          type: string
          enum: [pan_left, pan_right, tilt_up, tilt_down, zoom_in, zoom_out, stop, goto_preset, save_preset]
        speed:
          type: integer
          minimum: 1
          maximum: 100
          default: 50
          nullable: true
        preset_id:
          type: integer
          minimum: 1
          maximum: 255
          nullable: true

    PTZPreset:
      type: object
      properties:
        id:
          type: integer
          minimum: 1
          maximum: 255
        camera_id:
          type: integer
        name:
          type: string
          minLength: 1
          maxLength: 50
        pan:
          type: integer
          minimum: -180
          maximum: 180
        tilt:
          type: integer
          minimum: -90
          maximum: 90
        zoom:
          type: integer
          minimum: 0
          maximum: 100
        created_at:
          type: string
          format: date-time
        created_by:
          type: string
          format: uuid
        updated_at:
          type: string
          format: date-time

    # Event Schemas
    Event:
      type: object
      properties:
        id:
          type: string
          format: uuid
        event_type:
          $ref: '#/components/schemas/EventType'
        camera_id:
          type: integer
          nullable: true
        timestamp:
          type: string
          format: date-time
        severity:
          $ref: '#/components/schemas/EventSeverity'
        data:
          type: object
          description: Type-specific event data
        foreign_key:
          type: string
          nullable: true
        acknowledged:
          type: boolean
        acknowledged_by:
          type: string
          format: uuid
          nullable: true
        acknowledged_at:
          type: string
          format: date-time
          nullable: true

    EventType:
      type: string
      enum:
        - motion_detected
        - object_tracked
        - license_plate
        - perimeter_breach
        - camera_tamper
        - camera_online
        - camera_offline
        - recording_started
        - recording_stopped
        - storage_warning
        - alarm_triggered
        - alarm_cleared

    EventSeverity:
      type: string
      enum: [info, warning, error, critical]

    # Recording Schemas
    Recording:
      type: object
      properties:
        id:
          type: string
          format: uuid
        camera_id:
          type: integer
        start_time:
          type: string
          format: date-time
        end_time:
          type: string
          format: date-time
          nullable: true
        duration_seconds:
          type: integer
          nullable: true
        file_size_bytes:
          type: integer
          nullable: true
        trigger:
          $ref: '#/components/schemas/RecordingTrigger'
        status:
          type: string
          enum: [recording, completed, failed, exporting, exported]
        export_url:
          type: string
          format: uri
          nullable: true
        metadata:
          type: object
          properties:
            event_id:
              type: string
              format: uuid
              nullable: true
            pre_alarm_seconds:
              type: integer
            post_alarm_seconds:
              type: integer
            tags:
              type: array
              items:
                type: string
            notes:
              type: string
              nullable: true
        created_at:
          type: string
          format: date-time

    RecordingTrigger:
      type: string
      enum: [scheduled, event, manual, continuous]

    # Analytics Schemas
    AnalyticsConfig:
      type: object
      properties:
        camera_id:
          type: integer
        analytics_type:
          type: string
          enum: [vmd, npr, obtrack, gtect, cpa]
        enabled:
          type: boolean
        config:
          type: object
          description: Type-specific configuration
        updated_at:
          type: string
          format: date-time
        updated_by:
          type: string
          format: uuid

    # Error Schema
    Error:
      type: object
      properties:
        error_code:
          type: string
          description: Machine-readable error code
        message:
          type: string
          description: Human-readable error message
        details:
          type: object
          nullable: true
          description: Additional error details
        timestamp:
          type: string
          format: date-time

security:
  - BearerAuth: []
