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

# Authentication and configuration

> Provide HiddenLayer credentials to the Python SDK, choose a region or base URL, and configure timeouts, retries, proxies, and headers.

Every request to the HiddenLayer API must be authenticated, and it must go to the correct regional API host. This page explains how the SDK gets your credentials, how it decides where to send requests, and which client options you can change.

## Authentication methods

The SDK supports two ways to authenticate. Configure only one of them.

| Method | Credentials | When to use it |
| - | - | - |
| OAuth2 client credentials (recommended) | Client ID and client secret | Services, scripts, CI/CD pipelines, and most other cases. The SDK obtains and refreshes tokens for you. |
| Bearer token | A pre-issued access token | You already obtain tokens through another system and want to pass one in directly. |

### OAuth2 client credentials

OAuth2 client credentials is a standard flow in which an application authenticates as itself by exchanging a client ID and client secret for a short-lived access token. You create the client ID and client secret in the HiddenLayer Console. See [Create API Key](/docs/products/console/apikey_aisec_platform#create-api-key).

When you provide a client ID and client secret, the SDK handles the token for you:

1. Before the first API request, the client sends a `POST` request to `{base_url}/oauth2/token` with `grant_type=client_credentials`, authenticating with your client ID and client secret using HTTP Basic authentication.
2. The client caches the returned access token and adds it to each request as an `Authorization: Bearer <token>` header.
3. The client refreshes the token shortly before it expires, based on the `expires_in` value in the token response (3600 seconds if the response does not include it). Token handling is thread-safe.
4. If the API returns `401 Unauthorized` because the cached token expired, the client discards the token, gets a new one, and retries the request.

Because the token URL is built from the base URL, your credentials must belong to the same region as the base URL. A `prod-eu` API key does not work against the `prod-us` host.

> [!NOTE]
>
> If the token request fails, the SDK does not raise an error at that point. It sends the API request without a token, and the API responds with `401`, which the SDK raises as `AuthenticationError`. If you see `AuthenticationError`, check your client ID, client secret, and region.

### Bearer token

If you already have an access token, provide it as a bearer token with the `HIDDENLAYER_TOKEN` environment variable or the `bearer_token` argument. The SDK sends it as `Authorization: Bearer <token>` and does not refresh it. When the token expires, requests fail with `AuthenticationError` until you supply a new one.

> [!WARNING]
>
> If you configure both a bearer token and a client ID and client secret, the OAuth2 flow overwrites the bearer token's `Authorization` header. Configure only one method to avoid confusion.

If you configure neither, the client is still created, but requests are sent without credentials and fail with `AuthenticationError`.

## Environment variables

The client reads these environment variables when you create it. Arguments that you pass to the constructor take precedence over environment variables.

| Variable | Purpose | Example value |
| - | - | - |
| `HIDDENLAYER_CLIENT_ID` | Client ID for OAuth2 client credentials. Used as `client_id`. | Your client ID |
| `HIDDENLAYER_CLIENT_SECRET` | Client secret for OAuth2 client credentials. Used as `client_secret`. | Your client secret |
| `HIDDENLAYER_TOKEN` | Pre-issued bearer token. Used as `bearer_token`. | An access token |
| `HIDDENLAYER_BASE_URL` | Base URL for all requests. Use it for self-hosted deployments or a local container. | `http://localhost:8000` |
| `HIDDENLAYER_CUSTOM_HEADERS` | Extra HTTP headers added to every request, one `Name: value` pair per line. Headers passed in `default_headers` win on conflict. | `X-Team: ml-platform` |
| `HIDDENLAYER_LOG` | Enables SDK logging. Set to `info` or `debug`. | `info` |

> [!WARNING]
>
> `HIDDENLAYER_LOG=debug` logs request options, including JSON request bodies. These can contain prompts, model outputs, and other sensitive data. Do not enable debug logging in production or anywhere logs are shared. See [Security and production guidance](/docs/integrations/sdk_python/security).

### Set environment variables

Set your credentials in the terminal session that runs your code:

**macOS/Linux**

```bash theme={null}
export HIDDENLAYER_CLIENT_ID="your-client-id"
export HIDDENLAYER_CLIENT_SECRET="your-client-secret"
```

**Windows**

```powershell theme={null}
$env:HIDDENLAYER_CLIENT_ID = "your-client-id"
$env:HIDDENLAYER_CLIENT_SECRET = "your-client-secret"
```

Replace the placeholder values with your own. These commands set the variables only for the current terminal session. In production, use your platform's secret management, such as CI/CD secrets, a container orchestrator's secrets, or a cloud secret manager, to inject the variables at runtime.

> [!TIP]
>
> Typing a secret on the command line can save it in your shell history. For local development, a `.env` file (described next) avoids this.

### Optional: use a .env file

A `.env` file is a plain text file of `NAME=value` lines that you keep in your project folder. The SDK does not read `.env` files on its own. You can load one with the third-party `python-dotenv` package.

**1. Install python-dotenv**

```bash theme={null}
python -m pip install python-dotenv
```

**2. Create a .env file**

```text .env theme={null}
HIDDENLAYER_CLIENT_ID=your-client-id
HIDDENLAYER_CLIENT_SECRET=your-client-secret
```

**3. Keep the file out of version control**

Add `.env` to your `.gitignore` file so that you never commit your secret:

```text .gitignore theme={null}
.env
```

**4. Load the file before you create the client**

```python theme={null}
from dotenv import load_dotenv
from hiddenlayer import HiddenLayer

load_dotenv()  # Copies values from .env into os.environ.

client = HiddenLayer()
```

Call `load_dotenv()` before you create the client, because the client reads environment variables only when it is constructed.

## Choose a region or base URL

The base URL is the root address that the client sends every request to, for example `https://api.us.hiddenlayer.ai`. HiddenLayer provides two SaaS environments (regions):

| `environment` value | Base URL |
| - | - |
| `prod-us` (default) | `https://api.us.hiddenlayer.ai` |
| `prod-eu` | `https://api.eu.hiddenlayer.ai` |

Select the EU region with the `environment` argument:

```python theme={null}
from hiddenlayer import HiddenLayer

client = HiddenLayer(environment="prod-eu")
```

An unknown value, such as `environment="eu"`, raises `ValueError: Unknown environment: eu`.

### How the client resolves the base URL

The client picks the base URL in this order and uses the first match:

1. The `base_url` argument, if you pass one.
2. The `environment` argument, if you pass one.
3. The `HIDDENLAYER_BASE_URL` environment variable, if it is set.
4. The `prod-us` environment.

If you pass `environment` while `HIDDENLAYER_BASE_URL` is also set, the client cannot tell which one you intend and raises:

```text theme={null}
ValueError: Ambiguous URL; The `HIDDENLAYER_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None
```

To fix it, unset `HIDDENLAYER_BASE_URL`, remove the `environment` argument, or pass `base_url=None` together with `environment` to confirm that you want the environment:

```python theme={null}
from hiddenlayer import HiddenLayer

client = HiddenLayer(environment="prod-eu", base_url=None)
```

## Pass credentials explicitly

By default, the client reads credentials from environment variables. You can also pass them as constructor arguments, for example when your application loads secrets from a secret manager under different names. Read the values from your secret store or the environment. Never write them as literals in code.

```python theme={null}
import os

from hiddenlayer import HiddenLayer

client = HiddenLayer(
    client_id=os.environ["HIDDENLAYER_CLIENT_ID"],
    client_secret=os.environ["HIDDENLAYER_CLIENT_SECRET"],
    environment="prod-us",
)
```

To use a bearer token instead:

```python theme={null}
import os

from hiddenlayer import HiddenLayer

client = HiddenLayer(bearer_token=os.environ["HIDDENLAYER_TOKEN"])
```

> [!WARNING]
>
> Do not hard-code client secrets or tokens in source files, notebooks, or container images. Anyone with access to the code can use them, and secrets committed to version control remain in the history. If a secret is exposed, delete the API key in the Console and create a new one.

## Client options

All constructor arguments are keyword-only. The same arguments work for `HiddenLayer` and `AsyncHiddenLayer`.

| Option | Default | Description |
| - | - | - |
| `client_id` | `HIDDENLAYER_CLIENT_ID` | Client ID for OAuth2 client credentials. |
| `client_secret` | `HIDDENLAYER_CLIENT_SECRET` | Client secret for OAuth2 client credentials. |
| `bearer_token` | `HIDDENLAYER_TOKEN` | Pre-issued bearer token. |
| `environment` | `"prod-us"` | `"prod-us"` or `"prod-eu"`. |
| `base_url` | Resolved as described in [How the client resolves the base URL](#how-the-client-resolves-the-base-url) | Root URL for all requests. |
| `timeout` | 60 seconds total, 5 seconds to connect | A number of seconds (`float`) or an `httpx.Timeout` object for fine-grained control. |
| `max_retries` | `2` | How many times to retry failed requests that are safe to retry, such as connection errors, timeouts, 408, 409, 429, and 5xx responses. Set to `0` to disable retries. |
| `default_headers` | None | A dictionary of headers to send with every request. |
| `default_query` | None | A dictionary of query parameters to send with every request. |
| `http_client` | An SDK-managed `httpx` client | A custom HTTP client, for example `DefaultHttpxClient(...)` for proxies. Async clients accept `DefaultAsyncHttpxClient(...)` or `DefaultAioHttpClient()`. |

Example with custom timeout and retries:

```python theme={null}
import httpx
from hiddenlayer import HiddenLayer

client = HiddenLayer(
    # 20 seconds overall, 5 seconds to establish a connection.
    timeout=httpx.Timeout(20.0, connect=5.0),
    max_retries=3,
)
```

For details on retry behavior and timeout errors, see [Error handling](/docs/integrations/sdk_python/error_handling).

### Use a proxy

If your network requires an HTTP proxy, pass a custom HTTP client. `DefaultHttpxClient` keeps the SDK's default settings and adds your changes:

```python theme={null}
import os

from hiddenlayer import DefaultHttpxClient, HiddenLayer

client = HiddenLayer(
    http_client=DefaultHttpxClient(
        proxy=os.environ["HTTPS_PROXY"],  # For example, http://proxy.example.com:8080
    ),
)
```

The `proxy` argument is passed to `httpx`. Support for it depends on your installed `httpx` version, because older versions used a `proxies` argument instead.

### Override options for a single request

`client.with_options(...)` returns a copy of the client with some options changed. The original client is not modified. Use it when one call needs different settings, such as a longer timeout for a large request:

```python theme={null}
from hiddenlayer import HiddenLayer

client = HiddenLayer()

response = client.with_options(timeout=120.0, max_retries=5).interactions.analyze(
    metadata={"model": "gpt-4o", "requester_id": "user-1234"},
    input={"messages": [{"role": "user", "content": "Summarize this document."}]},
)
```

## Close the client

The client holds open HTTP connections so that it can reuse them. Connections are closed when the client is garbage collected. For predictable cleanup, use the client as a context manager, which closes it when the block ends:

```python theme={null}
from hiddenlayer import HiddenLayer

with HiddenLayer() as client:
    response = client.interactions.analyze(
        metadata={"model": "gpt-4o", "requester_id": "user-1234"},
        input={"messages": [{"role": "user", "content": "Hello"}]},
    )
    print(response.evaluation)
```

Alternatively, call `client.close()` when you are finished. In long-running applications, create one client and reuse it for all requests instead of creating a client per request.

## Async client

`AsyncHiddenLayer` accepts the same arguments and reads the same environment variables as `HiddenLayer`. Await each method call, and use `async with` or `await client.close()` to close it.

```python theme={null}
import asyncio

from hiddenlayer import AsyncHiddenLayer


async def main() -> None:
    async with AsyncHiddenLayer(environment="prod-us") as client:
        response = await client.interactions.analyze(
            metadata={"model": "gpt-4o", "requester_id": "user-1234"},
            input={"messages": [{"role": "user", "content": "Hello"}]},
        )
        print(response.evaluation)


asyncio.run(main())
```

To use the `aiohttp` backend for higher concurrency, install `"hiddenlayer-sdk[aiohttp]"` and pass `DefaultAioHttpClient()`:

```python theme={null}
import asyncio

from hiddenlayer import AsyncHiddenLayer, DefaultAioHttpClient


async def main() -> None:
    async with AsyncHiddenLayer(http_client=DefaultAioHttpClient()) as client:
        response = await client.interactions.analyze(
            metadata={"model": "gpt-4o", "requester_id": "user-1234"},
            input={"messages": [{"role": "user", "content": "Hello"}]},
        )
        print(response.evaluation)


asyncio.run(main())
```

## Differences from api.md

The SDK's generated [api.md](https://github.com/hiddenlayerai/hiddenlayer-sdk-python/blob/main/api.md) file has a "Regional Configuration" section that refers to a `HIDDEN_LAYER_BASE_URL` variable and lists `https://api.hiddenlayer.ai` as the US host. The SDK source code reads `HIDDENLAYER_BASE_URL` (no underscore between `HIDDEN` and `LAYER`) and uses `https://api.us.hiddenlayer.ai` for `prod-us`. Follow the values on this page.

## Next step

> **[Quickstart](/docs/integrations/sdk_python/quickstart)**
>
> Make your first authenticated API call.
