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

# Configuration

> Environment variables, init(), and credential rotation.

The SDK reads its credentials from the environment and starts on the first call. `init()` overrides that: it accepts credentials in code, pays setup cost up front, and registers identity getters.

## Environment variables

| Variable             | Default                    | Notes                                                                                                 |
| -------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------- |
| `BENTOLABS_API_KEY`  | (required)                 | From [platform.bentolabs.ai](https://platform.bentolabs.ai). Format `bl_pk_...` (validated up front). |
| `BENTOLABS_BASE_URL` | `https://api.bentolabs.ai` | Override only for self-hosted or local dev. Must be `http://` or `https://`.                          |

## In-code config

`init()` kwargs override env vars.

```python theme={null}
import bentolabs_sdk.analytics as bento

bento.init(api_key="bl_pk_explicit", base_url="http://localhost:8080")
```

`init()` is optional if you only use `bento.track_ai`. The first call lazy-initializes from env vars. Call `init()` explicitly to pay setup cost up front (cold-start sensitive workloads), pass credentials from code, or **register identity getters** (the next section). For [integrations](/python/integrations), call `bento.init()` before `bento.instrument()` so the getters are registered before the first captured call.

## Identity getters

`init()` accepts `user_id`, `session_id`, and `tags` kwargs. Bento invokes them on every span, whether captured by an integration or emitted by `bento.track_ai`, and writes the result to `gen_ai.user.id`, `gen_ai.conversation.id`, and `langfuse.tags`.

```python theme={null}
bento.init(
    user_id=lambda: get_current_user_id(),
    session_id=lambda: get_current_session_id(),
    tags=["env:prod", "tenant:acme"],
)
```

Each kwarg accepts three forms:

| Form              | Effect                                                                                                 |
| ----------------- | ------------------------------------------------------------------------------------------------------ |
| Zero-arg callable | Invoked on every span. Use for per-request identity (`ContextVar.get`, framework state, auth library). |
| Static value      | Stamped on every span as-is. Use for single-tenant apps.                                               |
| Omitted           | Keep whatever was previously registered (partial `init()` is an update, not a replace).                |
| Explicit `None`   | Clear the source for that field.                                                                       |

```python theme={null}
bento.init(user_id=get_user)            # registers user_id only
bento.init(session_id=get_session)      # user_id getter survives
bento.init(user_id=None)                # explicitly clears user_id
```

<Tip>
  A getter that raises is swallowed by the SDK and the field is dropped for that span. Telemetry must never break the host app.
</Tip>

See [Integrations → Identity at init](/python/integrations#identity-at-init) for the three common bridges (framework state, ContextVar, auth library), and [Identity helpers](/python/identity) for late-binding (`update_current_trace`, `propagate_attributes`).

## Lifecycle

| When                               | What to call                                                                                                 |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Long-running service               | Nothing. Lazy init on the first `track_ai` works.                                                            |
| Serverless or cold-start sensitive | Call `bento.init()` at module load to pay the setup cost up front instead of on a user request.              |
| Short-lived script or Lambda       | Call `bento.flush()` before exit. Spans batch by default; without flushing, the last few calls may not ship. |
| Rotating credentials               | `bento.shutdown()` then `bento.init(api_key="bl_pk_new...")`.                                                |

<Warning>
  Calling `init()` twice with conflicting credentials raises `BentoAuthError("already_initialized")`. Call `shutdown()` first to rotate.
</Warning>

## Errors

The SDK raises `BentoAuthError` with a typed `code` for fail-fast cases.

```python theme={null}
from bentolabs_sdk import BentoAuthError

try:
    bento.init()
except BentoAuthError as exc:
    if exc.code == "missing_api_key":
        # telemetry disabled or misconfigured
        pass
    elif exc.code == "invalid_api_key_format":
        # key doesn't start with bl_pk_
        pass
    elif exc.code == "already_initialized":
        # conflicting credentials, call shutdown() first
        pass
    raise
```

| Code                     | When                                                            |
| ------------------------ | --------------------------------------------------------------- |
| `missing_api_key`        | No key passed and `BENTOLABS_API_KEY` not set.                  |
| `invalid_api_key_format` | Key does not start with `bl_pk_`.                               |
| `already_initialized`    | `init()` called twice with conflicting `api_key` or `base_url`. |

## Resolution helpers

For tools that need the resolved config without standing up a tracer:

```python theme={null}
from bentolabs_sdk import resolve_options

cfg = resolve_options()
print(cfg.api_key, cfg.base_url)
```

Returns a frozen `BentoConfig(api_key, base_url)` dataclass. Same resolution order as `init()`.
