openapi: 3.1.0
info:
  title: Ephemeral Phone Control API
  version: 1.0.0
  description: Lifecycle API for remotely hosted Android phone instances.
servers:
  - url: http://localhost:8080
security:
  - bearerAuth: []
paths:
  /healthz:
    get:
      security: []
      responses:
        "200": {description: Healthy}
  /api/v1/capabilities:
    get:
      responses:
        "200":
          description: Runtime capabilities
          content:
            application/json:
                schema: {$ref: "#/components/schemas/Capabilities"}
  /api/v1/runtimes:
    get:
      responses:
        "200":
          description: Configured runtime hosts with live availability and allocation counts
          content:
            application/json:
              schema:
                type: object
                properties:
                  runtimes:
                    type: array
                    items: {$ref: "#/components/schemas/Runtime"}
  /api/v1/phones:
    get:
      responses:
        "200":
          description: Phone collection
          content:
            application/json:
              schema:
                type: object
                properties:
                  phones:
                    type: array
                    items: {$ref: "#/components/schemas/Phone"}
    post:
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/CreatePhone"}
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Phone"}
  /api/v1/phones/{phoneId}:
    parameters:
      - {$ref: "#/components/parameters/PhoneId"}
    get:
      responses:
        "200":
          description: Phone
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Phone"}
    delete:
      responses:
        "200": {description: Deleted}
  /api/v1/phones/{phoneId}/start:
    post:
      parameters: [{$ref: "#/components/parameters/PhoneId"}]
      responses: {"200": {description: Started}}
  /api/v1/phones/{phoneId}/stop:
    post:
      parameters: [{$ref: "#/components/parameters/PhoneId"}]
      responses: {"200": {description: Stopped}}
  /api/v1/phones/{phoneId}/lock:
    post:
      parameters: [{$ref: "#/components/parameters/PhoneId"}]
      responses: {"200": {description: Locked}}
  /api/v1/phones/{phoneId}/snapshot:
    post:
      parameters: [{$ref: "#/components/parameters/PhoneId"}]
      responses:
        "201":
          description: Snapshot created
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Snapshot"}
  /api/v1/phones/{phoneId}/connect:
    post:
      parameters: [{$ref: "#/components/parameters/PhoneId"}]
      responses:
        "201":
          description: Short-lived connection ticket
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Connection"}
  /api/v1/events:
    get:
      parameters:
        - name: limit
          in: query
          schema: {type: integer, minimum: 1, maximum: 500, default: 100}
      responses:
        "200": {description: Audit events}
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  parameters:
    PhoneId:
      name: phoneId
      in: path
      required: true
      schema: {type: string}
  schemas:
    CreatePhone:
      type: object
      required: [name]
      properties:
        name: {type: string, minLength: 1}
        mode: {type: string, enum: [persistent, disposable], default: persistent}
        android_os:
          type: string
          enum: [aosp, lineageos, grapheneos]
          default: aosp
          description: Android image family; the selected runtime must advertise support for it
        runtime_id: {type: string, description: Named runtime host from /api/v1/runtimes; defaults to the configured primary runtime}
        cpu: {type: integer, minimum: 1, default: 4}
        memory_mib: {type: integer, minimum: 1024, default: 4096}
        storage_gib: {type: integer, minimum: 8, default: 64}
        metadata: {type: object, additionalProperties: {type: string}}
    Phone:
      allOf:
        - {$ref: "#/components/schemas/CreatePhone"}
        - type: object
          required: [id, provider, state, created_at, updated_at]
          properties:
            id: {type: string}
            provider: {type: string}
            state: {type: string}
            last_error: {type: string}
            created_at: {type: string, format: date-time}
            updated_at: {type: string, format: date-time}
            last_started: {type: string, format: date-time}
    Snapshot:
      type: object
      properties:
        id: {type: string}
        phone_id: {type: string}
        provider: {type: string}
        location: {type: string}
        created_at: {type: string, format: date-time}
    Connection:
      type: object
      properties:
        phone_id: {type: string}
        url: {type: string}
        ticket: {type: string}
        expires_at: {type: string, format: date-time}
    Capabilities:
      type: object
      properties:
        provider: {type: string}
        hardware_isolation: {type: boolean}
        snapshots: {type: boolean}
        streaming: {type: boolean}
        sensors: {type: boolean}
        supported_android_os:
          type: array
          items: {type: string, enum: [aosp, lineageos, grapheneos]}
          description: Image families verified and configured on this runtime
    Runtime:
      allOf:
        - {$ref: "#/components/schemas/Capabilities"}
        - type: object
          required: [id, name, kind, online, default, capacity, phone_count, running_phone_count]
          properties:
            id: {type: string}
            name: {type: string}
            kind: {type: string, enum: [remote, mock, command]}
            online: {type: boolean}
            default: {type: boolean}
            capacity: {type: integer, minimum: 0, description: Zero means unbounded}
            phone_count: {type: integer, minimum: 0}
            running_phone_count: {type: integer, minimum: 0}
            message: {type: string}
