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

# Bash

> Give any model a sandboxed shell to run commands server-side

export const API_KEY_REF = '<OPENROUTER_API_KEY>';

<Badge color="blue">Beta</Badge>

<Note>
  **Beta**

  Server tools are currently in beta. The API and behavior may change.

  Sandboxed execution (`engine: "openrouter"`) is available on the global
  endpoint (`openrouter.ai`) only. Requests through the
  [in-region endpoints](/docs/guides/features/in-region-routing)
  (`eu.openrouter.ai`, `us.openrouter.ai`) are rejected.
</Note>

The `openrouter:bash` server tool gives a model the ability to run shell
commands. It mirrors Anthropic's native bash tool and is available on the
**Anthropic Messages API only** (`/api/v1/messages`). When the model needs to
run a command, it calls the tool; with server-side execution enabled,
OpenRouter runs the command inside an isolated, sandboxed Linux container and
returns the combined output and exit code.

<Note>
  **Messages API only**

  `openrouter:bash` is only available on the Anthropic Messages API. Requesting
  it on the Chat Completions or Responses API returns a `400` error.

  For **sandboxed** commands on those APIs, use
  [Shell](/docs/guides/features/server-tools/shell), which runs server-side in the same
  container infrastructure and is available on the Responses API. For
  **client-side** execution, send a normal function tool and run the command
  yourself.
</Note>

## How It Works

1. You include `{ "type": "openrouter:bash" }` in your `tools` array on a
   Messages API request.
2. Based on the user's prompt, the model decides whether it needs to run a
   command and emits the call with one or more shell commands.
3. With `engine: "openrouter"`, OpenRouter executes the commands sequentially
   in a sandboxed container.
4. The combined `stdout`, `stderr`, and `exitCode` are returned to the model.
5. The model incorporates the result into its response. It may run multiple
   command batches in a single request if needed.

Server-side execution is opt-in: set `engine: "openrouter"` on the tool (see
[Execution engine](#execution-engine)). With the default `engine` (`auto`), the
tool is a local, human-in-the-loop tool instead: the call is returned to your
application to run client-side, and nothing executes on OpenRouter's servers.

## Quick Start

Send the tool on a Messages API request. Set `engine: "openrouter"` to run
commands server-side in the OpenRouter sandbox.

<Template
  data={{
API_KEY_REF,
MODEL: 'anthropic/claude-sonnet-4.5'
}}
>
  <CodeGroup>
    ```typescript title="TypeScript" expandable lines theme={null}
    const response = await fetch('https://openrouter.ai/api/v1/messages', {
      method: 'POST',
      headers: {
        Authorization: 'Bearer {{API_KEY_REF}}',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        model: '{{MODEL}}',
        max_tokens: 1024,
        messages: [
          {
            role: 'user',
            content: 'List the files in the current directory and show me the OS version.'
          }
        ],
        tools: [
          { type: 'openrouter:bash', parameters: { engine: 'openrouter' } }
        ]
      }),
    });

    const data = await response.json();
    console.log(data);
    ```

    ```python title="Python" expandable lines theme={null}
    import requests

    response = requests.post(
      "https://openrouter.ai/api/v1/messages",
      headers={
        "Authorization": f"Bearer {{API_KEY_REF}}",
        "Content-Type": "application/json",
      },
      json={
        "model": "{{MODEL}}",
        "max_tokens": 1024,
        "messages": [
          {
            "role": "user",
            "content": "List the files in the current directory and show me the OS version."
          }
        ],
        "tools": [
          {"type": "openrouter:bash", "parameters": {"engine": "openrouter"}}
        ]
      }
    )

    data = response.json()
    print(data)
    ```

    ```bash title="cURL" lines theme={null}
    curl https://openrouter.ai/api/v1/messages \
      -H "Authorization: Bearer {{API_KEY_REF}}" \
      -H "Content-Type: application/json" \
      -d '{
        "model": "{{MODEL}}",
        "max_tokens": 1024,
        "messages": [
          {
            "role": "user",
            "content": "List the files in the current directory."
          }
        ],
        "tools": [
          {"type": "openrouter:bash", "parameters": {"engine": "openrouter"}}
        ]
      }'
    ```
  </CodeGroup>
</Template>

## Configuration

The bash tool accepts optional `parameters` to choose its execution
environment:

```json lines theme={null}
{
  "type": "openrouter:bash",
  "parameters": {
    "environment": { "type": "container_auto" }
  }
}
```

| Parameter | Type | Default | Description |
| - | - | - | - |
| `environment` | object | `container_auto` | Execution environment. Use `{ "type": "container_auto" }` for an OpenRouter-managed container, or `{ "type": "container_reference", "container_id": "..." }` to reuse an existing container. See [Containers](/docs/guides/features/containers) |
| `engine` | string | `auto` | Where commands run: `openrouter` runs them server-side in the OpenRouter sandbox; `auto`/`native` (the default) return the tool call to your application to run client-side. See [Execution engine](#execution-engine) |

Defaults and caps are server-enforced and may change while the tool is in beta:

| Limit | Default | Maximum |
| - | - | - |
| `timeout_ms` per batch | 120,000 (2 minutes) | 300,000 (5 minutes) |
| `max_output_length` per stream, per batch | 16,384 characters | 65,536 characters |
| `commands` per call | — | 100 |

When commands run in the OpenRouter sandbox, a `timeout_ms` or `max_output_length` above the maximum is clamped to it. A call with more than 100 commands is rejected.

### Network Policy

When commands run in the OpenRouter sandbox (`engine: "openrouter"`), containers have **no outbound internet access by default**. The container configuration objects accept a `network_policy` field:

```json lines theme={null}
{
  "type": "openrouter:bash",
  "parameters": {
    "engine": "openrouter",
    "environment": {
      "type": "container_auto",
      "network_policy": {
        "type": "allowlist",
        "allowed_domains": ["pypi.org", "files.pythonhosted.org"]
      }
    }
  }
}
```

| Policy | Behavior |
| - | - |
| `{ "type": "disabled" }` | No outbound internet access |
| `{ "type": "allowlist", "allowed_domains": [...] }` | Outbound access restricted to hosts matching the listed hostnames or glob patterns (max 50) |
| omitted | Defaults to `disabled` — no outbound internet access. For unrestricted egress, use an allowlist of `["*"]` |

The policy is **fixed when a container starts**: sending a different `network_policy` to a warm container fails the request with a `409`. Do not try to change a running container's policy — send the same policy for the container's lifetime.

Platform constraints for allowlisted traffic:

* Only ports 80 and 443 are reachable.
* DNS resolution is provided by the platform and cannot be overridden by container configuration.
* Entries are lowercase hostnames or glob patterns — no schemes, paths, or ports. `*` matches any run of characters (`*.example.com`, `google.*.com`). An exact hostname does not cover its subdomains: `example.com` does not allow `api.example.com`; use `*.example.com` or list each hostname.
* `pip install` needs both `pypi.org` and `files.pythonhosted.org` (or `*.pythonhosted.org`) in the allowlist.

Requests to hosts outside the policy fail inside the container with a connection error (HTTP traffic sees a `520` status), which the model can read on `stderr` and react to.

### Call Arguments

The model generates the call arguments. They mirror Anthropic's native bash
tool action:

| Field | Type | Description |
| - | - | - |
| `command` | string | A single shell command to run |
| `commands` | string\[] | Shell commands to run sequentially |
| `restart` | boolean | Reset the shell session (see [Restart](#restart)) |
| `timeout_ms` | integer | Maximum execution time for the batch |
| `max_output_length` | integer | Maximum characters returned per stream (`stdout` and `stderr` are each capped to this) |

## Anthropic Messages API native bash tool

On the Messages API you can also use Anthropic's native bash tool shape
(`{ "type": "bash_20250124", "name": "bash" }`) instead of `openrouter:bash`.

```json lines theme={null}
{
  "tools": [
    { "type": "bash_20250124", "name": "bash" }
  ]
}
```

The native `bash_20250124` tool runs **client-side** by default: OpenRouter
returns the `tool_use` to your application to execute locally, exactly as a
direct call to the provider would. To run commands server-side in OpenRouter's
sandbox instead, send the OpenRouter tool shape with `engine: "openrouter"`:

```json lines theme={null}
{
  "tools": [
    {
      "type": "openrouter:bash",
      "parameters": { "engine": "openrouter" }
    }
  ]
}
```

### Restart

Anthropic's bash tool supports a `restart` action that resets the shell
session. When the model emits `{ "restart": true }`, OpenRouter provisions a
fresh sandbox container, so commands run after a restart start from a clean
state. The reset applies for the remainder of the current agentic turn. To keep
a container alive across separate API requests in a conversation, send a stable
`session_id` on each request; the sandbox is keyed by it.

## Execution engine

The `engine` parameter controls where commands run. To check whether an
endpoint runs bash natively, read `native_tools` on the endpoint object. See
[Native Execution](/docs/guides/features/server-tools#native-execution).

* `openrouter`: run commands server-side in the OpenRouter sandbox.
* `auto` (default) / `native`: local, human-in-the-loop execution. The tool
  call is returned to your application, which runs the commands itself and
  sends the results back on the next request — no commands are executed on any
  server. The native `bash_20250124` tool always uses this behavior, since it
  has no `engine` field.

This lets you opt into OpenRouter's sandboxed execution with `openrouter`, while
the default leaves command execution entirely to your own application.

`engine` selects where commands run, not which APIs accept the tool: every
engine, `openrouter` included, is Messages-API-only. For sandboxed commands on
the Responses API, use [Shell](/docs/guides/features/server-tools/shell).

## Response Format

When the model calls the bash tool, it receives a response like:

```json lines theme={null}
{
  "command": "ls -la",
  "stdout": "total 0\ndrwxr-xr-x 2 root root 40 Jun  1 12:00 .\n",
  "stderr": "",
  "exitCode": 0
}
```

A non-zero `exitCode` indicates the command itself failed; the error output is
returned on `stderr` so the model can read and react to it.

## Output files

When a command runs in the OpenRouter sandbox (`engine: "openrouter"`) and creates or changes files in the home directory (`/workspace/home`), the tool result lists them in a `files` array, next to the `container_id` the command ran in:

```json lines theme={null}
{
  "container_id": "sess_abc123",
  "files": [
    {
      "type": "container_file_citation",
      "container_id": "sess_abc123",
      "file_id": "cfile_b3V0L3JlcG9ydC5jc3Y",
      "filename": "out/report.csv",
      "start_index": 0,
      "end_index": 0
    }
  ]
}
```

* Each tool result lists at most 10 files, most recently changed first. When a call changes more than 10 files, the older ones are left out of `files` but are still saved in the container.
* `files` lists every file a command created or changed, including scratch and intermediate files. Deleted files are not listed.
* The final assistant message does not carry these files as annotations. Read them from the tool results or from the container.

To get every file in the container, call `GET /api/v1/containers/{container_id}/files` after the response completes. It lists all saved files, not just the last 10, sorted by path, and leaves out files that were deleted. The list is paginated (100 files per page by default, up to 1000 with `limit`): while `has_more` is `true`, pass the response's `last_id` as the `after` parameter to get the next page. Download a file with `GET /api/v1/containers/{container_id}/files/{file_id}/content`. Container files are deleted 30 days after the container was last used; to keep one longer, [promote it to your workspace](/docs/guides/features/containers#save-a-container-file-to-your-workspace). See [Download container files](/docs/guides/features/containers#download-container-files) for the full API.

To separate the files you want to show users from scratch files, tell the model to write its final outputs to one directory, such as `~/outputs/`, and filter the file list on that `filename` prefix. Files written outside the home directory, such as `/tmp`, are not saved or listed.

## Pricing

Sandbox time (commands run with `engine: "openrouter"`) is billed at **\$0.0001 per second**. The clock starts when a request first runs a sandbox command and stops when the response completes. A container that is idle between requests is not billed.

A request that starts a new or sleeping container is billed a minimum of 30 seconds. Later requests that reuse the same warm container pay only their metered time.

## Security

Running shell commands is powerful and is sandboxed by design:

* Commands execute in an isolated container, not on OpenRouter
  infrastructure or your machine. With `container_auto` the container is
  ephemeral; with `container_reference` it persists across requests.
* Containers are scoped per account, so they are never shared across tenants.
* Network access is intended to be disabled by default at the container level.
* Execution time is bounded by `timeout_ms`, clamped to 5 minutes.
* `stdout` and `stderr` are each truncated to `max_output_length`, a per-stream
  cap clamped to 65,536 characters. See [Configuration](#configuration) for the
  defaults.

## Next Steps

* [Server Tools Overview](/docs/guides/features/server-tools). Learn about
  server tools
* [Containers](/docs/guides/features/containers). How sandbox
  containers work
* [Web Fetch](/docs/guides/features/server-tools/web-fetch). Fetch content
  from URLs
* [Tool Calling](/docs/guides/features/tool-calling). Learn about user-defined
  tool calling
