---
url: /services/managed/secrets.md
description: >-
  Use the SecretValue class from qhub-commons to securely inject API tokens and
  credentials as environment variables into Managed Services.
---

# Using Secrets in Services

This guide explains how to use the
`SecretValue` feature to securely handle sensitive information like API tokens, credentials, and passwords within your services.

## What is a Secret?

A Secret is a secure way to pass sensitive information to your service at runtime without exposing it, e.g., to your logs.
Unlike regular input parameters that are passed through the Service API request body, secrets are protected by multiple security mechanisms.

When you use a Secret, the platform:

* Securely injects the secret value as an environment variable into your service's runtime environment
* Automatically maps the environment variable to your function parameter based on naming conventions
* Provides a `SecretValue` abstraction from the `qhub-commons` library that prevents accidental exposure

::: warning Security Best Practices

When working with secrets, always follow these security guidelines:

* **Never log or print** the unwrapped secret value.
* **Use secrets only once** - the `unwrap()` method can only be called once per secret.
* **Never commit secrets** to version control or include them in your code.
* **Use environment variables** for local testing, not hardcoded values.

:::

## How to Use the `SecretValue` Class

To use a Secret in your service, you declare a parameter of type `SecretValue` in your `run` method.
The runtime will automatically detect this, load the secret from the corresponding environment variable, and inject a
`SecretValue` object that protects the sensitive data.

### The `SecretValue` Object

The `SecretValue` object, found in `qhub.commons.secret`, is a secure container that provides the following features:

* `unwrap() -> str`: Returns the actual secret value.
  **Can only be called once** - subsequent calls raise a `ValueError`.
* `is_locked`: A property that returns `True` if the secret has been unwrapped, `False` otherwise.
* **Automatic redaction**: String representations always show `[redacted]` or `SecretValue([redacted])` to prevent accidental exposure in logs.
* **Environment variable mapping**: Automatically loads from environment variables using the pattern `SECRET_{PARAMETER_NAME}` (uppercase).

### Environment Variable Naming Convention

The platform automatically maps secret parameters to environment variables using this convention:

| Parameter Name      | Environment Variable       |
|---------------------|----------------------------|
| `api_token`         | `SECRET_API_TOKEN`         |
| `ibmToken`          | `SECRET_IBM_TOKEN`         |
| `iqm_token`         | `SECRET_IQM_TOKEN`         |
| `database_password` | `SECRET_DATABASE_PASSWORD` |

The parameter name is converted to uppercase, and the `SECRET_` prefix is added automatically.

### Tutorial: Building a Service with Secrets

Let's walk through an example of a service that uses secrets to authenticate with an external API.

#### 1. Initialize a New Project

If you haven't already, create a new service project.
You can use the CLI to set up a new service:

```bash
qhubctl init

cd [user_code]

uv venv
source .venv/bin/activate
uv sync
```

For the rest of this guide, we assume that you created your service in a directory named `user_code`, with the main code in `user_code/src/`.

#### 2. Update the `run` Method

In your `program.py`, define a `run` method that accepts a `SecretValue` parameter.
The name of the parameter (e.g., `api_token`) is important, as it determines which environment variable will be used.

```python
# user_code/src/program.py

from qhub.commons.secret import SecretValue
from pydantic import BaseModel
import requests

class InputData(BaseModel):
    endpoint: str

def run(data: InputData, api_token: SecretValue) -> dict:
    """
    Makes an authenticated API request using a secret token.
    """

    # Use the token for authentication
    headers = {
        "Authorization": f"Bearer {api_token.unwrap()}"  # Unwrap the secret value (can only be done once)
    }

    try:
        response = requests.get(data.endpoint, headers=headers)
        response.raise_for_status()
        return {
            "status": "success",
            "data": response.json()
        }
    except requests.RequestException as e:
        return {
            "status": "error",
            "message": str(e)
        }
```

In this example, the `run` method expects a secret to be provided for the `api_token` parameter.
The platform will automatically load this from the `SECRET_API_TOKEN` environment variable.

#### 3. Local Testing with Secrets

When developing and testing your service locally, you need to provide the secret values through environment variables.
There are several ways to do this.

##### Option 1: Set Environment Variables Directly

The simplest approach is to set the environment variable before running your service:

```bash
# Set the secret environment variable
export SECRET_API_TOKEN="your-test-token-here"

# Run your service
python -m src
```

##### Option 2: Use a `.env` File

For better organization, you can create a `.env` file in your project root:

```bash
SECRET_API_TOKEN=your-test-token-here
```

::: danger Never Commit .env Files

Add `.env` to your `.gitignore` file to prevent accidentally committing secrets to version control:

```
# .gitignore
.env
```

:::

Then load the environment variables from the file:

```bash
# Load environment variables from .env file
export $(cat .env | xargs)

# Run your service
python -m src
```

#### 4. Using Multiple Secrets

If your service needs to authenticate with multiple external services, you can declare multiple secret parameters:

```python
from qhub.commons.secret import SecretValue
from pydantic import BaseModel
import requests

class InputData(BaseModel):
    fetch_weather: bool
    fetch_stocks: bool

def run(data: InputData, weather_api_key: SecretValue, stock_api_key: SecretValue) -> dict:
    """
    Fetches data from multiple APIs using different credentials.
    """
    results = {}

    if data.fetch_weather:
        weather_token = weather_api_key.unwrap()
        # Use weather_token for weather API...
        results["weather"] = {"status": "success"}

    if data.fetch_stocks:
        stock_token = stock_api_key.unwrap()
        # Use stock_token for stock API...
        results["stocks"] = {"status": "success"}

    return results
```

For local testing, you would set multiple environment variables:

```bash
export SECRET_WEATHER_API_KEY="weather-token"
export SECRET_STOCK_API_KEY="stock-token"

python -m src
```

#### 5. Combining Secrets with Other Input Types

You can combine secrets with regular JSON input and Data Pools in the same service:

```python
from typing import Dict, Any
from qhub.commons.secret import SecretValue
from qhub.commons.datapool import DataPool
from pydantic import BaseModel

class InputData(BaseModel):
    model_name: str
    endpoint: str

def run(
    data: InputData,
    api_token: SecretValue,
    models: DataPool
) -> dict:
    """
    Loads a model from a Data Pool and uploads it to an API using secret credentials.
    """
    # Unwrap the secret
    token = api_token.unwrap()

    # Load the model from the Data Pool
    model_path = models.list_files()[data.model_name]
    with open(model_path, "rb") as f:
        model_data = f.read()

    # Upload the model using the authenticated API
    headers = {"Authorization": f"Bearer {token}"}
    # ... upload logic ...

    return {"status": "success", "model": data.model_name}
```

For local testing:

```bash
# Set the secret
export SECRET_API_TOKEN="your-token"

# Run with both secret and data pool (assuming a datapool directory and files at './input/models'
python -m src
```

## OpenAPI Specification for Secrets

When you generate an OpenAPI specification for a service that uses a `SecretValue`, the
`qhubctl openapi` command automatically creates the correct schema for the secret parameter.

For example, given this service:

```python
def run(api_token: SecretValue) -> dict:
    pass
```

The command will generate the schema for `api_token` in the following way:

```yaml
schema:
  type: object
  properties:
    $secrets:
      type: object
      properties:
        api_token:
          type: string
```
