Vault Storage
How secrets are organized, classified, versioned, and managed — from simple API keys to complex multi-environment credential stores.
Namespaces
Every secret lives at a path — a forward-slash delimited string that forms the namespace hierarchy. Paths are arbitrary: you define the structure that makes sense for your project.
The most common pattern is environment/service/key-name — this maps naturally to scoped token patterns like secrets:read:production/openai/*.
# Flat namespace (simple projects)
openai-key
stripe-webhook
github-token
# Hierarchical namespaces (recommended for production)
production/openai/api-key
production/stripe/secret-key
production/stripe/webhook-secret
production/postgres/connection-string
# Multi-environment pattern
staging/openai/api-key
staging/stripe/secret-key
# Service-scoped namespaces
payments-service/stripe/secret-key
payments-service/stripe/webhook-secret
auth-service/google/client-secret
auth-service/jwt/signing-key
# Agent-scoped namespaces
agents/summarizer/openai-key
agents/researcher/tavily-key
agents/researcher/openai-keyNamespace tips
Use the environment as the top-level prefix (production/, staging/,development/). This lets you write scoped tokens that are strictly isolated per environment — a staging agent cannot accidentally read production secrets.
Secret types
The type field classifies a secret's nature. It is used for display, filtering, audit enrichment, and rotation-handler routing — not for access control.
| Type | Value | Examples |
|---|---|---|
| 🔑 | api_key | OpenAI, Anthropic, Tavily, Replicate, Pinecone API keys |
| 🔄 | oauth_token | GitHub OAuth, Google refresh tokens, Slack bot tokens |
| 🗄️ | db_credential | PostgreSQL, MySQL, MongoDB, Redis connection strings |
| 🔐 | ssh_key | Private SSH keys for server access or git operations |
| 💳 | payment_credential | Stripe secret key, PayPal API credentials, Coinbase |
| 📦 | custom | Anything else — JWT signing keys, webhook secrets, etc. |
Access tiers
The access tier controls whether a token request for that secret requires human approval. Assign tiers based on the blast radius if the credential were compromised.
standard✅ No approval requiredLow-risk credentials. Token requests are fulfilled instantly.
Examples: Read-only API keys, public data sources, sandbox/dev keys, logging service tokens, non-sensitive webhook secrets.
sensitive⚠️ Approval required (configurable)Elevated risk. Approval can be required or waived per-token request.
Examples: Production API keys with write access, OAuth tokens, staging database connections, internal service-to-service credentials.
critical🚨 Approval always requiredMaximum protection. Every token request requires explicit approval.
Examples: Production database connection strings, payment processor secret keys, SSH private keys, root/admin credentials, KMS keys.
Default to sensitive, not standard
When in doubt, use sensitive. It's easy to relax a tier later — it's much harder to audit all the unapproved accesses after the fact.
Creating secrets
Python
from agentsecretstore import AgentVault, SecretType, AccessTier
async def provision_secrets():
async with AgentVault() as vault:
# API key — standard tier, auto-expires in 90 days
await vault.create_secret(
path="production/openai/api-key",
value="sk-proj-...",
type=SecretType.API_KEY,
tier=AccessTier.STANDARD,
tags={"service": "openai", "team": "ml-platform"},
expires_in_days=90,
)
# Database credential — critical tier, requires approval
await vault.create_secret(
path="production/postgres/main",
value="postgresql://user:pass@host:5432/db",
type=SecretType.DB_CREDENTIAL,
tier=AccessTier.CRITICAL,
tags={"service": "postgres", "env": "production"},
description="Main production PostgreSQL connection string",
)
# OAuth token — sensitive tier, expires naturally
await vault.create_secret(
path="production/github/oauth-token",
value="ghp_xxxx...",
type=SecretType.OAUTH_TOKEN,
tier=AccessTier.SENSITIVE,
tags={"provider": "github", "scopes": "repo,read:org"},
)TypeScript
import { AgentVault, SecretType, AccessTier } from '@agentsecretstore/sdk';
const vault = new AgentVault();
await vault.createSecret({
path: 'production/openai/api-key',
value: 'sk-proj-...',
type: SecretType.API_KEY,
tier: AccessTier.STANDARD,
tags: { service: 'openai', team: 'ml-platform' },
expiresInDays: 90,
});Secret versioning
Every time you update a secret, the old value is preserved as a prior version. Versions are immutable after creation. You can list all versions, fetch a specific historical value, and roll back the active version at any time.
from agentsecretstore import AgentVault
async def rotate_and_rollback():
async with AgentVault() as vault:
# Get current version
secret = await vault.get_secret("production/openai/api-key")
print(f"Current version: {secret.version}") # e.g. v3
# Update creates a new version automatically
await vault.update_secret(
path="production/openai/api-key",
value="sk-proj-new-key...",
)
# List all versions
versions = await vault.list_versions("production/openai/api-key")
for v in versions:
print(f" {v.version}: created {v.created_at}, active={v.is_active}")
# Rollback to previous version
await vault.rollback_secret(
path="production/openai/api-key",
version="v2", # Roll back to specific version
)
# Get a specific historical version
old_secret = await vault.get_secret(
path="production/openai/api-key",
version="v1",
)Versions are stored encrypted
Each version uses its own envelope-encrypted DEK. Rolling back restores the previous DEK + ciphertext — the plaintext value at that version is never re-stored. Version history is retained for 90 days (Growth) or 365 days (Enterprise).
Metadata and tags
Every secret can carry arbitrary key-value tags and a human-readable description. Tags are indexed and searchable — useful for filtering secrets by environment, team, or service in the dashboard and API.
| Field | Type | Description |
|---|---|---|
| description | string | Free-text note shown in the dashboard and audit log |
| tags | object | Arbitrary key-value pairs; e.g. { team: "platform", env: "prod" } |
| created_by | string | Actor ID of the principal that created the secret (auto-set) |
| created_at | timestamp | ISO-8601 creation time (auto-set) |
| updated_at | timestamp | ISO-8601 last update time (auto-set) |
| version | string | Current active version identifier (e.g. "v3") |
| expires_at | timestamp | null | Optional expiration time; null = never expires |
.env bulk import
Migrating from .env files? The CLI and SDK can parse a standard .env file and create vault secrets in bulk, preserving the variable names as path suffixes.
CLI
# Method 1: Import directly from .env file
ass import .env --namespace production --tier standard
# Method 2: Pipe from stdin
cat .env | ass import --namespace staging
# Method 3: With tier overrides per prefix
ass import .env \
--namespace production \
--tier-prefix "DB_=critical" \
--tier-prefix "STRIPE_=sensitive" \
--tier-prefix "OPENAI_=standard"
# Preview without writing (dry run)
ass import .env --namespace production --dry-runPython SDK
from agentsecretstore import AgentVault
async def import_env_file(env_path: str, namespace: str):
"""Import a .env file into the vault under a namespace."""
async with AgentVault() as vault:
result = await vault.import_env(
path=env_path,
namespace=namespace,
tier_map={
"DB_": "critical",
"STRIPE_": "sensitive",
"OPENAI_": "standard",
},
dry_run=False,
)
print(f"Imported {result.created} secrets, skipped {result.skipped}")
for secret in result.secrets:
print(f" {secret.path} ({secret.tier})")Idempotent imports
Re-running the import on the same file will skip secrets that already exist at the same path. Use --overwrite (CLI) or overwrite=True (SDK) to force updates.
Expiration policies
Set an expires_at timestamp on any secret. Once expired, the vault refuses to serve the secret value — agents receive a 410 Gone response. Expiration does not delete the secret; it can be extended or renewed.
Dashboard alerts and webhook notifications fire 7 days and 1 day before expiration, giving your team time to rotate before agents start failing.
from agentsecretstore import AgentVault
from datetime import datetime, timezone, timedelta
async def set_expiration_policies():
async with AgentVault() as vault:
# Expire in 30 days
await vault.update_secret(
path="production/github/deploy-token",
expires_at=datetime.now(timezone.utc) + timedelta(days=30),
)
# Expire at a specific date (e.g., OAuth token expiry)
await vault.update_secret(
path="production/google/refresh-token",
expires_at=datetime(2025, 12, 31, 23, 59, 59, tzinfo=timezone.utc),
)
# Clear expiration (make permanent)
await vault.update_secret(
path="production/stripe/restricted-key",
expires_at=None,
)Scoped Tokens →
Issue least-privilege tokens that cover specific namespace paths.
Secret Rotation →
Set up scheduled and automated secret rotation.
Audit Trail →
Track every read, write, and token issuance against your secrets.
.env Migration Guide →
Step-by-step walkthrough for migrating from .env files.