openapi: 3.0.3
info:
  title: use.computer API
  description: macOS Computer Use API — create sandboxes, control mouse/keyboard, take screenshots, record sessions.
  version: 0.2.0

servers:
  - url: https://api.use.computer
    description: Customer API — paying customers, per-reservation `mk_live_*` keys
  - url: https://admin.use.computer
    description: Admin API — internal, `MMINI_API_KEY`
  - url: https://api.dev.use.computer
    description: Customer API (dev)
  - url: https://admin.dev.use.computer
    description: Admin API (dev)
  - url: http://localhost:8080
    description: Local admin
  - url: http://localhost:8084
    description: Local customer

# Both servers share the same /v1/sandboxes wire shapes; auth differs:
#  - Customer endpoints (api.use.computer): Bearer mk_live_*
#  - Admin endpoints (admin.use.computer): Bearer sk-* (MMINI_API_KEY)
security:
  - CustomerKey: []
  - AdminKey: []

tags:
  - name: Sandbox
    description: Create, inspect, and destroy sandbox sessions.
  - name: Mouse
    description: Mouse click, move, drag, scroll, and position.
  - name: Keyboard
    description: Type text, press keys, and hotkey combos.
  - name: Screenshot
    description: Capture full screen, region, or compressed screenshots.
  - name: Act
    description: Execute an action and optionally capture a screenshot in one call.
  - name: Recording
    description: Start, stop, list, download, and delete screen recordings.
  - name: Display
    description: Screen info and accessibility tree.
  - name: Files
    description: Upload files into the sandbox.
  - name: Connectivity
    description: VNC and SSH WebSocket proxies.
  - name: System
    description: Health checks and diagnostics.
  - name: Admin
    description: Server administration — Mac Minis, slots, sessions.
  - name: Reservations
    description: Customer reservation introspection (customer API only).

paths:
  /v1/reservations/me:
    get:
      tags: [Reservations]
      summary: Get caller's reservation
      description: |
        Returns the reservation tied to the `mk_live_*` API key in the Authorization
        header, including the leased Mac Mini IPs and time remaining. Customer API only.
      operationId: getMyReservation
      security:
        - CustomerKey: []
      responses:
        "200":
          description: Reservation details
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string, format: uuid }
                  user_id: { type: string, format: uuid }
                  mini_count: { type: integer }
                  hours: { type: integer }
                  start_at: { type: string, format: date-time }
                  end_at: { type: string, format: date-time }
                  total_cents: { type: integer }
                  status:
                    type: string
                    enum: [pending, active, completing, completed, cancelled]
                  mini_ips:
                    type: array
                    items: { type: string }
        "401":
          description: Invalid, revoked, or expired key

  /v1/sandboxes:
    post:
      tags: [Sandbox]
      summary: Create sandbox
      description: Claims a warm VM and returns sandbox ID.
      operationId: createSandbox
      responses:
        "201":
          description: Sandbox created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxCreated"
        "503":
          description: No warm VMs available

  /v1/sandboxes/{sandboxID}:
    get:
      tags: [Sandbox]
      summary: Get sandbox
      operationId: getSandbox
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      responses:
        "200":
          description: Sandbox details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxInfo"
        "404":
          description: Sandbox not found
    delete:
      tags: [Sandbox]
      summary: Delete sandbox
      operationId: deleteSandbox
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      responses:
        "204":
          description: Sandbox destroyed

  /v1/sandboxes/{sandboxID}/files:
    put:
      tags: [Files]
      summary: Upload file
      description: Upload raw bytes to a path inside the sandbox.
      operationId: uploadFile
      parameters:
        - $ref: "#/components/parameters/sandboxID"
        - name: path
          in: query
          required: true
          schema:
            type: string
          example: "~/Desktop/report.pdf"
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "204":
          description: File uploaded
    get:
      tags: [Files]
      summary: Download file
      description: Download a file from the sandbox via SSH.
      operationId: downloadFile
      parameters:
        - $ref: "#/components/parameters/sandboxID"
        - name: path
          in: query
          required: true
          schema:
            type: string
          example: "/tmp/output.txt"
      responses:
        "200":
          description: File contents
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary

  /v1/sandboxes/{sandboxID}/mouse/click:
    post:
      tags: [Mouse]
      summary: Click
      operationId: mouseClick
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [x, y]
              properties:
                x: { type: integer }
                y: { type: integer }
                button: { type: string, default: "left", enum: [left, right] }
                double: { type: boolean, default: false }
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"

  /v1/sandboxes/{sandboxID}/mouse/move:
    post:
      tags: [Mouse]
      summary: Move cursor
      operationId: mouseMove
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [x, y]
              properties:
                x: { type: integer }
                y: { type: integer }
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"

  /v1/sandboxes/{sandboxID}/mouse/drag:
    post:
      tags: [Mouse]
      summary: Drag
      operationId: mouseDrag
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [startX, startY, endX, endY]
              properties:
                startX: { type: integer }
                startY: { type: integer }
                endX: { type: integer }
                endY: { type: integer }
                button: { type: string, default: "left" }
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"

  /v1/sandboxes/{sandboxID}/mouse/scroll:
    post:
      tags: [Mouse]
      summary: Scroll
      operationId: mouseScroll
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [x, y]
              properties:
                x: { type: integer }
                y: { type: integer }
                direction: { type: string, default: "down", enum: [up, down, left, right] }
                amount: { type: integer, default: 3 }
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"

  /v1/sandboxes/{sandboxID}/mouse/position:
    get:
      tags: [Mouse]
      summary: Get cursor position
      operationId: mousePosition
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"

  /v1/sandboxes/{sandboxID}/keyboard/type:
    post:
      tags: [Keyboard]
      summary: Type text
      operationId: keyboardType
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [text]
              properties:
                text: { type: string }
                delay: { type: integer, description: "Delay between keystrokes in ms" }
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"

  /v1/sandboxes/{sandboxID}/keyboard/press:
    post:
      tags: [Keyboard]
      summary: Press key
      operationId: keyboardPress
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [key]
              properties:
                key: { type: string, example: "enter" }
                modifiers: { type: array, items: { type: string }, example: ["command"] }
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"

  /v1/sandboxes/{sandboxID}/keyboard/hotkey:
    post:
      tags: [Keyboard]
      summary: Hotkey combo
      operationId: keyboardHotkey
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [keys]
              properties:
                keys: { type: string, example: "command+c" }
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"

  /v1/sandboxes/{sandboxID}/screenshot:
    get:
      tags: [Screenshot]
      summary: Take screenshot
      operationId: takeScreenshot
      parameters:
        - $ref: "#/components/parameters/sandboxID"
        - name: show_cursor
          in: query
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: PNG image
          content:
            image/png:
              schema:
                type: string
                format: binary

  /v1/sandboxes/{sandboxID}/screenshot/region:
    get:
      tags: [Screenshot]
      summary: Screenshot region
      operationId: screenshotRegion
      parameters:
        - $ref: "#/components/parameters/sandboxID"
        - name: x
          in: query
          required: true
          schema: { type: integer }
        - name: y
          in: query
          required: true
          schema: { type: integer }
        - name: width
          in: query
          required: true
          schema: { type: integer }
        - name: height
          in: query
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: PNG image
          content:
            image/png:
              schema:
                type: string
                format: binary

  /v1/sandboxes/{sandboxID}/screenshot/compressed:
    get:
      tags: [Screenshot]
      summary: Compressed screenshot
      operationId: screenshotCompressed
      parameters:
        - $ref: "#/components/parameters/sandboxID"
        - name: format
          in: query
          schema:
            type: string
            default: jpeg
            enum: [jpeg, png]
        - name: quality
          in: query
          schema:
            type: integer
            default: 80
        - name: scale
          in: query
          schema:
            type: number
      responses:
        "200":
          description: Compressed image
          content:
            image/*:
              schema:
                type: string
                format: binary

  /v1/sandboxes/{sandboxID}/act:
    post:
      tags: [Act]
      summary: Act + screenshot
      description: Execute an action and optionally take a screenshot afterward in a single round trip.
      operationId: act
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [action]
              properties:
                action:
                  type: object
                  description: "Action to execute. `type` maps to a command: click, type, key, hotkey, scroll, drag, move."
                  example: { "type": "click", "x": 100, "y": 200 }
                screenshot_after:
                  type: boolean
                  default: true
                screenshot_delay_ms:
                  type: integer
                  default: 100
      responses:
        "200":
          description: Screenshot (if requested) or JSON result
          content:
            image/png:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                type: object

  /v1/sandboxes/{sandboxID}/recording/start:
    post:
      tags: [Recording]
      summary: Start recording
      operationId: recordingStart
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"

  /v1/sandboxes/{sandboxID}/recording/stop:
    post:
      tags: [Recording]
      summary: Stop recording
      operationId: recordingStop
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [id]
              properties:
                id: { type: string }
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"

  /v1/sandboxes/{sandboxID}/recordings:
    get:
      tags: [Recording]
      summary: List recordings
      operationId: recordingsList
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      responses:
        "200":
          description: List of recordings
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object

  /v1/sandboxes/{sandboxID}/recordings/{recordingID}:
    get:
      tags: [Recording]
      summary: Get recording
      operationId: recordingGet
      parameters:
        - $ref: "#/components/parameters/sandboxID"
        - $ref: "#/components/parameters/recordingID"
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"
    delete:
      tags: [Recording]
      summary: Delete recording
      operationId: recordingDelete
      parameters:
        - $ref: "#/components/parameters/sandboxID"
        - $ref: "#/components/parameters/recordingID"
      responses:
        "204":
          description: Recording deleted

  /v1/sandboxes/{sandboxID}/recordings/{recordingID}/download:
    get:
      tags: [Recording]
      summary: Download recording
      operationId: recordingDownload
      parameters:
        - $ref: "#/components/parameters/sandboxID"
        - $ref: "#/components/parameters/recordingID"
      responses:
        "200":
          description: Video file
          content:
            video/mp4:
              schema:
                type: string
                format: binary

  /v1/sandboxes/{sandboxID}/display/info:
    get:
      tags: [Display]
      summary: Display info
      operationId: displayInfo
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"

  /v1/sandboxes/{sandboxID}/display/windows:
    get:
      tags: [Display]
      summary: List windows (accessibility tree)
      operationId: displayWindows
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      responses:
        "200":
          $ref: "#/components/responses/CommandResult"

  /v1/sandboxes/{sandboxID}/vnc:
    get:
      tags: [Connectivity]
      summary: VNC WebSocket proxy
      description: Upgrades to WebSocket and proxies VNC traffic to the sandbox VM.
      operationId: vncProxy
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      responses:
        "101":
          description: WebSocket upgrade

  /v1/sandboxes/{sandboxID}/ssh:
    get:
      tags: [Connectivity]
      summary: SSH WebSocket proxy
      description: Upgrades to WebSocket and proxies SSH traffic to the sandbox VM.
      operationId: sshProxy
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      responses:
        "101":
          description: WebSocket upgrade

  /v1/sandboxes/{sandboxID}/exec:
    post:
      tags: [Sandbox]
      summary: Execute command
      description: Run a shell command on the sandbox VM via SSH.
      operationId: exec
      parameters:
        - $ref: "#/components/parameters/sandboxID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [command]
              properties:
                command:
                  type: string
                  example: "ls -la /tmp"
      responses:
        "200":
          description: Command output
          content:
            application/json:
              schema:
                type: object
                properties:
                  stdout: { type: string }
                  return_code: { type: integer }

  /health:
    get:
      tags: [System]
      summary: Health check
      operationId: health
      security: []
      responses:
        "200":
          description: Pool stats
          content:
            application/json:
              schema:
                type: object

  /admin/minis:
    get:
      tags: [Admin]
      summary: List Mac Minis
      description: All discovered Mac Minis with their VM slots.
      operationId: adminMinis
      responses:
        "200":
          description: Mac Minis
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    ip: { type: string }
                    slots:
                      type: array
                      nullable: true
                      items:
                        $ref: "#/components/schemas/SlotInfo"

  /admin/slots:
    get:
      tags: [Admin]
      summary: List all slots
      operationId: adminSlots
      responses:
        "200":
          description: All VM slots
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/SlotInfo"

  /admin/sessions:
    get:
      tags: [Admin]
      summary: List active sessions
      operationId: adminSessions
      responses:
        "200":
          description: Active sessions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/SandboxInfo"

components:
  parameters:
    sandboxID:
      name: sandboxID
      in: path
      required: true
      schema:
        type: string
        pattern: "^sb-[0-9a-f]{32}$"
      example: "sb-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
    recordingID:
      name: recordingID
      in: path
      required: true
      schema:
        type: string

  schemas:
    SandboxCreated:
      type: object
      properties:
        sandbox_id: { type: string, example: "sb-a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" }
        created_at: { type: string, format: date-time }
        vm_ip: { type: string, example: "192.168.64.4" }
        vnc_url: { type: string, example: "/v1/sandboxes/sb-.../vnc" }
        ssh_url: { type: string, example: "/v1/sandboxes/sb-.../ssh" }
    SandboxInfo:
      type: object
      properties:
        sandbox_id: { type: string }
        created_at: { type: string, format: date-time }
        last_active: { type: string, format: date-time }
        vm_ip: { type: string }
        vnc_url: { type: string }
        ssh_url: { type: string }
    SlotInfo:
      type: object
      properties:
        mac_mini_ip: { type: string }
        vm_name: { type: string }
        vm_ip: { type: string }
        status: { type: string, enum: [warm, occupied, cloning, down] }
        sandbox_id: { type: string }
        last_active: { type: string }

  securitySchemes:
    CustomerKey:
      type: http
      scheme: bearer
      description: |
        Per-reservation key (`mk_live_...`). Generated when a paid reservation
        activates; revoked when the reservation expires. Use against
        `https://api.use.computer`.
    AdminKey:
      type: http
      scheme: bearer
      description: |
        Internal admin key (`sk-...`). `MMINI_API_KEY` env var. Use against
        `https://admin.use.computer`.

  responses:
    CommandResult:
      description: Command result from computer-server
      content:
        application/json:
          schema:
            type: object
