openapi: "3.1.0"
info:
  title: VelaOS Cloud API
  version: "2.1.0"
  description: REST API for VelaOS endpoint management platform. Manages devices, groups, policies, apps, OTA updates, and users.
  contact:
    email: mail@velaos.ch
    url: https://velaos.ch
servers:
  - url: https://api.velaos.ch/api/v1
    description: Production
  - url: http://localhost:8080/api/v1
    description: Local development

paths:
  /auth/signup:
    post:
      summary: Create account + organization
      tags: [Auth]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password, name, org_name]
              properties:
                email: { type: string, format: email }
                password: { type: string, minLength: 8 }
                name: { type: string, maxLength: 255 }
                org_name: { type: string, maxLength: 255 }
      responses:
        "201": { description: Account created, returns Supabase session tokens }

  /auth/login:
    post:
      summary: Sign in with email + password
      tags: [Auth]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string }
                password: { type: string }
      responses:
        "200": { description: Returns access_token + refresh_token + user }
        "401": { description: Invalid credentials }

  /auth/refresh:
    post:
      summary: Refresh access token
      tags: [Auth]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [refresh_token]
              properties:
                refresh_token: { type: string }
      responses:
        "200": { description: New access_token + refresh_token }

  /devices:
    get:
      summary: List devices with optional filters
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: status, in: query, schema: { type: string, enum: [online, offline, pending, updating] } }
        - { name: group_id, in: query, schema: { type: string, format: uuid } }
        - { name: search, in: query, schema: { type: string } }
        - { name: page, in: query, schema: { type: integer, default: 1 } }
        - { name: limit, in: query, schema: { type: integer, default: 50, maximum: 100 } }
      responses:
        "200": { description: Paginated device list with total count }

  /devices/stats:
    get:
      summary: Aggregated device counts by status
      tags: [Devices]
      security: [{ bearer: [] }]
      responses:
        "200":
          description: Status counts
          content:
            application/json:
              schema:
                type: object
                properties:
                  total: { type: integer }
                  online: { type: integer }
                  offline: { type: integer }
                  pending: { type: integer }
                  updating: { type: integer }

  /devices/{id}:
    get:
      summary: Get device detail with health, apps, commands
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Full device object }
        "404": { description: Device not found }

  /devices/{id}/approve:
    post:
      summary: Approve a pending device
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Device approved }

  /devices/{id}/reboot:
    post:
      summary: Queue device reboot
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                delay_seconds: { type: integer, default: 30 }
                reason: { type: string }
      responses:
        "200": { description: Reboot queued with command_id }

  /devices/{id}/wipe:
    post:
      summary: Factory reset device (requires confirmation)
      tags: [Devices]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [confirm]
              properties:
                confirm: { type: boolean, enum: [true] }
      responses:
        "200": { description: Wipe queued }

  /devices/{id}/logs:
    post:
      summary: Collect diagnostic logs from device
      tags: [Devices]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Log collection queued }

  /devices/{id}/screenshot:
    post:
      summary: Capture device screenshot
      tags: [Devices]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Screenshot queued }

  /devices/{id}/diagnostics:
    post:
      summary: Run network diagnostics (ping/traceroute/DNS)
      tags: [Devices]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [target]
              properties:
                target: { type: string }
                type: { type: string, enum: [ping, traceroute, dns], default: ping }
      responses:
        "200": { description: Diagnostics queued }

  /devices/{id}/force-stop-app:
    post:
      summary: Force stop a running app
      description: Immediately terminates the specified application process on the device.
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [package_name]
              properties:
                package_name: { type: string, description: "Android package name (e.g. com.example.app)" }
      responses:
        "200": { description: Command queued }

  /devices/{id}/start-app:
    post:
      summary: Launch an installed app
      description: Opens the app's main activity on the device.
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [package_name]
              properties:
                package_name: { type: string }
      responses:
        "200": { description: Command queued }

  /devices/{id}/disable-app:
    post:
      summary: Hide an app from the device
      description: Uses DevicePolicyManager.setApplicationHidden to make the app invisible to the user.
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [package_name]
              properties:
                package_name: { type: string }
      responses:
        "200": { description: Command queued }

  /devices/{id}/enable-app:
    post:
      summary: Unhide a hidden app
      description: Reverses disable-app, making the app visible again.
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [package_name]
              properties:
                package_name: { type: string }
      responses:
        "200": { description: Command queued }

  /devices/{id}/block-uninstall:
    post:
      summary: Block or allow app uninstallation
      description: Prevents the user from uninstalling the specified app.
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [package_name, blocked]
              properties:
                package_name: { type: string }
                blocked: { type: boolean, description: "true = block uninstall, false = allow" }
      responses:
        "200": { description: Command queued }

  /devices/{id}/set-permission:
    post:
      summary: Grant, deny, or reset a runtime permission for an app
      description: Controls Android runtime permissions (camera, location, etc.) for a specific app.
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [package_name, permission, state]
              properties:
                package_name: { type: string }
                permission: { type: string, description: "Android permission (e.g. android.permission.CAMERA)" }
                state:
                  type: string
                  enum: [granted, denied, default]
                  description: "granted = always allow, denied = always deny, default = ask user"
      responses:
        "200": { description: Command queued }

  /devices/{id}/clear-app-data:
    post:
      summary: Clear all data for an app
      description: Deletes the app's local storage, cache, and databases. Cannot be undone.
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [package_name]
              properties:
                package_name: { type: string }
      responses:
        "200": { description: Command queued }

  /groups:
    get:
      summary: List groups with device counts
      tags: [Groups]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Group list with hierarchy }
    post:
      summary: Create a group
      tags: [Groups]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, maxLength: 255 }
                parent_id: { type: string, format: uuid }
                policy_id: { type: string, format: uuid }
      responses:
        "201": { description: Group created }

  /policies:
    get:
      summary: List custom policies
      tags: [Policies]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Policy list with version and device counts }
    post:
      summary: Create a policy (optionally from template)
      tags: [Policies]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                config: { type: object }
                template_id: { type: string, format: uuid }
      responses:
        "201": { description: Policy created }

  /apps/library:
    get:
      summary: List verified app library
      tags: [Apps]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Verified apps }

  /apps/upload:
    post:
      summary: Upload custom APK
      tags: [Apps]
      security: [{ bearer: [] }]
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                apk: { type: string, format: binary }
      responses:
        "201": { description: APK metadata saved }

  /apps/{id}/deploy:
    post:
      summary: Deploy app to devices
      tags: [Apps]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                group_id: { type: string, format: uuid }
                ring: { type: string, enum: [canary, pilot, broad, global] }
      responses:
        "200": { description: Deployment started }

  /ota/releases:
    get:
      summary: List OTA releases
      tags: [OTA]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Release list }

  /audit:
    get:
      summary: List audit log entries
      tags: [Audit]
      security: [{ bearer: [] }]
      parameters:
        - { name: page, in: query, schema: { type: integer } }
        - { name: limit, in: query, schema: { type: integer, default: 50 } }
      responses:
        "200": { description: Paginated audit entries }

  /users:
    get:
      summary: List team members
      tags: [Users]
      security: [{ bearer: [] }]
      responses:
        "200": { description: User list with roles and status }

  /users/invite:
    post:
      summary: Invite a user to the organization
      tags: [Users]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [email, role]
              properties:
                email: { type: string, format: email }
                role: { type: string, enum: [site_admin, helpdesk, readonly] }
                name: { type: string }
      responses:
        "201": { description: Invite sent }

  /tenant:
    get:
      summary: Get organization info
      tags: [Tenant]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Tenant name, org_code, settings }

  /enroll/{orgCode}:
    post:
      summary: Look up tenant by VelaOS Code (no auth required)
      tags: [Enrollment]
      parameters:
        - { name: orgCode, in: path, required: true, schema: { type: string } }
      responses:
        "200": { description: Tenant info + MQTT broker }
        "404": { description: Organization not found }

  /devices/register:
    post:
      summary: Register a device (no auth required)
      tags: [Enrollment]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [serial]
              properties:
                org_code: { type: string }
                serial: { type: string }
                mac: { type: string }
                model: { type: string }
                hostname: { type: string }
      responses:
        "201": { description: Device registered with MQTT credentials }

  # ==================== POLICY INHERITANCE ====================
  /policies/default:
    get:
      summary: Get tenant default policy
      tags: [Policies]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Returns tenant default policy and level }
    put:
      summary: Set tenant default policy
      tags: [Policies]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                policy: { type: object }
      responses:
        "200": { description: Policy updated with version number }

  /devices/{id}/policy:
    get:
      summary: Get merged effective policy for a device
      tags: [Policies]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Returns effective_policy and all 3 layers }

  /devices/{id}/policy/override:
    get:
      summary: Get device-level policy override
      tags: [Policies]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Device override policy }
    put:
      summary: Set device-level policy override
      tags: [Policies]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                policy: { type: object }
      responses:
        "200": { description: Override updated }

  /policies/{id}/diff:
    post:
      summary: Compare two policy versions
      tags: [Policies]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                version_a: { type: integer }
                version_b: { type: integer }
      responses:
        "200": { description: Returns array of field changes }

  # ==================== APP VERSIONS + RING DEPLOYMENT ====================
  /apps/{id}/versions:
    get:
      summary: List app versions
      tags: [Apps]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Array of app versions }
    post:
      summary: Create app version
      tags: [Apps]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [version, version_code, storage_path, sha256, size_bytes]
              properties:
                version: { type: string }
                version_code: { type: integer }
                storage_path: { type: string }
                sha256: { type: string }
                size_bytes: { type: integer }
                changelog: { type: string }
      responses:
        "201": { description: Version created }

  /deployments:
    get:
      summary: List ring deployments
      tags: [Apps]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Array of deployments with progress }
    post:
      summary: Create ring deployment
      tags: [Apps]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [app_id, version_id, ring]
              properties:
                app_id: { type: string, format: uuid }
                version_id: { type: string, format: uuid }
                group_id: { type: string, format: uuid }
                ring: { type: string, enum: [canary, pilot, broad, global] }
                soak_hours: { type: integer, default: 24 }
                failure_threshold: { type: number, default: 0.05 }
                auto_promote: { type: boolean, default: true }
      responses:
        "201": { description: Deployment created }

  /deployments/{id}/promote:
    post:
      summary: Promote deployment to next ring
      tags: [Apps]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Promoted to next ring }

  /deployments/{id}/pause:
    patch:
      summary: Pause or resume a deployment
      tags: [Apps]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                paused: { type: boolean }
      responses:
        "200": { description: Deployment paused/resumed }

  # ==================== API KEYS ====================
  /settings/api-keys:
    get:
      summary: List API keys (prefix only)
      tags: [Settings]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Array of API keys with prefix, name, dates }
    post:
      summary: Create API key
      tags: [Settings]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                scopes: { type: array, items: { type: string } }
      responses:
        "201": { description: Returns full key (shown once), prefix, id }

  /settings/api-keys/{id}:
    delete:
      summary: Revoke API key
      tags: [Settings]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Key revoked }

  # ==================== WEBHOOKS ====================
  /settings/webhooks:
    get:
      summary: List webhooks
      tags: [Settings]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Array of webhooks }
    post:
      summary: Create webhook
      tags: [Settings]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url: { type: string, format: uri }
                events: { type: array, items: { type: string, enum: [device.enrolled, device.approved, device.offline, device.online, command.completed, policy.updated, app.deployed] } }
      responses:
        "201": { description: Webhook created with HMAC signing secret (shown once) }

  /settings/webhooks/{id}:
    patch:
      summary: Update webhook
      tags: [Settings]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                url: { type: string }
                events: { type: array, items: { type: string } }
                active: { type: boolean }
      responses:
        "200": { description: Webhook updated }
    delete:
      summary: Delete webhook
      tags: [Settings]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Webhook deleted }

  /settings/webhooks/{id}/deliveries:
    get:
      summary: List webhook delivery history
      tags: [Settings]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Array of delivery attempts }

  # ==================== CERTIFICATES ====================
  /settings/ca:
    post:
      summary: Generate tenant CA certificate
      tags: [Certificates]
      security: [{ bearer: [] }]
      responses:
        "201": { description: CA cert generated, returns fingerprint and PEM }

  /settings/ca/download:
    get:
      summary: Download CA root certificate (PEM)
      tags: [Certificates]
      security: [{ bearer: [] }]
      responses:
        "200": { description: PEM file download }

  /devices/{id}/cert:
    post:
      summary: Issue device certificate signed by tenant CA
      tags: [Certificates]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "201": { description: Certificate issued and pushed via MQTT }

  /devices/{id}/certs:
    get:
      summary: List device certificates
      tags: [Certificates]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Array of certificates }

  /devices/{id}/certs/{certId}/revoke:
    post:
      summary: Revoke a device certificate
      tags: [Certificates]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
        - { name: certId, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                reason: { type: string }
      responses:
        "200": { description: Certificate revoked }

  # ==================== NETWORK PROFILES ====================
  /settings/network-profiles:
    get:
      summary: List 802.1X / WiFi network profiles
      tags: [Network]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Array of network profiles }
    post:
      summary: Create network profile
      tags: [Network]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name, type]
              properties:
                name: { type: string }
                type: { type: string, enum: [wifi_enterprise, ethernet_enterprise, wifi_psk, wifi_open] }
                ssid: { type: string }
                eap_method: { type: string, enum: [EAP-TLS, EAP-TTLS, PEAP, EAP-FAST] }
                cert_source: { type: string, enum: [scep, vela_ca, manual] }
                domain: { type: string }
                priority: { type: integer, default: 100 }
      responses:
        "201": { description: Profile created }

  /settings/network-profiles/{id}:
    patch:
      summary: Update network profile
      tags: [Network]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Profile updated }
    delete:
      summary: Delete network profile
      tags: [Network]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Profile deleted }

  # ==================== SCHEDULES ====================
  /schedules:
    get:
      summary: List scheduled actions
      tags: [Schedules]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Array of scheduled actions }
    post:
      summary: Create scheduled action
      tags: [Schedules]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name, action, target_type, schedule_type, schedule_time]
              properties:
                name: { type: string }
                action: { type: string, enum: [reboot, shutdown, wake, policy_sync, log_collect, screenshot, app_update_check] }
                target_type: { type: string, enum: [device, group, all] }
                target_id: { type: string, format: uuid }
                schedule_type: { type: string, enum: [once, daily, weekly, monthly] }
                schedule_time: { type: string, format: time }
                schedule_timezone: { type: string, default: UTC }
                require_idle: { type: boolean, default: false }
      responses:
        "201": { description: Schedule created }

  /schedules/{id}:
    patch:
      summary: Update scheduled action (enable/disable)
      tags: [Schedules]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Schedule updated }
    delete:
      summary: Delete scheduled action
      tags: [Schedules]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Schedule deleted }

  /schedules/{id}/runs:
    get:
      summary: List execution history for a schedule
      tags: [Schedules]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Array of execution runs }

  # ==================== FILE BRIDGE ====================
  /files:
    get:
      summary: List managed files
      tags: [Files]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Array of managed files }
    post:
      summary: Register a managed file
      tags: [Files]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name, original_filename, storage_path, sha256, size_bytes]
              properties:
                name: { type: string }
                original_filename: { type: string }
                storage_path: { type: string }
                sha256: { type: string }
                size_bytes: { type: integer }
                category: { type: string, enum: [document, certificate, config, branding, content] }
      responses:
        "201": { description: File registered }

  /files/{id}:
    delete:
      summary: Delete managed file
      tags: [Files]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: File deleted }

  /files/{id}/deploy:
    post:
      summary: Deploy file to devices
      tags: [Files]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                group_id: { type: string, format: uuid }
                target_path: { type: string, default: /sdcard/VelaContent/ }
      responses:
        "201": { description: Deployment created }

  /file-deployments:
    get:
      summary: List file deployment history
      tags: [Files]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Array of file deployments }

  # ==================== CONTENT PLAYLISTS ====================
  /playlists:
    get:
      summary: List content playlists
      tags: [Playlists]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Array of playlists }
    post:
      summary: Create content playlist
      tags: [Playlists]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                items: { type: array }
                transition: { type: string, enum: [fade, slide, cut], default: fade }
                schedule: { type: object }
      responses:
        "201": { description: Playlist created }

  /playlists/{id}:
    patch:
      summary: Update playlist
      tags: [Playlists]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Playlist updated }
    delete:
      summary: Delete playlist
      tags: [Playlists]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Playlist deleted }

  /playlists/{id}/assign:
    post:
      summary: Assign playlist to group/devices
      tags: [Playlists]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                group_id: { type: string, format: uuid }
                trigger_on: { type: string, enum: [idle, always, schedule, vdi_disconnect], default: idle }
      responses:
        "201": { description: Assignment created }

  /playlists/{id}/assignments:
    get:
      summary: List playlist assignments
      tags: [Playlists]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Array of assignments }

  # ==================== SSO ====================
  /settings/sso:
    get:
      summary: Get SSO configuration
      tags: [SSO]
      security: [{ bearer: [] }]
      responses:
        "200": { description: SSO config or configured=false }
    post:
      summary: Create/update SSO connection
      tags: [SSO]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [provider, email_domain]
              properties:
                provider: { type: string, enum: [saml, oidc] }
                provider_name: { type: string }
                email_domain: { type: string }
                saml_idp_entity_id: { type: string }
                saml_idp_sso_url: { type: string }
                saml_idp_metadata_url: { type: string }
                jit_provisioning: { type: boolean }
                default_role: { type: string, enum: [tenant_admin, operator, viewer] }
      responses:
        "201": { description: SSO configured }

  /settings/sso/activate:
    post:
      summary: Activate or deactivate SSO
      tags: [SSO]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                active: { type: boolean }
      responses:
        "200": { description: SSO status updated }

  /settings/sso/group-mappings:
    get:
      summary: List IdP group to role mappings
      tags: [SSO]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Array of group mappings }
    post:
      summary: Create group to role mapping
      tags: [SSO]
      security: [{ bearer: [] }]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [idp_group_name, velaos_role]
              properties:
                idp_group_name: { type: string }
                velaos_role: { type: string, enum: [tenant_admin, operator, viewer] }
      responses:
        "201": { description: Mapping created }

  /settings/sso/group-mappings/{id}:
    delete:
      summary: Delete group mapping
      tags: [SSO]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      responses:
        "200": { description: Mapping deleted }

  /settings/sso/login-events:
    get:
      summary: List SSO login events
      tags: [SSO]
      security: [{ bearer: [] }]
      responses:
        "200": { description: Recent SSO login attempts }

  /auth/sso/check:
    post:
      summary: Check if email domain has SSO (public)
      tags: [SSO]
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }
      responses:
        "200": { description: sso_enabled boolean and provider_name }

  # ── Phase 2: System & File Commands ──────────────────────────────

  /devices/{id}/shell:
    post:
      summary: Execute shell command on device
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [command]
              properties:
                command: { type: string, description: Shell command to execute }
                timeout_seconds: { type: integer, minimum: 1, maximum: 120, default: 30 }
      responses:
        "200": { description: Command queued }

  /devices/{id}/file-ops:
    post:
      summary: Perform file operation on device
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [op, path]
              properties:
                op: { type: string, enum: [delete, mkdir, write], description: "Operation type" }
                path: { type: string, description: "Target path (allowed: /sdcard/, /data/local/tmp/, /storage/)" }
                content: { type: string, description: "File content (required for write op)" }
      responses:
        "200": { description: Command queued }

  /devices/{id}/system-setting:
    post:
      summary: Set Android system setting
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [namespace, key, value]
              properties:
                namespace: { type: string, enum: [system, secure, global], description: "Settings namespace" }
                key: { type: string }
                value: { type: string }
      responses:
        "200": { description: Command queued }

  /devices/{id}/show-message:
    post:
      summary: Display message on device
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message]
              properties:
                message: { type: string }
                type: { type: string, enum: [toast, dialog], default: toast }
                duration: { type: string, enum: [short, long], default: short }
      responses:
        "200": { description: Command queued }

  /devices/{id}/set-wallpaper:
    post:
      summary: Set device wallpaper from URL
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [wallpaper_url]
              properties:
                wallpaper_url: { type: string, format: uri }
      responses:
        "200": { description: Command queued }

  /devices/{id}/intent:
    post:
      summary: Send Android Intent to device
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type, action]
              properties:
                type: { type: string, enum: [broadcast, activity, service], description: "Intent dispatch method" }
                action: { type: string, description: "Intent action string" }
                package: { type: string, description: "Target package (optional)" }
                extras: { type: object, description: "Key-value extras" }
      responses:
        "200": { description: Command queued }

  # ── Phase 5: Enhanced Live View ─────────────────────────────────

  /devices/{id}/input-tap:
    post:
      summary: Simulate screen tap on device
      tags: [Devices, Live View]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [x, y]
              properties:
                x: { type: integer, description: "X coordinate in device pixels" }
                y: { type: integer, description: "Y coordinate in device pixels" }
      responses:
        "200": { description: Command queued }

  /devices/{id}/input-text:
    post:
      summary: Type text on device
      tags: [Devices, Live View]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [text]
              properties:
                text: { type: string, description: "Text to type" }
      responses:
        "200": { description: Command queued }

  /devices/{id}/input-keyevent:
    post:
      summary: Send key event to device
      tags: [Devices, Live View]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [keycode]
              properties:
                keycode: { type: integer, description: "Android keycode (e.g. 3=HOME, 4=BACK, 66=ENTER)" }
      responses:
        "200": { description: Command queued }

  # ── Pi hardware commands (v2.2) ────────────────────────────────

  /devices/{id}/hdmi-cec:
    post:
      summary: HDMI CEC display power control
      description: Control the HDMI-connected display power state via CEC. Requires `cec-client` on the device.
      tags: [Devices, Pi Hardware]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [action]
              properties:
                action:
                  type: string
                  enum: [standby, on, wakeup, status]
                  description: standby=sleep the display; on/wakeup=power on; status=query current power state
      responses:
        "200": { description: Command queued with command_id }
        "400": { description: Invalid action }

  /devices/{id}/cpu-governor:
    post:
      summary: Set CPU scaling governor
      description: Sets the Linux kernel CPU frequency scaling governor on all cores of the Pi.
      tags: [Devices, Pi Hardware]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [governor]
              properties:
                governor:
                  type: string
                  enum: [performance, powersave, ondemand, schedutil, conservative, userspace]
      responses:
        "200": { description: Command queued }
        "400": { description: Invalid governor }

  /devices/{id}/gpio-write:
    post:
      summary: Write GPIO pin
      description: Write a value to a GPIO pin. Auto-exports the pin and sets direction=out.
      tags: [Devices, Pi Hardware]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [pin, value]
              properties:
                pin: { type: integer, description: "GPIO pin number (0-40 on Pi 5)" }
                value: { type: integer, enum: [0, 1] }
      responses:
        "200": { description: Command queued }

  /devices/{id}/gpio-read:
    post:
      summary: Read GPIO pin
      description: Read the current value of a GPIO pin. Agent replies on /response with `pin` and `value`.
      tags: [Devices, Pi Hardware]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [pin]
              properties:
                pin: { type: integer }
      responses:
        "200": { description: Command queued; poll /response for value }

  /devices/{id}/adb-network:
    post:
      summary: Toggle ADB over TCP
      description: Enable or disable ADB-over-network for remote debugging. Restarts adbd.
      tags: [Devices, Pi Hardware]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled]
              properties:
                enabled: { type: boolean }
                port: { type: integer, default: 5555 }
      responses:
        "200": { description: Command queued }

  /devices/{id}/batch:
    post:
      summary: Batch command (process_subcommands)
      description: >
        Execute up to 20 subcommands sequentially on the device. Each subcommand is a full
        command envelope with its own `cmd` and payload. The agent processes them in order
        and publishes a single aggregated response.
      tags: [Devices]
      security: [{ bearer: [] }]
      parameters:
        - { name: id, in: path, required: true, schema: { type: string, format: uuid } }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [subcommands]
              properties:
                subcommands:
                  type: array
                  maxItems: 20
                  items: { type: object }
      responses:
        "200": { description: Batch queued }
        "400": { description: Empty or >20 subcommands }

  # ── Phase 6: Policy Schema ─────────────────────────────────────

  /policies/schema:
    get:
      summary: Get JSON Schema for policy configuration
      description: >
        Returns the full JSON Schema describing all available policy settings.
        AI agents and automation tools use this to discover and understand
        every configurable setting before calling PATCH /policies/{id}.
      tags: [Policies]
      security: [{ bearer: [] }]
      responses:
        "200":
          description: JSON Schema for the policy config object
          content:
            application/json:
              schema:
                type: object

components:
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Supabase access token from /auth/login
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: VelaOS API key (vela_ prefix)
