> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gmicloud.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Control the sandbox shell (resize / close)

> Out-of-band control channel for the shell. Control commands never travel
over the WebSocket; this is a plain HTTP call and works from any replica.

- `action=resize` applies a new terminal size to the live PTY. It
  **requires an active attach** (otherwise `409 Shell.NotAttached`).
- `action=close` **irrevocably terminates** the shell: the PTY is killed,
  the state is deleted, and any existing WebSocket is severed within
  about one second. The old shell can never be re-attached; the next
  `POST /shell` creates a brand-new session. Close does **not** require
  an active connection.

Unknown `action` values or extra fields are rejected with
`400 Shell.InvalidRequest`.




## OpenAPI

````yaml /api-spec/sandbox_api.yaml post /shell/control
openapi: 3.0.3
info:
  title: GMI Sandbox API
  description: >-
    REST API for GMI Sandbox: create isolated execution environments from
    templates or durable snapshots, manage their lifecycle on the control plane,
    and run commands or transfer files through each sandbox's own data plane.
  contact:
    name: GMI Cloud Support
    email: support@gmicloud.ai
  version: '2.0'
servers:
  - url: https://console.gmicloud.ai/api/v2
    description: Control plane
security:
  - bearerAuth: []
tags:
  - name: Sandbox
    description: |
      Sandbox instance lifecycle management (Sandbox v2, /api/v2/sandboxes)
  - name: SandboxSnapshot
    description: Durable sandbox snapshot management (Sandbox v2, `/api/v2/snapshots`)
  - name: SandboxTemplate
    description: Sandbox template management (Sandbox v2, `/api/v2/templates`)
  - name: Sandbox-Exec
    description: >
      Sandbox execution data plane (accessed via the sandbox host and sandbox
      access token). These endpoints are not under the control plane's `/api/v2`
      path; clients connect directly to `https://{sandbox_key}.{domain}` and
      authenticate with `X-Access-Token`.
  - name: Sandbox-Shell
    description: >
      Sandbox interactive terminal (PTY) data plane. **These endpoints are not
      under `/api/v2`**: clients connect directly to
      `https://{sandbox_key}.{domain}` and authenticate with `X-Access-Token`.
      `{sandbox_key}`, `{domain}` and the `X-Access-Token` value are the
      `sandbox_key`, `domain` and `sandbox_access_token` returned by the
      control-plane create/connect endpoints; no extra endpoint is needed.


      Each sandbox has exactly **one logical shell session** and at most **one
      active WebSocket connection** at a time (a second concurrent attach is
      refused at handshake with `409`). Disconnecting does not destroy the shell
      — re-attaching resumes the same session; only `POST /shell/control` with
      `action=close` destroys it.
  - name: Sandbox-Files
    description: >
      Sandbox file data plane (`/files`). **These endpoints are not under
      `/api/v2`** and do not use `Authorization: Bearer`: they are served by the
      sandbox's own data-plane entry point — clients connect directly to
      `https://{sandbox_key}.{domain}` and authenticate with `X-Access-Token`.
      `{sandbox_key}` is the `sandbox_key` returned by the create/connect
      endpoints, used for data-plane host addressing. `{domain}` and
      `X-Access-Token` are the `domain` and `sandbox_access_token` from the same
      responses — no extra endpoint is needed to obtain them.


      This split is not historical baggage but the nature of a data plane: file
      transfer is long-lived, high-volume, and addressed per sandbox, differing
      from the control plane's short requests in both capacity model and failure
      domain, so they do not share an entry point.


      **One unified abstraction.** The same contract is served by two backend
      kinds (an in-sandbox file service / a data-center data-plane proxy);
      clients need not — and cannot — tell them apart. The cost is that the
      contract is their intersection: `Content-Length` on download and resumable
      download (`Range`/`206`) are **optional capabilities** that vary by data
      center, and clients must work correctly when they are absent. See the
      per-operation notes below.
paths:
  /shell/control:
    post:
      tags:
        - Sandbox-Shell
      summary: Control the sandbox shell (resize / close)
      description: >
        Out-of-band control channel for the shell. Control commands never travel

        over the WebSocket; this is a plain HTTP call and works from any
        replica.


        - `action=resize` applies a new terminal size to the live PTY. It
          **requires an active attach** (otherwise `409 Shell.NotAttached`).
        - `action=close` **irrevocably terminates** the shell: the PTY is
        killed,
          the state is deleted, and any existing WebSocket is severed within
          about one second. The old shell can never be re-attached; the next
          `POST /shell` creates a brand-new session. Close does **not** require
          an active connection.

        Unknown `action` values or extra fields are rejected with

        `400 Shell.InvalidRequest`.
      operationId: controlSandboxShell
      parameters:
        - $ref: '#/components/parameters/SandboxShellRequestIDParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SandboxShellControlRequest'
            examples:
              resize:
                summary: Resize the terminal
                value:
                  action: resize
                  cols: 120
                  rows: 32
              close:
                summary: Close the shell permanently
                value:
                  action: close
      responses:
        '200':
          description: |
            Control applied. `status=attached` after a successful resize;
            `status=closed` after a successful close.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxShellControlResponse'
              examples:
                resize:
                  summary: Resize applied
                  value:
                    request_id: req-7f3c
                    sandbox_key: sandbox-key
                    status: attached
                close:
                  summary: Shell closed
                  value:
                    request_id: req-7f3c
                    sandbox_key: sandbox-key
                    status: closed
        '400':
          $ref: '#/components/responses/SandboxShellBadRequest'
        '401':
          $ref: '#/components/responses/SandboxShellUnauthorized'
        '404':
          $ref: '#/components/responses/SandboxShellNotFound'
        '409':
          description: >
            State conflict, depending on the action:

            - `Shell.NotAttached` (resize): no active WebSocket connection —
              resize requires one.
            - `Shell.Creating` (close): a launch is still in progress and cannot
              be closed yet; retry shortly.
            - `Shell.Closing` (resize or close): the shell is already being torn
            down.

            - `Shell.RuntimeNotRunning`: the sandbox is not running.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxShellErrorResponse'
              example:
                code: Shell.NotAttached
                message: shell has no active connection
                request_id: req-7f3c
        '500':
          description: |
            `Shell.InternalError`: unexpected server-side failure.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxShellErrorResponse'
        '502':
          description: |
            Backend call failed, depending on the action:
            - `Shell.ResizeFailed` (resize): the new size could not be applied;
              the shell and connection stay alive, retry is safe.
            - `Shell.CloseFailed` (close): closing on the backend failed **but
              the shell state is already deleted — the shell is still gone**.
              Retrying cannot resurrect it; the next `POST /shell` starts a
              brand-new session.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxShellErrorResponse'
        '503':
          description: |
            Temporarily unavailable, one of `Shell.ProviderUnavailable` or
            `Shell.StoreUnavailable`. For `close` the shell state is kept in the
            closing state, so retrying the close is safe.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SandboxShellErrorResponse'
      security:
        - sandboxAccessToken: []
      servers:
        - url: https://{sandbox_key}.{domain}
          description: Sandbox data-plane host
          variables:
            sandbox_key:
              default: sandbox-key
              description: >-
                Data-plane addressing key (`sandbox_key` from the control-plane
                response).
            domain:
              default: sandbox.example.com
              description: Sandbox data-plane domain from the control-plane response.
components:
  parameters:
    SandboxShellRequestIDParam:
      name: X-Request-ID
      in: header
      required: false
      description: >
        Ignored. The server generates its own request ID (`req-<uuid>`) for
        every request and

        returns it in the `X-Request-ID` response header and the body's
        `request_id`.
      schema:
        type: string
        minLength: 1
        maxLength: 128
  schemas:
    SandboxShellControlRequest:
      type: object
      additionalProperties: false
      required:
        - action
      description: |
        Exactly one control command:
        - `action=resize` requires `cols` and `rows` (each 1..1000) and an
          active WebSocket attach.
        - `action=close` takes no other fields and does not require an active
          connection.
        Unknown `action` values or extra fields are rejected with
        `400 Shell.InvalidRequest`.
      properties:
        action:
          type: string
          enum:
            - resize
            - close
          description: |
            `resize` applies a new terminal size to the live PTY; `close`
            irrevocably terminates the shell (PTY killed, state deleted, any
            existing WebSocket severed within about one second).
        cols:
          type: integer
          minimum: 1
          maximum: 1000
          description: >-
            Terminal width in columns. Required when `action=resize`; must be
            omitted for `close`.
        rows:
          type: integer
          minimum: 1
          maximum: 1000
          description: >-
            Terminal height in rows. Required when `action=resize`; must be
            omitted for `close`.
    SandboxShellControlResponse:
      type: object
      required:
        - request_id
        - sandbox_key
        - status
      properties:
        request_id:
          type: string
          description: >-
            Server-generated request ID (`req-<uuid>`), identical to the
            `X-Request-ID` response header.
        sandbox_key:
          type: string
          description: >-
            Echo of the data-plane addressing key (i.e. `sandbox_key`, the
            leftmost label of the request host).
        status:
          $ref: '#/components/schemas/SandboxShellStatus'
    SandboxShellErrorResponse:
      type: object
      description: >-
        Data-plane error body, in the platform error envelope (without
        `trace_id`).
      required:
        - code
        - message
        - request_id
      properties:
        code:
          type: string
          description: >-
            Machine-readable error code (`Shell.*`; a 401 is always
            `Auth.Unauthorized`). The set of codes and their meanings are
            documented on each response.
          example: Shell.NotFound
        message:
          type: string
          description: Stable, sanitized human-readable error message.
        request_id:
          type: string
          description: >-
            Server-generated ID for this request (`req-<uuid>`), identical to
            the `X-Request-ID` response header. A caller-supplied `X-Request-ID`
            is not used.
          example: req-3f2b6c1e-8a0d-4f6b-9c3e-2d7a1b5e9f40
    SandboxShellStatus:
      type: string
      description: |
        Public status of the sandbox shell session.
        - `creating`: a launch is in progress (only appears with `202`).
        - `ready`: the PTY exists and can be attached.
        - `attached`: the shell exists and currently has an active WebSocket
          connection (a second attach would get `409`).
        - `closed`: the shell has been terminated by `action=close` and can
          never be re-attached.
      enum:
        - creating
        - ready
        - attached
        - closed
  responses:
    SandboxShellBadRequest:
      description: |
        Invalid request (`Shell.InvalidRequest`): the body contains unknown or
        extra fields, an unknown `action`, or out-of-bounds values
        (e.g. `cols`/`rows` outside 1..1000).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SandboxShellErrorResponse'
          example:
            code: Shell.InvalidRequest
            message: request body contains unknown field
            request_id: req-7f3c
    SandboxShellUnauthorized:
      description: >
        Missing or invalid `X-Access-Token`, as on the other data-plane
        endpoints. The body is always

        `{"code":"Auth.Unauthorized","message":"unauthorized","request_id":"..."}`;
        missing, malformed,

        expired, and wrong-sandbox tokens are indistinguishable.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SandboxShellErrorResponse'
          example:
            code: Auth.Unauthorized
            message: unauthorized
            request_id: req-3f2b6c1e-8a0d-4f6b-9c3e-2d7a1b5e9f40
    SandboxShellNotFound:
      description: |
        No shell session exists (`Shell.NotFound`): none has been launched yet —
        call `POST /shell` first — or the shell has already been closed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/SandboxShellErrorResponse'
          example:
            code: Shell.NotFound
            message: no shell session for this sandbox
            request_id: req-7f3c
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >
        API Key or Access Token, always sent as `Authorization: Bearer <token>`.
        The gateway recognizes the token type automatically.
    sandboxAccessToken:
      type: apiKey
      in: header
      name: X-Access-Token
      description: >
        Sandbox data-plane token — the `sandbox_access_token` returned by the
        create/connect endpoints. Used for `/files`, Sandbox-Exec, and other
        data-plane endpoints; the control plane (`/api/v2`) does not accept it,
        and conversely the control plane's `Authorization: Bearer` is invalid on
        the data plane.


        The token is bound to its sandbox: using sandbox A's token against
        sandbox B's host fails. It stays valid until the sandbox's lifetime
        ends; there is no separate rotation endpoint (`rotate_traffic_token`
        rotates the port traffic token `traffic_access_token` and does not
        affect this one).

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.