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/gemini/*.
# Flat namespace (simple projects)
gemini-key
stripe-webhook
github-token
# Hierarchical namespaces (recommended for production)
production/gemini/GEMINI_API_KEY
production/stripe/STRIPE_SECRET_KEY
production/stripe/WEBHOOK_SECRET
production/postgres/DATABASE_URL
# Multi-environment pattern
staging/gemini/GEMINI_API_KEY
staging/stripe/STRIPE_SECRET_KEY
# Service-scoped namespaces
payments-service/stripe/STRIPE_SECRET_KEY
payments-service/stripe/WEBHOOK_SECRET
auth-service/google/CLIENT_SECRET
auth-service/jwt/SIGNING_KEY
# Agent-scoped namespaces
agents/summarizer/GEMINI_API_KEY
agents/researcher/TAVILY_API_KEY
agents/researcher/GEMINI_API_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 secret_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 | Gemini, Tavily, Replicate, Pinecone, and provider 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 |
| 📜 | certificate | TLS certificates and other certificate material |
| 📦 | 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
import os
from agentsecretstore import AgentVault
async def provision_secrets():
async with AgentVault(agent_key=os.environ["ASS_AGENT_KEY"]) as vault:
# API key — sensitive tier, with lifecycle metadata for your scheduler.
gemini = await vault.set_secret(
"production/gemini/GEMINI_API_KEY",
"gemini-api-key-example",
secret_type="api_key",
access_tier="sensitive",
metadata={
"service": "gemini",
"team": "ml-platform",
"rotate_after_days": 90,
},
)
print(f"Stored Gemini key version {gemini.version}")
# Database credential — critical tier, requires approval
await vault.set_secret(
"production/postgres/DATABASE_URL",
"postgresql://user:pass@host:5432/db",
secret_type="db_credential",
access_tier="critical",
metadata={
"service": "postgres",
"env": "production",
"description": "Main production PostgreSQL connection string",
},
)
# OAuth token — sensitive tier, expires naturally
await vault.set_secret(
"production/github/OAUTH_TOKEN",
"ghp_placeholder",
secret_type="oauth_token",
access_tier="sensitive",
metadata={"provider": "github", "scopes": "repo,read:org"},
)TypeScript
import { AgentVault } from '@agentsecretstore/sdk';
async function provisionSecrets() {
const vault = new AgentVault({ agentKey: process.env.ASS_AGENT_KEY! });
const stored = await vault.setSecret(
'production/gemini/GEMINI_API_KEY',
'gemini-api-key-example',
{
secretType: 'api_key',
accessTier: 'sensitive',
metadata: {
service: 'gemini',
team: 'ml-platform',
rotate_after_days: 90,
},
},
);
console.log(`Stored version ${stored.version}`);
}
provisionSecrets().catch((error) => {
console.error(error);
process.exit(1);
});Secret versioning
Every time you store a replacement value, Agent Secret Store creates a new encrypted version and returns the active version number. The public SDKs expose the active version on reads and writes today. To list older versions or make one current again, see Deleting and restoring.
import os
from agentsecretstore import AgentVault
async def update_secret_version(new_key: str):
async with AgentVault(agent_key=os.environ["ASS_AGENT_KEY"]) as vault:
previous = await vault.get_secret_with_metadata(
"production/gemini/GEMINI_API_KEY"
)
print(f"Current version: {previous.version}")
updated = await vault.set_secret(
"production/gemini/GEMINI_API_KEY",
new_key,
secret_type="api_key",
access_tier=previous.access_tier,
metadata=previous.metadata,
)
print(f"Active version is now {updated.version}")Version metadata is available on reads
Use get_secret_with_metadata when an agent needs the active version, secret type, access tier, or metadata. Use get_secret when it only needs the plaintext value.
Metadata and tags
Every secret can carry arbitrary metadata. Store simple JSON fields such as service, owner, rotation cadence, or runbook links there; the dashboard and API return metadata alongside secret records without exposing plaintext values in list responses.
| Field | Type | Description |
|---|---|---|
| metadata | object | Arbitrary key-value pairs; e.g. { team: "platform", env: "prod" } |
| secret_type | string | One of api_key, oauth_token, db_credential, ssh_key, certificate, custom |
| access_tier | string | standard, sensitive, or critical |
| created_at | timestamp | ISO-8601 creation time (auto-set) |
| updated_at | timestamp | ISO-8601 last update time (auto-set) |
| version | integer | Current active version number |
.env bulk import
Migrating from .env files? The CLI can parse a standard .env file and create vault secrets in bulk, preserving the variable names as path suffixes. SDKs can use the same import endpoint by sending the file content.
CLI
# Method 1: Import directly from .env file
ass env import .env --namespace production
# Preview without writing
ass env import .env --namespace production --dry-runPython SDK
import os
from pathlib import Path
from agentsecretstore import AgentVault
async def import_env_file(env_path: str, namespace: str):
"""Import a .env file into the vault under a namespace."""
content = Path(env_path).read_text()
async with AgentVault(agent_key=os.environ["ASS_AGENT_KEY"]) as vault:
result = await vault.import_env(
content=content,
namespace=namespace,
secret_type="custom",
access_tier="standard",
)
print(f"Imported {result.imported_count} secrets")
for key in result.imported:
print(f" {namespace}/{key}")Imports create versions
Re-running an import for an existing key writes a new version. Scoped agent tokens can import only into namespaces covered by their secrets:write scope.
Deleting and restoring
Deleting a secret moves it and all its versions to trash. List the trash with ass secrets trash and bring a secret back with ass secrets restore namespace/KEY. In the SDKs, use list_trash and restore_secret. Agents cannot restore sensitive or critical secrets; the owner must. Restore fails with SECRET_EXISTS if the path has been written again, and your plan's secret limit still applies.
To undo a bad write, list versions with ass secrets history namespace/KEY and run ass secrets rollback namespace/KEY 2. The old version is copied forward as a new current version, so history is never rewritten and the access tier never drops. The dashboard Secrets page has the same actions: a History button on each secret and a Trash card.
ass secrets delete production/gemini/GEMINI_API_KEY
ass secrets trash
ass secrets restore production/gemini/GEMINI_API_KEY
ass secrets history production/gemini/GEMINI_API_KEY
ass secrets rollback production/gemini/GEMINI_API_KEY 1Deleting the whole account
Account deletion waits 30 days. Agents, API keys and tokens stop working at once, and you can restore the account from the sign-in page until the purge date. After that, all secrets, agents, tokens and audit history are deleted and the vault's KMS key is scheduled for destruction, so nothing can be decrypted.
Lifecycle metadata
The current public write API does not enforce secret expiration timestamps. Store expiry, rotation owner, and runbook links in metadata, then let your scheduler or policy workflow act on those fields.
This keeps lifecycle intent close to the credential without pretending the vault will deny reads after a date until native expiration enforcement is exposed.
import os
from agentsecretstore import AgentVault
async def record_lifecycle_policy():
async with AgentVault(agent_key=os.environ["ASS_AGENT_KEY"]) as vault:
await vault.set_secret(
"production/github/DEPLOY_TOKEN",
"github-deploy-token-placeholder",
secret_type="oauth_token",
access_tier="sensitive",
metadata={
"expires_at": "2026-06-01T00:00:00Z",
"rotation_owner": "platform",
"rotation_runbook": "runbooks/github-deploy-token.md",
},
)Scoped Tokens →
Issue least-privilege tokens that cover specific namespace paths.
Secret Rotation →
Plan manual rotation and scheduler-owned automation.
Audit Trail →
Track every read, write, and token issuance against your secrets.
.env Migration Guide →
Step-by-step walkthrough for migrating from .env files.