openapi: 3.1.0
info:
  title: CubePod Canonical API (E2B-Compatible Superset)
  version: 0.4.0
  description: |
    Unified Control & Data Plane API for CubePod.
    - 100% wire-compatible with E2B Control Plane (`/sandboxes`) and Data Plane (`podd` on `:49983`).
    - Extends E2B with dual-block-volume persistence (`rootfs` + `workspace`), 1s CoW `fork`,
      stateless background command `cursor` logs, Server-Sent Events (SSE) command streaming,
      atomic `archive-sync` for Skills, zero-trust `cpodref_` egress secret bindings,
      signed HTTP/WS/WebRTC panel proxying, and pre-warmed Kata MicroVM pools (`/warmpools`).

paths:
  /health:
    get:
      summary: Health check of the Gateway and cluster state
      operationId: getHealth
      responses:
        "200":
          description: Gateway is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "ok"
                  service:
                    type: string
                    example: "cubepod-gateway"
                  sandboxes:
                    type: integer
                  autoResumeCount:
                    type: integer

  /openapi.yaml:
    get:
      summary: Retrieve the OpenAPI 3.1 specification for CubePod
      operationId: getOpenAPISpec
      responses:
        "200":
          description: OpenAPI specification file
          content:
            application/yaml:
              schema:
                type: string

  /sandboxes:
    post:
      summary: Create or claim a CubePod sandbox (E2B compatible + CubePod extensions)
      operationId: createSandbox
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateSandboxRequest"
      responses:
        "201":
          description: Sandbox created and ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxInfo"
        "409":
          description: Referenced template is not ready
        "503":
          description: Template ready command timed out (sandbox retained in Failed phase for log inspection)
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: integer
                    example: 503
                  message:
                    type: string
                    example: "template ready command timed out"
                  sandboxID:
                    type: string
                  logPath:
                    type: string
                    example: "/var/log/cubepod/start-cmd.log"
    get:
      summary: List all sandboxes with optional state and template filters
      operationId: listSandboxes
      parameters:
        - name: state
          in: query
          required: false
          schema:
            type: string
            enum: [Running, Paused, Provisioning, Failed, Terminated]
        - name: templateID
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: List of sandboxes
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/SandboxInfo"

  /sandboxes/{sandboxID}:
    get:
      summary: Get sandbox status and endpoints
      operationId: getSandbox
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Sandbox details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxInfo"
        "404":
          description: Sandbox not found
    delete:
      summary: Kill and delete sandbox
      operationId: deleteSandbox
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
        - name: deleteVolumes
          in: query
          description: Whether to delete persistent RootFS and Workspace PVCs
          schema:
            type: boolean
            default: false
      responses:
        "204":
          description: Sandbox deleted successfully

  /sandboxes/{sandboxID}/pause:
    post:
      summary: Suspend sandbox (flush filesystem, delete MicroVM pod, retain RootFS & Workspace Block PVCs)
      operationId: pauseSandbox
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Sandbox paused (0 CPU/RAM consumed)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxInfo"

  /sandboxes/{sandboxID}/connect:
    post:
      summary: Connect to sandbox, automatically resuming from Paused state if needed (E2B compatible)
      operationId: connectSandbox
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Sandbox is running and ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxInfo"

  /sandboxes/{sandboxID}/timeout:
    post:
      summary: Set sandbox idle timeout
      operationId: setTimeoutSandbox
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                timeoutSeconds:
                  type: integer
                  example: 1800
      responses:
        "200":
          description: Timeout updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxInfo"

  /sandboxes/{sandboxID}/refreshes:
    post:
      summary: Refresh sandbox idle timeout and keepalive lease (E2B standard)
      operationId: refreshSandbox
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                duration:
                  type: integer
                  description: Duration in seconds to extend the sandbox timeout
                  example: 300
      responses:
        "204":
          description: Sandbox timeout refreshed successfully
        "200":
          description: Sandbox timeout refreshed
        "404":
          description: Sandbox not found

  /sandboxes/{sandboxID}/lease:
    post:
      summary: Acquire or renew lease protection against automatic idle pausing
      operationId: setLeaseSandbox
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                leaseSeconds:
                  type: integer
                  example: 300
      responses:
        "200":
          description: Lease renewed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxInfo"

  /sandboxes/{sandboxID}/reset-rootfs:
    post:
      summary: Reset the persistent RootFS Upper PVC to a clean Base Image state while preserving /workspace
      operationId: resetRootfs
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: RootFS reset complete, /workspace intact
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxInfo"

  /sandboxes/{sandboxID}/fork:
    post:
      summary: Clone an existing sandbox in ~1s via CSI Copy-on-Write block snapshots
      operationId: forkSandbox
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                volumeKey:
                  type: string
                metadata:
                  type: object
                  additionalProperties:
                    type: string
      responses:
        "201":
          description: Forked sandbox ready
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SandboxInfo"

  /sandboxes/{sandboxID}/commands:
    post:
      summary: Execute command in sandbox (E2B standard route alias for /commands/run)
      operationId: runCommandStandard
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RunCommandRequest"
      responses:
        "200":
          description: Command execution result or background process metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CommandResponse"

  /sandboxes/{sandboxID}/commands/run:
    post:
      summary: Execute command in sandbox (foreground synchronous or background asynchronous)
      operationId: runCommand
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RunCommandRequest"
      responses:
        "200":
          description: Command execution result or background process metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CommandResponse"

  /sandboxes/{sandboxID}/code/execute:
    post:
      summary: Execute Python code via in-guest Jupyter code interpreter on port 49999 (e2b-code-interpreter)
      operationId: executeCode
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CodeExecuteRequest"
      responses:
        "200":
          description: Streaming NDJSON execution chunks (stdout, stderr, results, errors)
          content:
            application/x-ndjson:
              schema:
                type: string
            application/json:
              schema:
                $ref: "#/components/schemas/CodeExecuteResponse"
        "404":
          description: Sandbox not found

  /sandboxes/{sandboxID}/commands/{cmdID}/logs:
    get:
      summary: Read incremental command logs via stateless byte-offset cursor
      operationId: getCommandLogs
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
        - name: cmdID
          in: path
          required: true
          schema:
            type: string
        - name: cursor
          in: query
          required: false
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: Command log slice
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CommandLogsResponse"

  /sandboxes/{sandboxID}/commands/{cmdID}/status:
    get:
      summary: Inspect background command process status, PID, exit code, and log cursor
      operationId: getCommandStatus
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
        - name: cmdID
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Command status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CommandStatusResponse"
        "404":
          description: Command not found

  /sandboxes/{sandboxID}/commands/{cmdID}/stream:
    get:
      summary: Stream incremental command logs over Server-Sent Events (SSE text/event-stream) until completion
      operationId: streamCommandLogs
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
        - name: cmdID
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: SSE event stream of command log frames
          content:
            text/event-stream:
              schema:
                type: string
                example: "data: {\"id\":\"cmd-1\",\"content\":\"hello\\n\",\"cursor\":6,\"running\":false,\"exit_code\":0}\n\n"

  /sandboxes/{sandboxID}/commands/{cmdID}/kill:
    post:
      summary: Send SIGKILL to the command process group (-pgid)
      operationId: killCommand
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
        - name: cmdID
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Command killed
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  killed:
                    type: boolean

  /sandboxes/{sandboxID}/files:
    get:
      summary: Read file content from sandbox
      operationId: getFile
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
        - name: path
          in: query
          required: true
          schema:
            type: string
        - name: format
          in: query
          required: false
          schema:
            type: string
            enum: [raw, json]
      responses:
        "200":
          description: File content
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
            application/json:
              schema:
                type: object
                properties:
                  path:
                    type: string
                  content:
                    type: string
                  size:
                    type: integer
        "404":
          description: File not found
    put:
      summary: Upload file to sandbox with optional UID/GID and mode settings
      operationId: putFile
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
        - name: path
          in: query
          required: false
          schema:
            type: string
        - name: uid
          in: query
          required: false
          schema:
            type: integer
            default: 1000
        - name: gid
          in: query
          required: false
          schema:
            type: integer
            default: 1000
        - name: mode
          in: query
          required: false
          schema:
            type: string
            default: "0644"
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
          application/json:
            schema:
              type: object
              properties:
                path:
                  type: string
                content:
                  type: string
      responses:
        "200":
          description: File uploaded successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  path:
                    type: string
                  size:
                    type: integer
    delete:
      summary: Delete file or directory from sandbox workspace
      operationId: deleteFile
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
        - name: path
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: File deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  deleted:
                    type: string
        "404":
          description: File not found

  /sandboxes/{sandboxID}/files/list:
    get:
      summary: List files and directories in sandbox workspace
      operationId: listFiles
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
        - name: path
          in: query
          required: false
          schema:
            type: string
            default: "/workspace"
      responses:
        "200":
          description: Directory entries
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FileListResponse"
        "404":
          description: Directory not found

  /sandboxes/{sandboxID}/files/archive-sync:
    post:
      summary: Synchronize skill archives (tar.gz) into sandbox memory/filesystem
      operationId: archiveSync
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
        - name: destination
          in: query
          required: false
          schema:
            type: string
            default: "/workspace"
      requestBody:
        required: true
        content:
          application/x-tar:
            schema:
              type: string
              format: binary
          application/gzip:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: Archive unpacked
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                  extracted:
                    type: integer

  /sandboxes/{sandboxID}/desktop/start:
    post:
      summary: Lazily start interactive desktop, browser (CDP), and web terminal stack (CEP-0021)
      operationId: startDesktop
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DesktopStartRequest"
      responses:
        "200":
          description: Desktop stack running with signed /panel/{token}/ URLs
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DesktopStartResponse"
        "400":
          description: Invalid resolution format or malformed request body
        "404":
          description: Sandbox not found
        "422":
          description: Sandbox image does not include cubepod-desktop stack
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DesktopNotInstalledError"

  /sandboxes/{sandboxID}/desktop/status:
    get:
      summary: Query interactive desktop stack status and daemon PIDs (CEP-0021)
      operationId: getDesktopStatus
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Desktop stack status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DesktopStatusResponse"
        "404":
          description: Sandbox not found
        "422":
          description: Sandbox image does not include cubepod-desktop stack
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DesktopNotInstalledError"

  /sandboxes/{sandboxID}/desktop/stop:
    post:
      summary: Stop interactive desktop, browser, and web terminal daemons (CEP-0021)
      operationId: stopDesktop
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Desktop stack stopped
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DesktopStatusResponse"
        "404":
          description: Sandbox not found
        "422":
          description: Sandbox image does not include cubepod-desktop stack
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DesktopNotInstalledError"

  /sandboxes/{sandboxID}/endpoints/{port}:
    get:
      summary: Get public ingress host/URL for sandbox port (E2B get_host compatible)
      operationId: getEndpointHost
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
        - name: port
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Host domain or public endpoint URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  host:
                    type: string
                    example: "8080-cpod-12345.cubepod.local"
                  url:
                    type: string
                    example: "http://8080-cpod-12345.cubepod.local"
                  endpoint:
                    type: string
                  port:
                    type: integer
                  sandboxID:
                    type: string
    post:
      summary: Mint HMAC-SHA256 signed panel proxy token for interactive web/VNC/HTTP ports
      operationId: mintEndpointToken
      parameters:
        - name: sandboxID
          in: path
          required: true
          schema:
            type: string
        - name: port
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                expiresInSeconds:
                  type: integer
                  default: 3600
                targetUrl:
                  type: string
      responses:
        "200":
          description: Signed proxy URL and token
          content:
            application/json:
              schema:
                type: object
                properties:
                  url:
                    type: string
                  token:
                    type: string
                  expiresAt:
                    type: string
                    format: date-time

  /panel/{token}:
    get:
      summary: Reverse-proxy HTTP/WebSocket/WebRTC traffic into sandbox port using signed token
      operationId: proxyPanel
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Proxy response from sandbox port

  /internal/v1/egress/exchange:
    post:
      summary: Swap zero-trust cpodref_ reference tokens for real external API credentials
      operationId: egressExchange
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                ref:
                  type: string
                host:
                  type: string
      responses:
        "200":
          description: Real secret returned
          content:
            application/json:
              schema:
                type: object
                properties:
                  value:
                    type: string

  /warmpools:
    post:
      summary: Register and scale a pre-warmed Kata MicroVM standby pool
      operationId: createWarmPool
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWarmPoolRequest"
      responses:
        "201":
          description: Warm pool reconciled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WarmPoolInfo"
    get:
      summary: List all active warm standby pools and total ready count
      operationId: listWarmPools
      responses:
        "200":
          description: Warm pool status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WarmPoolsListResponse"

  /templates:
    get:
      summary: List available sandbox templates (JSON array of E2BTemplate when E2BTemplateAPI gate is enabled; legacy wrapper when off)
      operationId: listTemplates
      parameters:
        - name: teamID
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: Available sandbox templates
          content:
            application/json:
              schema:
                oneOf:
                  - type: array
                    items:
                      $ref: "#/components/schemas/E2BTemplate"
                  - type: object
                    properties:
                      templates:
                        type: array
                        items:
                          $ref: "#/components/schemas/TemplateInfo"
        "400":
          description: Template count exceeds v1 cap (5000)
    post:
      summary: Create or request rebuild of a template (alias of POST /v3/templates)
      operationId: createTemplateV1
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TemplateBuildRequestV3"
      responses:
        "202":
          description: Template created or new build queued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TemplateRequestBuildResponse"
        "409":
          description: Build already in progress, slug collision, or idempotency mismatch

  /v2/templates:
    get:
      summary: List templates with cursor pagination (X-Next-Token response header)
      operationId: listTemplatesV2
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
        - name: nextToken
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Paginated E2B templates
          headers:
            X-Next-Token:
              schema:
                type: string
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/E2BTemplate"
    post:
      summary: Create or request rebuild of a template (v2 alias of POST /v3/templates)
      operationId: createTemplateV2
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TemplateBuildRequestV3"
      responses:
        "202":
          description: Template created or new build queued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TemplateRequestBuildResponse"

  /v3/templates:
    post:
      summary: Request template creation or rebuild (E2B v3)
      operationId: requestTemplateBuildV3
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TemplateBuildRequestV3"
      responses:
        "202":
          description: Template created or new build queued
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TemplateRequestBuildResponse"
        "409":
          description: Build already in progress, alias/slug collision, or idempotency mismatch

  /templates/{templateID}:
    get:
      summary: Get a template along with its builds
      operationId: getTemplate
      parameters:
        - name: templateID
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Template with builds
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TemplateWithBuilds"
        "404":
          description: Template not found
    patch:
      summary: Update template metadata (e.g. public visibility)
      operationId: updateTemplate
      parameters:
        - name: templateID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                public:
                  type: boolean
      responses:
        "200":
          description: Template updated
    delete:
      summary: Delete a template (returns 409 if referenced or building unless force=true)
      operationId: deleteTemplate
      parameters:
        - name: templateID
          in: path
          required: true
          schema:
            type: string
        - name: force
          in: query
          schema:
            type: boolean
            default: false
      responses:
        "204":
          description: Template deleted
        "404":
          description: Template not found
        "409":
          description: Template is currently building or referenced by active PodBox/PodWarmPool

  /v2/templates/{templateID}:
    patch:
      summary: Update template visibility and return template names
      operationId: updateTemplateV2
      parameters:
        - name: templateID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                public:
                  type: boolean
      responses:
        "200":
          description: Template updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  names:
                    type: array
                    items:
                      type: string

  /templates/aliases/{alias}:
    get:
      summary: Resolve a template by alias or tag
      operationId: getTemplateByAlias
      parameters:
        - name: alias
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Resolved template
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/E2BTemplate"
        "404":
          description: Alias not found

  /templates/{templateID}/files/{hash}:
    get:
      summary: Check build context cache presence and obtain upload URL
      operationId: getTemplateFileUploadLink
      parameters:
        - name: templateID
          in: path
          required: true
          schema:
            type: string
        - name: hash
          in: path
          required: true
          schema:
            type: string
            pattern: "^[0-9a-f]{64}$"
      responses:
        "201":
          description: Upload metadata returned
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TemplateBuildFileUpload"
        "400":
          description: Invalid hash format
        "404":
          description: Template not found
        "501":
          description: Template build is not enabled
    put:
      summary: Upload tar or gzip-compressed tar build context for filesHash
      operationId: uploadTemplateFileContext
      parameters:
        - name: templateID
          in: path
          required: true
          schema:
            type: string
        - name: hash
          in: path
          required: true
          schema:
            type: string
            pattern: "^[0-9a-f]{64}$"
      responses:
        "201":
          description: Build context stored
        "400":
          description: Invalid tar stream or path traversal
        "409":
          description: Different content already stored for hash
        "413":
          description: Upload exceeds configured context limit
        "501":
          description: Template build is not enabled

  /v2/templates/{templateID}/builds/{buildID}:
    post:
      summary: Start an image or recipe build for a template build ID
      operationId: startTemplateBuildV2
      parameters:
        - name: templateID
          in: path
          required: true
          schema:
            type: string
        - name: buildID
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TemplateBuildStartV2"
      responses:
        "202":
          description: Build accepted
        "400":
          description: Invalid build specification or missing context tar
        "404":
          description: Template or build not found
        "409":
          description: Build state conflict
        "501":
          description: Template build is not enabled

  /templates/{templateID}/builds/{buildID}/status:
    get:
      summary: Poll build status and incremental logs
      operationId: getTemplateBuildStatus
      parameters:
        - name: templateID
          in: path
          required: true
          schema:
            type: string
        - name: buildID
          in: path
          required: true
          schema:
            type: string
        - name: logsOffset
          in: query
          schema:
            type: integer
            default: 0
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
            maximum: 100
        - name: level
          in: query
          schema:
            type: string
            enum: [debug, info, warn, error]
      responses:
        "200":
          description: Current build status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TemplateBuildInfo"
        "404":
          description: Template or build not found

  /templates/{templateID}/builds/{buildID}/logs:
    get:
      summary: Read template build log entries
      operationId: getTemplateBuildLogs
      parameters:
        - name: templateID
          in: path
          required: true
          schema:
            type: string
        - name: buildID
          in: path
          required: true
          schema:
            type: string
        - name: cursor
          in: query
          schema:
            type: integer
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
            maximum: 100
        - name: direction
          in: query
          schema:
            type: string
            enum: [forward, backward]
        - name: level
          in: query
          schema:
            type: string
            enum: [debug, info, warn, error]
      responses:
        "200":
          description: Build logs
          content:
            application/json:
              schema:
                type: object
                properties:
                  logs:
                    type: array
                    items:
                      $ref: "#/components/schemas/BuildLogEntry"
        "501":
          description: Template build is not enabled

  /templates/tags:
    post:
      summary: Assign tags to a template build
      operationId: assignTemplateTags
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [target, tags]
              properties:
                target:
                  type: string
                tags:
                  type: array
                  items:
                    type: string
      responses:
        "201":
          description: Tags assigned
    delete:
      summary: Remove tags from a template
      operationId: deleteTemplateTags
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, tags]
              properties:
                name:
                  type: string
                tags:
                  type: array
                  items:
                    type: string
      responses:
        "204":
          description: Tags deleted

  /templates/{templateID}/tags:
    get:
      summary: List assigned tags for a template
      operationId: getTemplateTags
      parameters:
        - name: templateID
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Template tags
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/TemplateTagInfo"

components:
  schemas:
    CreateSandboxRequest:
      type: object
      properties:
        templateID:
          type: string
          default: "cubeplex-ubuntu24-desktop"
        template_id:
          type: string
          description: E2B canonical alias for templateID
        timeout:
          type: integer
          default: 1800
        timeoutSeconds:
          type: integer
          description: E2B canonical alias for timeout
        metadata:
          type: object
          additionalProperties:
            type: string
        envVars:
          type: object
          additionalProperties:
            type: string
        env_vars:
          type: object
          description: E2B canonical alias for envVars
          additionalProperties:
            type: string
        autoPause:
          type: boolean
          default: true
        auto_pause:
          type: boolean
          default: true
        autoResume:
          type: boolean
          default: true
        auto_resume:
          type: boolean
          default: true
        extension:
          type: object
          description: CubePod enterprise extensions
          properties:
            volumeKey:
              type: string
              description: Deterministic persistent storage key (e.g. ws-01-user-99)
            rootfsSize:
              type: string
              default: "10Gi"
            workspaceSize:
              type: string
              default: "20Gi"
            networkPolicy:
              type: object
              properties:
                defaultAction:
                  type: string
                  enum: [Allow, Deny]
                allowHosts:
                  type: array
                  items:
                    type: string
                upstreamProxy:
                  type: string
                secretBindings:
                  type: array
                  items:
                    type: object
                    properties:
                      envName:
                        type: string
                      placeholder:
                        type: string
                      allowedHosts:
                        type: array
                        items:
                          type: string

    SandboxInfo:
      type: object
      required:
        - sandboxID
        - templateID
        - state
      properties:
        sandboxID:
          type: string
        templateID:
          type: string
        clientID:
          type: string
          example: "cpod-client-01"
        envdVersion:
          type: string
          example: "1.1.0"
        state:
          type: string
          enum: [Running, Paused, Provisioning, Pausing, Resuming, Failed, Terminated]
        startedAt:
          type: string
          format: date-time
        endAt:
          type: string
          format: date-time
        cpuCount:
          type: integer
          example: 2
        memoryMB:
          type: integer
          example: 2048
        diskSizeMB:
          type: integer
          example: 10240
        envdPort:
          type: integer
          default: 49983
        poddURL:
          type: string
        rootfsGeneration:
          type: integer
          default: 1
        volumeKey:
          type: string
        metadata:
          type: object
          additionalProperties:
            type: string
        createdAt:
          type: string
          format: date-time
        timeoutSeconds:
          type: integer
        leaseSeconds:
          type: integer
        autoResumeCount:
          type: integer

    CodeExecuteRequest:
      type: object
      required:
        - code
      properties:
        code:
          type: string
          description: Python code snippet to execute
          example: "import matplotlib.pyplot as plt\nprint('hello from code interpreter')"
        context_id:
          type: string
          nullable: true
        language:
          type: string
          default: "python"
        env_vars:
          type: object
          additionalProperties:
            type: string

    CodeExecuteResponse:
      type: object
      description: Event chunk emitted in NDJSON stream
      properties:
        type:
          type: string
          enum: [stdout, stderr, result, error, number_of_executions]
        text:
          type: string
        png:
          type: string
          description: Base64-encoded PNG image data
        html:
          type: string
        name:
          type: string
        value:
          type: string
        traceback:
          type: string
        execution_count:
          type: integer
        timestamp:
          type: integer

    RunCommandRequest:
      type: object
      required:
        - command
      properties:
        command:
          type: string
          example: "echo hello"
        cmd:
          type: string
        workdir:
          type: string
          default: "/workspace"
        envs:
          type: object
          additionalProperties:
            type: string
        background:
          type: boolean
          default: false

    CommandResponse:
      type: object
      properties:
        id:
          type: string
        cmdID:
          type: string
        running:
          type: boolean
        pid:
          type: integer
        exitCode:
          type: integer
        stdout:
          type: string
        stderr:
          type: string

    CommandLogsResponse:
      type: object
      properties:
        id:
          type: string
        cmdID:
          type: string
        content:
          type: string
        cursor:
          type: integer
        running:
          type: boolean
        exitCode:
          type: integer

    CommandStatusResponse:
      type: object
      properties:
        id:
          type: string
        cmdID:
          type: string
        pid:
          type: integer
        running:
          type: boolean
        exit_code:
          type: integer
        exitCode:
          type: integer
        cursor:
          type: integer

    FileListResponse:
      type: object
      properties:
        path:
          type: string
        entries:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              path:
                type: string
              is_dir:
                type: boolean
              size:
                type: integer
              mode:
                type: string

    CreateWarmPoolRequest:
      type: object
      properties:
        name:
          type: string
          default: "default-pool"
        templateRef:
          type: string
          default: "cubeplex-ubuntu24-desktop"
        replicas:
          type: integer
          default: 2

    WarmPoolInfo:
      type: object
      properties:
        name:
          type: string
        poolName:
          type: string
        templateRef:
          type: string
        replicas:
          type: integer
        readyReplicas:
          type: integer
        availableReplicas:
          type: integer

    WarmPoolsListResponse:
      type: object
      properties:
        warmPools:
          type: array
          items:
            $ref: "#/components/schemas/WarmPoolInfo"
        warmPoolReadyCount:
          type: integer
        totalReady:
          type: integer

    TemplateInfo:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        image:
          type: string
        description:
          type: string

    TemplateBuildRequestV3:
      type: object
      properties:
        name:
          type: string
        alias:
          type: string
        tags:
          type: array
          items:
            type: string
        cpuCount:
          type: integer
        memoryMB:
          type: integer
        minFreeDiskMb:
          type: integer

    TemplateRequestBuildResponse:
      type: object
      required: [templateID, buildID, public, aliases, names, tags]
      properties:
        templateID:
          type: string
        buildID:
          type: string
        public:
          type: boolean
        aliases:
          type: array
          items:
            type: string
        names:
          type: array
          items:
            type: string
        tags:
          type: array
          items:
            type: string

    TemplateBuildStep:
      type: object
      required: [type, args]
      properties:
        type:
          type: string
          enum: [RUN, COPY, ADD, WORKDIR, USER, ENV]
        args:
          type: array
          items:
            type: string
        filesHash:
          type: string
        force:
          type: boolean

    TemplateBuildStartV2:
      type: object
      properties:
        fromImage:
          type: string
        fromTemplate:
          type: string
        fromImageRegistry:
          type: object
          properties:
            type:
              type: string
            username:
              type: string
            password:
              type: string
        force:
          type: boolean
        startCmd:
          type: string
        readyCmd:
          type: string
        steps:
          type: array
          items:
            $ref: "#/components/schemas/TemplateBuildStep"

    BuildLogEntry:
      type: object
      required: [timestamp, level, message]
      properties:
        timestamp:
          type: string
          format: date-time
        level:
          type: string
          enum: [debug, info, warn, error]
        message:
          type: string

    BuildStatusReason:
      type: object
      required: [message]
      properties:
        message:
          type: string
        step:
          type: string
        logEntries:
          type: array
          items:
            $ref: "#/components/schemas/BuildLogEntry"

    TemplateBuildInfo:
      type: object
      required: [templateID, buildID, status, logs, logEntries]
      properties:
        templateID:
          type: string
        buildID:
          type: string
        status:
          type: string
          enum: [waiting, building, ready, error]
        reason:
          $ref: "#/components/schemas/BuildStatusReason"
        logs:
          type: array
          items:
            type: string
        logEntries:
          type: array
          items:
            $ref: "#/components/schemas/BuildLogEntry"

    TemplateBuildFileUpload:
      type: object
      required: [present]
      properties:
        present:
          type: boolean
        url:
          type: string
        headers:
          type: object
          additionalProperties:
            type: string

    E2BTemplateBuildSummary:
      type: object
      required: [buildID, status, createdAt, updatedAt, cpuCount, memoryMB]
      properties:
        buildID:
          type: string
        templateID:
          type: string
        status:
          type: string
          enum: [waiting, building, ready, error]
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        cpuCount:
          type: integer
        memoryMB:
          type: integer
        reason:
          $ref: "#/components/schemas/BuildStatusReason"

    E2BTemplate:
      type: object
      required:
        - templateID
        - buildID
        - cpuCount
        - memoryMB
        - diskSizeMB
        - public
        - aliases
        - names
        - createdAt
        - updatedAt
        - createdBy
        - lastSpawnedAt
        - spawnCount
        - buildCount
        - envdVersion
        - buildStatus
      properties:
        templateID:
          type: string
        buildID:
          type: string
        cpuCount:
          type: integer
        memoryMB:
          type: integer
        diskSizeMB:
          type: integer
        public:
          type: boolean
        aliases:
          type: array
          items:
            type: string
        names:
          type: array
          items:
            type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        createdBy:
          nullable: true
        lastSpawnedAt:
          type: string
          format: date-time
          nullable: true
        spawnCount:
          type: integer
        buildCount:
          type: integer
        envdVersion:
          type: string
        buildStatus:
          type: string
          enum: [waiting, building, ready, error]

    TemplateWithBuilds:
      allOf:
        - $ref: "#/components/schemas/E2BTemplate"
        - type: object
          required: [builds]
          properties:
            builds:
              type: array
              items:
                $ref: "#/components/schemas/E2BTemplateBuildSummary"

    TemplateTagInfo:
      type: object
      required: [tag, buildID]
      properties:
        tag:
          type: string
        buildID:
          type: string

    DesktopStartRequest:
      type: object
      properties:
        resolution:
          type: string
          pattern: "^[0-9]{3,5}x[0-9]{3,5}x(16|24|32)$"
          example: "1280x800x24"
        initialUrl:
          type: string
          example: "about:blank"
        expiresInSeconds:
          type: integer
          default: 3600

    DesktopStartResponse:
      type: object
      required: [status, desktopUrl, terminalUrl, cdpUrl, expiresAt]
      properties:
        status:
          type: string
          example: "running"
        desktopUrl:
          type: string
          example: "/panel/<token>/vnc.html?autoconnect=true&resize=scale"
        terminalUrl:
          type: string
          example: "/panel/<token>/"
        cdpUrl:
          type: string
          example: "/panel/<token>/"
        expiresAt:
          type: string
          format: date-time

    DesktopStatusResponse:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [running, degraded, stopped]
        display:
          type: string
          example: ":99"
        resolution:
          type: string
          example: "1280x800x24"
        ports:
          type: object
          properties:
            desktop:
              type: integer
              example: 8080
            terminal:
              type: integer
              example: 7681
            cdp:
              type: integer
              example: 9222
        pids:
          type: object
          additionalProperties:
            type: integer

    DesktopNotInstalledError:
      type: object
      required: [error, message]
      properties:
        error:
          type: string
          example: "desktop_not_installed"
        message:
          type: string
          example: "sandbox image does not include cubepod-desktop stack"
