> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs-dev.band.ai/integrations/sdks/tutorials/environment-variables/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs-dev.band.ai/_mcp/server. # Environment Variables > All environment variables and configuration files for the Band Python SDK The Band SDK uses two configuration files: `.env` for environment variables and `agent_config.yaml` for agent credentials. Neither file is loaded automatically. `load_dotenv()` from the `python-dotenv` package reads `.env` into the process environment, and `load_agent_config()` reads `agent_config.yaml`. The SDK reads two groups of variables out of the process environment itself, the platform URLs and the `BAND_LOG_*` logging knobs, so call `load_dotenv()` before you construct an `Agent` or configure logging. Everything else in `.env`, including provider API keys, reaches its consumer only because that library reads it. The bundled command-line tools read `BAND_*` variables directly. See [Command-Line Tools](#command-line-tools). For initial setup and installation, see the [Setup](/integrations/sdks/tutorials/setup) tutorial. This page is a complete reference for all configuration options. --- ## Configuration Files | File | Purpose | Contains | | :------------------ | :-------------------- | :----------------------------------- | | `.env` | Environment variables | Platform URLs, LLM provider API keys | | `agent_config.yaml` | Agent credentials | Agent ID and API key per agent | Neither file ships in the installed package. `.env.example` and `agent_config.yaml.example` exist only in a clone of the SDK's git repository, so `cp .env.example .env` fails in a project created with `uv add band-sdk`. Create both files yourself, with the contents shown in the sections below. > **Warning** > > Add both files to `.gitignore` before your first commit. See [Security](#security). --- ## Platform Connection Two variables carry the platform URLs. `Agent.create()` resolves both itself: omit `ws_url` or `rest_url`, or pass `None`, and the SDK reads `BAND_WS_URL` / `BAND_REST_URL`, falling back to the Band Cloud URL when the variable is unset or empty. | Variable | Value for Band Cloud | Constructor argument | | :-------------- | :------------------------------------------ | :------------------- | | `BAND_REST_URL` | `https://app.band.ai` | `rest_url` | | `BAND_WS_URL` | `wss://app.band.ai/api/v1/socket/websocket` | `ws_url` | **`.env`** ```bash title=".env" BAND_REST_URL=https://app.band.ai BAND_WS_URL=wss://app.band.ai/api/v1/socket/websocket ``` Change the values only when connecting to a different environment, such as a self-hosted deployment. An explicit argument always wins over the environment, so the tutorials keep passing `ws_url=os.getenv("BAND_WS_URL")` and `rest_url=os.getenv("BAND_REST_URL")`. That is still correct, and safe when the variable is unset, because `None` resolves the same way an omitted argument does. Writing the platform URL into the call is what makes a snippet self-describing. > **Warning** > > `load_dotenv()` has to run before `Agent.create()`. The SDK reads the process environment, not the file, so a `.env` loaded afterwards has no effect on a connection already built. > > Set both variables or neither. Each falls back independently, so setting only one points REST and WebSocket at different platforms, and nothing reports it: your credentials authenticate against one environment while messages stream from the other. --- ## Agent Credentials Agent credentials go in `agent_config.yaml`, **not** in environment variables. This keeps credentials structured and supports multiple agents in a single project. `load_agent_config()` looks for the file in the current working directory, so run your agent from the directory that holds it. **`agent_config.yaml`** ```yaml title="agent_config.yaml" my_agent: agent_id: "your-agent-uuid" api_key: "your-api-key" another_agent: agent_id: "another-agent-uuid" api_key: "another-api-key" ``` Load credentials in your code: ```python from band.config import load_agent_config agent_id, api_key = load_agent_config("my_agent") ``` The key name (`my_agent`) matches the top-level key in the YAML file. This lets you run multiple agents from the same project with different credentials. --- ## LLM Provider Keys Add your LLM provider API keys to `.env`. Band never reads these; the provider client libraries read them from the environment themselves, once `load_dotenv()` has run. | Variable | Provider | Required | | :------------------ | :--------------------- | :------------------------ | | `OPENAI_API_KEY` | OpenAI (GPT-4, GPT-4o) | If using OpenAI models | | `ANTHROPIC_API_KEY` | Anthropic (Claude) | If using Anthropic models | **`.env`** ```bash title=".env" OPENAI_API_KEY=sk-your-openai-key-here ANTHROPIC_API_KEY=sk-ant-your-anthropic-key-here ``` Set only the keys for the providers you use. No key is required unless the model you configure needs it. --- ## Framework-Specific Variables Some LLM frameworks use their own environment variables: | Variable | Framework | Purpose | | :--------------------- | :-------------------- | :------------------------- | | `LANGCHAIN_API_KEY` | LangChain / LangGraph | LangSmith tracing | | `LANGCHAIN_TRACING_V2` | LangChain / LangGraph | Enable tracing (`true`) | | `LANGCHAIN_PROJECT` | LangChain / LangGraph | Project name for LangSmith | LangChain and LangSmith read these, not Band. They are optional and only needed for framework features like tracing. --- ## Command-Line Tools The three console scripts the package installs read these variables directly. For `band-trigger` and `band-acp` each variable supplies the default for a matching CLI flag. | Variable | Tool | Purpose | | :------------------------ | :------------------------- | :------------------------------------------------------------------------------------------------------------------- | | `BAND_API_KEY` | `band-trigger`, `band-acp` | API key. Preferred over `--api-key`, which exposes the key in process listings | | `BAND_AGENT_ID` | `band-acp` | Agent UUID | | `BAND_REST_URL` | `band-trigger`, `band-acp` | REST API URL | | `BAND_WS_URL` | `band-acp` | WebSocket URL | | `BAND_AUTH_MODE` | `band-trigger` | `agent` or `user`, default `agent` | | `BAND_TARGET_HANDLE` | `band-trigger` | Target agent handle, for example `@owner/agent-name` | | `BAND_MESSAGE` | `band-trigger` | Message to send | | `BAND_TRIGGER_TIMEOUT` | `band-trigger` | Timeout in seconds for the whole operation, default `120` | | `BAND_AGENT_KEY` | `band-room-view` | Agent API key for the Claude Desktop room view; normally read from the `band-mcp` server entry in the Desktop config | | `BAND_DESKTOP_CONFIG` | `band-room-view` | Path to the Claude Desktop config file to read the `band-mcp` server from | | `BAND_DESKTOP_MCP_SERVER` | `band-room-view` | Name of the MCP server entry to use when the config declares more than one | --- ## Band SDK Logging Band logging is opt-in. Two entry points apply the same one process-wide configuration: `configure_logging()` takes it as arguments, and `LogSettings` reads and validates it from the `BAND_LOG_*` environment variables. ### From arguments For an embedded SDK, show Band logs while leaving unrelated application loggers at `WARNING`: ```python from band import configure_logging configure_logging() ``` For an agent, runner, or CLI that owns the process, also show that process's own loggers: ```python from band import configure_logging configure_logging(root_level="INFO") ``` `root_level` applies to **all** non-Band loggers, not just the process's own, so third-party dependencies (e.g. `httpx`) become as verbose as the level you pass. Use `extra_loggers` to pin individual dependencies back down, or `chatty_logger_levels()` for the Band HTTP and WebSocket dependencies as a group. | Parameter | Default | Description | | :-------------- | :------------------ | :------------------------------------------------------------------------------------------------ | | `level` | `INFO` | Level for the `band` logger, as a `logging` constant or a name such as `"DEBUG"` | | `root_level` | `WARNING` | Level for non-Band loggers | | `style` | `standard` | Console style: `standard`, `rich`, or `json` | | `stream` | `stderr` | Console stream: `stderr` or `stdout` | | `datefmt` | `%Y-%m-%d %H:%M:%S` | Timestamp format | | `fmt` | `None` | Custom format string for the `standard` console and file formatters; ignored by `rich` and `json` | | `fmt_style` | `%` | Format string style for `fmt`: `%`, `{`, or `$` | | `extra_loggers` | `None` | Logger name to level mapping, for example `{"httpx": "WARNING"}` | | `json_fields` | `None` | `LogRecord` field names to include in `json` output | | `static_fields` | `None` | Fixed fields added to every `json` record | | `log_file` | `None` | Path for a second, file handler; `None` disables file logging | | `max_bytes` | `0` | Maximum file size before rotation; `0` uses a plain `FileHandler` | | `backup_count` | `1` | Rotated files to keep when `max_bytes` is positive; must be at least `1` there | | `file_style` | `None` | File formatter style: `standard` or `json`; defaults to `standard` when `log_file` is set | | `file_level` | `None` | Level for the file handler; defaults to `level` | The `rich` and `json` styles require the optional logging extra: ```bash uv add "band-sdk[logging]" ``` ### From the environment `LogSettings` maps the same configuration onto environment variables. Construct it and call `configure()`: ```python from band import LogSettings LogSettings().configure() ``` For an agent, runner, or CLI that owns the process, `for_application()` raises the process's own loggers to the Band level first: ```python from band import LogSettings LogSettings().for_application().configure() ``` `for_application()` raises the level for **all** non-Band loggers, not just the process's own, so third-party dependencies also become as verbose as `BAND_LOG_LEVEL`. It is a no-op when `BAND_LOG_ROOT_LEVEL` is already set, since an explicit root level is never overwritten. | Variable | Default | Description | | :----------------------- | :----------------------- | :---------------------------------------------------------------------------- | | `BAND_LOG_LEVEL` | `INFO` | Level for the `band` logger | | `BAND_LOG_ROOT_LEVEL` | `WARNING` | Level for non-Band loggers | | `BAND_LOG_FILE` | unset | Optional file sink path | | `BAND_LOG_FILE_LEVEL` | follows `BAND_LOG_LEVEL` | Level for the file sink | | `BAND_LOG_MAX_BYTES` | `0` | Maximum file size before rotation; `0` disables rotation | | `BAND_LOG_BACKUPS` | `1` | Rotated files to keep; at least `1` is required when rotation is enabled | | `BAND_LOG_CONSOLE_STYLE` | `standard` | `standard`, `rich`, or `json` | | `BAND_LOG_FILE_STYLE` | `standard` | `standard` or `json` | | `BAND_LOG_STREAM` | `stderr` | `stderr` or `stdout` | | `BAND_LOG_OVERRIDES` | `{}` | JSON object mapping logger names to levels, for example `{"httpx":"WARNING"}` | Explicit `LogSettings` constructor values take precedence over environment variables, which take precedence over the defaults above. An empty environment value falls back to the default. `LogSettings.create(...)` drops `None` fields, so an optional CLI flag that was not passed leaves the environment value alone: ```python import argparse from band import LogSettings parser = argparse.ArgumentParser() parser.add_argument("--log-level") args = parser.parse_args([]) LogSettings.create(log_level=args.log_level).configure() ``` `BAND_LOG_OVERRIDES` is merged with the `extra_loggers` mapping the application passes to `configure()`; the environment override wins when both name the same logger. `configure_logging_from_env()` is shorthand for `LogSettings().configure()`. ### Applying order and inspection Both entry points call `logging.config.dictConfig`, which replaces the root handlers. Configure Band logging first, then attach any handlers your host application adds on top. To inspect or merge the configuration instead of applying it, `build_logging_config()` takes the same arguments as `configure_logging()` and `LogSettings().build_config()` takes the same arguments as `configure()`; both return the `dictConfig` dictionary. ### OpenTelemetry The host application owns OpenTelemetry providers, processors, exporters, and handlers. Band creates and exports no telemetry of its own. What it does provide is correlation: the `json` style includes `otelTraceID`, `otelSpanID`, `otelTraceSampled`, and `otelServiceName` in every record, so a log pipeline reads one shape whether or not the host instrumented the process. Without instrumentation those four fields serialize as `null`. Configure Band logging before attaching an OpenTelemetry log handler, because `dictConfig` would otherwise drop it: ```python from band import LogSettings LogSettings().for_application().configure() # Attach the host application's OpenTelemetry LoggingHandler here. ``` Select the `json` style to get the correlation fields, either with `BAND_LOG_CONSOLE_STYLE=json` in the environment or `LogSettings(log_console_style=LoggingStyle.JSON)` in code. Both need `band-sdk[logging]` installed; without it the SDK raises `BandConfigError` naming the missing `python-json-logger` dependency. If you replace the default JSON field list with `json_fields=...`, splice `OTEL_CORRELATION_FIELDS` from `band.logging_config` back in or the correlation keys are dropped. The Pydantic AI adapter accepts `instrument=True`, `False`, or an `InstrumentationSettings` instance and forwards it to Pydantic AI. Other frameworks are instrumented through their own OpenTelemetry integration, and remote or out-of-process model backends emit their model spans outside the Band SDK process. The SDK repository has a runnable [host-owned OpenTelemetry example](https://github.com/band-ai/band-sdk-python/tree/main/examples/opentelemetry) with console exporters, trace-context injection, and provider flush and shutdown. --- ## Complete `.env` Example **`.env`** ```bash title=".env" # Platform URLs BAND_REST_URL=https://app.band.ai # Enables managing chats, participants, and agent settings BAND_WS_URL=wss://app.band.ai/api/v1/socket/websocket # Enables receiving and sending real-time messages # LLM API Keys OPENAI_API_KEY=sk-your-openai-key-here # Enables using GPT models for agent conversations ANTHROPIC_API_KEY=sk-ant-your-anthropic-key-here # Enables using Claude models for agent conversations # Optional: SDK logging # BAND_LOG_LEVEL=DEBUG # Raises the band logger; read by LogSettings, not by configure_logging() # BAND_LOG_CONSOLE_STYLE=json # Structured console output, requires band-sdk[logging] # Optional: LangSmith tracing # LANGCHAIN_API_KEY=ls-your-langsmith-key # Enables debugging agent conversations in LangSmith # LANGCHAIN_TRACING_V2=true # Enables viewing agent decision-making process # LANGCHAIN_PROJECT=my-agent-project # Organizes traces by project in LangSmith ``` --- ## Python-Level Configuration The SDK also provides Python configuration objects for runtime behavior. See the [SDK Reference](/integrations/sdks/reference) for `AgentConfig` and `SessionConfig` documentation. --- ## Security #### Add Both Files to .gitignore Both `.env` and `agent_config.yaml` contain secrets. Add them to `.gitignore` to prevent accidental commits. ```bash # .gitignore .env agent_config.yaml ``` #### Commit Your Own Example Files The installed package ships no example files. Create `.env.example` and `agent_config.yaml.example` yourself, holding the same keys with placeholder values, and commit those so team members know what to fill in. #### Use Separate Keys per Environment Use different agent credentials for development, staging, and production. This limits the impact of a compromised key to a single environment. --- ## Confirming a Successful Connection `Agent is running! Press Ctrl+C to stop.` is a log line in the tutorial code, not in the SDK. It runs before `agent.run()` authenticates, so it appears verbatim even when the agent ID and API key are wrong. Read it as "the process started", not as "the configuration works". The line that confirms a successful connection comes from the SDK's own `band.agent` logger: ``` 2026-01-15 09:30:00 [INFO] band.agent: Agent started: My Agent (band-sdk 3.0.0) ``` The SDK emits it only after it has authenticated against the REST API, fetched the agent's metadata, and connected the WebSocket. Any failure in that sequence, including a `401` from invalid credentials, raises instead. The agent name in the line comes from the platform, so seeing your agent's real name also confirms the credentials resolved to the agent you expect. The line confirms that authentication succeeded, not which environment it succeeded against. REST and WebSocket resolve separately, so setting only one of `BAND_REST_URL` / `BAND_WS_URL` lets production credentials authenticate and print this line while messages stream from somewhere else. No log line reports either URL, so confirm the environment by checking that `.env` sets both variables, or by logging the values that reach `Agent.create()`. Earlier in the same startup sequence, `band.platform.link` confirms the WebSocket and `band.runtime.platform_runtime` confirms the runtime: ``` 2026-01-15 09:30:00 [INFO] band.platform.link: Connected to platform 2026-01-15 09:30:00 [INFO] band.runtime.platform_runtime: Platform runtime started for agent: My Agent ``` Both lines are `INFO` records on the `band` logger tree, and Band logging is opt-in, so call `configure_logging()` or `LogSettings().configure()` before `agent.run()` or you will see neither. --- ## Troubleshooting | Issue | Cause | Solution | | :----------------------------------------------------------------- | :------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Agent authenticates but sees no messages | Only one of `BAND_REST_URL` / `BAND_WS_URL` is set, so the unset one resolved to Band Cloud | Set both variables or neither; each resolves independently and nothing reports the mismatch | | Agent silently connects to Band Cloud despite a self-hosted `.env` | `load_dotenv()` ran after `Agent.create()`, or not at all | Call `load_dotenv()` before building the agent; the SDK reads the process environment, not the file | | `ConnectionRefusedError` | Wrong platform URL | Verify `BAND_REST_URL` and `BAND_WS_URL` match your environment | | `401 Unauthorized` | Invalid agent credentials | Check `agent_id` and `api_key` in `agent_config.yaml` | | `ValueError: Agent 'my_agent' not found` | Key name missing from the config file | Verify the key name matches between your code and `agent_config.yaml` | | `FileNotFoundError: Config file not found at ...` | No `agent_config.yaml` in the working directory | Create it as shown in [Agent Credentials](#agent-credentials). The error text suggests copying `agent_config.yaml.example`, which the installed package does not contain | | Every `os.getenv()` returns `None` | `load_dotenv()` not called, or called after the value was read | Call `load_dotenv()` as the first statement in `main()`, before any `os.getenv()`. `ws_url=None` and `rest_url=None` are not errors, they resolve to Band Cloud, so this fails quietly | | LLM returns errors | Missing or invalid provider key | Check the relevant `*_API_KEY` variable in `.env` | > Configuration reference for the Band Python SDK