Skip to content

Store API Keys Securely

Manage sensitive credentials with encrypted storage

Store API keys, passwords, and credentials securely. Bifrost encrypts secrets using Fernet encryption and scopes them per organization.

SettingsConfiguration+ Add Config

  • Key: api_key
  • Type: Secret
  • Value: sk_live_xxxxx (your actual secret)

Stores the value encrypted in the database automatically.

from bifrost import config
@workflow(name="call_api")
async def call_api(context: ExecutionContext):
# Get secret (works for both Secret and Secret Reference types)
api_key = await config.get("api_key")
headers = {"Authorization": f"Bearer {api_key}"}
response = await make_api_call(url, headers=headers)
return response

Store third-party API credentials:

@workflow(name="external_api")
async def external_api(context: ExecutionContext):
api_key = await config.get("stripe_api_key")
response = requests.post(
"https://api.stripe.com/v1/charges",
headers={"Authorization": f"Bearer {api_key}"}
)
return response.json()

Store database passwords securely:

@workflow(name="db_query")
async def db_query(context: ExecutionContext):
db_password = await config.get("postgres_password")
conn = await asyncpg.connect(
host="db.example.com",
user="bifrost",
password=db_password,
database="production"
)
result = await conn.fetch("SELECT * FROM users")
await conn.close()
return result

Verify incoming webhooks:

import hmac
import hashlib
@workflow(name="verify_webhook")
async def verify_webhook(context: ExecutionContext, payload: str, signature: str):
webhook_secret = await config.get("webhook_secret")
# Calculate expected signature
expected = hmac.new(
webhook_secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
raise ValueError("Invalid webhook signature")
return {"verified": True}

Secrets are automatically isolated per organization:

  • Org A stores api_key with value sk_live_aaa
  • Org B stores api_key with value sk_live_bbb
  • Workflows automatically use their org’s value
# Automatically uses current org's secret
api_key = await config.get("api_key")

Each organization has completely separate secrets in the database.

  1. SettingsConfiguration
  2. Find config → Click Edit
  3. Enter new value → Save
# Update secret value
await config.set("api_key", "sk_live_new_value", is_secret=True)

✅ Do This:

# Get encrypted secret
api_key = await config.get("api_key")
# Log without exposing value
logger.info("Making API call with stored credentials")
# Don't return in response
return {"success": True, "user_id": user.id}

❌ Don’t Do This:

# Hardcoded secret
api_key = "sk_live_123abc"
# Logged secret value
logger.info(f"API key: {api_key}")
# Returned in response
return {"api_key": api_key}

Always handle missing secrets gracefully:

@workflow(name="safe_api_call")
async def safe_api_call(context: ExecutionContext):
try:
api_key = await config.get("api_key")
except KeyError:
return {
"success": False,
"error": "API key not configured",
"action": "Add 'api_key' to Configuration in Settings"
}
# Use api_key...
return {"success": True}
  • Encryption: All secrets encrypted using Fernet (symmetric encryption)
  • Key Management: Encryption key derived from BIFROST_SECRET_KEY environment variable
  • Audit Logs: All secret access is logged
  • Never Exposed: Secrets never returned in API responses or logs
  • Org Isolation: Complete separation between organizations

Config not found: Verify config exists in SettingsConfiguration for your organization.

Decryption errors: Ensure BIFROST_SECRET_KEY hasn’t changed. Changing the key will invalidate existing encrypted secrets.