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

# Stream Terminal Output and Its End

> You can rely on:
- Input order is kept and output applies backpressure on PTY routes.
- Closing an observer does not by itself end the process.

You can set: input, dimensions. Observation controls where supported.

Not promised: unlimited concurrent observers.

Server-sent events. Each data field contains one JSON TerminalEvent; the SSE id
is its sequence. Output events carry exact bytes as base64. An exit event follows
all output. An error event may arrive after HTTP 200 and ends the stream without
implying the shell stopped. EOF without an exit or error event is an incomplete
stream, not success.

Output is retained in a 1 MiB sliding window until 60 seconds after the shell
ends. Last-Event-ID resumes after that event while it is retained; a dropped
sequence returns 410, or an error event for a reader that falls behind, and is
never skipped silently. Without Last-Event-ID the stream starts at the oldest
retained event.




## OpenAPI

````yaml openapi.json GET /sandboxes/{sandbox_id}/terminals/{terminal_id}/events
openapi: 3.1.1
info:
  title: BoxRouter API
  version: 0.10.281
  description: >
    Hosted sandbox control using a BoxRouter API key. Provider credentials are

    configured separately and never included in workload requests. Same-origin
    browser

    sessions are also accepted. Browser mutations require the configured Origin
    and

    the session-bound X-CSRF-Token; after an organization switch, every
    cookie-authenticated

    mutation must also send X-BoxRouter-Selection-Version with the version read
    from /auth/me.

    A stale version returns 409 organization_changed before side effects; its
    details contain

    the current selection_version. Bearer keys do not require browser CSRF or
    selection headers. Core adapters

    execute on BoxRouter servers.

    Sandbox IDs are BoxRouter resource IDs; every access is scoped to the
    caller's

    workspace. Routing occurs only at creation. Subsequent operations retain the

    original provider, upstream account and billing mode.

    Mutation endpoints accept an Idempotency-Key. Clients should generate one
    per

    logical mutation and retain it for reconciliation. The server records
    workspace,

    method, path and request identity before dispatch. Buffered requests include
    the full

    body hash. Streaming uploads reserve the key first, lock concurrent
    dispatch, and

    finalize the body hash as bytes arrive; interrupted uploads remain
    unresolved and

    must not be redispatched. A replay must validate the complete body before
    returning

    a recorded success. Reusing a key with different input

    returns 409; identical input returns the recorded result or an
    unresolved-operation

    error without dispatching again. Records with unknown outcomes are retained
    until

    resolved; completed records remain available for at least 24 hours. This
    does not

    promise exactly-once upstream execution or authorize automatic mutation
    retries.


    ## Guarantees


    Each operation's description states what you can rely on, what you can set
    and what is not promised. These rules apply to all operations:


    - File operations may act with more authority than the command user: do not
    rely on file permissions to keep the file API out. Commands run as the
    command user.

    - Some features need capabilities. A request that names them runs only on
    infrastructure that supports them, and an operation the sandbox was not
    created for returns a clear error; behaviour never changes silently.
servers:
  - url: https://api.boxrouter.ai/api/v1
security:
  - BoxRouterKey: []
  - BrowserSession: []
tags:
  - name: agents
    description: Discover launch manifests available to this workspace.
  - name: identity
    description: Browser sign-in and self-service API-key administration.
  - name: organizations
    description: >-
      Browser-only team organizations, invitations and membership
      administration.
  - name: launches
    description: Create an interactive sandbox from an agent manifest.
  - name: launch-runs
    description: >-
      Reserve, list, observe, and stop unattended multi-copy launch runs and
      their copies.
  - name: sandboxes
    description: Create and manage owned sandbox resources.
  - name: commands
    description: Execute Bash commands in an existing sandbox.
  - name: files
    description: Transfer exact file bytes within an existing sandbox.
  - name: providers
    description: Discover the caller's eligible supply and capabilities.
  - name: operations
    description: Reconcile uncertain mutations without replaying them.
  - name: provider-accounts
    description: >-
      Manage encrypted hosted BYOK connections separately from workload
      requests.
  - name: credits
    description: >-
      Prepaid credit balance, purchases and saved payment methods; never
      required for BYOK work.
  - name: spend-limits
    description: >-
      The cap on the managed compute a workspace can spend; BYOK work never
      counts.
  - name: processes
    description: >-
      Background commands with streamed output and completion on every provider;
      stdin where supported.
  - name: previews
    description: URLs and enforced credentials for sandbox ports on every provider.
  - name: volumes
    description: 'BoxRouter Volumes: one folder with checkpoints, restorable on any provider'
  - name: terminals
    description: Real PTY sessions on every provider through a guest helper.
  - name: secrets
    description: Manage workspace-scoped secret values without returning them.
  - name: artifacts
    description: Temporary workspace-owned inputs for provider builds and file transfers.
  - name: pricing
    description: >-
      The providers' published prices, as dated versions anyone can read without
      a key.
  - name: github
    description: >-
      The shared GitHub App connection used by Deployments and Actions;
      installations remain workspace-scoped.
  - name: actions
    description: >-
      GitHub Actions settings, policy previews and workspace-owned run, job and
      placement projections.
  - name: routing
    description: >-
      Workspace-wide routing authority, per-provider funding order and
      availability preview.
  - name: images
    description: Prepare and remove workspace-scoped provider copies of images.
paths:
  /sandboxes/{sandbox_id}/terminals/{terminal_id}/events:
    parameters:
      - $ref: '#/components/parameters/SandboxId'
      - $ref: '#/components/parameters/TerminalId'
    get:
      tags:
        - terminals
      summary: Stream terminal output and its end
      description: >
        You can rely on:

        - Input order is kept and output applies backpressure on PTY routes.

        - Closing an observer does not by itself end the process.


        You can set: input, dimensions. Observation controls where supported.


        Not promised: unlimited concurrent observers.


        Server-sent events. Each data field contains one JSON TerminalEvent; the
        SSE id

        is its sequence. Output events carry exact bytes as base64. An exit
        event follows

        all output. An error event may arrive after HTTP 200 and ends the stream
        without

        implying the shell stopped. EOF without an exit or error event is an
        incomplete

        stream, not success.


        Output is retained in a 1 MiB sliding window until 60 seconds after the
        shell

        ends. Last-Event-ID resumes after that event while it is retained; a
        dropped

        sequence returns 410, or an error event for a reader that falls behind,
        and is

        never skipped silently. Without Last-Event-ID the stream starts at the
        oldest

        retained event.
      operationId: streamTerminalEvents
      parameters:
        - name: Last-Event-ID
          in: header
          description: Last fully consumed event sequence from this terminal stream.
          schema:
            type: string
            pattern: ^[0-9]+$
      responses:
        '200':
          description: >-
            Typed SSE stream with exactly one exit or error event on normal
            completion.
          headers:
            Server-Timing:
              $ref: '#/components/headers/ServerTiming'
            X-Event-Retention-Seconds:
              description: Minimum retention after the shell ends for this stream's events.
              schema:
                type: integer
                minimum: 1
          content:
            text/event-stream:
              schema:
                $ref: '#/components/schemas/SandboxTerminalEventSSE'
        '404':
          $ref: '#/components/responses/Error'
        '410':
          $ref: '#/components/responses/Error'
        default:
          $ref: '#/components/responses/Error'
components:
  parameters:
    SandboxId:
      name: sandbox_id
      in: path
      required: true
      description: Workspace-owned BoxRouter sandbox ID, not an upstream ID.
      schema:
        type: string
        minLength: 1
        pattern: ^[^\u0000]*$
    TerminalId:
      name: terminal_id
      in: path
      required: true
      description: BoxRouter terminal ID owned by the addressed sandbox and workspace.
      schema:
        type: string
        minLength: 1
  headers:
    ServerTiming:
      description: >
        Durations of server-side phases, for diagnosing slow requests. Phase
        names are not stable

        and may change.
      schema:
        type: string
      x-boxrouter-server-timing:
        version: 1
        unit: ms
        serviceAggregateMetric: service
        maxHeaderBytes: 8192
        maxOperationRows: 32
        decimalPlaces: 3
        safeNamePattern: ^[A-Za-z0-9_.:-]{1,64}$
        aggregates:
          - metric: service
            fieldStem: cloud
          - metric: database
            fieldStem: database
          - metric: code
            fieldStem: code
          - metric: provider_connect
            fieldStem: provider_connect
          - metric: provider_wait
            fieldStem: provider_wait
          - metric: accept
            fieldStem: accept
          - metric: queue
            fieldStem: queue
          - metric: admission
            fieldStem: admission
          - metric: encode
            fieldStem: encode
        aggregateGrammar:
          duration:
            parameter: dur
            type: number
            finite: true
            minimum: 0
          descriptionParameter: desc
          unknownDescription: unknown
          missingMeans: unreported
        operationGrammar:
          metric: op
          nameParameter: desc
          namePattern: safeNamePattern
          start:
            parameter: start
            type: number
            finite: true
            sign: signed
            relativeTo: recorderWindow
          duration:
            parameter: dur
            type: number
            finite: true
            minimum: 0
          outcomeParameter: outcome
          outcomes:
            - returned
            - raised
          requiresAggregateDependencyUnion: false
        omissionGrammar:
          metric: op-omitted
          count:
            parameter: desc
            type: integer
            minimum: 0
          duration:
            parameter: dur
            type: number
            finite: true
            minimum: 0
            sumOf: knownOmittedMeasuredRows
          requiresAggregateDependencyUnion: false
        coverageGrammar:
          metric: op-coverage
          descriptionParameter: desc
          unknownValue: unknown
          absentState: unreported
          states:
            - unknown
            - unreported
          absenceImpliesCompleteness: false
    RequestId:
      description: >-
        Server-generated correlation identifier; contains no credentials or
        payloads.
      schema:
        type: string
  schemas:
    SandboxTerminalEventSSE:
      type: object
      additionalProperties: false
      required:
        - data
      properties:
        id:
          type: string
          description: >-
            Hosted replay cursor when supplied; availability and retention
            follow the operation.
        event:
          type: string
        data:
          $ref: '#/components/schemas/SandboxTerminalEvent'
      description: >-
        SSE transport envelope. The JSON payload retains its own type and native
        fields; EOF is not workload completion.
    SandboxTerminalEvent:
      oneOf:
        - $ref: '#/components/schemas/SandboxTerminalOutputEvent'
        - $ref: '#/components/schemas/SandboxTerminalExitEvent'
        - $ref: '#/components/schemas/SandboxTerminalErrorEvent'
      discriminator:
        propertyName: type
        mapping:
          output: '#/components/schemas/SandboxTerminalOutputEvent'
          exit: '#/components/schemas/SandboxTerminalExitEvent'
          error: '#/components/schemas/SandboxTerminalErrorEvent'
    ApiErrorResponse:
      type: object
      required:
        - error
      description: >-
        The one error body every failing call answers. It leads with what to do:
        `retry` says whether to send the call again, `fix` says what to change,
        `outcome` says what happened, and `doc_url` links to the page that
        explains the code. Clients retry `retry: safe` for you with the same
        idempotency key.
      x-sandbox-provider-spec-codes:
        - no_eligible_route
        - knob_not_named
        - unrecognised_word
        - invalid_request
        - not_named
        - gate_unavailable
        - account_unhealthy
        - lifetime_limit
        - lifetime_used_up
        - not_running
        - invalid_state
        - sandbox_ended
        - not_found
        - provider_error
        - timeout
        - contract_violation
      properties:
        error:
          type: object
          required:
            - code
            - message
            - request_id
            - outcome
            - retry
            - retry_after_seconds
          properties:
            code:
              type: string
              description: >
                Stable machine category. Operation failures use
                SandboxProviderSpec error codes,

                including invalid_request, invalid_state, not_found,
                unauthorized,

                incompatible_requirements, no_eligible_route,
                capacity_unavailable,

                capability_unavailable, contract_violation, resource_limit,
                timeout and

                provider_error. Hosted service failures add method_not_allowed,

                unauthenticated, forbidden, actions_sandbox_protected,

                insufficient_credit, spend_limit_exceeded,

                provider_not_enabled, credentials_rejected, conflict,
                rate_limited, provider_quota, request_timeout, overloaded,

                database_unavailable, payment_method_required, payment_declined,
                promotion_already_redeemed, account_exists, and internal_error.
                unsupported_feature and

                output_limit are earlier names still returned by some routes.

                Organization and membership operations also use
                organization_changed, last_admin,

                stale_membership, directory_managed and change_in_progress. Key
                ownership and

                invitation operations use idempotency_key_reused and conflict.

                Credit refunds and purchase quotes also use
                refund_window_closed,

                nothing_to_refund, refund_below_minimum, refund_in_progress,

                purchase_in_progress, dispute_open, already_refunded,

                refund_amount_changed, refund_fee_unavailable, quote_changed,

                session_required, refund_daily_limit, refund_rate_limit,

                refund_retry_requires_support, idempotency_key_reused and
                fraud_review.

                Card setup adds card_setup_rate_limit (429, with Retry-After): a
                workspace may start five

                card setups an hour.

                A provider that refuses the request for its own plan or quota
                limit answers

                429 provider_quota with the provider's words in native_message;
                rate_limited is

                BoxRouter's own limit.

                Routing Prevent Overrides refuses a request containing
                preferences.order,

                preferences.sort or preferences.allow_fallbacks with
                routing_overrides_forbidden,

                outcome not_started and details.fields naming the prohibited
                inputs. No provider

                effect occurs.

                Launch operations also use invalid_request, secret_missing,
                agent_not_found,

                no_eligible_route and launch_failed.

                A process or terminal is observed by one API task. When that
                task stops, process

                output, result and stdin answer 410 process_observer_lost
                (reading or cancelling the

                process still works), and a terminal on a provider that cannot
                reattach it answers

                410 terminal_observer_lost. invalid_observer_hop (403) refuses
                an internal hop

                between API tasks that is not signed; clients never send one.

                Opening a preview that the provider answered with other access
                than the request

                asked for returns 502 preview_access_refused: that preview is
                never returned, and

                details say whether it was ended.

                Starting a process with wait_for_port answers 409
                port_not_listening, outcome partial,

                when the process exits before the port accepts connections or
                the port stays closed for

                30 seconds; the process is ended and details carry the output so
                far.

                A request that must store content (a volume, a saved artifact)
                under transient content

                retention answers 409 privacy_persistence_conflict with outcome
                not_started.

                A sandbox request that stops before anything is created also
                answers

                lifetime_cap_exceeded, knob_not_named and unrecognised_word
                (400), and price_unknown and

                policy_denied (409), each with outcome not_started; the Actions
                codes job_claim_lost,

                actions_authority_changed, actions_candidate_ceiling_withdrawn
                and

                actions_candidate_outside_ceiling (409) say the job's claim,
                settings or admitted routes

                changed; provider_budget_exhausted (409) says a run's provider
                budget allows no

                further create. A create that names an image Cloud cannot use
                answers 400

                image_architecture_unsupported, image_digest_resolution_failed,

                image_preparation_unsupported or
                private_image_managed_route_unavailable, and

                privacy_admission_unavailable when privacy cannot be enforced;
                each says what to change.

                Launch and source-repository refusals name the repository or
                manifest fault (code_ref_not_found,

                code_repository_*, source_*, invalid_source_manifest,
                invalid_manifest_encoding,

                invalid_run_spec, repository_*, reserved_env_refused,
                launch_run_position_conflict,

                code_reference_moved, dockerfile_context_unsupported,
                image_tag_unresolved,

                recovery_sandbox_mismatch); the GitHub-side ones
                (source_rate_limited,

                source_resolution_failed, source_resolution_timeout,
                code_repository_resolution_failed)

                are safe to resend. image_setup_timeout,
                managed_funding_unavailable,

                lifetime_funding_conflict, candidate_admission_unavailable,
                secrets_unavailable and the

                Actions candidate codes (actions_candidate_record_invalid,
                actions_create_budget_exhausted,

                actions_create_format_mismatch) complete the creation and
                extension refusals.

                dispatch_limit_reached, inline_secret_values_refused and
                launch_incomplete come from

                launch runs, provider_policy_unknown from an operation whose
                privacy evidence cannot be

                verified, and quota_exceeded from a full managed provider
                account.

                Volume operations also use volume_busy, volume_lease_lost,
                volume_not_attached,

                volume_incompatible, volume_save_failed and
                volume_restore_failed. Creating a

                sandbox on a volume whose writer is ending waits for the release
                (at most 120

                seconds) before it can answer volume_busy.

                A team member's new personal API key is paused until WorkOS
                membership sync catches

                up: it answers 403 forbidden whose message and fix say the key
                becomes active once

                the team membership syncs, usually within a minute, and the call
                can be retried.

                Clients must preserve and handle unknown categories explicitly.
              x-boxrouter-error-codes:
                account_exists:
                  - 409
                account_unhealthy:
                  - 402
                actions_authority_changed:
                  - 409
                actions_candidate_ceiling_withdrawn:
                  - 409
                actions_candidate_outside_ceiling:
                  - 409
                actions_candidate_record_invalid:
                  - 409
                actions_create_budget_exhausted:
                  - 409
                actions_create_format_mismatch:
                  - 409
                actions_sandbox_protected:
                  - 403
                agent_not_found:
                  - 404
                already_refunded:
                  - 409
                authorization_error:
                  - 403
                candidate_admission_unavailable:
                  - 503
                capability_unavailable:
                  - 409
                  - 503
                capability_unverified:
                  - 409
                card_setup_rate_limit:
                  - 429
                change_in_progress:
                  - 409
                code_ref_not_found:
                  - 404
                code_reference_moved:
                  - 409
                code_repository_not_authorized:
                  - 403
                code_repository_not_found:
                  - 404
                code_repository_not_private:
                  - 400
                code_repository_not_public:
                  - 404
                code_repository_resolution_failed:
                  - 502
                command_failed:
                  - 409
                command_observation_busy:
                  - 409
                conflict:
                  - 409
                contract_violation:
                  - 409
                credentials_rejected:
                  - 409
                  - 422
                database_unavailable:
                  - 503
                directory_managed:
                  - 409
                dispatch_limit_reached:
                  - 409
                dispute_open:
                  - 409
                dockerfile_context_unsupported:
                  - 400
                expired:
                  - 410
                forbidden:
                  - 403
                fraud_review:
                  - 409
                gate_unavailable:
                  - 409
                idempotency_key_reused:
                  - 409
                  - 422
                identity_unavailable:
                  - 503
                image_architecture_unsupported:
                  - 400
                image_digest_resolution_failed:
                  - 400
                image_preparation_unsupported:
                  - 400
                image_setup_timeout:
                  - 504
                image_tag_unresolved:
                  - 400
                inline_secret_values_refused:
                  - 422
                insufficient_credit:
                  - 402
                internal_error:
                  - 500
                invalid_manifest_encoding:
                  - 400
                invalid_observer_hop:
                  - 403
                invalid_request:
                  - 400
                  - 413
                  - 422
                  - 428
                invalid_run_spec:
                  - 400
                invalid_source_manifest:
                  - 400
                invalid_state:
                  - 409
                job_claim_lost:
                  - 409
                knob_not_named:
                  - 400
                last_admin:
                  - 409
                launch_failed:
                  - 409
                launch_incomplete:
                  - 409
                launch_run_position_conflict:
                  - 409
                lifetime_cap_exceeded:
                  - 400
                lifetime_funding_conflict:
                  - 409
                lifetime_limit:
                  - 409
                lifetime_used_up:
                  - 409
                managed_funding_unavailable:
                  - 503
                method_not_allowed:
                  - 405
                no_eligible_route:
                  - 409
                not_found:
                  - 404
                not_named:
                  - 409
                not_running:
                  - 409
                nothing_to_refund:
                  - 409
                organization_changed:
                  - 409
                output_limit:
                  - 413
                overloaded:
                  - 503
                payment_declined:
                  - 402
                payment_method_required:
                  - 409
                payment_provider_rejected:
                  - 502
                payment_provider_unavailable:
                  - 503
                payments_unavailable:
                  - 503
                policy_denied:
                  - 409
                port_not_listening:
                  - 409
                preview_access_refused:
                  - 502
                price_unknown:
                  - 409
                privacy_admission_unavailable:
                  - 400
                privacy_persistence_conflict:
                  - 409
                private_image_managed_route_unavailable:
                  - 400
                process_observer_lost:
                  - 410
                promotion_already_redeemed:
                  - 409
                promotion_code_invalid:
                  - 404
                provider_budget_exhausted:
                  - 409
                provider_error:
                  - 502
                provider_not_enabled:
                  - 409
                provider_policy_unknown:
                  - 409
                provider_quota:
                  - 429
                purchase_in_progress:
                  - 409
                quota_exceeded:
                  - 429
                quote_changed:
                  - 409
                rate_limited:
                  - 429
                recovery_sandbox_mismatch:
                  - 409
                refund_amount_changed:
                  - 409
                refund_below_minimum:
                  - 409
                refund_daily_limit:
                  - 429
                refund_fee_unavailable:
                  - 409
                refund_in_progress:
                  - 409
                refund_rate_limit:
                  - 429
                refund_retry_requires_support:
                  - 409
                refund_window_closed:
                  - 409
                repository_file_unsupported:
                  - 400
                repository_too_large:
                  - 400
                request_timeout:
                  - 408
                reserved_env_refused:
                  - 400
                  - 422
                resource_limit:
                  - 413
                  - 507
                routing_overrides_forbidden:
                  - 400
                run_limit_exceeded:
                  - 429
                sandbox_ended:
                  - 410
                secret_missing:
                  - 400
                secrets_unavailable:
                  - 503
                session_required:
                  - 403
                source_file_not_found:
                  - 400
                source_file_too_large:
                  - 413
                source_rate_limited:
                  - 503
                source_ref_not_found:
                  - 400
                source_repository_not_accessible:
                  - 404
                source_repository_not_found:
                  - 400
                source_repository_not_private:
                  - 400
                source_repository_not_public:
                  - 400
                source_resolution_failed:
                  - 502
                source_resolution_timeout:
                  - 504
                spend_limit_exceeded:
                  - 402
                stale_membership:
                  - 409
                terminal_closed:
                  - 410
                terminal_observer_lost:
                  - 410
                timeout:
                  - 504
                unauthenticated:
                  - 401
                unavailable:
                  - 503
                unrecognised_word:
                  - 400
                unsupported_feature:
                  - 422
                volume_busy:
                  - 409
                volume_incompatible:
                  - 409
                volume_lease_lost:
                  - 409
                volume_not_attached:
                  - 409
                volume_restore_failed:
                  - 502
                volume_save_failed:
                  - 409
                  - 502
                volume_unavailable:
                  - 409
                wrong_region:
                  - 421
              x-boxrouter-error-retry:
                account_exists: after_fix
                account_unhealthy: after_fix
                actions_authority_changed: after_fix
                actions_candidate_ceiling_withdrawn: after_fix
                actions_candidate_outside_ceiling: after_fix
                actions_candidate_record_invalid: after_fix
                actions_create_budget_exhausted: after_fix
                actions_create_format_mismatch: after_fix
                actions_sandbox_protected: never
                agent_not_found: after_fix
                already_refunded: never
                authorization_error: check_outcome
                candidate_admission_unavailable: never
                capability_unavailable: check_outcome
                capability_unverified: after_fix
                card_setup_rate_limit: safe
                change_in_progress: safe
                code_ref_not_found: after_fix
                code_reference_moved: after_fix
                code_repository_not_authorized: after_fix
                code_repository_not_found: after_fix
                code_repository_not_private: after_fix
                code_repository_not_public: after_fix
                code_repository_resolution_failed: safe
                command_failed: after_fix
                command_observation_busy: safe
                conflict: check_outcome
                contract_violation: check_outcome
                credentials_rejected: after_fix
                database_unavailable: check_outcome
                directory_managed: never
                dispatch_limit_reached: after_fix
                dispute_open: after_fix
                dockerfile_context_unsupported: after_fix
                expired: check_outcome
                forbidden: after_fix
                fraud_review: safe
                gate_unavailable: after_fix
                idempotency_key_reused: after_fix
                identity_unavailable: safe
                image_architecture_unsupported: after_fix
                image_digest_resolution_failed: after_fix
                image_preparation_unsupported: after_fix
                image_setup_timeout: after_fix
                image_tag_unresolved: after_fix
                inline_secret_values_refused: after_fix
                insufficient_credit: after_fix
                internal_error: never
                invalid_manifest_encoding: after_fix
                invalid_observer_hop: after_fix
                invalid_request: after_fix
                invalid_run_spec: after_fix
                invalid_source_manifest: after_fix
                invalid_state: check_outcome
                job_claim_lost: after_fix
                knob_not_named: after_fix
                last_admin: after_fix
                launch_failed: check_outcome
                launch_incomplete: check_outcome
                launch_run_position_conflict: after_fix
                lifetime_cap_exceeded: after_fix
                lifetime_funding_conflict: after_fix
                lifetime_limit: check_outcome
                lifetime_used_up: after_fix
                managed_funding_unavailable: safe
                method_not_allowed: after_fix
                no_eligible_route: after_fix
                not_found: check_outcome
                not_named: after_fix
                not_running: after_fix
                nothing_to_refund: never
                organization_changed: safe
                output_limit: check_outcome
                overloaded: safe
                payment_declined: after_fix
                payment_method_required: after_fix
                payment_provider_rejected: check_outcome
                payment_provider_unavailable: check_outcome
                payments_unavailable: safe
                policy_denied: after_fix
                port_not_listening: after_fix
                preview_access_refused: never
                price_unknown: after_fix
                privacy_admission_unavailable: after_fix
                privacy_persistence_conflict: after_fix
                private_image_managed_route_unavailable: after_fix
                process_observer_lost: check_outcome
                promotion_already_redeemed: never
                promotion_code_invalid: after_fix
                provider_budget_exhausted: never
                provider_error: check_outcome
                provider_not_enabled: after_fix
                provider_policy_unknown: after_fix
                provider_quota: after_fix
                purchase_in_progress: safe
                quota_exceeded: safe
                quote_changed: after_fix
                rate_limited: safe
                recovery_sandbox_mismatch: check_outcome
                refund_amount_changed: after_fix
                refund_below_minimum: never
                refund_daily_limit: safe
                refund_fee_unavailable: safe
                refund_in_progress: safe
                refund_rate_limit: safe
                refund_retry_requires_support: never
                refund_window_closed: never
                repository_file_unsupported: after_fix
                repository_too_large: after_fix
                request_timeout: safe
                reserved_env_refused: after_fix
                resource_limit: check_outcome
                routing_overrides_forbidden: after_fix
                run_limit_exceeded: after_fix
                sandbox_ended: check_outcome
                secret_missing: after_fix
                secrets_unavailable: never
                session_required: never
                source_file_not_found: after_fix
                source_file_too_large: after_fix
                source_rate_limited: safe
                source_ref_not_found: after_fix
                source_repository_not_accessible: after_fix
                source_repository_not_found: after_fix
                source_repository_not_private: after_fix
                source_repository_not_public: after_fix
                source_resolution_failed: safe
                source_resolution_timeout: safe
                spend_limit_exceeded: after_fix
                stale_membership: after_fix
                terminal_closed: after_fix
                terminal_observer_lost: after_fix
                timeout: check_outcome
                unauthenticated: after_fix
                unavailable: safe
                unrecognised_word: after_fix
                unsupported_feature: after_fix
                volume_busy: after_fix
                volume_incompatible: after_fix
                volume_lease_lost: after_fix
                volume_not_attached: after_fix
                volume_restore_failed: check_outcome
                volume_save_failed: check_outcome
                volume_unavailable: after_fix
                wrong_region: after_fix
            message:
              type: string
            details:
              type: object
              description: >
                Code-specific context, spelled for programs. no_eligible_route
                carries considered,

                excluded, closest and nearest; knob_not_named the knob and its
                values;

                unrecognised_word the path, the word and the known ones;
                not_named the gate, what

                the call needed beyond the grant and the corrected requirements
                line; gate_unavailable

                the gate and account; lifetime_limit the ceiling and this run. A
                creation that

                stopped before a sandbox existed may also carry
                `fallbacks_allowed`, `stopped_reason`

                and `attempts`, spelled and bounded as in the sandbox route
                selection.

                capability_unavailable may identify the needed capability. A
                launch preparation failure can follow sandbox creation;

                inspect outcome and cleanup details before creating another.
                Clients must

                preserve fields they do not know.
              properties:
                selection_version:
                  type: integer
                  minimum: 0
                  description: >-
                    Current selected organization version for
                    organization_changed.
                considered:
                  type: integer
                  minimum: 0
                  description: no_eligible_route only; how many routes were considered.
                excluded:
                  type: array
                  description: >-
                    no_eligible_route only; every considered route that cannot
                    serve the request. Each entry names its `provider` and the
                    checks it failed (`failed` for a sandbox request, `reasons`
                    and `exclusions` for a launch); clients must preserve fields
                    they do not know.
                  items:
                    type: object
                    required:
                      - provider
                    properties:
                      provider:
                        type: string
                closest:
                  type:
                    - object
                    - 'null'
                  description: >-
                    no_eligible_route only; the excluded route that fails fewest
                    checks, or null.
                nearest:
                  type: array
                  description: >-
                    no_eligible_route only; the smallest changes that would make
                    a route eligible, nearest first. An entry names the change
                    (`change` and `then_eligible` for a sandbox request,
                    `provider`, `distance` and `failing` for a launch); clients
                    must preserve fields they do not know.
                  items:
                    type: object
                requirement:
                  type: string
                  description: >-
                    The SandboxProviderSpec family whose requirement would make
                    this operation available.
                purchase_id:
                  type: string
                  description: >-
                    Retained failed credit purchase ID, when a saved-card charge
                    was declined.
                preview:
                  $ref: '#/components/schemas/CreditRefundPreview'
                  description: >-
                    Current refund preview returned with 409
                    refund_amount_changed.
                quote:
                  $ref: '#/components/schemas/CreditPurchaseQuote'
                  description: Current purchase quote returned with 409 quote_changed.
                redemption:
                  $ref: '#/components/schemas/CreditPromotion'
                  description: >-
                    Original promotion redemption returned with 409
                    promotion_already_redeemed.
                scope:
                  type: string
                  enum:
                    - workspace
                  description: >-
                    spend_limit_exceeded only; which configured cap refused
                    admission.
                period:
                  type: string
                  enum:
                    - total
                    - daily
                    - monthly
                  description: spend_limit_exceeded only; the cap period that applies.
                limit_nanodollars:
                  type: integer
                  minimum: 1
                  description: spend_limit_exceeded only; exact configured USD nanodollars.
                requested_nanodollars:
                  type: integer
                  minimum: 1
                  description: spend_limit_exceeded only; exact proposed managed hold.
                sign_in_method:
                  type: string
                  description: >-
                    account_exists only: how the existing account with this
                    verified address signs in (google, github, email or other).
                masked_email:
                  type: string
                  description: >-
                    account_exists only: that account's address, masked as
                    `t…@gmail.com`.
                failure_reason:
                  type: string
                  maxLength: 100
                  description: >-
                    Short safe card-decline reason; raw Stripe messages are not
                    returned.
                names:
                  type: array
                  uniqueItems: true
                  items:
                    $ref: '#/components/schemas/SecretName'
                  description: All manifest secret names that could not be resolved.
                fields:
                  type: array
                  uniqueItems: true
                  items:
                    type: string
                    enum:
                      - preferences.order
                      - preferences.sort
                      - preferences.allow_fallbacks
                  description: >-
                    routing_overrides_forbidden only; the prohibited request
                    inputs.
                volume_id:
                  type: string
                  description: BoxRouter Volume ID involved in the error.
                writer_sandbox_id:
                  type: string
                  description: >-
                    volume_busy only; the sandbox that holds the volume's writer
                    lease, when it is a recorded sandbox.
                checkpoint:
                  type: integer
                  minimum: 1
                  description: Volume checkpoint involved in the error.
                limit_bytes:
                  type: integer
                  minimum: 0
                observed_bytes:
                  type: integer
                  minimum: 0
                mismatch:
                  type: object
                  additionalProperties: false
                  required:
                    - field
                    - recorded
                    - requested
                  properties:
                    field:
                      type: string
                      enum:
                        - image
                        - arch
                    recorded:
                      type: string
                    requested:
                      type: string
                volume_phase:
                  type: string
                  enum:
                    - check
                    - branch
                    - lease
                    - identity
                    - install
                    - credential
                    - mount
                    - metadata
                    - checkpoint
                    - commit
                    - write
                phase:
                  type: string
                  enum:
                    - code
                    - setup
                    - files
                    - background
                    - previews
                  description: Preparation phase that failed.
                command:
                  type: string
                  description: Command whose nonzero exit stopped launch preparation.
                exit_code:
                  type: integer
                  description: >-
                    Exit code of the failed command, or of the process that
                    exited before its port listened.
                stderr_tail:
                  type: string
                  description: >
                    At most the final 4 KiB of stderr from the failed command,
                    with resolved secret values

                    redacted. port_not_listening carries the same tail of the
                    process's stderr.
                stdout_tail:
                  type: string
                  description: >-
                    port_not_listening only; at most the final 4 KiB of the
                    process's stdout.
                agent:
                  type: string
                  description: Unknown catalog agent name.
                port:
                  type: integer
                  minimum: 1
                  maximum: 65535
                  description: >-
                    The sandbox port whose preview was refused
                    (preview_access_refused) or that never accepted connections
                    (port_not_listening).
                revoked:
                  type: boolean
                  description: >
                    preview_access_refused only; true when the unwanted preview
                    was ended and no

                    access to the port remains. False: it may still be reachable
                    (outcome unknown).
                preview_id:
                  type: string
                  description: >
                    preview_access_refused only; the identifier the provider
                    gave the preview that may

                    still be reachable. Present only for a public preview.
                url:
                  type: string
                  format: uri
                  description: >
                    preview_access_refused only; the address of a public preview
                    that may still be

                    reachable. Never present for a private preview, whose
                    address holds a credential,

                    and never when it was ended.
            request_id:
              type: string
            doc_url:
              type:
                - string
                - 'null'
              format: uri
              description: >-
                Where the documentation explains this code:
                `https://boxrouter.ai/docs/error-codes#` followed by the code,
                for example
                `https://boxrouter.ai/docs/error-codes#no_eligible_route`. The
                page has one anchored row per code with what it means and what
                to do. BoxRouter sends it with every error; it is null for a
                code that has no documented row. Clients must accept an error
                body that omits it.
            fix:
              type:
                - string
                - 'null'
              minLength: 1
              description: >-
                Actionable change for the caller when the request cannot be
                completed. A refusal (4xx) always carries one; a server or
                provider fault, including an error event that ends a terminal or
                process stream, can answer null.
            operation_id:
              type:
                - string
                - 'null'
              description: >-
                Durable server operation ID for reconciliation after uncertain
                mutations.
            outcome:
              type: string
              enum:
                - not_started
                - completed
                - partial
                - unknown
              description: >
                What is known about the requested side effect. `not_started`
                means nothing ran

                anywhere, at BoxRouter or at a provider; a launch-setup or
                process-start failure

                after dispatch is `partial` or `unknown`. Unknown and partial
                forbid automatic

                replay and always carry `retry: check_outcome`. An HTTP status
                alone does not establish

                outcome.

                Many refusals report not_started before the provider is asked
                anything. A read

                (GET or HEAD) has no side effect to be unsure about, so its
                failures report

                not_started and carry the code's own retry value. A launch
                preparation failure

                happens after the sandbox was created and setup commands may
                already have run, and

                deleting the sandbox does not undo effects outside it: it
                reports partial once the

                cleanup is confirmed and unknown if it is not. A process that
                wait_for_port ended

                reports partial. volume_restore_failed on a resume or create,

                and volume_save_failed after the guest was read, report unknown
                or partial; they

                carry the operation_id when the call was recorded, and their fix
                does not offer to

                send the call again unchanged.
            retry:
              type: string
              enum:
                - safe
                - after_fix
                - check_outcome
                - never
              x-boxrouter-open-enum: true
              description: >
                What the caller may do next, fixed per code
                (x-boxrouter-error-retry): safe resends the

                same request, after_fix changes the request or setup first,
                check_outcome reads

                `outcome` and the resource before resending, never does not
                resend. Branch on this

                instead of the HTTP status.
            retry_after_seconds:
              type:
                - integer
                - 'null'
              minimum: 0
              description: >-
                How long to wait before a `retry: safe` resend, as the
                Retry-After header says it; null when there is no wait to name.
            provider:
              type:
                - string
                - 'null'
            native_code:
              type:
                - string
                - 'null'
              description: >-
                The provider's machine-readable error code, unchanged, when it
                reports one.
            native_status:
              type:
                - integer
                - 'null'
              description: >-
                The provider's HTTP status, unchanged, when the failure came
                from a provider response.
            native_message:
              type:
                - string
                - 'null'
              maxLength: 4096
              description: >-
                The provider's own error message when it answered with an HTTP
                4xx refusal (for example a plan limit); null otherwise. Capped
                at 512 characters, with whitespace collapsed. Credential secrets
                and token-shaped strings are removed before return. The literal
                `[redacted]` marker is used when all credential secret fields
                are at least 12 characters; legacy saved secrets shorter than 12
                characters are removed without the marker.
    SandboxTerminalOutputEvent:
      type: object
      required:
        - type
        - sequence
        - data_base64
      properties:
        type:
          type: string
          const: output
        sequence:
          type: integer
          minimum: 0
        data_base64:
          type: string
          contentEncoding: base64
          description: Exact PTY output bytes. Chunk boundaries may change.
    SandboxTerminalExitEvent:
      type: object
      required:
        - type
        - sequence
        - exit_code
      properties:
        type:
          type: string
          const: exit
        sequence:
          type: integer
          minimum: 0
        exit_code:
          type: integer
    SandboxTerminalErrorEvent:
      type: object
      required:
        - type
        - error
      properties:
        type:
          type: string
          const: error
        sequence:
          type: integer
          minimum: 0
        error:
          $ref: '#/components/schemas/error'
    CreditRefundPreview:
      type: object
      additionalProperties: false
      required:
        - unused
        - held_excluded
        - fee
        - principal
        - tax
        - total
        - card
      properties:
        unused:
          type: integer
          minimum: 0
          description: Unused purchased credit in US cents, before held credit is excluded.
        held_excluded:
          type: integer
          minimum: 0
          description: Credit held for running sandboxes, in US cents.
        fee:
          type: integer
          minimum: 0
          description: Original Stripe processing fee retained, in US cents.
        principal:
          type: integer
          minimum: 0
          description: Tax-exclusive principal returned to the original card, in US cents.
        tax:
          type: integer
          minimum: 0
          description: Tax Stripe adds to the credit note, in US cents.
        total:
          type: integer
          minimum: 0
          description: Total Stripe will refund, in US cents.
        card:
          $ref: '#/components/schemas/MaskedCreditCard'
    CreditPurchaseQuote:
      type: object
      additionalProperties: false
      required:
        - amount
        - tax
        - total
        - address_required
      properties:
        amount:
          type: integer
          minimum: 1
          description: >-
            Tax-exclusive credit amount in US cents; this is what is added to
            the balance.
        fee_minor:
          type: integer
          minimum: 0
          description: >-
            BoxRouter's purchase fee in US cents, computed by the server and
            charged on top of amount. Tax applies to amount plus fee. Absent on
            older services.
        tax:
          type: integer
          minimum: 0
          description: Estimated tax in US cents, on the credits and the fee.
        total:
          type: integer
          minimum: 1
          description: >-
            Estimated total to charge in US cents: amount plus fee_minor plus
            tax.
        address_required:
          type: boolean
        missing:
          type: array
          items:
            type: string
          description: Required billing address fields missing for an exact tax quote.
    CreditPromotion:
      type: object
      additionalProperties: false
      required:
        - code
        - amount_nanodollars
        - description
        - redeemed_at
      properties:
        code:
          type: string
          description: The code in its canonical upper-case form.
        amount_nanodollars:
          type: string
          maxLength: 19
          pattern: >-
            ^(?:[1-9][0-9]{0,17}|[1-8][0-9]{18}|9[0-1][0-9]{17}|92[0-1][0-9]{16}|922[0-2][0-9]{15}|9223[0-2][0-9]{14}|92233[0-6][0-9]{13}|922337[0-1][0-9]{12}|92233720[0-2][0-9]{10}|922337203[0-5][0-9]{9}|9223372036[0-7][0-9]{8}|92233720368[0-4][0-9]{7}|922337203685[0-3][0-9]{6}|9223372036854[0-6][0-9]{5}|92233720368547[0-6][0-9]{4}|922337203685477[0-4][0-9]{3}|9223372036854775[0-7][0-9]{2}|922337203685477580[0-7])$
          description: >-
            Credit granted, in billionths of a US dollar, as a canonical
            positive signed 64-bit nanodollar string.
        description:
          type: string
          description: What the credit is for, as the promotion describes it.
        redeemed_at:
          type: string
          format: date-time
    SecretName:
      type: string
      minLength: 1
      maxLength: 128
      pattern: ^[A-Z][A-Z0-9_]{0,127}$
    error:
      type: object
      required:
        - code
        - message
        - request_id
        - outcome
        - retry
        - retry_after_seconds
      properties:
        code:
          type: string
          description: >
            Stable machine category. Operation failures use SandboxProviderSpec
            error codes,

            including invalid_request, invalid_state, not_found, unauthorized,

            incompatible_requirements, no_eligible_route, capacity_unavailable,

            capability_unavailable, contract_violation, resource_limit, timeout
            and

            provider_error. Hosted service failures add method_not_allowed,

            unauthenticated, forbidden, actions_sandbox_protected,

            insufficient_credit, spend_limit_exceeded,

            provider_not_enabled, credentials_rejected, conflict, rate_limited,
            provider_quota, request_timeout, overloaded,

            database_unavailable, payment_method_required, payment_declined,
            promotion_already_redeemed, account_exists, and internal_error.
            unsupported_feature and

            output_limit are earlier names still returned by some routes.

            Organization and membership operations also use
            organization_changed, last_admin,

            stale_membership, directory_managed and change_in_progress. Key
            ownership and

            invitation operations use idempotency_key_reused and conflict.

            Credit refunds and purchase quotes also use refund_window_closed,

            nothing_to_refund, refund_below_minimum, refund_in_progress,

            purchase_in_progress, dispute_open, already_refunded,

            refund_amount_changed, refund_fee_unavailable, quote_changed,

            session_required, refund_daily_limit, refund_rate_limit,

            refund_retry_requires_support, idempotency_key_reused and
            fraud_review.

            Card setup adds card_setup_rate_limit (429, with Retry-After): a
            workspace may start five

            card setups an hour.

            A provider that refuses the request for its own plan or quota limit
            answers

            429 provider_quota with the provider's words in native_message;
            rate_limited is

            BoxRouter's own limit.

            Routing Prevent Overrides refuses a request containing
            preferences.order,

            preferences.sort or preferences.allow_fallbacks with
            routing_overrides_forbidden,

            outcome not_started and details.fields naming the prohibited inputs.
            No provider

            effect occurs.

            Launch operations also use invalid_request, secret_missing,
            agent_not_found,

            no_eligible_route and launch_failed.

            A process or terminal is observed by one API task. When that task
            stops, process

            output, result and stdin answer 410 process_observer_lost (reading
            or cancelling the

            process still works), and a terminal on a provider that cannot
            reattach it answers

            410 terminal_observer_lost. invalid_observer_hop (403) refuses an
            internal hop

            between API tasks that is not signed; clients never send one.

            Opening a preview that the provider answered with other access than
            the request

            asked for returns 502 preview_access_refused: that preview is never
            returned, and

            details say whether it was ended.

            Starting a process with wait_for_port answers 409
            port_not_listening, outcome partial,

            when the process exits before the port accepts connections or the
            port stays closed for

            30 seconds; the process is ended and details carry the output so
            far.

            A request that must store content (a volume, a saved artifact) under
            transient content

            retention answers 409 privacy_persistence_conflict with outcome
            not_started.

            A sandbox request that stops before anything is created also answers

            lifetime_cap_exceeded, knob_not_named and unrecognised_word (400),
            and price_unknown and

            policy_denied (409), each with outcome not_started; the Actions
            codes job_claim_lost,

            actions_authority_changed, actions_candidate_ceiling_withdrawn and

            actions_candidate_outside_ceiling (409) say the job's claim,
            settings or admitted routes

            changed; provider_budget_exhausted (409) says a run's provider
            budget allows no

            further create. A create that names an image Cloud cannot use
            answers 400

            image_architecture_unsupported, image_digest_resolution_failed,

            image_preparation_unsupported or
            private_image_managed_route_unavailable, and

            privacy_admission_unavailable when privacy cannot be enforced; each
            says what to change.

            Launch and source-repository refusals name the repository or
            manifest fault (code_ref_not_found,

            code_repository_*, source_*, invalid_source_manifest,
            invalid_manifest_encoding,

            invalid_run_spec, repository_*, reserved_env_refused,
            launch_run_position_conflict,

            code_reference_moved, dockerfile_context_unsupported,
            image_tag_unresolved,

            recovery_sandbox_mismatch); the GitHub-side ones
            (source_rate_limited,

            source_resolution_failed, source_resolution_timeout,
            code_repository_resolution_failed)

            are safe to resend. image_setup_timeout,
            managed_funding_unavailable,

            lifetime_funding_conflict, candidate_admission_unavailable,
            secrets_unavailable and the

            Actions candidate codes (actions_candidate_record_invalid,
            actions_create_budget_exhausted,

            actions_create_format_mismatch) complete the creation and extension
            refusals.

            dispatch_limit_reached, inline_secret_values_refused and
            launch_incomplete come from

            launch runs, provider_policy_unknown from an operation whose privacy
            evidence cannot be

            verified, and quota_exceeded from a full managed provider account.

            Volume operations also use volume_busy, volume_lease_lost,
            volume_not_attached,

            volume_incompatible, volume_save_failed and volume_restore_failed.
            Creating a

            sandbox on a volume whose writer is ending waits for the release (at
            most 120

            seconds) before it can answer volume_busy.

            A team member's new personal API key is paused until WorkOS
            membership sync catches

            up: it answers 403 forbidden whose message and fix say the key
            becomes active once

            the team membership syncs, usually within a minute, and the call can
            be retried.

            Clients must preserve and handle unknown categories explicitly.
          x-boxrouter-error-codes:
            account_exists:
              - 409
            account_unhealthy:
              - 402
            actions_authority_changed:
              - 409
            actions_candidate_ceiling_withdrawn:
              - 409
            actions_candidate_outside_ceiling:
              - 409
            actions_candidate_record_invalid:
              - 409
            actions_create_budget_exhausted:
              - 409
            actions_create_format_mismatch:
              - 409
            actions_sandbox_protected:
              - 403
            agent_not_found:
              - 404
            already_refunded:
              - 409
            authorization_error:
              - 403
            candidate_admission_unavailable:
              - 503
            capability_unavailable:
              - 409
              - 503
            capability_unverified:
              - 409
            card_setup_rate_limit:
              - 429
            change_in_progress:
              - 409
            code_ref_not_found:
              - 404
            code_reference_moved:
              - 409
            code_repository_not_authorized:
              - 403
            code_repository_not_found:
              - 404
            code_repository_not_private:
              - 400
            code_repository_not_public:
              - 404
            code_repository_resolution_failed:
              - 502
            command_failed:
              - 409
            command_observation_busy:
              - 409
            conflict:
              - 409
            contract_violation:
              - 409
            credentials_rejected:
              - 409
              - 422
            database_unavailable:
              - 503
            directory_managed:
              - 409
            dispatch_limit_reached:
              - 409
            dispute_open:
              - 409
            dockerfile_context_unsupported:
              - 400
            expired:
              - 410
            forbidden:
              - 403
            fraud_review:
              - 409
            gate_unavailable:
              - 409
            idempotency_key_reused:
              - 409
              - 422
            identity_unavailable:
              - 503
            image_architecture_unsupported:
              - 400
            image_digest_resolution_failed:
              - 400
            image_preparation_unsupported:
              - 400
            image_setup_timeout:
              - 504
            image_tag_unresolved:
              - 400
            inline_secret_values_refused:
              - 422
            insufficient_credit:
              - 402
            internal_error:
              - 500
            invalid_manifest_encoding:
              - 400
            invalid_observer_hop:
              - 403
            invalid_request:
              - 400
              - 413
              - 422
              - 428
            invalid_run_spec:
              - 400
            invalid_source_manifest:
              - 400
            invalid_state:
              - 409
            job_claim_lost:
              - 409
            knob_not_named:
              - 400
            last_admin:
              - 409
            launch_failed:
              - 409
            launch_incomplete:
              - 409
            launch_run_position_conflict:
              - 409
            lifetime_cap_exceeded:
              - 400
            lifetime_funding_conflict:
              - 409
            lifetime_limit:
              - 409
            lifetime_used_up:
              - 409
            managed_funding_unavailable:
              - 503
            method_not_allowed:
              - 405
            no_eligible_route:
              - 409
            not_found:
              - 404
            not_named:
              - 409
            not_running:
              - 409
            nothing_to_refund:
              - 409
            organization_changed:
              - 409
            output_limit:
              - 413
            overloaded:
              - 503
            payment_declined:
              - 402
            payment_method_required:
              - 409
            payment_provider_rejected:
              - 502
            payment_provider_unavailable:
              - 503
            payments_unavailable:
              - 503
            policy_denied:
              - 409
            port_not_listening:
              - 409
            preview_access_refused:
              - 502
            price_unknown:
              - 409
            privacy_admission_unavailable:
              - 400
            privacy_persistence_conflict:
              - 409
            private_image_managed_route_unavailable:
              - 400
            process_observer_lost:
              - 410
            promotion_already_redeemed:
              - 409
            promotion_code_invalid:
              - 404
            provider_budget_exhausted:
              - 409
            provider_error:
              - 502
            provider_not_enabled:
              - 409
            provider_policy_unknown:
              - 409
            provider_quota:
              - 429
            purchase_in_progress:
              - 409
            quota_exceeded:
              - 429
            quote_changed:
              - 409
            rate_limited:
              - 429
            recovery_sandbox_mismatch:
              - 409
            refund_amount_changed:
              - 409
            refund_below_minimum:
              - 409
            refund_daily_limit:
              - 429
            refund_fee_unavailable:
              - 409
            refund_in_progress:
              - 409
            refund_rate_limit:
              - 429
            refund_retry_requires_support:
              - 409
            refund_window_closed:
              - 409
            repository_file_unsupported:
              - 400
            repository_too_large:
              - 400
            request_timeout:
              - 408
            reserved_env_refused:
              - 400
              - 422
            resource_limit:
              - 413
              - 507
            routing_overrides_forbidden:
              - 400
            run_limit_exceeded:
              - 429
            sandbox_ended:
              - 410
            secret_missing:
              - 400
            secrets_unavailable:
              - 503
            session_required:
              - 403
            source_file_not_found:
              - 400
            source_file_too_large:
              - 413
            source_rate_limited:
              - 503
            source_ref_not_found:
              - 400
            source_repository_not_accessible:
              - 404
            source_repository_not_found:
              - 400
            source_repository_not_private:
              - 400
            source_repository_not_public:
              - 400
            source_resolution_failed:
              - 502
            source_resolution_timeout:
              - 504
            spend_limit_exceeded:
              - 402
            stale_membership:
              - 409
            terminal_closed:
              - 410
            terminal_observer_lost:
              - 410
            timeout:
              - 504
            unauthenticated:
              - 401
            unavailable:
              - 503
            unrecognised_word:
              - 400
            unsupported_feature:
              - 422
            volume_busy:
              - 409
            volume_incompatible:
              - 409
            volume_lease_lost:
              - 409
            volume_not_attached:
              - 409
            volume_restore_failed:
              - 502
            volume_save_failed:
              - 409
              - 502
            volume_unavailable:
              - 409
            wrong_region:
              - 421
          x-boxrouter-error-retry:
            account_exists: after_fix
            account_unhealthy: after_fix
            actions_authority_changed: after_fix
            actions_candidate_ceiling_withdrawn: after_fix
            actions_candidate_outside_ceiling: after_fix
            actions_candidate_record_invalid: after_fix
            actions_create_budget_exhausted: after_fix
            actions_create_format_mismatch: after_fix
            actions_sandbox_protected: never
            agent_not_found: after_fix
            already_refunded: never
            authorization_error: check_outcome
            candidate_admission_unavailable: never
            capability_unavailable: check_outcome
            capability_unverified: after_fix
            card_setup_rate_limit: safe
            change_in_progress: safe
            code_ref_not_found: after_fix
            code_reference_moved: after_fix
            code_repository_not_authorized: after_fix
            code_repository_not_found: after_fix
            code_repository_not_private: after_fix
            code_repository_not_public: after_fix
            code_repository_resolution_failed: safe
            command_failed: after_fix
            command_observation_busy: safe
            conflict: check_outcome
            contract_violation: check_outcome
            credentials_rejected: after_fix
            database_unavailable: check_outcome
            directory_managed: never
            dispatch_limit_reached: after_fix
            dispute_open: after_fix
            dockerfile_context_unsupported: after_fix
            expired: check_outcome
            forbidden: after_fix
            fraud_review: safe
            gate_unavailable: after_fix
            idempotency_key_reused: after_fix
            identity_unavailable: safe
            image_architecture_unsupported: after_fix
            image_digest_resolution_failed: after_fix
            image_preparation_unsupported: after_fix
            image_setup_timeout: after_fix
            image_tag_unresolved: after_fix
            inline_secret_values_refused: after_fix
            insufficient_credit: after_fix
            internal_error: never
            invalid_manifest_encoding: after_fix
            invalid_observer_hop: after_fix
            invalid_request: after_fix
            invalid_run_spec: after_fix
            invalid_source_manifest: after_fix
            invalid_state: check_outcome
            job_claim_lost: after_fix
            knob_not_named: after_fix
            last_admin: after_fix
            launch_failed: check_outcome
            launch_incomplete: check_outcome
            launch_run_position_conflict: after_fix
            lifetime_cap_exceeded: after_fix
            lifetime_funding_conflict: after_fix
            lifetime_limit: check_outcome
            lifetime_used_up: after_fix
            managed_funding_unavailable: safe
            method_not_allowed: after_fix
            no_eligible_route: after_fix
            not_found: check_outcome
            not_named: after_fix
            not_running: after_fix
            nothing_to_refund: never
            organization_changed: safe
            output_limit: check_outcome
            overloaded: safe
            payment_declined: after_fix
            payment_method_required: after_fix
            payment_provider_rejected: check_outcome
            payment_provider_unavailable: check_outcome
            payments_unavailable: safe
            policy_denied: after_fix
            port_not_listening: after_fix
            preview_access_refused: never
            price_unknown: after_fix
            privacy_admission_unavailable: after_fix
            privacy_persistence_conflict: after_fix
            private_image_managed_route_unavailable: after_fix
            process_observer_lost: check_outcome
            promotion_already_redeemed: never
            promotion_code_invalid: after_fix
            provider_budget_exhausted: never
            provider_error: check_outcome
            provider_not_enabled: after_fix
            provider_policy_unknown: after_fix
            provider_quota: after_fix
            purchase_in_progress: safe
            quota_exceeded: safe
            quote_changed: after_fix
            rate_limited: safe
            recovery_sandbox_mismatch: check_outcome
            refund_amount_changed: after_fix
            refund_below_minimum: never
            refund_daily_limit: safe
            refund_fee_unavailable: safe
            refund_in_progress: safe
            refund_rate_limit: safe
            refund_retry_requires_support: never
            refund_window_closed: never
            repository_file_unsupported: after_fix
            repository_too_large: after_fix
            request_timeout: safe
            reserved_env_refused: after_fix
            resource_limit: check_outcome
            routing_overrides_forbidden: after_fix
            run_limit_exceeded: after_fix
            sandbox_ended: check_outcome
            secret_missing: after_fix
            secrets_unavailable: never
            session_required: never
            source_file_not_found: after_fix
            source_file_too_large: after_fix
            source_rate_limited: safe
            source_ref_not_found: after_fix
            source_repository_not_accessible: after_fix
            source_repository_not_found: after_fix
            source_repository_not_private: after_fix
            source_repository_not_public: after_fix
            source_resolution_failed: safe
            source_resolution_timeout: safe
            spend_limit_exceeded: after_fix
            stale_membership: after_fix
            terminal_closed: after_fix
            terminal_observer_lost: after_fix
            timeout: check_outcome
            unauthenticated: after_fix
            unavailable: safe
            unrecognised_word: after_fix
            unsupported_feature: after_fix
            volume_busy: after_fix
            volume_incompatible: after_fix
            volume_lease_lost: after_fix
            volume_not_attached: after_fix
            volume_restore_failed: check_outcome
            volume_save_failed: check_outcome
            volume_unavailable: after_fix
            wrong_region: after_fix
        message:
          type: string
        details:
          type: object
          description: >
            Code-specific context, spelled for programs. no_eligible_route
            carries considered,

            excluded, closest and nearest; knob_not_named the knob and its
            values;

            unrecognised_word the path, the word and the known ones; not_named
            the gate, what

            the call needed beyond the grant and the corrected requirements
            line; gate_unavailable

            the gate and account; lifetime_limit the ceiling and this run. A
            creation that

            stopped before a sandbox existed may also carry `fallbacks_allowed`,
            `stopped_reason`

            and `attempts`, spelled and bounded as in the sandbox route
            selection.

            capability_unavailable may identify the needed capability. A launch
            preparation failure can follow sandbox creation;

            inspect outcome and cleanup details before creating another. Clients
            must

            preserve fields they do not know.
          properties:
            selection_version:
              type: integer
              minimum: 0
              description: Current selected organization version for organization_changed.
            considered:
              type: integer
              minimum: 0
              description: no_eligible_route only; how many routes were considered.
            excluded:
              type: array
              description: >-
                no_eligible_route only; every considered route that cannot serve
                the request. Each entry names its `provider` and the checks it
                failed (`failed` for a sandbox request, `reasons` and
                `exclusions` for a launch); clients must preserve fields they do
                not know.
              items:
                type: object
                required:
                  - provider
                properties:
                  provider:
                    type: string
            closest:
              type:
                - object
                - 'null'
              description: >-
                no_eligible_route only; the excluded route that fails fewest
                checks, or null.
            nearest:
              type: array
              description: >-
                no_eligible_route only; the smallest changes that would make a
                route eligible, nearest first. An entry names the change
                (`change` and `then_eligible` for a sandbox request, `provider`,
                `distance` and `failing` for a launch); clients must preserve
                fields they do not know.
              items:
                type: object
            requirement:
              type: string
              description: >-
                The SandboxProviderSpec family whose requirement would make this
                operation available.
            purchase_id:
              type: string
              description: >-
                Retained failed credit purchase ID, when a saved-card charge was
                declined.
            preview:
              $ref: '#/components/schemas/CreditRefundPreview'
              description: Current refund preview returned with 409 refund_amount_changed.
            quote:
              $ref: '#/components/schemas/CreditPurchaseQuote'
              description: Current purchase quote returned with 409 quote_changed.
            redemption:
              $ref: '#/components/schemas/CreditPromotion'
              description: >-
                Original promotion redemption returned with 409
                promotion_already_redeemed.
            scope:
              type: string
              enum:
                - workspace
              description: >-
                spend_limit_exceeded only; which configured cap refused
                admission.
            period:
              type: string
              enum:
                - total
                - daily
                - monthly
              description: spend_limit_exceeded only; the cap period that applies.
            limit_nanodollars:
              type: integer
              minimum: 1
              description: spend_limit_exceeded only; exact configured USD nanodollars.
            requested_nanodollars:
              type: integer
              minimum: 1
              description: spend_limit_exceeded only; exact proposed managed hold.
            sign_in_method:
              type: string
              description: >-
                account_exists only: how the existing account with this verified
                address signs in (google, github, email or other).
            masked_email:
              type: string
              description: >-
                account_exists only: that account's address, masked as
                `t…@gmail.com`.
            failure_reason:
              type: string
              maxLength: 100
              description: >-
                Short safe card-decline reason; raw Stripe messages are not
                returned.
            names:
              type: array
              uniqueItems: true
              items:
                $ref: '#/components/schemas/SecretName'
              description: All manifest secret names that could not be resolved.
            fields:
              type: array
              uniqueItems: true
              items:
                type: string
                enum:
                  - preferences.order
                  - preferences.sort
                  - preferences.allow_fallbacks
              description: routing_overrides_forbidden only; the prohibited request inputs.
            volume_id:
              type: string
              description: BoxRouter Volume ID involved in the error.
            writer_sandbox_id:
              type: string
              description: >-
                volume_busy only; the sandbox that holds the volume's writer
                lease, when it is a recorded sandbox.
            checkpoint:
              type: integer
              minimum: 1
              description: Volume checkpoint involved in the error.
            limit_bytes:
              type: integer
              minimum: 0
            observed_bytes:
              type: integer
              minimum: 0
            mismatch:
              type: object
              additionalProperties: false
              required:
                - field
                - recorded
                - requested
              properties:
                field:
                  type: string
                  enum:
                    - image
                    - arch
                recorded:
                  type: string
                requested:
                  type: string
            volume_phase:
              type: string
              enum:
                - check
                - branch
                - lease
                - identity
                - install
                - credential
                - mount
                - metadata
                - checkpoint
                - commit
                - write
            phase:
              type: string
              enum:
                - code
                - setup
                - files
                - background
                - previews
              description: Preparation phase that failed.
            command:
              type: string
              description: Command whose nonzero exit stopped launch preparation.
            exit_code:
              type: integer
              description: >-
                Exit code of the failed command, or of the process that exited
                before its port listened.
            stderr_tail:
              type: string
              description: >
                At most the final 4 KiB of stderr from the failed command, with
                resolved secret values

                redacted. port_not_listening carries the same tail of the
                process's stderr.
            stdout_tail:
              type: string
              description: >-
                port_not_listening only; at most the final 4 KiB of the
                process's stdout.
            agent:
              type: string
              description: Unknown catalog agent name.
            port:
              type: integer
              minimum: 1
              maximum: 65535
              description: >-
                The sandbox port whose preview was refused
                (preview_access_refused) or that never accepted connections
                (port_not_listening).
            revoked:
              type: boolean
              description: >
                preview_access_refused only; true when the unwanted preview was
                ended and no

                access to the port remains. False: it may still be reachable
                (outcome unknown).
            preview_id:
              type: string
              description: >
                preview_access_refused only; the identifier the provider gave
                the preview that may

                still be reachable. Present only for a public preview.
            url:
              type: string
              format: uri
              description: >
                preview_access_refused only; the address of a public preview
                that may still be

                reachable. Never present for a private preview, whose address
                holds a credential,

                and never when it was ended.
        request_id:
          type: string
        doc_url:
          type:
            - string
            - 'null'
          format: uri
          description: >-
            Where the documentation explains this code:
            `https://boxrouter.ai/docs/error-codes#` followed by the code, for
            example `https://boxrouter.ai/docs/error-codes#no_eligible_route`.
            The page has one anchored row per code with what it means and what
            to do. BoxRouter sends it with every error; it is null for a code
            that has no documented row. Clients must accept an error body that
            omits it.
        fix:
          type:
            - string
            - 'null'
          minLength: 1
          description: >-
            Actionable change for the caller when the request cannot be
            completed. A refusal (4xx) always carries one; a server or provider
            fault, including an error event that ends a terminal or process
            stream, can answer null.
        operation_id:
          type:
            - string
            - 'null'
          description: >-
            Durable server operation ID for reconciliation after uncertain
            mutations.
        outcome:
          type: string
          enum:
            - not_started
            - completed
            - partial
            - unknown
          description: >
            What is known about the requested side effect. `not_started` means
            nothing ran

            anywhere, at BoxRouter or at a provider; a launch-setup or
            process-start failure

            after dispatch is `partial` or `unknown`. Unknown and partial forbid
            automatic

            replay and always carry `retry: check_outcome`. An HTTP status alone
            does not establish

            outcome.

            Many refusals report not_started before the provider is asked
            anything. A read

            (GET or HEAD) has no side effect to be unsure about, so its failures
            report

            not_started and carry the code's own retry value. A launch
            preparation failure

            happens after the sandbox was created and setup commands may already
            have run, and

            deleting the sandbox does not undo effects outside it: it reports
            partial once the

            cleanup is confirmed and unknown if it is not. A process that
            wait_for_port ended

            reports partial. volume_restore_failed on a resume or create,

            and volume_save_failed after the guest was read, report unknown or
            partial; they

            carry the operation_id when the call was recorded, and their fix
            does not offer to

            send the call again unchanged.
        retry:
          type: string
          enum:
            - safe
            - after_fix
            - check_outcome
            - never
          x-boxrouter-open-enum: true
          description: >
            What the caller may do next, fixed per code
            (x-boxrouter-error-retry): safe resends the

            same request, after_fix changes the request or setup first,
            check_outcome reads

            `outcome` and the resource before resending, never does not resend.
            Branch on this

            instead of the HTTP status.
        retry_after_seconds:
          type:
            - integer
            - 'null'
          minimum: 0
          description: >-
            How long to wait before a `retry: safe` resend, as the Retry-After
            header says it; null when there is no wait to name.
        provider:
          type:
            - string
            - 'null'
        native_code:
          type:
            - string
            - 'null'
          description: >-
            The provider's machine-readable error code, unchanged, when it
            reports one.
        native_status:
          type:
            - integer
            - 'null'
          description: >-
            The provider's HTTP status, unchanged, when the failure came from a
            provider response.
        native_message:
          type:
            - string
            - 'null'
          maxLength: 4096
          description: >-
            The provider's own error message when it answered with an HTTP 4xx
            refusal (for example a plan limit); null otherwise. Capped at 512
            characters, with whitespace collapsed. Credential secrets and
            token-shaped strings are removed before return. The literal
            `[redacted]` marker is used when all credential secret fields are at
            least 12 characters; legacy saved secrets shorter than 12 characters
            are removed without the marker.
    MaskedCreditCard:
      type: object
      additionalProperties: false
      properties:
        brand:
          type:
            - string
            - 'null'
          description: Card brand; absent for Link methods.
        last4:
          type:
            - string
            - 'null'
          pattern: ^[0-9]{4}$
          description: Last four card digits; absent for Link methods.
        type:
          type: string
          enum:
            - card
            - link
          x-boxrouter-open-enum: true
        email:
          type: string
          format: email
          description: Link account email; present only when type is link.
      allOf:
        - if:
            properties:
              type:
                const: link
            required:
              - type
          then:
            required:
              - email
            properties:
              brand:
                type: 'null'
              last4:
                type: 'null'
        - if:
            properties:
              type:
                const: card
            required:
              - type
          then:
            required:
              - brand
              - last4
            properties:
              brand:
                type: string
              last4:
                type: string
                pattern: ^[0-9]{4}$
  responses:
    Error:
      description: >
        Structured error. `retry` says whether to send the call again (`now`,
        `after_fix`, `inspect` or `never`) and `retry_after_seconds` how long to
        wait first. Unowned and unknown resource IDs both return 404. Admission
        overload returns 503 with code overloaded, outcome not_started and
        Retry-After. It reserves no operation and dispatches no provider work.
        Shared workspace/provider admission budgets return 429 with code
        rate_limited, outcome not_started and Retry-After. An incomplete request
        body can return 408 request_timeout before dispatch. Durable-state
        failures return 503 database_unavailable with outcome unknown; do not
        infer that execution did not occur. A read (GET or HEAD) that meets the
        same failure changed nothing: its message says the state is temporarily
        unavailable, and it is safe to repeat. A provider that cannot be reached
        when a create is prepared returns 502 provider_error with outcome
        not_started; nothing was created, and the create can be retried with the
        same Idempotency-Key or sent to another provider with `providers`.

        Native errors are sanitized; upstream credentials, request bodies and

        another workspace's metadata are never included. Server-Timing is
        present only when the response reached Dispatcher or held-response
        preparation; an earlier response, including pre-Dispatcher admission
        overload, may omit it.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestId'
        Server-Timing:
          $ref: '#/components/headers/ServerTiming'
        Retry-After:
          description: >-
            Seconds before retrying an eligible read; not permission to replay a
            mutation.
          schema:
            type: integer
            minimum: 0
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
  securitySchemes:
    BoxRouterKey:
      type: http
      scheme: bearer
      description: BoxRouter API key; never an upstream provider key.
    BrowserSession:
      type: apiKey
      in: cookie
      name: __Host-boxrouter_session
      description: >-
        HttpOnly server-side session. Local HTTP uses boxrouter_session. Browser
        mutations require the session CSRF token and the configured Origin;
        execution API keys cannot administer sessions or customer keys.

````

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