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

# Run a command

> Starts a command or reconnects to one with the same idempotency key. The response is newline-delimited JSON. Its first event is `started`. Output `data` and terminal repaint `data` are base64. The highest stdout and stderr `seq` values are reconnect cursors. `exit` is terminal. A `heartbeat` can appear after 30 seconds without another event.



## OpenAPI

````yaml /sailbox-openapi.json post /sailboxes/{sailbox_id}/exec
openapi: 3.1.0
info:
  title: Sail Sailbox API
  version: '2026-08-26'
  description: >-
    Create and operate Sailboxes over HTTP. Sailboxes are fast-booting virtual
    machines that you can pause, resume, and checkpoint.


    Run commands, stream files, manage lifecycle and networking, and inspect
    usage. Turning on SSH and building an image still require an SDK or the CLI.
    See the [HTTP API guide](/sailboxes-http-api).
servers:
  - url: https://sailbox-api.sailresearch.com/v1
security:
  - BearerAuth: []
tags:
  - name: Apps
    description: Groups that own Sailboxes. One app per workload.
  - name: Lifecycle
    description: >-
      A Sailbox's whole life: create it, watch it, stop and start it, terminate
      it.
  - name: Checkpoints
    description: Save a Sailbox to start from later, and copy a running one.
  - name: Exec
    description: Run commands and control their standard input and terminals.
  - name: Files
    description: Stream regular files into and out of a Sailbox.
  - name: Networking
    description: >-
      Publish ports, serve them on hostnames you own, and identify a Sailbox to
      other Sailboxes.
  - name: Secrets
    description: >-
      Organization values that HTTP policies can add to matching requests. Sail
      never returns a stored value.
  - name: HTTP policies
    description: Rules for HTTPS requests a Sailbox sends.
  - name: Volumes
    description: >-
      Shared storage that outlives any one Sailbox. Alpha: these endpoints can
      still change.
  - name: SSH access
    description: Certificates for connecting to a Sailbox with `ssh`.
  - name: Usage
    description: Resource usage and spend.
  - name: Identity
    description: Who your API key belongs to.
paths:
  /sailboxes/{sailbox_id}/exec:
    post:
      tags:
        - Exec
      summary: Run a command
      description: >-
        Starts a command or reconnects to one with the same idempotency key. The
        response is newline-delimited JSON. Its first event is `started`. Output
        `data` and terminal repaint `data` are base64. The highest stdout and
        stderr `seq` values are reconnect cursors. `exit` is terminal. A
        `heartbeat` can appear after 30 seconds without another event.
      operationId: execSailboxCommand
      parameters:
        - $ref: '#/components/parameters/SailboxId'
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            minLength: 1
            pattern: ^[A-Za-z0-9._~-]+$
            not:
              enum:
                - .
                - ..
          description: >-
            Exec id for retries and reconnects. It can contain only letters,
            digits, `-`, `.`, `_`, and `~`; it cannot equal `.` or `..`; and it
            must match `idempotency_key` when both are sent.
        - $ref: '#/components/parameters/OwnerOverrideReason'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SailboxExecRequest'
      responses:
        '200':
          description: Exec event stream.
          content:
            application/x-ndjson:
              schema:
                type: string
              example: |
                {"type":"started","exec_request_id":"exec_123"}
                {"type":"stdout","data":"aGkK","seq":1}
                {"type":"exit","status":"succeeded","return_code":0}
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/BodyTooLarge'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/ServerError'
        '503':
          $ref: '#/components/responses/Unavailable'
        '504':
          $ref: '#/components/responses/Timeout'
components:
  parameters:
    SailboxId:
      name: sailbox_id
      in: path
      required: true
      schema:
        type: string
      description: >-
        Id of the Sailbox, as returned by create. It is `sb_` followed by a
        UUID.
    OwnerOverrideReason:
      name: X-Sail-Owner-Override-Reason
      in: header
      required: false
      schema:
        type: string
        pattern: ^[ -~]*$
      description: >-
        Lets an organization admin operate a `private` Sailbox someone else
        created. Say why in plain text; the reason goes into your organization's
        audit log, and the operation is refused without it. Ignored on Sailboxes
        you could already operate.


        Pause, resume, sleep, scheduled wake, upgrade, and terminate accept an
        override. Attaching or clearing an HTTP policy accepts one too. Running
        commands and moving files accept one through an SDK or the CLI.
        Publishing and unpublishing ports, adding and removing custom domains,
        checkpointing, and creating from a checkpoint stay with the creator.
  schemas:
    SailboxExecRequest:
      type: object
      additionalProperties: false
      required:
        - command
      properties:
        command:
          oneOf:
            - type: string
              minLength: 1
            - type: array
              minItems: 1
              items:
                type: string
          description: >-
            Shell command string or argument array. Shell features apply only to
            a string.
        timeout:
          type: integer
          minimum: 1
          description: Command timeout in seconds.
        background:
          type: boolean
          default: false
          description: Detach a string command from standard input and output.
        cwd:
          type: string
          description: Working directory for a string command.
        open_stdin:
          type: boolean
          default: false
          description: Keep standard input open for writes.
        pty:
          type: boolean
          default: false
          description: Run the command in a pseudo-terminal. This implies `open_stdin`.
        term:
          type: string
          description: Initial `TERM` value. A PTY defaults to `xterm-256color`.
        cols:
          type: integer
          minimum: 0
          description: Initial PTY width. Zero uses the guest default.
        rows:
          type: integer
          minimum: 0
          description: Initial PTY height. Zero uses the guest default.
        env:
          type: object
          propertyNames:
            pattern: ^[A-Za-z_][A-Za-z0-9_]*$
          additionalProperties:
            type: string
          description: >-
            Environment values for the command. Names must match
            `[A-Za-z_][A-Za-z0-9_]*`. Names and values cannot contain a NUL
            character.
        user:
          type: string
          description: Guest user in the same form as the SDK `user` option.
        idempotency_key:
          type: string
          pattern: ^[A-Za-z0-9._~-]*$
          not:
            enum:
              - .
              - ..
          description: >-
            Stable exec id for a retry or stream reconnect. A supplied key can
            contain only letters, digits, `-`, `.`, `_`, and `~`, and it cannot
            equal `.` or `..`.
        stdout_resume_seq:
          type: integer
          format: int64
          minimum: 0
          description: Highest stdout sequence already received.
        stderr_resume_seq:
          type: integer
          format: int64
          minimum: 0
          description: Highest stderr sequence already received.
        forward_ports:
          type: boolean
          default: false
        forward_browser:
          type: boolean
          default: false
        forward_clipboard:
          type: boolean
          default: false
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/ErrorObject'
    ErrorObject:
      type: object
      required:
        - message
        - type
        - param
        - code
      properties:
        message:
          type: string
          description: Human-readable description of what went wrong.
        type:
          type: string
          description: >-
            Error category: `invalid_request_error`, `authentication_error`,
            `permission_error`, `billing_error`, `not_found_error`,
            `conflict_error`, `rate_limit_error`, `quota_exceeded_error`, or
            `server_error`.
        param:
          oneOf:
            - type: string
            - type: 'null'
          description: >-
            The request field the error refers to, when the error is about one
            field.
        code:
          oneOf:
            - type: string
            - type: integer
            - type: 'null'
          description: Stable short code for the error, when one applies.
        billing_url:
          type: string
          format: uri
          description: Page where you add credits. Sent with a 402.
      additionalProperties: true
  responses:
    BadRequest:
      description: The request was rejected. See the error message for the offending field.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              message: memory_limit_gib for size m must be between 8 and 128
              type: invalid_request_error
              param: null
              code: null
    Unauthorized:
      description: The API key is missing or invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              message: Invalid API key
              type: authentication_error
              param: null
              code: invalid_api_key
    PaymentRequired:
      description: >-
        The organization is out of credits. The error carries a `billing_url`
        pointing at the page where you add them. Retry once you have.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              message: >-
                Your API key has been disabled due to insufficient credits.
                Visit https://app.sailresearch.com/billing to add credits.
              type: billing_error
              param: null
              code: credits_exhausted
              billing_url: https://app.sailresearch.com/billing
    Forbidden:
      description: The API key is not allowed to perform this operation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              message: sailboxes require an organization-scoped API key
              type: permission_error
              param: null
              code: null
    NotFound:
      description: No Sailbox with that id exists in your organization.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              message: sailbox "sb_9c8f1e2a-3b4d-4f5a-8c7e-1d2f3a4b5c6d" not found
              type: not_found_error
              param: null
              code: null
    Conflict:
      description: The Sailbox is not in a state that allows this operation.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              message: sailbox listeners require persistence
              type: conflict_error
              param: null
              code: null
    BodyTooLarge:
      description: The request body is larger than the 64 KiB this endpoint accepts.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              message: request body too large
              type: invalid_request_error
              param: null
              code: null
    TooManyRequests:
      description: >-
        Too many requests in flight, or the organization is at a limit. A
        `Retry-After` header means the request never started, so retry it
        unchanged after that delay. Without one, the request ran and hit the
        limit `message` names. Some of those clear on their own, such as a
        shortage of public ports; others need you to free something up first.
        Send the request again once the cause is gone.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              message: >-
                Too many concurrent requests. Please retry after some of your
                organization's in-flight requests complete.
              type: rate_limit_error
              param: null
              code: rate_limited
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
    ServerError:
      description: Server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              message: failed to fetch sailbox
              type: server_error
              param: null
              code: null
    Unavailable:
      description: >-
        Sail is momentarily unavailable. Retry after the delay in the
        `Retry-After` header when one is present, and with backoff otherwise.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              message: Authentication service unavailable
              type: server_error
              param: null
              code: null
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
    Timeout:
      description: >-
        The operation outran its deadline, or a retry sent with the same
        `Idempotency-Key` waited about 30 seconds and the original request was
        still going. Retry with the same key.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              message: idempotent request still in flight
              type: server_error
              param: null
              code: null
  headers:
    RetryAfter:
      description: >-
        How many seconds to wait before retrying. On a 429 it also means the
        request never started, so the retry can reuse the same
        `Idempotency-Key`.
      schema:
        type: integer
        minimum: 0
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key

````