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

# Run a shell command

> Runs `cmd` inside the sandbox via a shell, so pipes, `&&`, redirects, and
installed CLI tools all work. Sandbox must be in status `ready`.

Pass `stream: true` to receive `stdout` / `stderr` as they arrive. The
response then uses `Content-Type: text/event-stream` with one JSON
object per `data:` line; the final event is `{ "type": "done", ... }`.
See `ExecStreamFrame` for the event shape.




## OpenAPI

````yaml /api-reference/sandboxes.openapi.yaml post /sandboxes/{id}/exec
openapi: 3.0.3
info:
  title: Brimble Sandbox API
  description: >
    REST API for managing Brimble sandboxes, ephemeral compute environments with

    optional persistent storage. Covers lifecycle (create / pause / resume /
    destroy),

    egress policy, runtime operations (exec, runCode, file upload/download),

    observability (logs, stats), and snapshots.


    ## Conventions


    - **Response envelope:** non-`204` JSON responses use `{ "message": string,
    "data"?: <payload> }`.
      The schemas below describe the **`data`** payload only.
    - **Errors:** every non-2xx response uses `{ "message": string }`. The
    message is
      user-facing.
    - **Async transitions:** pause / resume / destroy return immediately;
      the status change is visible on the next `GET` or in the dashboard.
      **Create is synchronous** — `POST /sandboxes` blocks until `status` is `ready`.
    - **IDs:** every `id` is a 24-char hex string.
  version: 1.0.0
  contact:
    name: Brimble Engineering
    url: https://brimble.io
servers:
  - url: https://sandbox.brimble.io
    description: Production
security:
  - brimbleKey: []
tags:
  - name: Sandboxes
    description: Lifecycle and metadata
  - name: Runtime
    description: Exec, code, files
  - name: Observability
    description: Logs and stats
  - name: Snapshots
    description: Manual & automatic snapshots
  - name: Volumes
    description: >-
      Persistent disks, pre-provisioned independently of sandbox lifecycle, then
      attached to a sandbox or project
paths:
  /sandboxes/{id}/exec:
    post:
      tags:
        - Runtime
      summary: Run a shell command
      description: >
        Runs `cmd` inside the sandbox via a shell, so pipes, `&&`, redirects,
        and

        installed CLI tools all work. Sandbox must be in status `ready`.


        Pass `stream: true` to receive `stdout` / `stderr` as they arrive. The

        response then uses `Content-Type: text/event-stream` with one JSON

        object per `data:` line; the final event is `{ "type": "done", ... }`.

        See `ExecStreamFrame` for the event shape.
      operationId: execCommand
      parameters:
        - $ref: '#/components/parameters/SandboxIdParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ExecInput'
      responses:
        '200':
          description: |
            Command completed.

            When the request body has `stream: true`, the response is
            `text/event-stream`, one `ExecStreamFrame` per `data:` event, the
            last being a `done` event.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExecResultEnvelope'
            text/event-stream:
              schema:
                type: string
                description: SSE stream of `ExecStreamFrame` JSON objects.
              example: |
                : open

                data: {"type":"stdout","data":"hello\n"}

                data: {"type":"stderr","data":"warn\n"}

                data: {"type":"done","exit_code":0,"duration_ms":142}
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  parameters:
    SandboxIdParam:
      in: path
      name: id
      required: true
      schema:
        type: string
        pattern: ^[a-f0-9]{24}$
      description: 24-char hex id of the sandbox.
  schemas:
    ExecInput:
      type: object
      required:
        - cmd
      properties:
        cmd:
          type: string
          description: Shell command to run. Pipes / `&&` / redirects all work.
        timeout_seconds:
          type: integer
          minimum: 1
          maximum: 300
          description: Defaults to 30. Process is killed at the limit.
        cwd:
          type: string
          description: Absolute path inside the sandbox. Defaults to `/`.
        env:
          type: object
          additionalProperties:
            type: string
          description: >
            Extra environment variables for this command only. Layered on top of

            the sandbox's existing environment; same-named keys here override
            the

            sandbox-level ones for this call. Values must be strings. Scoped to
            a

            single invocation, the next call starts with the sandbox defaults

            again.
          example:
            NODE_ENV: production
            DATABASE_URL: postgres://...
        stream:
          type: boolean
          description: >
            When true, the server keeps the connection open and streams

            stdout/stderr as Server-Sent Events (`text/event-stream`). Each
            event

            is a JSON object on a `data:` line; the last event is always

            `{ "type": "done", ... }` or `{ "type": "error", ... }`.
    ExecResultEnvelope:
      type: object
      required:
        - message
        - data
      properties:
        message:
          type: string
          example: Exec completed
        data:
          $ref: '#/components/schemas/ExecResult'
    ExecResult:
      type: object
      required:
        - stdout
        - stderr
        - exit_code
        - duration_ms
      properties:
        stdout:
          type: string
        stderr:
          type: string
        exit_code:
          type: integer
        duration_ms:
          type: integer
    ErrorResponse:
      type: object
      required:
        - message
      properties:
        message:
          type: string
          description: Human-readable, user-facing error reason.
  responses:
    BadRequest:
      description: Validation error / invalid state transition
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            invalidId:
              summary: Invalid sandbox id
              value:
                message: Invalid sandbox id
            statusTransition:
              summary: Wrong status
              value:
                message: Sandbox is paused; only ready sandboxes can be paused
            fileNotDir:
              summary: Parent dir missing on upload
              value:
                message: 'Destination directory does not exist: /work'
            duplicateVolumeName:
              summary: Duplicate volume name
              value:
                message: A volume named "node-cache" already exists
            volumeAttached:
              summary: Delete attempted on attached volume
              value:
                message: Volume is attached; detach it before deleting
    NotFound:
      description: >-
        Sandbox or related resource not found (also returned when owned by
        another user)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            sandboxNotFound:
              value:
                message: Sandbox not found
            snapshotNotFound:
              value:
                message: Snapshot not found
            volumeNotFound:
              value:
                message: Volume not found
  securitySchemes:
    brimbleKey:
      type: apiKey
      in: header
      name: x-brimble-key
      description: |
        Your account-level Brimble API key. Find it in the dashboard under
        your profile drawer → **API key** (click the avatar in the sidebar).
        Available on paid plans only.

````