# KeyEnv Documentation > KeyEnv is a secrets management platform for developer teams. It provides encrypted storage, syncing, and injection of environment variables across projects, environments, and CI/CD pipelines. KeyEnv includes a CLI, web dashboard, REST API, and SDKs for 10+ languages. Secrets are encrypted at rest with AES-256-GCM, versioned with full audit trails, and scoped by environment-level permissions (none/read/write/admin). Every documentation page is also available as raw markdown by appending `.md` to its URL, for example `https://keyenv.dev/docs/cli.md`. The OpenAPI specification is published at `/openapi.yaml` and `/openapi.json`. This file contains the complete KeyEnv documentation as a single markdown document. --- # Documentation ## KeyEnv Documentation Learn how to use KeyEnv to manage secrets across your development workflow. Source: https://keyenv.dev/docs/ KeyEnv is a secure secrets management platform that helps teams sync environment variables across development, staging, and production environments. ### Quick Links - [Quick Start](/docs/getting-started): Get up and running in 5 minutes - [CLI Reference](/docs/cli): Complete command documentation - [Web App Guide](/docs/web-app): Manage secrets in the browser - [Secret Rotation](/docs/web-app/rotations): Auto-rotate database credentials - [SDKs & Integrations](/docs/sdks): Node.js, Python SDKs & GitHub Action ### Why KeyEnv? - **Secure by default** - Secrets are encrypted at rest and in transit - **Database credential rotation** - Automatically rotate PostgreSQL and MySQL passwords - **Version history** - Track every change with full audit logs - **Team collaboration** - Share secrets securely with your team - **CLI-first workflow** - Sync secrets with a single command ### Getting Started Install the CLI and sync your first secrets: ```bash # Install via Homebrew brew install keyenv # Login to your account keyenv login # Initialize a project keyenv init # Pull secrets to .env keyenv pull ``` Ready to dive in? Start with our [Quick Start Guide](/docs/getting-started). --- # Getting Started ## Getting Started Get up and running with KeyEnv in minutes. Source: https://keyenv.dev/docs/getting-started/ This guide will help you set up KeyEnv and sync your first secrets. ### Prerequisites - A KeyEnv account ([sign up here](https://app.keyenv.dev/signup)) - Node.js 18+ or any modern development environment ### Steps 1. [Install the CLI](/docs/getting-started/installation) 2. [Create your first project](/docs/getting-started/first-project) 3. [Understand core concepts](/docs/getting-started/concepts) Ready? Let's [install the CLI](/docs/getting-started/installation). ## Installation Install the KeyEnv CLI on your machine. Source: https://keyenv.dev/docs/getting-started/installation/ The KeyEnv CLI is available for macOS, Linux, and Windows. ### Quick Install (Recommended) Install KeyEnv with a single command: ```bash curl -fsSL https://keyenv.dev/install.sh | bash ``` This script automatically detects your operating system and architecture, downloads the correct binary, and installs it to `~/.keyenv/bin`. #### Install a Specific Version ```bash curl -fsSL https://keyenv.dev/install.sh | bash -s -- v0.1.0 ``` #### Custom Install Location ```bash KEYENV_INSTALL_DIR=/usr/local/bin curl -fsSL https://keyenv.dev/install.sh | bash ``` After installation, add KeyEnv to your PATH if prompted: ```bash export PATH="$HOME/.keyenv/bin:$PATH" ``` Add this line to your `~/.bashrc`, `~/.zshrc`, or shell profile to make it permanent. --- ### Alternative Installation Methods #### Binary Download Download pre-built binaries from [GitHub Releases](https://github.com/keyenv/keyenv/releases). **macOS (Apple Silicon):** ```bash curl -L https://github.com/keyenv/keyenv/releases/latest/download/keyenv-aarch64-apple-darwin.tar.gz | tar xz sudo mv keyenv /usr/local/bin/ ``` **macOS (Intel):** ```bash curl -L https://github.com/keyenv/keyenv/releases/latest/download/keyenv-x86_64-apple-darwin.tar.gz | tar xz sudo mv keyenv /usr/local/bin/ ``` **Linux (x64):** ```bash curl -L https://github.com/keyenv/keyenv/releases/latest/download/keyenv-x86_64-unknown-linux-gnu.tar.gz | tar xz sudo mv keyenv /usr/local/bin/ ``` **Linux (ARM64):** ```bash curl -L https://github.com/keyenv/keyenv/releases/latest/download/keyenv-aarch64-unknown-linux-gnu.tar.gz | tar xz sudo mv keyenv /usr/local/bin/ ``` #### Windows Download `keyenv-x86_64-pc-windows-msvc.zip` from [GitHub Releases](https://github.com/keyenv/keyenv/releases), extract it, and add the folder to your PATH. #### Build from Source Requires Rust 1.70+: ```bash git clone https://github.com/keyenv/keyenv.git cd keyenv/cli cargo build --release ``` The binary will be at `target/release/keyenv`. --- ### Verify Installation ```bash keyenv --version ``` You should see output like: ``` keyenv 0.1.0 ``` ### Troubleshooting #### "command not found" after installation Make sure `~/.keyenv/bin` is in your PATH: ```bash echo $PATH | grep -q ".keyenv" && echo "PATH is set" || echo "PATH not set" ``` Add to your shell profile: ```bash echo 'export PATH="$HOME/.keyenv/bin:$PATH"' >> ~/.zshrc source ~/.zshrc ``` #### Permission denied If using `/usr/local/bin`, you may need sudo: ```bash sudo mv keyenv /usr/local/bin/ ``` Or use the default install location (`~/.keyenv/bin`) which doesn't require sudo. ### Next Steps Now that you have the CLI installed, [create your first project](/docs/getting-started/first-project). ## Your First Project Create a KeyEnv project and sync your first secrets in under 5 minutes. Source: https://keyenv.dev/docs/getting-started/first-project/ This tutorial walks you through the complete KeyEnv workflow: authenticating, creating a project, managing secrets, and running your application with secrets injected. **Prerequisites:** - [KeyEnv CLI installed](/docs/getting-started/installation) - A KeyEnv account ([sign up here](https://app.keyenv.dev/signup)) --- ### Step 1: Login Authenticate with your KeyEnv account: ```bash keyenv login ``` This opens your browser for authentication. Once complete, you'll see: ``` ✓ Logged in as you@example.com ✓ Access token saved to keychain ``` Verify your session anytime with: ```bash keyenv whoami ``` --- ### Step 2: Initialize a Project Navigate to your project directory: ```bash cd ~/my-project keyenv init ``` You'll be prompted to either select an existing project or create a new one: ``` ? Select a project: ❯ Create new project my-existing-project another-project ``` If creating new, enter a project name: ``` ? Project name: my-awesome-app ? Default environment: development ✓ Project 'my-awesome-app' created ✓ Configuration saved to .keyenv.toml ``` This creates a `.keyenv.toml` configuration file: ```toml project_id = "abc123-def456-..." default_environment = "development" ``` > **Note** > > The `.keyenv.toml` file is safe to commit. It only contains the project ID, not secrets. --- ### Step 3: Add Your First Secrets Add secrets using the CLI: ```bash # Set a simple secret keyenv set DATABASE_URL "postgres://localhost:5432/mydb" # Set multiple secrets keyenv set API_KEY "your-api-key" keyenv set JWT_SECRET "your-jwt-secret" ``` For sensitive values, pipe from stdin to avoid shell history: ```bash echo "sk_live_abc123..." | keyenv set STRIPE_SECRET_KEY - ``` List your secrets to verify: ```bash keyenv list ``` Output: ``` Environment: development DATABASE_URL postgres://localhost:5432/mydb API_KEY your-*****-key JWT_SECRET ******** STRIPE_SECRET_KEY sk_live_***** ``` --- ### Step 4: Pull Secrets to .env Sync secrets to a local `.env` file: ```bash keyenv pull ``` Output: ``` ✓ Pulled 4 secrets to .env ``` Your `.env` file now contains: ``` DATABASE_URL=postgres://localhost:5432/mydb API_KEY=your-api-key JWT_SECRET=your-jwt-secret STRIPE_SECRET_KEY=sk_live_abc123... ``` > **Warning** > > Add `.env` to your `.gitignore` to prevent accidentally committing secrets. --- ### Step 5: Run with Secrets Injected The recommended way to use secrets is with `keyenv run`. This injects secrets as environment variables without writing them to disk: ```bash keyenv run -- npm start ``` Or with any command: ```bash # Node.js keyenv run -- node server.js # Python keyenv run -- python app.py # Docker keyenv run -- docker-compose up ``` The `--` separates KeyEnv options from your command. --- ### Step 6: Work with Different Environments KeyEnv supports multiple environments (development, staging, production). Specify the environment using the `-e/--env` flag: ```bash # Pull staging secrets keyenv pull --env staging # Run with production secrets keyenv run --env production -- npm start # Set a default environment in .keyenv.toml # Edit the file and change: default_environment = "staging" ``` > **Note** > > The `-e/--env` flag works with most commands (`pull`, `push`, `run`, `set`, `get`, `list`). To change your default environment, edit the `default_environment` value in your `.keyenv.toml` file. --- ### What's Next? You've learned the basics! Here's where to go next: - **[Core Concepts](/docs/getting-started/concepts)** - Understand projects, environments, and teams - **[CLI Reference](/docs/cli)** - Complete command documentation - **[CI/CD Integration](/docs/guides/ci-cd)** - Set up secrets in your pipeline - **[Docker Guide](/docs/guides/docker)** - Use KeyEnv with containers --- ### Quick Reference | Command | Description | |---------|-------------| | `keyenv login` | Authenticate with KeyEnv | | `keyenv init` | Initialize a project | | `keyenv set KEY value` | Set a secret | | `keyenv get KEY` | Get a secret value | | `keyenv list` | List all secrets | | `keyenv pull` | Sync secrets to .env | | `keyenv run -- cmd` | Run command with secrets | | `keyenv push` | Push local .env to server | ## Core Concepts Understand the key concepts behind KeyEnv. Source: https://keyenv.dev/docs/getting-started/concepts/ Understanding these concepts will help you get the most out of KeyEnv. ### Projects A **project** is a collection of secrets for a single application or service. Each project can have multiple environments. - Projects are identified by a unique ID - You can switch between projects with `keyenv switch` - Projects can be shared with team members ### Environments **Environments** are isolated sets of secrets within a project. Common environments include: | Environment | Purpose | |-------------|---------| | `development` | Local development | | `staging` | Pre-production testing | | `production` | Live application | Each environment has its own set of secrets with the same keys but different values: ``` # development DATABASE_URL=postgres://localhost/mydb # production DATABASE_URL=postgres://prod-server/mydb ``` ### Secrets A **secret** is a key-value pair. Secrets have: - **Key**: The environment variable name (e.g., `DATABASE_URL`) - **Value**: The sensitive data - **Version**: Incremented on each update - **Description**: Optional documentation ### Version History Every change to a secret is tracked. You can: - View the history of changes with `keyenv history SECRET_NAME` - See who made each change and when - Audit access and modifications ### Environment Permissions **Environment permissions** control who can access secrets in each environment. Permission levels: | Role | Description | |------|-------------| | `none` | No access | | `read` | View secrets only | | `write` | View and modify secrets | | `admin` | Full access + manage permissions | This allows you to: - Give developers full access to `development` - Restrict `production` to senior engineers - Grant read-only access for debugging > **Note** > > Team admins automatically have full access to all environments. ### Service Tokens **Service tokens** are API keys for CI/CD and automated systems. They: - Have limited permissions (specific project/environment) - Can be scoped to a single environment for extra security - Never expire but can be revoked - Are used instead of user authentication in automated pipelines ```bash # In CI/CD, set the token as an environment variable export KEYENV_TOKEN="env_..." keyenv pull ``` ### Team Collaboration KeyEnv supports team workflows: - **Members** can view and modify secrets - **Admins** can manage team settings and members - All changes are attributed to the user who made them ### Next Steps Now that you understand the concepts, explore: - [CLI commands](/docs/cli) for all operations - [Web app guide](/docs/web-app) for browser-based management ## FAQ Frequently asked questions about KeyEnv. Source: https://keyenv.dev/docs/getting-started/faq/ ### General #### What is KeyEnv? KeyEnv is a secrets management platform that helps teams securely store, sync, and share environment variables across development workflows. It replaces insecure practices like sharing `.env` files via Slack or email. #### How is KeyEnv different from .env files? | Aspect | .env Files | KeyEnv | |--------|-----------|--------| | Security | Unencrypted, often committed to git | Encrypted at rest, never in git | | Sharing | Copy/paste, email, Slack | Secure team access with roles | | Sync | Manual | Automatic with CLI | | Audit | None | Full audit trail | | Environments | Manual management | Built-in dev/staging/prod | #### Is KeyEnv free? Yes! The Free plan includes: - 3 projects - 100 secrets per environment - 1 team member - All core features See our [pricing page](/pricing) for plan details. ### Security #### How are secrets encrypted? Secrets are encrypted using AES-256-GCM before storage. Each secret has a unique initialization vector (IV). The encryption key is securely managed and never exposed. #### Can KeyEnv employees see my secrets? No. Secrets are encrypted at rest with keys that KeyEnv employees cannot access. We follow zero-knowledge principles for secret storage. #### Is KeyEnv SOC 2 compliant? We are working towards SOC 2 Type II compliance. Contact us for our current security documentation. #### What happens if KeyEnv is down? The CLI caches secrets locally (encrypted) so your applications continue working. Once connectivity is restored, secrets sync automatically. ### CLI #### How do I install the CLI? ```bash curl -fsSL https://keyenv.dev/install.sh | bash ``` See the [installation guide](/docs/getting-started/installation) for more options. #### The CLI says "not authenticated" Run `keyenv login` to authenticate. If you're in a CI/CD environment, set the `KEYENV_TOKEN` environment variable with a service token. #### How do I switch environments? ```bash # Pull secrets from staging keyenv pull -e staging # Run command with production secrets keyenv run -e production -- npm start ``` #### Can I use KeyEnv offline? Yes, once you've pulled secrets, they're cached locally. You can work offline and sync when back online. ### Teams & Collaboration #### How do I invite team members? 1. Go to your project's **Team** page 2. Click **Invite Member** 3. Enter their email and select a role 4. They'll receive an invitation email #### What are the different roles? | Role | Can View Secrets | Can Edit Secrets | Can Manage Team | |------|-----------------|------------------|-----------------| | Member | Yes | Yes | No | | Admin | Yes | Yes | Yes | #### Can I have different access per environment? Yes! You can set per-environment permissions (admin, write, read, or none) for each team member. Go to **Project Settings > Permissions** to configure environment-level access. ### CI/CD & Automation #### How do I use KeyEnv in GitHub Actions? ```yaml steps: - name: Install KeyEnv run: curl -fsSL https://keyenv.dev/install.sh | bash - name: Pull secrets env: KEYENV_TOKEN: ${{ secrets.KEYENV_TOKEN }} run: keyenv pull -e production ``` See the [CI/CD guide](/docs/guides/ci-cd) for more examples. #### Should I use a service token or my personal login? Always use service tokens for automation: - **Service tokens**: For CI/CD, scripts, automated processes - **Personal login**: For local development only #### How do I rotate a service token? 1. Create a new service token 2. Update your CI/CD secrets with the new token 3. Verify the new token works 4. Revoke the old token ### Billing & Plans #### How do I upgrade my plan? Go to **Settings** → **Billing** and click **Upgrade**. You'll be redirected to our secure checkout. #### Can I cancel anytime? Yes. Cancel from the billing page. You'll retain access until the end of your billing period. #### Do you offer annual billing? Annual billing with discounts is coming soon. Contact us for early access. ### Troubleshooting #### "Project not found" error - Verify you're in the correct directory (check for `.keyenv.toml`) - Run `keyenv init` if you haven't set up the project - Check that you have access to the project #### "Permission denied" error - Verify your role has the required permissions - For service tokens, check the token has access to the environment - Try logging out and back in: `keyenv logout && keyenv login` #### Secrets not updating ```bash # Force a fresh pull keyenv pull --force # List current secrets to verify keyenv list ``` #### Need more help? - Email: support@keyenv.dev - Documentation: [keyenv.dev/docs](/docs) --- # CLI Reference ## CLI Reference Complete reference for all KeyEnv CLI commands. Source: https://keyenv.dev/docs/cli/ The KeyEnv CLI provides commands for managing secrets from your terminal. ### Global Options These options work with all commands: | Option | Description | |--------|-------------| | --json | Output JSON instead of human-readable text | | -q, --quiet | Minimize output (only essential info) | | --no-color | Disable colored output | | -v, --verbose | Enable verbose output | | -h, --help | Print help information | | -V, --version | Print version | ### Command Categories #### Authentication - [`login`](/docs/cli/authentication#login) - Authenticate with KeyEnv - [`logout`](/docs/cli/authentication#logout) - Clear stored credentials - [`whoami`](/docs/cli/authentication#whoami) - Display current user #### Projects - [`init`](/docs/cli/projects#init) - Initialize a project - [`projects`](/docs/cli/projects#projects) - List all projects - [`switch`](/docs/cli/projects#switch) - Switch to a different project #### Secrets - [`pull`](/docs/cli/secrets#pull) - Pull secrets from server - [`push`](/docs/cli/secrets#push) - Push secrets to server - [`list`](/docs/cli/secrets#list) - List all secrets - [`get`](/docs/cli/secrets#get) - Get a secret value - [`set`](/docs/cli/secrets#set) - Set a secret value - [`delete`](/docs/cli/secrets#delete) - Delete a secret - [`export`](/docs/cli/reference#export) - Export secrets to various formats #### Workflow - [`run`](/docs/cli/workflow#run) - Run command with secrets - [`diff`](/docs/cli/workflow#diff) - Compare local and remote secrets - [`history`](/docs/cli/workflow#history) - View secret history #### Security - [`scan`](/docs/cli/reference#security) - Scan for hardcoded secrets in your codebase #### Tokens - [`tokens list`](/docs/cli/reference#tokens) - List all service tokens - [`tokens create`](/docs/cli/reference#tokens) - Create a new service token - [`tokens revoke`](/docs/cli/reference#tokens) - Revoke a service token - [`tokens rotate`](/docs/cli/reference#tokens) - Rotate a service token #### Teams - [`team list`](/docs/cli/reference#teams) - List your teams - [`team show`](/docs/cli/reference#teams) - Show team details and members - [`team invite`](/docs/cli/reference#teams) - Invite a member to a team - [`team role`](/docs/cli/reference#teams) - Update a member's role - [`team remove`](/docs/cli/reference#teams) - Remove a member from a team #### Rotations - [`rotations`](/docs/cli/reference#rotations) - Manage secret rotation configurations #### Update - [`update check`](/docs/cli/reference#update) - Check for CLI updates - [`update install`](/docs/cli/reference#update) - Install the latest CLI version #### Utility - [`completions`](/docs/cli/reference#shell-completions) - Generate shell completion scripts See the [full reference](/docs/cli/reference) for all commands on a single page. ## Authentication Commands CLI commands for authentication and user management. Source: https://keyenv.dev/docs/cli/authentication/ Commands for managing your KeyEnv authentication. ### login Authenticate with the KeyEnv server. ```bash keyenv login [OPTIONS] ``` #### Options | Option | Description | |--------|-------------| | -t, --token <TOKEN> | Use a service token instead of browser auth | | -s, --server <URL> | Specify a custom server URL | #### Examples ```bash # Interactive browser authentication keyenv login # Login with a service token (for CI/CD) keyenv login --token env_abc123... # Login to a self-hosted server keyenv login --server https://keyenv.mycompany.com ``` #### Notes - Browser authentication opens your default browser - Tokens are stored securely in your system keychain (macOS/Windows) or encrypted file (Linux) - Service tokens are typically used in CI/CD environments --- ### logout Log out and clear stored credentials. ```bash keyenv logout ``` #### Examples ```bash keyenv logout # ✓ Logged out successfully ``` #### Notes - Removes the token from your keychain/credential store - You'll need to run `keyenv login` again to use the CLI --- ### whoami Display the currently authenticated user. ```bash keyenv whoami ``` #### Examples ```bash keyenv whoami # User: you@example.com # Server: https://api.keyenv.dev # Current Project: my-project ``` #### JSON Output ```bash keyenv whoami --json ``` ```json { "id": "user_abc123", "email": "you@example.com", "name": "Your Name", "server": "https://api.keyenv.dev", "current_project": "my-project" } ``` ## Project Commands CLI commands for managing KeyEnv projects. Source: https://keyenv.dev/docs/cli/projects/ Commands for initializing and managing KeyEnv projects. ### init Initialize a new KeyEnv project in the current directory. ```bash keyenv init [OPTIONS] ``` #### Options | Option | Description | |--------|-------------| | -n, --name <NAME> | Create a new project with this name | | -p, --project <ID> | Link to an existing project by ID | #### Examples ```bash # Interactive initialization keyenv init # Create a new project keyenv init --name "my-api" # Link to existing project keyenv init --project abc123-def456-... ``` #### What It Does 1. Prompts you to select or create a project 2. Creates a `.keyenv.toml` file with project configuration 3. Sets the default environment (usually `development`) #### Configuration File ```toml # .keyenv.toml project_id = "abc123-def456-..." default_environment = "development" ``` --- ### projects Manage KeyEnv projects. Requires a subcommand. ```bash keyenv projects ``` #### Subcommands | Command | Description | |---------|-------------| | list | List all available projects | | show <id> | Show details for a specific project | --- ### projects list List all available projects. ```bash keyenv projects list ``` #### Examples ```bash keyenv projects list # Projects: # • my-api (abc123...) ← current # • frontend (def456...) # • shared-services (ghi789...) ``` #### JSON Output ```bash keyenv projects list --json ``` ```json { "projects": [ { "id": "abc123-...", "name": "my-api", "current": true }, { "id": "def456-...", "name": "frontend" } ], "current_project": "abc123-..." } ``` --- ### projects show Show details for a specific project. ```bash keyenv projects show ``` #### Arguments | Argument | Description | |----------|-------------| | ID | The project ID to show details for | #### Examples ```bash keyenv projects show abc123-def456-... # Project: my-api # ID: abc123-def456-... # Environments: development, staging, production # Created: 2024-01-15 ``` #### JSON Output ```bash keyenv projects show abc123-def456-... --json ``` ```json { "id": "abc123-def456-...", "name": "my-api", "environments": ["development", "staging", "production"], "created_at": "2024-01-15T10:30:00Z" } ``` --- ### switch Switch to a different project. ```bash keyenv switch ``` #### Arguments | Argument | Description | |----------|-------------| | PROJECT | Project name or ID to switch to | #### Examples ```bash # Switch by name keyenv switch frontend # Switch by ID keyenv switch def456-... ``` #### Notes - Updates your global config to use the selected project - Affects all subsequent commands in any directory - Use `keyenv init` in a project directory for per-directory configuration ## Secret Commands CLI commands for managing secrets. Source: https://keyenv.dev/docs/cli/secrets/ Commands for managing secrets within a project. ### pull Pull secrets from the server to a local `.env` file. ```bash keyenv pull [OPTIONS] ``` #### Options | Option | Description | |--------|-------------| | -e, --env <ENV> | Environment to pull from (default: development) | | -f, --force | Overwrite existing file without confirmation | #### Examples ```bash # Pull development secrets to .env keyenv pull # Pull production secrets to .env keyenv pull -e production # Pull staging secrets, overwrite existing keyenv pull -e staging --force ``` --- ### push Push local secrets from a `.env` file to the server. ```bash keyenv push [OPTIONS] ``` #### Options | Option | Description | |--------|-------------| | -e, --env <ENV> | Environment to push to (default: development) | | -f, --force | Overwrite existing secrets | #### Examples ```bash # Push .env to development keyenv push # Force overwrite existing secrets keyenv push --force ``` #### Behavior Without `--force`: - New secrets are created - Existing secrets are skipped - You'll see a summary: "Created: 5, Skipped: 3" With `--force`: - New secrets are created - Existing secrets are updated - You'll see: "Created: 2, Updated: 6" --- ### list List all secrets in the current project (keys only, values hidden). ```bash keyenv list [OPTIONS] ``` #### Options | Option | Description | |--------|-------------| | -e, --env <ENV> | Environment (default: development) | #### Examples ```bash keyenv list # Secrets (development): # • DATABASE_URL (v3) # • STRIPE_KEY (v1) # • API_SECRET (v2) # # 3 secrets ``` #### JSON Output ```bash keyenv list --json ``` ```json { "environment": "development", "secrets": [ { "key": "DATABASE_URL", "version": 3 }, { "key": "STRIPE_KEY", "version": 1 } ], "count": 2 } ``` --- ### get Get a specific secret value. ```bash keyenv get [OPTIONS] ``` #### Arguments | Argument | Description | |----------|-------------| | KEY | The secret key to retrieve | #### Options | Option | Description | |--------|-------------| | -e, --env <ENV> | Environment (default: development) | #### Examples ```bash # Get a secret value keyenv get DATABASE_URL # Get from production keyenv get DATABASE_URL -e production # Use in scripts export DB=$(keyenv get DATABASE_URL -q) ``` --- ### set Set a secret value (creates or updates). ```bash keyenv set [OPTIONS] ``` #### Arguments | Argument | Description | |----------|-------------| | KEY | The secret key | | VALUE | The secret value (use - to read from stdin) | #### Options | Option | Description | |--------|-------------| | -e, --env <ENV> | Environment (default: development) | | -g, --generate <TYPE> | Generate a random secret value (type: password, uuid, hex, base64, alphanumeric) | | -l, --length <N> | Length of generated secret in characters (default: 32, requires --generate) | #### Examples ```bash # Set a secret keyenv set DATABASE_URL "postgres://localhost/mydb" # Set in production keyenv set API_KEY "sk_live_..." -e production # Read from stdin (for sensitive values) echo "super-secret" | keyenv set MY_SECRET - # Pipe from a command op read "op://vault/item/password" | keyenv set PASSWORD - # Generate a random password keyenv set API_SECRET --generate password # Generate a UUID keyenv set REQUEST_ID --generate uuid # Generate a 64-character hex secret keyenv set ENCRYPTION_KEY --generate hex --length 64 ``` --- ### delete Delete a secret. ```bash keyenv delete [OPTIONS] ``` #### Arguments | Argument | Description | |----------|-------------| | KEY | The secret key to delete | #### Options | Option | Description | |--------|-------------| | -e, --env <ENV> | Environment (default: development) | | -y, --yes | Skip confirmation prompt | #### Examples ```bash # Delete with confirmation keyenv delete OLD_API_KEY # Delete without confirmation keyenv delete OLD_API_KEY --yes # Delete from production keyenv delete DEPRECATED_KEY -e production -y ``` > **Warning** > > Deletion is permanent. Consider using version history before deleting if you might need the value later. ## Workflow Commands CLI commands for development workflow. Source: https://keyenv.dev/docs/cli/workflow/ Commands that integrate with your development workflow. ### run Run a command with secrets injected as environment variables. ```bash keyenv run [OPTIONS] -- ``` #### Options | Option | Description | |--------|-------------| | -e, --env <ENV> | Environment (default: development) | #### Examples ```bash # Run npm start with secrets keyenv run -- npm start # Run with production secrets keyenv run -e production -- node server.js # Run any command keyenv run -- python manage.py runserver # Complex commands (use quotes) keyenv run -- sh -c "echo $DATABASE_URL && npm test" ``` #### How It Works 1. Fetches secrets from the server 2. Injects them as environment variables 3. Runs your command with those variables 4. Secrets are never written to disk > **Note** > > Use `keyenv run` when you want secrets available but don't want a `.env` file on disk. --- ### diff Show differences between local `.env` file and remote secrets. ```bash keyenv diff [OPTIONS] ``` #### Options | Option | Description | |--------|-------------| | -e, --env <ENV> | Environment (default: development) | #### Examples ```bash keyenv diff # Comparing .env with development: # + NEW_LOCAL_KEY (local only) # - REMOVED_KEY (remote only) # ~ CHANGED_KEY (modified) # # + local only - remote only ~ modified ``` #### Output | Symbol | Meaning | |--------|---------| | + | Key exists locally but not on server | | - | Key exists on server but not locally | | ~ | Key exists in both but values differ | #### JSON Output ```bash keyenv diff --json ``` ```json { "environment": "development", "has_differences": true, "differences": [ { "key": "NEW_LOCAL_KEY", "status": "local_only" }, { "key": "REMOVED_KEY", "status": "remote_only" }, { "key": "CHANGED_KEY", "status": "modified" } ] } ``` --- ### history Show version history for a secret. ```bash keyenv history [OPTIONS] ``` #### Arguments | Argument | Description | |----------|-------------| | KEY | The secret key to show history for | #### Options | Option | Description | |--------|-------------| | -e, --env <ENV> | Environment (default: development) | | -l, --limit <N> | Number of entries to show (default: 10) | #### Examples ```bash keyenv history DATABASE_URL # History for DATABASE_URL (development): # v3 2024-01-15 10:30 user@example.com postgres://prod... # v2 2024-01-10 14:22 user@example.com postgres://staging... # v1 2024-01-05 09:00 admin@example.com postgres://dev... ``` #### JSON Output ```bash keyenv history DATABASE_URL --json ``` ```json { "key": "DATABASE_URL", "environment": "development", "history": [ { "version": 3, "changed_at": "2024-01-15T10:30:00Z", "changed_by": "user@example.com", "value_preview": "postgres://prod..." } ] } ``` > **Note** > > Values are partially masked in the output. Use `keyenv get` to retrieve the full value. ## Full Reference All CLI commands on a single page. Source: https://keyenv.dev/docs/cli/reference/ Quick reference for all KeyEnv CLI commands. ### Authentication | Command | Description | |---------|-------------| | keyenv login | Authenticate with KeyEnv | | keyenv logout | Clear stored credentials | | keyenv whoami | Display current user | ### Projects | Command | Description | |---------|-------------| | keyenv init | Initialize project in current directory | | keyenv projects | List all projects | | keyenv switch <project> | Switch to a different project | ### Secrets | Command | Description | |---------|-------------| | keyenv pull | Pull secrets to .env file | | keyenv push | Push .env secrets to server | | keyenv list | List secret keys | | keyenv get <key> | Get a secret value | | keyenv set <key> <value> | Set a secret | | keyenv delete <key> | Delete a secret | | keyenv export | Export secrets to various formats | ### Export Export secrets to various formats (dotenv, JSON). | Command | Description | |---------|-------------| | keyenv export | Export secrets to stdout | | keyenv export -e <env> | Export from specific environment | | keyenv export -f <format> | Export in format (dotenv, json) | | keyenv export -o <file> | Export to file | #### Options | Option | Description | |--------|-------------| | -e, --env <ENV> | Environment to export from (default: development) | | -f, --format <FORMAT> | Output format: dotenv, json (default: dotenv) | | -o, --output <FILE> | Write output to file instead of stdout | #### Examples ```bash # Export as dotenv to stdout keyenv export # Export production secrets as JSON keyenv export -e production -f json # Export to a file keyenv export -o .env.local ``` ### Workflow | Command | Description | |---------|-------------| | keyenv run -- <cmd> | Run command with secrets | | keyenv diff | Compare local and remote | | keyenv history <key> | View secret history | ### Permissions | Command | Description | |---------|-------------| | keyenv permissions list | List permissions for an environment | | keyenv permissions set <email> <role> | Set a user's permission (none/read/write/admin) | | keyenv permissions delete <email> | Remove a user's permission | | keyenv permissions my | Show your permissions for all environments | ### Tokens Manage service tokens for CI/CD and programmatic access. | Command | Description | |---------|-------------| | keyenv tokens list | List all service tokens | | keyenv tokens create --name <name> --scope <scope> | Create a new service token | | keyenv tokens revoke <id> | Revoke a service token | | keyenv tokens rotate <id> | Rotate a service token | #### Options | Option | Commands | Description | |--------|----------|-------------| | -n, --name <NAME> | create | Token name | | -s, --scope <SCOPE> | create | Token scope: read, write, admin (repeatable) | | -p, --project <ID> | create | Project ID (defaults to current project) | | -e, --env <ID> | create | Restrict to a specific environment ID | | --expires <DAYS> | create | Token expiry in days (omit for never) | | -y, --yes | revoke | Skip confirmation prompt | | --grace-period <MINS> | rotate | Grace period in minutes for old token (default: 5, max: 60) | #### Examples ```bash # List all tokens keyenv tokens list # Create a read-only token for CI keyenv tokens create --name deploy-ci --scope read # Create an admin token that expires in 90 days keyenv tokens create --name admin-token --scope admin --expires 90 # Revoke a token keyenv tokens revoke st_abc123 # Revoke without confirmation keyenv tokens revoke st_abc123 --yes # Rotate a token with 30-minute grace period keyenv tokens rotate st_abc123 --grace-period 30 ``` ### Teams Manage teams and team members. | Command | Description | |---------|-------------| | keyenv team list | List your teams | | keyenv team create <name> | Create a new team | | keyenv team show <id> | Show team details and members | | keyenv team use <id> | Set default team for current session | | keyenv team invite <email> [role] [--team <team_id>] | Invite a member (default: member) | | keyenv team role <team> <user> <role> | Update a member's role | | keyenv team remove <team> <user> | Remove a member from a team | | keyenv team invitations <team> | List pending invitations | | keyenv team revoke-invite <team> <invitation> | Revoke a pending invitation | | keyenv team resend-invite <team> <invitation> | Resend an invitation email | | keyenv team delete <team> | Delete a team | | keyenv team audit <team> | View team audit log | #### Options | Option | Commands | Description | |--------|----------|-------------| | role | invite | Role for invited user: admin or member (default: member) | | -y, --yes | remove, revoke-invite, delete | Skip confirmation prompt | #### Examples ```bash # List all teams keyenv team list # Create a new team keyenv team create "My Team" # Show team details keyenv team show team_abc123 # Set default team for the session keyenv team use team_abc123 # Invite a member keyenv team invite user@example.com member --team team_abc123 # Invite as admin keyenv team invite user@example.com admin --team team_abc123 # Change a member's role keyenv team role team_abc123 user_xyz admin # Remove a member keyenv team remove team_abc123 user_xyz # List pending invitations keyenv team invitations team_abc123 # Revoke a pending invitation keyenv team revoke-invite team_abc123 inv_xyz # Resend an invitation email keyenv team resend-invite team_abc123 inv_xyz # Delete a team (requires confirmation) keyenv team delete team_abc123 # View audit log keyenv team audit team_abc123 ``` ### Security | Command | Description | |---------|-------------| | keyenv scan | Scan directory for hardcoded secrets | | keyenv scan <path> | Scan specific path | | keyenv scan --severity <level> | Filter by severity (critical, high, medium, low) | | keyenv scan --json | Output findings as JSON | | keyenv scan --upload | Upload scan results to KeyEnv for tracking | | keyenv scan --hook | Install pre-commit hook | | keyenv scan --hook --remove | Remove pre-commit hook | ### Update Check for and install CLI updates. | Command | Description | |---------|-------------| | keyenv update check | Check if a new version is available | | keyenv update install | Download and install the latest version | #### Examples ```bash # Check for updates keyenv update check # Current version: 0.5.0 # Latest version: 0.6.0 # Run 'keyenv update install' to update # Install the latest version keyenv update install ``` ### Rotations Manage automatic secret rotation configurations. | Command | Description | |---------|-------------| | keyenv rotations list | List all rotation configurations | | keyenv rotations show <id> | Show details of a rotation | | keyenv rotations trigger <id> | Manually trigger a rotation | | keyenv rotations history <id> | View rotation execution history | #### Options | Option | Description | |--------|-------------| | -e, --env <ENV> | Environment (default: development) | #### Examples ```bash # List all rotations keyenv rotations list # Show rotation details keyenv rotations show rot_abc123 # Manually trigger a rotation keyenv rotations trigger rot_abc123 # View rotation history keyenv rotations history rot_abc123 ``` ### Shell Completions | Command | Description | |---------|-------------| | keyenv completions <shell> | Generate shell completion script | Supported shells: `bash`, `zsh`, `fish`, `powershell`, `elvish` #### Setup **Bash** (add to `~/.bashrc`): ```bash source <(keyenv completions bash) ``` **Zsh** (add to `~/.zshrc`): ```bash # Option 1: Direct source source <(keyenv completions zsh) # Option 2: Save to fpath (faster startup) keyenv completions zsh > ~/.zfunc/_keyenv # Then ensure ~/.zfunc is in fpath and run: autoload -Uz compinit && compinit ``` **Fish** (run once): ```bash keyenv completions fish > ~/.config/fish/completions/keyenv.fish ``` **PowerShell** (add to profile): ```powershell keyenv completions powershell | Out-String | Invoke-Expression ``` ### Global Options | Option | Description | |--------|-------------| | --json | JSON output | | -q, --quiet | Minimal output | | --no-color | Disable colored output | | -v, --verbose | Enable verbose output | | -h, --help | Show help | | -V, --version | Show version | ### Common Flags | Flag | Commands | Description | |------|----------|-------------| | -e, --env | Most | Target environment | | -f, --force | pull, push | Overwrite without asking | ### Environment Variables | Variable | Description | |----------|-------------| | KEYENV_TOKEN | Service token for authentication | | KEYENV_API_URL | Custom API server URL | ### Exit Codes | Code | Meaning | |------|---------| | 0 | Success | | 1 | General error | | 2 | Authentication error | | 3 | Not found | --- # Web App ## Web App Manage secrets through the KeyEnv web interface. Source: https://keyenv.dev/docs/web-app/ The KeyEnv web app provides a visual interface for managing secrets, teams, and projects. ### Overview Access the web app at [app.keyenv.dev](https://app.keyenv.dev). #### Key Features - **Visual secret management** - Create, edit, and delete secrets - **Team collaboration** - Invite members and manage permissions - **Audit logs** - Track all changes to your secrets - **Environment switching** - Easily switch between environments ### Sections - [Projects](/docs/web-app/projects) - Create and manage projects - [Environments](/docs/web-app/environments) - Configure environments - [Secrets](/docs/web-app/secrets) - Manage secret values - [Teams](/docs/web-app/teams) - Collaborate with your team - [Service Tokens](/docs/web-app/service-tokens) - Set up CI/CD access - [Billing](/docs/web-app/billing) - Manage plans and subscriptions - [Profile](/docs/web-app/profile) - View your profile, security, activity, and account settings ## Projects Creating and managing projects in the web app. Source: https://keyenv.dev/docs/web-app/projects/ Projects are the top-level container for your secrets. Each project typically represents one application or service. ### Creating a Project 1. Click the project dropdown in the header 2. Click **New Project** 3. Enter a project name (e.g., "my-api", "frontend") 4. Optionally add a description 5. Click **Create** Your project is created with a default `development` environment. ### Project Settings Access project settings by clicking the gear icon next to the project name. #### General Settings - **Name**: The display name of your project - **Description**: Optional description for documentation - **Project ID**: Unique identifier used by the CLI #### Danger Zone - **Delete Project**: Permanently removes the project and all secrets > **Warning** > > Deleting a project cannot be undone. Make sure to export your secrets first if needed. ### Switching Projects Use the project dropdown in the header to switch between projects. The currently selected project is displayed in the dropdown button. ### Project List The project dropdown shows all projects you have access to: - Projects you created - Projects shared with you by team members ## Environments Managing environments for different stages of development. Source: https://keyenv.dev/docs/web-app/environments/ Environments allow you to maintain separate sets of secrets for different stages of your development workflow. ### Default Environment When you create a project, a `development` environment is created by default. You can add additional environments like `staging` and `production` as needed. | Environment | Purpose | |-------------|---------| | `development` | Local development and testing | | `staging` | Pre-production testing | | `production` | Live application | ### Switching Environments Use the environment tabs at the top of the secrets page to switch between environments. Each environment maintains its own: - Secret values - Version history - Access logs ### Environment Isolation Secrets are completely isolated between environments: ``` # development DATABASE_URL=postgres://localhost/mydb_dev # staging DATABASE_URL=postgres://staging-server/mydb_staging # production DATABASE_URL=postgres://prod-server/mydb_prod ``` This ensures you never accidentally use production credentials in development. ### Environment Permissions Control who can access each environment with granular permissions. #### Permission Levels | Role | Description | |------|-------------| | `none` | No access to the environment | | `read` | Can view secrets (pull, list, get) | | `write` | Can view and modify secrets (create, update, delete) | | `admin` | Full access including managing other users' permissions | #### Managing Permissions 1. Navigate to an environment in your project 2. Click the **⋮** menu next to the environment tabs 3. Select **Manage Access** 4. Add team members and assign their permission level > **Note** > > Team admins automatically have full access to all environments. #### Default Permissions Set default permissions for new team members in **Project Settings → Default Permissions**. When a new member joins the team, they'll automatically receive these permissions for each environment. ### Best Practices 1. **Never copy production secrets to development** - Use separate credentials for each environment 2. **Use similar key names** - Keep the same keys across environments for consistency 3. **Document differences** - Use descriptions to note environment-specific requirements 4. **Restrict production access** - Use environment permissions to limit who can view production secrets 5. **Use read-only for CI/CD** - Service tokens for CI/CD only need `read` access > **Note** > > The CLI defaults to the `development` environment. Use the `-e` flag to target other environments. ## Environment Permissions Control access to secrets with granular environment-level permissions. Source: https://keyenv.dev/docs/web-app/permissions/ Environment permissions give you fine-grained control over who can access secrets in each environment. This allows teams to restrict production access while giving developers full access to development environments. ### Permission Roles Each team member can have one of four permission levels per environment: | Role | View Secrets | Modify Secrets | Manage Permissions | |------|--------------|----------------|-------------------| | `none` | ❌ | ❌ | ❌ | | `read` | ✅ | ❌ | ❌ | | `write` | ✅ | ✅ | ❌ | | `admin` | ✅ | ✅ | ✅ | > **Note** > > Team admins automatically have full access to all environments regardless of individual permission settings. ### Managing Permissions #### Via the Web App 1. Navigate to your project's secrets page 2. Click the **⋮** menu next to the environment tabs 3. Select **Manage Access** 4. In the modal: - View current permissions for all team members - Add new permissions by selecting a team member and role - Edit existing permissions by clicking on the role badge - Remove permissions with the delete button #### Via the CLI ```bash # List permissions for an environment keyenv permissions list --env production # Set a user's permission keyenv permissions set user@example.com write --env production # Remove a user's permission keyenv permissions delete user@example.com --env production # View your own permissions keyenv permissions my ``` #### Via the SDK ```typescript // Node.js const permissions = await client.listPermissions('project-id', 'production'); await client.setPermission('project-id', 'production', 'user-id', 'write'); ``` ```python # Python permissions = client.list_permissions("project-id", "production") client.set_permission("project-id", "production", "user-id", "write") ``` ### Default Permissions Configure default permissions that are automatically applied when new team members join. #### Setting Defaults 1. Go to **Project Settings** 2. Find the **Default Permissions** section 3. Set the default role for each environment 4. Click **Save Changes** When a new member joins the team, they'll automatically receive these default permissions. > **Warning** > > Default permissions only apply to new team members. Existing members keep their current permissions. ### Service Token Permissions Service tokens can be scoped to specific environments for additional security: - **Single-environment tokens**: Create tokens that can only access one environment - **Read-only tokens**: Use `read` scope for CI/CD that only needs to pull secrets - **Write tokens**: Use `write` scope only when the token needs to modify secrets ```bash # Example: CI/CD token with read-only access to production KEYENV_TOKEN="env_..." keyenv pull --env production ``` ### Team Roles and Environment Permissions KeyEnv uses a two-tier permission model. Understanding how these layers interact is key to setting up secure access for your team. #### Team Admin vs Environment Permission **Team role** (`admin` or `member`) is set at the team level and applies across all projects. **Environment permission** (`none`, `read`, `write`, `admin`) is set per-environment within a project. These two layers interact as follows: - **Team admins** automatically receive `admin` access to every environment in every project. No environment-level permission is checked. This means team admins can always view secrets, modify secrets, and manage permissions in any environment. - **Team members** have no implicit access. Their effective permission for a given environment is determined solely by their environment-level permission. If no permission is set, they have `none` (no access). #### Permission Resolution When a user tries to access secrets in an environment, the system resolves their effective permission: 1. Check if the user is a team admin. If yes, grant `admin` access (done). 2. Look up the user's environment-level permission for this specific environment. 3. If no explicit permission exists, the effective role is `none`. This means a team member with `write` on `development` and `read` on `production` can edit dev secrets but only view production secrets. A team admin skips this check entirely. #### Default Permissions and New Members When a new member joins the team (by accepting an invitation), default permissions are applied automatically: 1. The system checks each project for configured default permissions. 2. For each environment with a default role, the new member receives that role. 3. If no default is configured for an environment, the member gets `none`. Only team admins can configure project default permissions. Defaults do not retroactively change existing members' permissions. #### Permission Cleanup on Member Removal When a member is removed from a team: - All their environment-level permissions across all projects are removed - Access is revoked immediately - Service tokens created by that user continue to work (revoke them separately if needed) #### Practical Examples **Small startup (5 developers, 1 DevOps lead)** Set up the DevOps lead as a team admin. Add all developers as team members with defaults: ``` development: write (everyone can edit dev secrets) staging: write (everyone can deploy to staging) production: read (developers can view but not change production) ``` The DevOps lead, as a team admin, automatically has full access to production for deployments and rotations. **Agency with client projects** Use team members for contractors with minimal access: ``` development: write (contractors work in dev) staging: none (no staging access) production: none (no production access) ``` Keep at least two internal staff as team admins for full access. **Large team with environment owners** Promote environment leads to `admin` on their specific environments: ``` development: Most developers → write, Dev Lead → admin staging: QA team → write, QA Lead → admin production: DevOps → admin, Everyone else → read ``` Environment admins can manage permissions for their environment without needing to be team admins. ### Best Practices #### Principle of Least Privilege - Give users the minimum access they need - Use `read` for users who only need to view secrets - Reserve `admin` for team leads who manage access #### Production Security - Limit `write` and `admin` access to production - Use `read` access for most developers in production - Require approval workflows for production changes #### Audit Trail All permission changes are logged in the audit trail: - Who changed the permission - What the previous and new roles were - When the change occurred ### Common Scenarios #### Development Team Setup ``` development: All developers → write staging: All developers → write production: All developers → read, DevOps → admin ``` #### Contractor Access ``` development: Contractor → write staging: Contractor → read production: Contractor → none ``` #### CI/CD Pipeline ``` Service Token: read access to required environments only ``` ## Secrets Managing secrets in the web app. Source: https://keyenv.dev/docs/web-app/secrets/ The secrets page is where you create, view, and manage your environment variables. ### Viewing Secrets Secrets are displayed in a list showing: - **Key**: The environment variable name - **Value**: Hidden by default (click to reveal) - **Version**: Current version number - **Updated**: When it was last modified ### Creating a Secret 1. Click **Add Secret** 2. Enter the key name (e.g., `DATABASE_URL`) 3. Enter the secret value 4. Optionally add a description 5. Click **Save** #### Key Naming Conventions - Use UPPERCASE with underscores: `DATABASE_URL`, `API_KEY` - Be descriptive: `STRIPE_SECRET_KEY` vs just `KEY` - Group related secrets: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` ### Editing a Secret 1. Click on a secret row to select it 2. Click the edit icon or press Enter 3. Modify the value 4. Click **Save** Each edit creates a new version, preserving the history. ### Viewing Value Secret values are hidden by default for security. To view: 1. Click the eye icon next to the value 2. The value is revealed 3. Click the eye icon again to hide it ### Version History Click on a secret to see its version history: - Previous values (partially masked) - Who made each change - When changes were made ### Deleting a Secret 1. Select the secret 2. Click the delete icon 3. Confirm the deletion > **Warning** > > Deleted secrets cannot be recovered. The version history is also removed. ### Bulk Operations #### Import from .env 1. Click **Import** 2. Paste your `.env` file contents or upload a file 3. Review the secrets to be imported 4. Click **Import** #### Export to .env 1. Click **Export** 2. Download the `.env` file > **Note** > > For automated sync, use the CLI: `keyenv pull` and `keyenv push` ## Secret Rotation Automatically rotate database credentials with zero downtime. Source: https://keyenv.dev/docs/web-app/rotations/ Secret rotation automatically rotates database credentials on a schedule, eliminating manual password management and reducing security risks. ### What Is Secret Rotation? Secret rotation automatically: - Creates new database credentials on a configurable schedule - Updates your environment with the new credentials - Removes old credentials after successful rotation - Notifies you via webhooks when rotations occur ### Supported Databases | Database | Status | Connection Methods | |----------|--------|-------------------| | PostgreSQL | Supported | Direct, Lambda Proxy | | MySQL | Supported | Direct, Lambda Proxy | #### Connection Methods **Direct Connection**: KeyEnv connects directly to your database over the internet. Your database must be publicly accessible (with proper firewall rules) or accessible via VPN/peering. **Lambda Proxy**: For databases in private VPCs or behind firewalls, KeyEnv can execute rotations through an AWS Lambda function deployed in your VPC. The Lambda acts as a secure bridge between KeyEnv and your private database. ### How It Works KeyEnv uses a **two-secret strategy** for zero-downtime rotations: 1. **Setup**: You provide admin credentials for your database 2. **Two Credentials**: KeyEnv creates and maintains two sets of credentials 3. **Rotation**: When rotating, KeyEnv creates a new credential, verifies it works, then removes the old one 4. **Injection**: The active credentials are automatically injected into your environment This approach ensures your applications always have valid credentials, even during rotation. ### Injected Secrets When you create a rotation (e.g., named "main_database"), these secrets are automatically created in your environment: ```bash MAIN_DATABASE_HOST # Database hostname MAIN_DATABASE_PORT # Database port MAIN_DATABASE_DATABASE # Database name MAIN_DATABASE_USERNAME # Current active username MAIN_DATABASE_PASSWORD # Current active password MAIN_DATABASE_URL # Full connection URL ``` The naming convention is your rotation name converted to `UPPER_SNAKE_CASE`. ### Creating a Rotation 1. Go to **Project** → **Rotations** tab 2. Click **Create Rotation** 3. Fill in the wizard steps: #### Step 1: Database Type Select PostgreSQL or MySQL. #### Step 2: Connection Method Choose how KeyEnv will connect to your database: | Method | Use Case | |--------|----------| | **Direct** | Database is publicly accessible or via VPN | | **Proxied (Lambda)** | Database is in a private VPC | #### Step 3: Connection Details | Field | Description | |-------|-------------| | **Host** | Database server hostname or IP | | **Port** | Database port (5432 for PostgreSQL, 3306 for MySQL) | | **Database** | Database name to connect to | | **Admin Username** | Username with permission to create/drop users | | **Admin Password** | Password for the admin user | | **SSL Mode** | Connection encryption mode | ##### SSL Mode Options | Mode | Description | |------|-------------| | **Require** | Encrypt connection, don't verify certificate (default) | | **Verify Full** | Encrypt and verify server certificate + hostname | | **Verify CA** | Encrypt and verify certificate authority | | **Disable** | No encryption (only for local development) | > **Warning** > > The admin credentials must have permission to create users, grant permissions, and drop users. These are stored encrypted and never logged. #### Step 3b: Lambda Proxy Configuration (if proxied) If you selected proxied connection, you'll need to provide: | Field | Description | |-------|-------------| | **Lambda ARN** | The ARN of your deployed rotation proxy Lambda | | **AWS Region** | The AWS region where the Lambda is deployed | | **Shared Secret** | A secret key for HMAC request signing (auto-generated if blank) | > **Note** > > See [Setting Up Lambda Proxy](#setting-up-lambda-proxy) for deployment instructions. #### Step 4: Rotation Settings | Setting | Description | |---------|-------------| | **Name** | A name for this rotation (becomes prefix for injected secrets) | | **Rotation Interval** | How often to rotate (1-365 days) | #### Step 5: Test & Confirm KeyEnv tests the connection before saving. If the test fails, check your credentials and network connectivity. ### Managing Rotations #### View Rotation Details Click on a rotation card to see: - **Overview**: Current status, next rotation date, injected secrets - **History**: Past rotation events with timestamps and outcomes - **Webhooks**: Configured webhook notifications #### Update Settings 1. Click the rotation card 2. Update the rotation interval or pause/resume the rotation 3. Save changes #### Trigger Manual Rotation If you need to rotate credentials immediately: 1. Click the rotation card 2. Click **Rotate Now** 3. Confirm the action Manual rotations don't affect the scheduled rotation timeline. #### Pause/Resume Rotation To temporarily stop automatic rotations: 1. Click the rotation card 2. Toggle the status to **Paused** Paused rotations won't run on schedule but can still be triggered manually. #### Delete a Rotation 1. Click the rotation card 2. Click **Delete** 3. Confirm deletion > **Warning** > > Deleting a rotation removes the injected secrets and cleans up created database users. Make sure your applications are updated before deleting. ### Webhooks Get notified when rotation events occur. #### Creating a Webhook 1. Click the rotation card 2. Go to the **Webhooks** tab 3. Click **Add Webhook** 4. Enter the webhook URL 5. Select events to receive #### Webhook Events | Event | Description | |-------|-------------| | `rotation.started` | Rotation process has begun | | `rotation.completed` | Rotation completed successfully | | `rotation.failed` | Rotation failed with an error | #### Webhook Payload ```json { "event": "rotation.completed", "rotation": { "id": "rot_abc123", "name": "main_database", "integration_type": "postgresql" }, "timestamp": "2026-01-20T12:00:00Z", "version": 5 } ``` Webhooks include a signature header (`X-KeyEnv-Signature`) for verification. ### Best Practices 1. **Start with longer intervals** - Begin with 30 or 90 day rotations, then shorten as confidence grows 2. **Use webhooks** - Set up notifications to monitor rotation health 3. **Test in staging first** - Verify your application handles credential updates before enabling in production 4. **Monitor rotation history** - Check for failed rotations regularly 5. **Use connection pooling** - Connection pools handle credential changes more gracefully ### Troubleshooting #### Connection Test Fails - Verify the database is accessible from KeyEnv's servers - Check that the admin user has CREATE USER and GRANT permissions - Ensure the port is open and SSL settings are correct #### Rotation Fails Check the rotation history for error details. Common issues: - **Auth errors**: Admin credentials may have changed or expired - **Access errors**: Admin user may have lost permissions - **Network errors**: Database may be temporarily unreachable #### Application Can't Connect - Pull the latest secrets: `keyenv pull` - Restart your application to pick up new environment variables - Verify the rotation completed successfully in the history tab ### Setting Up Lambda Proxy For databases in private VPCs that can't be accessed directly from the internet, you can deploy a Lambda function in your VPC to act as a rotation proxy. #### Architecture ``` ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ │ KeyEnv API │────▶│ AWS Lambda │────▶│ Your Database │ │ (rotation svc) │ │ (your VPC) │ │ (private) │ └─────────────────┘ └──────────────────┘ └─────────────────┘ ``` #### Deployment Steps 1. **Create an AWS Lambda function** in your VPC with access to your database subnet 2. **Deploy the KeyEnv rotation proxy code** (available in our GitHub repository) 3. **Configure VPC settings** to allow the Lambda to connect to your database 4. **Note the Lambda ARN** and provide it when creating the rotation #### Security The Lambda proxy uses HMAC-SHA256 signed requests: - Requests include a timestamp and signature - Timestamps are validated within a 5-minute window (replay protection) - Shared secret is stored encrypted in KeyEnv, never logged - All communication uses TLS encryption > **Note** > > Contact support for detailed Lambda deployment templates and assistance with VPC configuration. ### Security - Admin credentials are encrypted at rest using AES-256-GCM - Credentials are never logged or exposed in API responses - Each rotation creates unique, randomly-generated passwords - Old credentials are dropped from the database after successful rotation - Lambda proxy requests are HMAC-signed with timestamp validation ## Teams Create teams, manage members, and control access to your projects. Source: https://keyenv.dev/docs/web-app/teams/ Teams are the organizational unit in KeyEnv. Every project belongs to a team, and team membership determines who can access your secrets. When you sign up, a personal team is created automatically. ### Team Lifecycle #### Creating a Team 1. Click your team name in the sidebar 2. Click **Create Team** 3. Enter a team name (up to 100 characters) 4. Click **Create** The creator automatically becomes the team's first **admin**. New teams start on the Free plan. #### Viewing Team Details Navigate to **Team** in the sidebar to see: - Team name and plan - All current members with their roles - Pending invitations #### Renaming a Team Team admins can rename a team from the team settings page. Click the edit icon next to the team name, enter the new name, and save. #### Deleting a Team Team deletion is handled by system administrators. If you need to delete a team, contact support. Deleting a team permanently removes all projects, secrets, and member associations. > **Warning** > > Team deletion cannot be undone. Make sure to export any secrets you need before requesting deletion. ### Team Roles KeyEnv has two team-level roles: **admin** and **member**. | Capability | Admin | Member | |---|---|---| | View team members and invitations | Yes | Yes | | Access all environments (bypasses env permissions) | Yes | No | | Invite and remove members | Yes | No | | Change member roles | Yes | No | | Create and delete projects | Yes | No | | Manage billing and plan | Yes | No | | Set default permissions | Yes | No | | View audit logs | Yes | No | | View and edit secrets | Yes | Per-environment | | Manage environment permissions | Yes | Only if granted `admin` on that environment | > **Note** > > Team admins automatically have full access to all environments in every project. Regular members get access based on their per-environment permissions. See [Environment Permissions](/docs/web-app/permissions) for details. #### Changing a Member's Role 1. Go to **Team** in the sidebar 2. Find the member in the list 3. Click the role dropdown next to their name 4. Select the new role You cannot demote the last admin. Every team must have at least one admin at all times. ### Inviting Members Only team admins can invite new members. #### Sending an Invitation 1. Go to **Team** in the sidebar 2. Click **Invite Member** 3. Enter the invitee's email address 4. Select a role (`admin` or `member`) 5. Click **Send Invite** The invitee receives an email with a link to join. If they do not have a KeyEnv account, they will be prompted to create one. > **Note** > > Members receive email notifications when invited to a team, when their role changes, and when they are removed from a team. #### Invitation Expiration Invitations expire after **30 days**. If an invitation expires: - The invitee can no longer accept it - An admin must resend the invitation to generate a fresh link Resending refreshes both the token and the expiration date. #### Invitations and Member Limits Pending invitations count toward your team's member limit. On the Free plan (1 member limit), you cannot send an invitation because the creator already fills the slot. Upgrade to Pro for unlimited members. #### Managing Pending Invitations Admins can view, resend, and revoke pending invitations: - **View**: Go to the **Invitations** tab on the team page - **Resend**: Click the resend button next to the invitation - **Revoke**: Click the revoke button to cancel a pending invitation ### Removing Members Team admins can remove any member from the team: 1. Go to **Team** in the sidebar 2. Find the member in the list 3. Click the remove button 4. Confirm the removal Non-admin members can only remove themselves (leave the team). They cannot remove other members. > **Warning** > > Removed members immediately lose access to all project secrets. Their environment-level permissions are cleaned up automatically. You cannot remove the last admin from a team. The sole admin must promote another member to admin before leaving. ### Team Roles and Environment Permissions KeyEnv uses a two-tier permission model: 1. **Team role** (`admin` or `member`) -- controls team-level operations 2. **Environment permission** (`none`, `read`, `write`, or `admin`) -- controls per-environment secret access How they interact: - **Team admins** bypass environment permissions entirely. They have full `admin` access to every environment in every project. - **Team members** rely on their per-environment permissions. If a member has no permission set for an environment, they have `none` (no access). #### Default Permissions Only team admins can configure default permissions for each environment. When a new member joins the team, they automatically receive these defaults. For example, you might set: - `development` -> `write` (all new members can edit dev secrets) - `staging` -> `read` (all new members can view staging) - `production` -> `none` (no production access by default) See [Environment Permissions](/docs/web-app/permissions) for full details on managing per-environment access. ### Plan Limits Team capabilities depend on your plan: | Resource | Free | Pro | Enterprise | |---|---|---|---| | Team members | 1 | Unlimited | Unlimited | | Projects | 3 | Unlimited | Unlimited | | Secrets per environment | 100 | Unlimited | Unlimited | | Environments per project | 3 | Unlimited | Unlimited | When you reach a limit, you can still use existing resources but cannot create new ones. Upgrade to Pro to remove all limits. See [Billing](/docs/web-app/billing) for details on upgrading and managing your plan. ### Audit Trail All team actions are logged: - Team created - Member invited, accepted, removed - Role changes - Invitation sent, resent, or revoked Access the audit log from the team settings page. Only team admins can view audit logs. ### Best Practices 1. **Keep multiple admins** -- If your sole admin loses access, no one can manage the team. Always have at least two admins for critical teams. 2. **Review access regularly** -- Remove members who no longer need access. Check pending invitations and revoke stale ones. 3. **Use service tokens for CI/CD** -- Do not share personal credentials with automated systems. Create scoped [service tokens](/docs/web-app/service-tokens) instead. 4. **Set default permissions thoughtfully** -- Configure project defaults so new members get appropriate access without manual setup. 5. **Restrict production access** -- Give most members `read` or `none` for production and reserve `write`/`admin` for senior engineers and DevOps. ## Service Tokens Setting up service tokens for CI/CD. Source: https://keyenv.dev/docs/web-app/service-tokens/ Service tokens provide secure access for automated systems like CI/CD pipelines. ### What Are Service Tokens? Service tokens are API keys that: - Authenticate without user interaction - Have configurable scope (one or more projects, optional environment restriction) - Support three permission levels: `read`, `write`, and `admin` - Can optionally expire after a set number of days - Are logged separately in the audit trail - Track last usage time for security auditing ### Token Scopes Every service token requires at least one scope. Scopes control what the token is allowed to do: | Scope | Description | |-------|-------------| | `read` | View and pull secrets (read-only access) | | `write` | Create, update, and delete secrets; create, rotate, and revoke tokens | | `admin` | Full administrative access to projects and environments | You can assign multiple scopes to a single token. For example, a CI/CD deploy token might need both `read` and `write`, while a monitoring tool only needs `read`. ### Creating a Service Token 1. Navigate to **Tokens** in the project navigation 2. Click **Create Token** 3. Enter a name (e.g., "GitHub Actions - Production") 4. Select one or more projects to grant access to 5. Optionally restrict access to a specific environment within the selected projects 6. Choose the scopes the token should have 7. Optionally set an expiration (in days) 8. Click **Create** > **Warning** > > Copy the token immediately! It's only shown once. #### Multi-Project Scoping Tokens can be scoped to **multiple projects** simultaneously, as long as all projects belong to the same team. This is useful when a single CI/CD pipeline deploys across several related projects. #### Environment Scoping By default, a token has access to all environments within its scoped projects. You can optionally restrict a token to a **single environment** (e.g., only `production`). This is recommended for production deploy tokens that should not be able to read staging or development secrets. #### Token Expiration By default, tokens never expire. When creating a token, you can set `expires_in_days` to have the token automatically expire after a given number of days. This is useful for: - Temporary CI tokens for short-lived feature branches - Contractor access that should automatically end - Compliance requirements that mandate periodic credential rotation For example, a token with `expires_in_days: 7` will stop working 7 days after creation. ### Token Format Service tokens start with `env_` followed by a random string: ``` env_abc123def456... ``` ### Using Service Tokens #### In CI/CD Set the token as an environment variable: ```yaml # GitHub Actions env: KEYENV_TOKEN: ${{ secrets.KEYENV_TOKEN }} steps: - run: keyenv pull -e production ``` #### With the CLI ```bash # Set the token export KEYENV_TOKEN="env_abc123..." # Now use CLI commands keyenv pull ``` #### Programmatic Access ```bash # Login with token keyenv login --token "env_abc123..." ``` ### Managing Tokens #### View Tokens The tokens list shows: - Token name - Projects and environments it can access - Scopes assigned - When it was created - Last used date > **Note** > > Tokens track `last_used_at` timestamps automatically. Use this to identify unused tokens that can be safely revoked. #### Revoke a Token 1. Find the token in the list 2. Click **Revoke** 3. Confirm revocation Revoked tokens immediately stop working. #### Rotate a Token Token rotation creates a new token while keeping the old one valid during a grace period. This allows zero-downtime updates across your CI/CD pipelines. 1. Find the token in the list 2. Click **Rotate** 3. Choose a grace period (default: 5 minutes, max: 60 minutes) 4. Copy the new token and update your systems 5. The old token automatically expires after the grace period > **Note** > > During the grace period, both old and new tokens are valid. This prevents CI/CD job failures while you update your secrets. **Via API:** ```bash curl -X POST https://api.keyenv.dev/tokens/{token_id}/rotate \ -H "Authorization: Bearer $KEYENV_TOKEN" \ -H "Content-Type: application/json" \ -d '{"grace_period_minutes": 10}' ``` Response: ```json { "new_token": { "token": "env_newtoken123...", "id": "tok_abc123" }, "old_token_expires_at": "2026-01-23T12:10:00Z" } ``` ### Security Notifications Team admins receive email notifications when service tokens are created, rotated, or revoked. These alerts include the token name and the email of the user who performed the action, helping teams stay aware of credential changes. ### Service Token Limitations Service tokens are designed for automated access to secrets and cannot perform all operations available to authenticated users. Specifically, service tokens **cannot**: - Manage team members or invitations - Access billing endpoints - Change team-level permission defaults These operations require a user session with the appropriate team admin role. ### Security Best Practices 1. **One token per use case** - Create separate tokens for different CI/CD workflows 2. **Limit scope** - Only grant access to needed projects and environments 3. **Use the narrowest scope** - Prefer `read` over `write` when the token only needs to pull secrets 4. **Set expiration for temporary access** - Use `expires_in_days` for short-lived tokens 5. **Rotate regularly** - Use the rotation feature for zero-downtime updates 6. **Never commit tokens** - Use your CI/CD's secret management 7. **Monitor usage** - Check `last_used_at` timestamps and audit logs for unexpected access 8. **Restrict to specific environments** - Use environment scoping to limit blast radius ## Billing Manage your team's plan, upgrade, and understand usage limits. Source: https://keyenv.dev/docs/web-app/billing/ KeyEnv offers three plan tiers. Each team has its own plan and billing. Only team admins can view and manage billing. ### Plan Tiers #### Free The default plan for every new team. Includes: | Resource | Limit | |---|---| | Team members | 1 | | Projects | 3 | | Secrets per environment | 100 | | Environments per project | 3 | The Free plan is suitable for personal projects and solo developers. All core features (encryption, CLI, environment permissions, audit trail) are included. #### Team For teams that need collaboration and higher limits. Includes: | Resource | Limit | |---|---| | Team members | Unlimited | | Projects | Unlimited | | Secrets per environment | Unlimited | | Environments per project | Unlimited | **Pricing**: $4/user/month or $40/user/year ($3.33/month, save 17%). Billed per active team member -- the seat count adjusts automatically when members join or leave. #### Enterprise For organizations with advanced requirements. Includes everything in Team plus: - Priority support - Custom integrations - SLA guarantees Contact us at [support@keyenv.dev](mailto:support@keyenv.dev) for Enterprise pricing. ### Free Trial New teams receive a **14-day free trial** of Team plan features when they first sign up. During the trial: - All Team plan limits are available (unlimited projects, secrets, members, and environments) - No payment method is required - An email notification is sent **3 days before the trial ends** - When the trial expires, the team automatically downgrades to the Free plan > **Note** > > You can upgrade to a paid Team plan at any time during the trial. If you upgrade, the trial ends immediately and billing begins. ### Upgrading to Team Only team admins can upgrade the plan. 1. Go to **Team** in the sidebar 2. Click **Billing** or **Upgrade** 3. Choose monthly or annual billing 4. Complete payment through the Stripe checkout page 5. Your plan upgrades immediately after payment The upgrade takes effect instantly. All limits are lifted and you can invite members, create projects, and add secrets without restrictions. ### Subscription States Your subscription can be in one of the following states: | State | Description | |---|---| | `active` | Subscription is current and fully paid. All Team plan features are available. | | `trialing` | Team is in the 14-day free trial period. All Team plan features are available. | | `past_due` | A payment has failed. The system will retry automatically. Features remain available while retries are in progress. | | `canceled` | Subscription has been canceled. The team reverts to the Free plan at the end of the current billing period. | ### Managing Your Subscription After upgrading, team admins can manage the subscription through the Stripe billing portal: 1. Go to **Team** in the sidebar 2. Click **Billing** 3. Click **Manage Subscription** From the billing portal you can: - Update your payment method - View invoices and payment history - Switch between monthly and annual billing - Cancel your subscription ### Downgrading When you cancel your Team subscription, the following happens: - Your plan reverts to Free at the end of the current billing period - **Existing resources are kept** -- your projects, secrets, members, and environments remain intact - **New creation is blocked** -- if your usage exceeds Free plan limits, you cannot create new resources of that type until you reduce usage or re-upgrade - Team admins receive a notification about the over-limit state > **Note** > > KeyEnv never deletes your data on downgrade. Your existing secrets and projects remain accessible. You just cannot create new ones beyond Free plan limits. ### Over-Limit State If your team's usage exceeds the plan limits (typically after a downgrade from Team to Free), you enter an over-limit state: - A warning banner appears in the web app - The API returns a `402 Payment Required` error when attempting to create resources that are over the limit - Existing resources continue to work normally (read, update, delete) #### Resolving Over-Limit To resolve an over-limit state, you can either: 1. **Reduce usage** -- Delete projects, secrets, or environments to bring usage within Free plan limits 2. **Remove members** -- Remove team members to get within the 1-member Free limit 3. **Upgrade** -- Re-upgrade to Team to remove all limits ### Sponsored Teams In some cases, a KeyEnv administrator may sponsor a team, granting it Team features at no cost. Sponsored teams: - Have all Team plan limits (unlimited everything) - Do not need a Stripe subscription - Cannot be charged through the checkout flow (checkout is blocked for sponsored teams) - May include an optional note from the administrator explaining the sponsorship ### Email Notifications KeyEnv sends email notifications for the following billing events: - **Subscription created** -- when a team upgrades to the Team plan - **Trial ending** -- sent 3 days before the free trial expires - **Payment failed** -- when a payment attempt is unsuccessful (includes the next retry date) - **Subscription renewed** -- when a subscription is successfully renewed at the start of a new billing period ### Viewing Usage Any team member can view the team's current resource usage: 1. Go to **Team** in the sidebar 2. The usage section shows current counts and limits for: - Projects - Team members - Secrets (highest count across all environments) - Environments (highest count across all projects) Usage warnings appear when you reach 80% of a limit. At-limit indicators appear when you have reached the maximum. ## Profile View your profile information, manage security settings, review activity, and control your account. Source: https://keyenv.dev/docs/web-app/profile/ The profile page is your personal hub in KeyEnv. Access it by clicking your avatar in the sidebar. It is organized into four tabs: General, Security, Activity, and Account. ### General The General tab shows your profile information and team memberships. #### Profile Information Your profile card displays: - **Avatar** -- Your profile picture (synced from your sign-in provider) - **Name** -- Your full name - **Email** -- Your primary email address - **Member since** -- The date you created your KeyEnv account To edit your name, avatar, or email, click **Edit Profile**. This opens your Clerk account settings in a new tab where you can update your personal information. #### Team Memberships Below your profile card, you can see every team you belong to along with: - **Team name** - **Your role** -- `admin` or `member` - **Join date** -- When you joined that team This gives you a quick overview of your access across all teams without navigating to each one individually. ### Security The Security tab lets you review your sign-in methods, two-factor authentication status, and active sessions. #### Connected Accounts This section lists all sign-in methods linked to your account: - **Password** -- Whether email/password sign-in is enabled or not set - **OAuth providers** -- Connected accounts such as Google, GitHub, Microsoft, or Apple, along with the email address associated with each #### Two-Factor Authentication Shows whether 2FA is currently enabled or disabled. Click **Enable 2FA** (or **Manage 2FA** if already enabled) to configure two-factor authentication through your account security settings. This opens Clerk's security page in a new tab. > **Note** > > Two-factor authentication adds an extra layer of security by requiring a code from your authenticator app each time you sign in. It is strongly recommended for all users. #### Active Sessions Lists all devices and browsers where you are currently signed in. Each session shows: - **Browser name and version** - **Location** (city and country) - **IP address** - **Last active time** Your current session is labeled with a **Current** badge. You can revoke any other session by clicking the **Revoke** button, which immediately signs you out of that device. > **Warning** > > Revoking a session takes effect immediately. The signed-in device will need to authenticate again to regain access. ### Activity The Activity tab displays a chronological log of your actions across all teams. #### Viewing Activity Activity is displayed in a table with the following columns: | Column | Description | |---|---| | Timestamp | When the action occurred | | Team | Which team the action belongs to | | Action | The type of action (e.g., created, updated, deleted) | | Details | The affected resource name or identifier | Actions are color-coded by category: - **Create** actions (e.g., created a project, added a secret) - **Read** actions (e.g., accessed secrets) - **Update** actions (e.g., updated a secret, changed a role) - **Delete** actions (e.g., deleted a project, removed a member) #### Filtering Click the **Filters** button to narrow down your activity log. Available filters: - **Action** -- Filter by a specific action type from the dropdown - **Start Date** -- Show only activity from this date onward - **End Date** -- Show only activity up to this date Click **Apply Filters** to update the results, or **Clear** to remove all filters. #### Pagination Activity logs are paginated at 25 entries per page. When there are multiple pages, navigation controls appear at the bottom of the table showing: - The range of entries currently displayed (e.g., "Showing 1 to 25 of 142") - Page number buttons for direct navigation - Previous and next arrows ### Account The Account tab provides data privacy controls and account deletion. #### Data Export You can request a full export of your KeyEnv data. The export includes: - Account information - Projects and environments - Secret names and metadata (values remain encrypted) - Team memberships - Audit logs - API token metadata To export your data: 1. Click **Export** on the Data Export card 2. Review what is included in the export 3. Click **Request Export** 4. You will receive an email notification when the export is ready Once the export is ready, return to this page and click **Download** to save the file. Exports have an expiration date -- if an export expires, you can request a new one. > **Note** > > Secret values are exported in encrypted form for security. The export contains metadata about your secrets, not the plaintext values. #### Account Deletion You can permanently delete your KeyEnv account from the Danger Zone section. Deleting your account removes: - Your account and profile - All projects you own - All secrets in your projects - Team memberships - API tokens - Audit history To delete your account: 1. Click **Delete** on the Delete Account card 2. Review the list of data that will be removed 3. Type **DELETE** in the confirmation field 4. Click **Delete My Account** > **Warning** > > Account deletion cannot be undone. All your data is permanently removed from KeyEnv's servers. Export your data first if you need a copy. --- # SDKs & Integrations ## SDKs & Integrations Official SDKs and integrations for KeyEnv. Source: https://keyenv.dev/docs/sdks/ Use KeyEnv in your applications with our official SDKs and integrations. ### SDKs - [Node.js SDK](/docs/sdks/nodejs): TypeScript/JavaScript SDK for server-side apps - [Python SDK](/docs/sdks/python): Python SDK with full type annotations - [Go SDK](/docs/sdks/go): Go SDK with full type safety - [Rust SDK](/docs/sdks/rust): Rust SDK with async/await support - [Java SDK](/docs/sdks/java): Java SDK with Java 11+ and async support (Beta) - [PHP SDK](/docs/sdks/php): PHP SDK for PHP 8.0+ applications (Beta) - [Ruby SDK](/docs/sdks/ruby): Ruby SDK for Ruby 3.0+ applications (Beta) - [.NET SDK](/docs/sdks/dotnet): .NET SDK for .NET 6.0+ with async patterns (Beta) ### Runtimes - [Bun](/docs/sdks/bun): Use KeyEnv with Bun applications - [Deno](/docs/sdks/deno): Use KeyEnv with Deno applications ### CI/CD Integrations - [GitHub Action](/docs/sdks/github-action): Inject secrets into GitHub Actions workflows - [Secret Scanner Action](/docs/sdks/scan-action): Scan for hardcoded secrets in your codebase - [Bitbucket Pipelines](/docs/sdks/bitbucket-pipelines): Inject secrets into Bitbucket Pipelines ### Serverless - [AWS Lambda Layer](/docs/sdks/aws-lambda): Pre-built layer for Lambda functions - [Azure Functions](/docs/sdks/azure-functions): Use KeyEnv with Azure Functions - [Google Cloud Functions](/docs/sdks/google-cloud-functions): Use KeyEnv with Cloud Functions - [Netlify Build Plugin](/docs/sdks/netlify): Auto-inject secrets during Netlify builds ### Framework Integrations - [Next.js](/docs/sdks/nextjs): Server Components, API Routes, and Vercel deployment - [Django](/docs/sdks/django): Django settings.py integration with environment-aware config - [Laravel](/docs/sdks/laravel): Service Provider, Facade, and Artisan commands for Laravel - [Spring Boot](/docs/sdks/spring-boot): Auto-configuration, @Value injection, and ConfigurationProperties - [Rails](/docs/sdks/rails): Rails initializer setup with credentials and database.yml integration ### Platform Integrations - [Kubernetes (ESO)](/docs/guides/kubernetes): Sync secrets to K8s via External Secrets Operator - [Docker](/docs/guides/docker): Use KeyEnv with Docker and docker-compose - [Vercel](/docs/guides/vercel): Deploy to Vercel with KeyEnv secrets - [Railway](/docs/guides/railway): Sync secrets to Railway projects ### When to Use What | Use Case | Recommended | |----------|-------------| | Local development | CLI (`keyenv pull`, `keyenv run`) | | Node.js/TypeScript apps | Node.js SDK | | Python apps | Python SDK | | Java apps | Java SDK | | PHP apps | PHP SDK | | Ruby apps | Ruby SDK | | .NET/C# apps | .NET SDK | | Next.js apps | Next.js integration (Node.js SDK) | | Django apps | Django integration (Python SDK) | | Laravel apps | Laravel integration (PHP SDK) | | Spring Boot apps | Spring Boot integration (Java SDK) | | Rails apps | Rails integration (Ruby SDK) | | ASP.NET Core apps | .NET SDK | | Go apps | Go SDK | | Rust apps | Rust SDK | | Bun apps | Node.js SDK or CLI | | Deno apps | CLI or direct API | | GitHub Actions (inject secrets) | GitHub Action | | GitHub Actions (scan for secrets) | Secret Scanner Action | | Bitbucket Pipelines | Bitbucket Pipe | | Other CI/CD | CLI | | AWS Lambda | Lambda Layer | | Azure Functions | Azure Functions integration | | Google Cloud Functions | Cloud Functions integration | | Netlify | Netlify Build Plugin | | Vercel | Next.js integration or Node.js SDK | | Scripts & automation | SDK or CLI | | Docker builds | CLI or GitHub Action | | Kubernetes | External Secrets Operator (ESO) integration | | Security scanning | Secret Scanner Action or CLI (`keyenv scan`) | ### Authentication All SDKs use **service tokens** for authentication. Create a token in the KeyEnv dashboard: 1. Go to **Settings > Service Tokens** 2. Click **Create Token** 3. Select scope (project-specific recommended) 4. Copy the token and store it securely ```bash # Set as environment variable export KEYENV_TOKEN="your-service-token" ``` ## API Reference Complete REST API documentation for KeyEnv Source: https://keyenv.dev/docs/sdks/api-reference/ The KeyEnv API is a RESTful HTTP API that provides programmatic access to manage projects, environments, secrets, teams, and service tokens. ### Base URL ``` https://api.keyenv.dev ``` ### Authentication The API supports two authentication methods: #### Bearer Token (JWT) For web app and CLI authentication using Clerk-issued JWTs: ```bash Authorization: Bearer ``` #### Service Token For CI/CD and programmatic access, use service tokens (prefixed with `env_`): ```bash Authorization: Bearer env_ ``` Service tokens have scoped access to specific projects and environments with `read` and/or `write` permissions. ### Rate Limiting API requests are rate limited to **60 requests per minute** per user/token. Rate limit headers are included in responses: | Header | Description | |--------|-------------| | `X-RateLimit-Limit` | Maximum requests per minute | | `X-RateLimit-Remaining` | Remaining requests in current window | | `X-RateLimit-Reset` | Unix timestamp when the limit resets | ### Error Handling All errors follow a consistent format: ```json { "error": "Human-readable error message" } ``` Common HTTP status codes: | Code | Description | |------|-------------| | 400 | Bad request - invalid input | | 401 | Unauthorized - missing or invalid authentication | | 403 | Forbidden - insufficient permissions | | 404 | Resource not found | | 409 | Conflict - resource already exists | | 429 | Rate limit exceeded | | 500 | Internal server error | ### OpenAPI Specification The complete OpenAPI specification is published alongside these docs: - **YAML**: [https://keyenv.dev/openapi.yaml](/openapi.yaml) - **JSON**: [https://keyenv.dev/openapi.json](/openapi.json) Import either file into [Swagger Editor](https://editor.swagger.io/), [Postman](https://www.postman.com/), [Insomnia](https://insomnia.rest/), or an AI agent to explore the API programmatically. > **Note** > > The [API Reference](/docs/api) section documents every endpoint, parameter, and response schema. It is generated from this specification, so it never drifts from the running API. ### Key Endpoints #### Health Check ```bash GET /health ``` Returns API health status. No authentication required. #### Projects | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/v1/projects` | List all projects | | POST | `/api/v1/projects` | Create a project | | GET | `/api/v1/projects/{id}` | Get a project | | PATCH | `/api/v1/projects/{id}` | Update a project | | DELETE | `/api/v1/projects/{id}` | Delete a project | #### Environments | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/v1/projects/{id}/environments` | List environments | | POST | `/api/v1/projects/{id}/environments` | Create an environment | | GET | `/api/v1/projects/{id}/environments/{env}` | Get an environment | | DELETE | `/api/v1/projects/{id}/environments/{env}` | Delete an environment | #### Secrets | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/v1/projects/{id}/environments/{env}/secrets` | List secrets | | POST | `/api/v1/projects/{id}/environments/{env}/secrets` | Create a secret | | GET | `/api/v1/projects/{id}/environments/{env}/secrets/{key}` | Get a secret | | PUT | `/api/v1/projects/{id}/environments/{env}/secrets/{key}` | Update a secret | | DELETE | `/api/v1/projects/{id}/environments/{env}/secrets/{key}` | Delete a secret | | GET | `/api/v1/projects/{id}/environments/{env}/secrets/export` | Export secrets (JSON) | | POST | `/api/v1/projects/{id}/environments/{env}/secrets/bulk` | Bulk import | #### Service Tokens | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/v1/tokens` | List tokens | | POST | `/api/v1/tokens` | Create a token | | DELETE | `/api/v1/tokens/{id}` | Delete a token | | POST | `/api/v1/tokens/{id}/rotate` | Rotate a token | #### Permissions | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/v1/projects/{id}/my-permissions` | Get my permissions | | GET | `/api/v1/projects/{id}/environments/{env}/permissions` | List permissions | | PUT | `/api/v1/projects/{id}/environments/{env}/permissions/{userId}` | Set permission | | DELETE | `/api/v1/projects/{id}/environments/{env}/permissions/{userId}` | Delete permission | #### Teams | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/v1/teams` | List teams | | POST | `/api/v1/teams` | Create a team | | GET | `/api/v1/teams/{id}` | Get a team | | POST | `/api/v1/teams/{id}/members` | Invite a member | | DELETE | `/api/v1/teams/{id}/members/{userId}` | Remove a member | #### Audit Logs Requires team admin role. Regular members receive `403 Forbidden`. | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/v1/audit` | List audit logs (admin only) | | GET | `/api/v1/teams/{id}/audit` | List team audit logs (admin only) | #### Additional Endpoints The API also includes endpoints for: - **Billing & Usage**: `/api/v1/teams/{id}/usage`, `/api/v1/teams/{id}/billing` - Team usage metrics and billing information - **Team Invitations**: `/api/v1/teams/{id}/invitations` - Manage pending team invitations - **Account Management**: `/api/v1/account`, `/api/v1/account/data-export` - Account settings and data export - **Secret Rotations**: `/api/v1/rotations/` - Configure automatic secret rotation - **CLI Authentication**: `/api/v1/auth/cli/` - Device authorization flow for CLI login Refer to the [OpenAPI specification](https://github.com/keyenv/keyenv/blob/main/api/openapi.yaml) for complete endpoint documentation. ### Example: List Secrets ```bash curl -X GET "https://api.keyenv.dev/api/v1/projects/{projectId}/environments/production/secrets" \ -H "Authorization: Bearer env_your_token_here" ``` Response: ```json { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "key": "DATABASE_URL", "value": "postgres://...", "version": 3, "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-20T14:45:00Z" }, { "id": "550e8400-e29b-41d4-a716-446655440001", "key": "API_KEY", "value": "sk_live_...", "version": 1, "inherited_from": "development", "created_at": "2024-01-15T10:30:00Z", "updated_at": "2024-01-15T10:30:00Z" } ] } ``` ### Example: Create a Secret ```bash curl -X POST "https://api.keyenv.dev/api/v1/projects/{projectId}/environments/production/secrets" \ -H "Authorization: Bearer env_your_token_here" \ -H "Content-Type: application/json" \ -d '{ "key": "NEW_SECRET", "value": "secret_value_here" }' ``` Response: ```json { "data": { "id": "550e8400-e29b-41d4-a716-446655440002", "key": "NEW_SECRET", "version": 1, "created_at": "2024-01-21T09:00:00Z", "updated_at": "2024-01-21T09:00:00Z" } } ``` ### Example: Export Secrets The export endpoint returns secrets in JSON format: ```bash curl -X GET "https://api.keyenv.dev/api/v1/projects/{projectId}/environments/production/secrets/export" \ -H "Authorization: Bearer env_your_token_here" ``` Response: ```json { "data": [ { "key": "DATABASE_URL", "value": "postgres://user:pass@localhost:5432/db" }, { "key": "API_KEY", "value": "sk_live_abc123" }, { "key": "REDIS_URL", "value": "redis://localhost:6379", "inherited_from": "development" } ] } ``` The `inherited_from` field is present when a secret is inherited from a parent environment. ### External Secrets Operator (ESO) KeyEnv provides ESO-compatible webhook endpoints for Kubernetes integration: | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/v1/eso/secret` | Get single secret | | GET | `/api/v1/eso/secrets` | Get all secrets (for dataFrom) | See the [Kubernetes ESO Integration Guide](/docs/sdks/kubernetes-eso) for setup instructions. ### SDKs For easier integration, use our official SDKs: - [Node.js SDK](/docs/sdks/nodejs) - [Python SDK](/docs/sdks/python) - [Go SDK](/docs/sdks/go) - [Rust SDK](/docs/sdks/rust) The SDKs handle authentication, error handling, and provide type-safe interfaces to the API. ## Node.js SDK Official Node.js/TypeScript SDK for KeyEnv. Source: https://keyenv.dev/docs/sdks/nodejs/ Official Node.js SDK for KeyEnv with full TypeScript support. ### Installation ```bash npm install @keyenv/node ``` Or with other package managers: ```bash yarn add @keyenv/node pnpm add @keyenv/node ``` ### Quick Start ```typescript import { KeyEnv } from '@keyenv/node'; const client = new KeyEnv({ token: process.env.KEYENV_TOKEN!, }); // Load secrets into process.env await client.loadEnv('your-project-id', 'production'); console.log(process.env.DATABASE_URL); ``` ### Initialize the Client ```typescript import { KeyEnv } from '@keyenv/node'; const client = new KeyEnv({ token: 'your-service-token', timeout: 30000, // optional, default 30s }); ``` ### Loading Secrets #### Load into process.env The simplest way to use secrets in your application: ```typescript const count = await client.loadEnv('project-id', 'production'); console.log(`Loaded ${count} secrets`); // Now use them console.log(process.env.DATABASE_URL); console.log(process.env.API_KEY); ``` #### Export as Object Get secrets as a key-value object: ```typescript const env = await client.exportSecretsAsObject('project-id', 'production'); console.log(env.DATABASE_URL); console.log(env.API_KEY); ``` #### Export as Array Get secrets with metadata: ```typescript const secrets = await client.exportSecrets('project-id', 'production'); for (const secret of secrets) { console.log(`${secret.key}=${secret.value}`); } ``` ### Managing Secrets #### Get a Single Secret ```typescript const secret = await client.getSecret('project-id', 'production', 'DATABASE_URL'); console.log(secret.value); console.log(secret.description); ``` #### Set a Secret Creates or updates a secret: ```typescript await client.setSecret('project-id', 'production', 'API_KEY', 'sk_live_...'); // With description await client.setSecret('project-id', 'production', 'API_KEY', 'sk_live_...', 'Production API key'); ``` #### Delete a Secret ```typescript await client.deleteSecret('project-id', 'production', 'OLD_KEY'); ``` ### Bulk Operations #### Bulk Import Import multiple secrets at once: ```typescript const result = await client.bulkImport('project-id', 'development', [ { key: 'DATABASE_URL', value: 'postgres://localhost/mydb' }, { key: 'REDIS_URL', value: 'redis://localhost:6379' }, { key: 'API_KEY', value: 'sk_test_...', description: 'Test API key' }, ], { overwrite: true }); console.log(`Created: ${result.created}, Updated: ${result.updated}`); ``` #### Generate .env File ```typescript import { writeFileSync } from 'fs'; const envContent = await client.generateEnvFile('project-id', 'production'); writeFileSync('.env', envContent); ``` ### Projects & Environments #### List Projects ```typescript const projects = await client.listProjects(); for (const project of projects) { console.log(`${project.name} (${project.id})`); } ``` #### Get Project Details ```typescript const project = await client.getProject('project-id'); console.log(`Project: ${project.name}`); for (const env of project.environments) { console.log(` - ${env.name}`); } ``` #### List Environments ```typescript const environments = await client.listEnvironments('project-id'); for (const env of environments) { console.log(env.name); } ``` ### Environment Permissions Manage who can access secrets in each environment. #### List Permissions ```typescript const permissions = await client.listPermissions('project-id', 'production'); for (const perm of permissions) { console.log(`${perm.user_email}: ${perm.role}`); } ``` #### Set Permission ```typescript // Grant write access to a user await client.setPermission('project-id', 'production', 'user-id', 'write'); ``` #### Delete Permission ```typescript await client.deletePermission('project-id', 'production', 'user-id'); ``` #### Get My Permissions ```typescript const { permissions, is_team_admin } = await client.getMyPermissions('project-id'); for (const perm of permissions) { console.log(`${perm.environment_name}: ${perm.role} (can_write: ${perm.can_write})`); } ``` #### Bulk Set Permissions ```typescript await client.bulkSetPermissions('project-id', 'production', [ { userId: 'user-1', role: 'write' }, { userId: 'user-2', role: 'read' }, ]); ``` #### Project Defaults ```typescript // Get default permissions const defaults = await client.getProjectDefaults('project-id'); // Set default permissions for new team members await client.setProjectDefaults('project-id', [ { environmentName: 'development', defaultRole: 'write' }, { environmentName: 'staging', defaultRole: 'read' }, { environmentName: 'production', defaultRole: 'none' }, ]); ``` ### Error Handling ```typescript import { KeyEnv, KeyEnvError } from '@keyenv/node'; try { await client.getSecret('project-id', 'production', 'MISSING_KEY'); } catch (error) { if (error instanceof KeyEnvError) { console.error(`Error ${error.status}: ${error.message}`); if (error.status === 401) { console.error('Invalid or expired token'); } else if (error.status === 403) { console.error('Access denied'); } else if (error.status === 404) { console.error('Secret not found'); } } } ``` ### TypeScript Types The SDK exports all types for TypeScript users: ```typescript import type { Secret, SecretWithValue, Project, Environment, BulkImportResult } from '@keyenv/node'; ``` ### API Reference #### Constructor Options | Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | `token` | `string` | Yes | - | Service token | | `timeout` | `number` | No | `30000` | Request timeout (ms) | #### Methods | Method | Description | |--------|-------------| | `getCurrentUser()` | Get current user/token info | | `listProjects()` | List all accessible projects | | `getProject(id)` | Get project with environments | | `listEnvironments(projectId)` | List environments in a project | | `listSecrets(projectId, env)` | List secret keys (no values) | | `exportSecrets(projectId, env)` | Export secrets with values | | `exportSecretsAsObject(projectId, env)` | Export as key-value object | | `getSecret(projectId, env, key)` | Get single secret | | `setSecret(projectId, env, key, value)` | Create or update secret | | `deleteSecret(projectId, env, key)` | Delete secret | | `bulkImport(projectId, env, secrets)` | Bulk import secrets | | `loadEnv(projectId, env)` | Load secrets into process.env | | `generateEnvFile(projectId, env)` | Generate .env file content | | `listPermissions(projectId, env)` | List permissions for an environment | | `setPermission(projectId, env, userId, role)` | Set user's permission | | `deletePermission(projectId, env, userId)` | Delete user's permission | | `bulkSetPermissions(projectId, env, permissions)` | Bulk set permissions | | `getMyPermissions(projectId)` | Get current user's permissions | | `getProjectDefaults(projectId)` | Get default permissions | | `setProjectDefaults(projectId, defaults)` | Set default permissions | ### Examples #### Express.js Application ```typescript import express from 'express'; import { KeyEnv } from '@keyenv/node'; async function main() { // Load secrets before starting server const client = new KeyEnv({ token: process.env.KEYENV_TOKEN! }); await client.loadEnv(process.env.KEYENV_PROJECT!, 'production'); const app = express(); app.get('/', (req, res) => { res.json({ status: 'ok' }); }); app.listen(process.env.PORT || 3000); } main(); ``` #### Next.js API Route ```typescript // pages/api/config.ts import { KeyEnv } from '@keyenv/node'; let secretsLoaded = false; export default async function handler(req, res) { if (!secretsLoaded) { const client = new KeyEnv({ token: process.env.KEYENV_TOKEN! }); await client.loadEnv(process.env.KEYENV_PROJECT!, 'production'); secretsLoaded = true; } res.json({ apiUrl: process.env.API_URL, // Don't expose sensitive secrets! }); } ``` ## Python SDK Official Python SDK for KeyEnv. Source: https://keyenv.dev/docs/sdks/python/ Official Python SDK for KeyEnv with full type annotations. ### Installation ```bash pip install keyenv ``` Or with poetry: ```bash poetry add keyenv ``` ### Quick Start ```python from keyenv import KeyEnv import os client = KeyEnv(token=os.environ["KEYENV_TOKEN"]) # Load secrets into os.environ client.load_env("your-project-id", "production") print(os.environ["DATABASE_URL"]) ``` ### Initialize the Client ```python from keyenv import KeyEnv client = KeyEnv( token="your-service-token", timeout=30.0, # optional, default 30s ) ``` #### Context Manager Use as a context manager for automatic cleanup: ```python with KeyEnv(token="your-token") as client: secrets = client.export_secrets("project-id", "production") ``` ### Loading Secrets #### Load into os.environ The simplest way to use secrets in your application: ```python count = client.load_env("project-id", "production") print(f"Loaded {count} secrets") # Now use them print(os.environ["DATABASE_URL"]) print(os.environ["API_KEY"]) ``` #### Export as Dictionary Get secrets as a key-value dictionary: ```python env = client.export_secrets_as_dict("project-id", "production") print(env["DATABASE_URL"]) print(env["API_KEY"]) ``` #### Export as List Get secrets with metadata: ```python secrets = client.export_secrets("project-id", "production") for secret in secrets: print(f"{secret.key}={secret.value}") ``` ### Managing Secrets #### Get a Single Secret ```python secret = client.get_secret("project-id", "production", "DATABASE_URL") print(secret.value) print(secret.description) ``` #### Set a Secret Creates or updates a secret: ```python client.set_secret("project-id", "production", "API_KEY", "sk_live_...") # With description client.set_secret( "project-id", "production", "API_KEY", "sk_live_...", description="Production API key" ) ``` #### Delete a Secret ```python client.delete_secret("project-id", "production", "OLD_KEY") ``` ### Bulk Operations #### Bulk Import Import multiple secrets at once: ```python from keyenv import BulkSecretItem result = client.bulk_import( "project-id", "development", [ BulkSecretItem(key="DATABASE_URL", value="postgres://localhost/mydb"), BulkSecretItem(key="REDIS_URL", value="redis://localhost:6379"), {"key": "API_KEY", "value": "sk_test_..."}, # Also accepts dicts ], overwrite=True, ) print(f"Created: {result.created}, Updated: {result.updated}") ``` #### Generate .env File ```python env_content = client.generate_env_file("project-id", "production") with open(".env", "w") as f: f.write(env_content) ``` ### Projects & Environments #### List Projects ```python projects = client.list_projects() for project in projects: print(f"{project.name} ({project.id})") ``` #### Get Project Details ```python project = client.get_project("project-id") print(f"Project: {project.name}") for env in project.environments: print(f" - {env.name}") ``` #### List Environments ```python environments = client.list_environments("project-id") for env in environments: print(env.name) ``` ### Environment Permissions Manage who can access secrets in each environment. #### List Permissions ```python permissions = client.list_permissions("project-id", "production") for perm in permissions: print(f"{perm.user_email}: {perm.role}") ``` #### Set Permission ```python # Grant write access to a user client.set_permission("project-id", "production", "user-id", "write") ``` #### Delete Permission ```python client.delete_permission("project-id", "production", "user-id") ``` #### Get My Permissions ```python permissions, is_team_admin = client.get_my_permissions("project-id") for perm in permissions: print(f"{perm.environment_name}: {perm.role} (can_write: {perm.can_write})") ``` #### Bulk Set Permissions ```python client.bulk_set_permissions("project-id", "production", [ {"user_id": "user-1", "role": "write"}, {"user_id": "user-2", "role": "read"}, ]) ``` #### Project Defaults ```python # Get default permissions defaults = client.get_project_defaults("project-id") # Set default permissions for new team members client.set_project_defaults("project-id", [ {"environment_name": "development", "default_role": "write"}, {"environment_name": "staging", "default_role": "read"}, {"environment_name": "production", "default_role": "none"}, ]) ``` ### Error Handling ```python from keyenv import KeyEnv, KeyEnvError try: secret = client.get_secret("project-id", "production", "MISSING_KEY") except KeyEnvError as e: print(f"Error {e.status}: {e.message}") if e.status == 401: print("Invalid or expired token") elif e.status == 403: print("Access denied") elif e.status == 404: print("Secret not found") ``` ### Type Hints The SDK includes full type annotations for better IDE support: ```python from keyenv import Secret, SecretWithValue, Project, Environment ``` ### API Reference #### Constructor Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `token` | `str` | Yes | - | Service token | | `timeout` | `float` | No | `30.0` | Request timeout (seconds) | #### Methods | Method | Description | |--------|-------------| | `get_current_user()` | Get current user/token info | | `list_projects()` | List all accessible projects | | `get_project(id)` | Get project with environments | | `list_environments(project_id)` | List environments in a project | | `list_secrets(project_id, env)` | List secret keys (no values) | | `export_secrets(project_id, env)` | Export secrets with values | | `export_secrets_as_dict(project_id, env)` | Export as dictionary | | `get_secret(project_id, env, key)` | Get single secret | | `set_secret(project_id, env, key, value)` | Create or update secret | | `delete_secret(project_id, env, key)` | Delete secret | | `bulk_import(project_id, env, secrets)` | Bulk import secrets | | `load_env(project_id, env)` | Load secrets into os.environ | | `generate_env_file(project_id, env)` | Generate .env file content | | `list_permissions(project_id, env)` | List permissions for an environment | | `set_permission(project_id, env, user_id, role)` | Set user's permission | | `delete_permission(project_id, env, user_id)` | Delete user's permission | | `bulk_set_permissions(project_id, env, permissions)` | Bulk set permissions | | `get_my_permissions(project_id)` | Get current user's permissions | | `get_project_defaults(project_id)` | Get default permissions | | `set_project_defaults(project_id, defaults)` | Set default permissions | ### Examples #### FastAPI Application ```python from fastapi import FastAPI from keyenv import KeyEnv import os # Load secrets at startup client = KeyEnv(token=os.environ["KEYENV_TOKEN"]) client.load_env(os.environ["KEYENV_PROJECT"], "production") app = FastAPI() @app.get("/") async def root(): return {"status": "ok"} @app.get("/config") async def config(): return { "api_url": os.environ.get("API_URL"), # Don't expose sensitive secrets! } ``` #### Django Settings ```python # settings.py from keyenv import KeyEnv import os # Load secrets before Django initializes if os.environ.get("KEYENV_TOKEN"): client = KeyEnv(token=os.environ["KEYENV_TOKEN"]) client.load_env(os.environ["KEYENV_PROJECT"], os.environ.get("ENV", "development")) # Now use secrets in settings DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "HOST": os.environ.get("DB_HOST"), "NAME": os.environ.get("DB_NAME"), "USER": os.environ.get("DB_USER"), "PASSWORD": os.environ.get("DB_PASSWORD"), } } SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY") ``` #### Flask Application ```python from flask import Flask from keyenv import KeyEnv import os # Load secrets client = KeyEnv(token=os.environ["KEYENV_TOKEN"]) client.load_env(os.environ["KEYENV_PROJECT"], "production") app = Flask(__name__) app.config["SECRET_KEY"] = os.environ["FLASK_SECRET_KEY"] @app.route("/") def hello(): return {"status": "ok"} ``` #### Script/CLI Tool ```python #!/usr/bin/env python3 import argparse from keyenv import KeyEnv import os def main(): parser = argparse.ArgumentParser() parser.add_argument("--env", default="development") args = parser.parse_args() client = KeyEnv(token=os.environ["KEYENV_TOKEN"]) secrets = client.export_secrets_as_dict("my-project", args.env) print(f"Database: {secrets['DATABASE_URL']}") # Do something with secrets... if __name__ == "__main__": main() ``` ## Go SDK Official Go SDK for KeyEnv. Source: https://keyenv.dev/docs/sdks/go/ Official Go SDK for KeyEnv with full type safety. ### Installation ```bash go get github.com/keyenv/go-sdk ``` ### Quick Start ```go package main import ( "context" "fmt" "os" "github.com/keyenv/go-sdk" ) func main() { client, err := keyenv.New(keyenv.Config{ Token: os.Getenv("KEYENV_TOKEN"), }) if err != nil { panic(err) } ctx := context.Background() // Load secrets into environment if _, err := client.LoadEnv(ctx, "your-project-id", "production"); err != nil { panic(err) } fmt.Println(os.Getenv("DATABASE_URL")) } ``` ### Initialize the Client ```go client, err := keyenv.New(keyenv.Config{ Token: "your-service-token", Timeout: 30 * time.Second, // optional, default 30s CacheTTL: 5 * time.Minute, // optional, default disabled }) ``` ### Loading Secrets #### Load into Environment The simplest way to use secrets in your application: ```go ctx := context.Background() count, err := client.LoadEnv(ctx, "project-id", "production") if err != nil { log.Fatal(err) } fmt.Printf("Loaded %d secrets\n", count) // Now use them fmt.Println(os.Getenv("DATABASE_URL")) ``` #### Export as Map Get secrets as a map: ```go secrets, err := client.ExportSecretsAsMap(ctx, "project-id", "production") if err != nil { log.Fatal(err) } fmt.Println(secrets["DATABASE_URL"]) ``` #### Export with Metadata Get secrets with full metadata: ```go secrets, err := client.ExportSecrets(ctx, "project-id", "production") if err != nil { log.Fatal(err) } for _, secret := range secrets { fmt.Printf("%s=%s\n", secret.Key, secret.Value) } ``` ### Managing Secrets #### Get a Single Secret ```go secret, err := client.GetSecret(ctx, "project-id", "production", "DATABASE_URL") if err != nil { log.Fatal(err) } fmt.Println(secret.Value) ``` #### Set a Secret Creates or updates a secret: ```go err := client.SetSecret(ctx, "project-id", "production", "API_KEY", "sk_live_...") if err != nil { log.Fatal(err) } // With description description := "Production API key" err = client.SetSecretWithDescription(ctx, "project-id", "production", "API_KEY", "sk_live_...", &description) ``` #### Delete a Secret ```go err := client.DeleteSecret(ctx, "project-id", "production", "OLD_KEY") ``` ### Bulk Operations #### Bulk Import ```go result, err := client.BulkImport(ctx, "project-id", "development", []keyenv.SecretInput{ {Key: "DATABASE_URL", Value: "postgres://localhost/mydb"}, {Key: "REDIS_URL", Value: "redis://localhost:6379"}, }, keyenv.BulkImportOptions{Overwrite: true}) fmt.Printf("Created: %d, Updated: %d\n", result.Created, result.Updated) ``` #### Generate .env File ```go content, err := client.GenerateEnvFile(ctx, "project-id", "production") if err != nil { log.Fatal(err) } os.WriteFile(".env", []byte(content), 0644) ``` ### Projects & Environments #### List Projects ```go projects, err := client.ListProjects(ctx) for _, project := range projects { fmt.Printf("%s (%s)\n", project.Name, project.ID) } ``` #### Get Project Details ```go project, err := client.GetProject(ctx, "project-id") fmt.Printf("Project: %s\n", project.Name) for _, env := range project.Environments { fmt.Printf(" - %s\n", env.Name) } ``` ### Environment Permissions Manage who can access secrets in each environment. #### List Permissions ```go permissions, err := client.ListPermissions(ctx, "project-id", "production") if err != nil { log.Fatal(err) } for _, perm := range permissions { fmt.Printf("%s: %s\n", perm.UserEmail, perm.Role) } ``` #### Set Permission ```go // Grant write access to a user err := client.SetPermission(ctx, "project-id", "production", "user-id", "write") if err != nil { log.Fatal(err) } ``` #### Delete Permission ```go err := client.DeletePermission(ctx, "project-id", "production", "user-id") ``` #### Get My Permissions ```go result, err := client.GetMyPermissions(ctx, "project-id") if err != nil { log.Fatal(err) } fmt.Printf("Is team admin: %v\n", result.IsTeamAdmin) for _, perm := range result.Permissions { fmt.Printf("%s: %s (can_write: %v)\n", perm.EnvironmentName, perm.Role, perm.CanWrite) } ``` #### Bulk Set Permissions ```go err := client.BulkSetPermissions(ctx, "project-id", "production", []keyenv.PermissionInput{ {UserID: "user-1", Role: "write"}, {UserID: "user-2", Role: "read"}, }) ``` #### Project Defaults ```go // Get default permissions defaults, err := client.GetProjectDefaults(ctx, "project-id") for _, d := range defaults { fmt.Printf("%s: %s\n", d.EnvironmentName, d.DefaultRole) } // Set default permissions for new team members err = client.SetProjectDefaults(ctx, "project-id", []keyenv.ProjectDefaultInput{ {EnvironmentName: "development", DefaultRole: "write"}, {EnvironmentName: "staging", DefaultRole: "read"}, {EnvironmentName: "production", DefaultRole: "none"}, }) ``` ### Error Handling ```go secret, err := client.GetSecret(ctx, "project-id", "production", "MISSING_KEY") if err != nil { var keyenvErr *keyenv.Error if errors.As(err, &keyenvErr) { switch keyenvErr.Status { case 401: log.Fatal("Invalid or expired token") case 403: log.Fatal("Access denied") case 404: log.Fatal("Secret not found") default: log.Fatalf("Error %d: %s", keyenvErr.Status, keyenvErr.Message) } } } ``` ### Caching Enable caching for better performance in serverless environments: ```go client, _ := keyenv.New(keyenv.Config{ Token: os.Getenv("KEYENV_TOKEN"), CacheTTL: 5 * time.Minute, }) ctx := context.Background() // Cached for 5 minutes secrets, _ := client.ExportSecrets(ctx, "project-id", "production") // Clear cache manually client.ClearCache("project-id", "production") ``` ### API Reference #### Constructor Options | Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | `Token` | `string` | Yes | - | Service token | | `Timeout` | `time.Duration` | No | `30s` | Request timeout | | `CacheTTL` | `time.Duration` | No | `0` | Cache TTL (0 = disabled) | #### Methods All methods require a `context.Context` as the first parameter. | Method | Description | |--------|-------------| | `GetCurrentUser(ctx)` | Get current user/token info | | `ListProjects(ctx)` | List all accessible projects | | `GetProject(ctx, id)` | Get project with environments | | `ListEnvironments(ctx, projectId)` | List environments | | `ListSecrets(ctx, projectId, env)` | List secret keys (no values) | | `ExportSecrets(ctx, projectId, env)` | Export secrets with values | | `ExportSecretsAsMap(ctx, projectId, env)` | Export as map | | `GetSecret(ctx, projectId, env, key)` | Get single secret | | `SetSecret(ctx, projectId, env, key, value)` | Create or update secret | | `DeleteSecret(ctx, projectId, env, key)` | Delete secret | | `BulkImport(ctx, projectId, env, secrets, opts)` | Bulk import secrets | | `LoadEnv(ctx, projectId, env)` | Load secrets into os.Environ | | `GenerateEnvFile(ctx, projectId, env)` | Generate .env file content | | `ClearCache(projectId, env)` | Clear cached secrets (no ctx) | | `ListPermissions(ctx, projectId, env)` | List environment permissions | | `SetPermission(ctx, projectId, env, userId, role)` | Set user permission | | `DeletePermission(ctx, projectId, env, userId)` | Remove user permission | | `GetMyPermissions(ctx, projectId)` | Get current user's permissions | | `BulkSetPermissions(ctx, projectId, env, perms)` | Bulk set permissions | | `GetProjectDefaults(ctx, projectId)` | Get default permissions | | `SetProjectDefaults(ctx, projectId, defaults)` | Set default permissions | ### Examples #### HTTP Server ```go package main import ( "context" "log" "net/http" "os" "github.com/keyenv/go-sdk" ) func main() { client, _ := keyenv.New(keyenv.Config{ Token: os.Getenv("KEYENV_TOKEN"), }) // Load secrets before starting server client.LoadEnv(context.Background(), os.Getenv("KEYENV_PROJECT"), "production") http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("OK")) }) log.Fatal(http.ListenAndServe(":"+os.Getenv("PORT"), nil)) } ``` #### Lambda Function ```go package main import ( "context" "os" "time" "github.com/aws/aws-lambda-go/lambda" "github.com/keyenv/go-sdk" ) var client *keyenv.Client func init() { client, _ = keyenv.New(keyenv.Config{ Token: os.Getenv("KEYENV_TOKEN"), CacheTTL: 5 * time.Minute, // Cache across warm invocations }) client.LoadEnv(context.Background(), os.Getenv("KEYENV_PROJECT"), "production") } func handler(ctx context.Context) (string, error) { return os.Getenv("API_KEY"), nil } func main() { lambda.Start(handler) } ``` ## Rust SDK Official Rust SDK for KeyEnv. Source: https://keyenv.dev/docs/sdks/rust/ Official Rust SDK for KeyEnv with async/await support. ### Installation Add to your `Cargo.toml`: ```toml [dependencies] keyenv = "1.2" tokio = { version = "1", features = ["full"] } ``` Or using cargo: ```bash cargo add keyenv tokio --features tokio/full ``` ### Quick Start ```rust use keyenv::KeyEnv; use std::env; #[tokio::main] async fn main() -> Result<(), keyenv::Error> { let client = KeyEnv::builder() .token(env::var("KEYENV_TOKEN").expect("KEYENV_TOKEN not set")) .build()?; // Load secrets into environment client.load_env("your-project-id", "production").await?; println!("{}", env::var("DATABASE_URL").unwrap()); Ok(()) } ``` ### Initialize the Client ```rust use keyenv::KeyEnv; use std::time::Duration; let client = KeyEnv::builder() .token("your-service-token") .timeout(Duration::from_secs(30)) // optional .cache_ttl(Duration::from_secs(300)) // optional .build()?; ``` ### Loading Secrets #### Load into Environment The simplest way to use secrets in your application: ```rust let count = client.load_env("project-id", "production").await?; println!("Loaded {} secrets", count); // Now use them println!("{}", env::var("DATABASE_URL").unwrap()); ``` #### Export as HashMap Get secrets as a HashMap: ```rust let secrets = client.export_secrets_as_map("project-id", "production").await?; println!("{}", secrets.get("DATABASE_URL").unwrap()); ``` #### Export with Metadata Get secrets with full metadata: ```rust let secrets = client.export_secrets("project-id", "production").await?; for secret in secrets { println!("{}={}", secret.key, secret.value); } ``` ### Managing Secrets #### Get a Single Secret ```rust let secret = client.get_secret("project-id", "production", "DATABASE_URL").await?; println!("{}", secret.value); ``` #### Set a Secret Creates or updates a secret: ```rust client.set_secret("project-id", "production", "API_KEY", "sk_live_...").await?; // With description client.set_secret_with_description( "project-id", "production", "API_KEY", "sk_live_...", "Production API key" ).await?; ``` #### Delete a Secret ```rust client.delete_secret("project-id", "production", "OLD_KEY").await?; ``` ### Bulk Operations #### Bulk Import ```rust use keyenv::{SecretInput, BulkImportOptions}; let result = client.bulk_import( "project-id", "development", vec![ SecretInput::new("DATABASE_URL", "postgres://localhost/mydb"), SecretInput::new("REDIS_URL", "redis://localhost:6379"), ], BulkImportOptions { overwrite: true }, ).await?; println!("Created: {}, Updated: {}", result.created, result.updated); ``` #### Generate .env File ```rust use std::fs; let content = client.generate_env_file("project-id", "production").await?; fs::write(".env", content)?; ``` ### Projects & Environments #### List Projects ```rust let projects = client.list_projects().await?; for project in projects { println!("{} ({})", project.name, project.id); } ``` #### Get Project Details ```rust let project = client.get_project("project-id").await?; println!("Project: {}", project.name); for env in project.environments { println!(" - {}", env.name); } ``` ### Error Handling ```rust use keyenv::{KeyEnv, Error}; match client.get_secret("project-id", "production", "MISSING_KEY").await { Ok(secret) => println!("{}", secret.value), Err(Error::Api { status, message, .. }) => { match status { 401 => eprintln!("Invalid or expired token"), 403 => eprintln!("Access denied"), 404 => eprintln!("Secret not found"), _ => eprintln!("Error {}: {}", status, message), } } Err(e) => eprintln!("Error: {}", e), } ``` ### Caching Enable caching for better performance: ```rust let client = KeyEnv::builder() .token(env::var("KEYENV_TOKEN")?) .cache_ttl(Duration::from_secs(300)) // 5 minutes .build()?; // Cached for 5 minutes let secrets = client.export_secrets("project-id", "production").await?; // Clear cache manually client.clear_cache(Some("project-id"), Some("production")); ``` ### API Reference #### Builder Options | Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | `token` | `String` | Yes | - | Service token | | `timeout` | `Duration` | No | `30s` | Request timeout | | `cache_ttl` | `Duration` | No | `0` | Cache TTL (0 = disabled) | #### Methods | Method | Description | |--------|-------------| | `get_current_user()` | Get current user/token info | | `list_projects()` | List all accessible projects | | `get_project(id)` | Get project with environments | | `list_environments(project_id)` | List environments | | `list_secrets(project_id, env)` | List secret keys (no values) | | `export_secrets(project_id, env)` | Export secrets with values | | `export_secrets_as_map(project_id, env)` | Export as HashMap | | `get_secret(project_id, env, key)` | Get single secret | | `set_secret(project_id, env, key, value)` | Create or update secret | | `delete_secret(project_id, env, key)` | Delete secret | | `bulk_import(project_id, env, secrets)` | Bulk import secrets | | `load_env(project_id, env)` | Load secrets into env vars | | `generate_env_file(project_id, env)` | Generate .env file content | | `clear_cache(project_id, env)` | Clear cached secrets | ### Examples #### Actix Web Server ```rust use actix_web::{web, App, HttpServer, Responder}; use keyenv::KeyEnv; use std::env; async fn index() -> impl Responder { "OK" } #[actix_web::main] async fn main() -> std::io::Result<()> { let client = KeyEnv::builder() .token(env::var("KEYENV_TOKEN").unwrap()) .build() .unwrap(); // Load secrets before starting server client.load_env( &env::var("KEYENV_PROJECT").unwrap(), "production" ).await.unwrap(); HttpServer::new(|| { App::new().route("/", web::get().to(index)) }) .bind(("0.0.0.0", 8080))? .run() .await } ``` #### Lambda Function ```rust use keyenv::KeyEnv; use lambda_runtime::{service_fn, LambdaEvent, Error}; use once_cell::sync::Lazy; use std::env; static CLIENT: Lazy = Lazy::new(|| { KeyEnv::builder() .token(env::var("KEYENV_TOKEN").unwrap()) .cache_ttl(std::time::Duration::from_secs(300)) .build() .unwrap() }); async fn handler(_event: LambdaEvent<()>) -> Result { CLIENT.load_env(&env::var("KEYENV_PROJECT")?, "production").await?; Ok(env::var("API_KEY")?) } #[tokio::main] async fn main() -> Result<(), Error> { lambda_runtime::run(service_fn(handler)).await } ``` ## Java SDK Official Java SDK for KeyEnv. Source: https://keyenv.dev/docs/sdks/java/ Official Java SDK for KeyEnv with Java 11+ support and async/await patterns. > **Warning** > > **Beta Release**: This SDK is currently in development. APIs may change before the stable 1.0 release. ### Installation #### Maven Add the JitPack repository and dependency to your `pom.xml`: ```xml jitpack.io https://jitpack.io com.github.keyenv java-sdk v1.1.0 ``` #### Gradle Add the JitPack repository and dependency to your `build.gradle`: ```groovy repositories { maven { url 'https://jitpack.io' } } dependencies { implementation 'com.github.keyenv:java-sdk:v1.1.0' } ``` Or with Kotlin DSL (`build.gradle.kts`): ```kotlin repositories { maven("https://jitpack.io") } dependencies { implementation("com.github.keyenv:java-sdk:v1.1.0") } ``` ### Quick Start ```java import dev.keyenv.KeyEnv; public class Application { public static void main(String[] args) { KeyEnv client = KeyEnv.create(System.getenv("KEYENV_TOKEN")); // Load secrets into system properties int count = client.loadEnv("your-project-id", "production"); System.out.println("Loaded " + count + " secrets"); // Access secrets via System.getProperty System.out.println(System.getProperty("DATABASE_URL")); } } ``` ### Initialize the Client #### Simple Initialization ```java import dev.keyenv.KeyEnv; KeyEnv client = KeyEnv.create(System.getenv("KEYENV_TOKEN")); ``` #### Builder Pattern For more control, use the builder: ```java import dev.keyenv.KeyEnv; import java.time.Duration; KeyEnv client = KeyEnv.builder() .token(System.getenv("KEYENV_TOKEN")) .timeout(Duration.ofSeconds(60)) .cacheTtl(Duration.ofMinutes(5)) .build(); ``` ### Loading Secrets #### Load into System Properties The simplest way to use secrets in your application: ```java int count = client.loadEnv("project-id", "production"); System.out.println("Loaded " + count + " secrets"); // Access via System.getProperty String dbUrl = System.getProperty("DATABASE_URL"); String apiKey = System.getProperty("API_KEY"); ``` #### Get Secrets as Map Get secrets as a key-value map: ```java import java.util.Map; Map secrets = client.exportSecretsAsMap("project-id", "production"); System.out.println(secrets.get("DATABASE_URL")); System.out.println(secrets.get("API_KEY")); ``` #### Export as List Get secrets with metadata: ```java import dev.keyenv.types.SecretWithValueAndInheritance; import java.util.List; List secrets = client.exportSecrets("project-id", "production"); for (SecretWithValueAndInheritance secret : secrets) { System.out.println(secret.getKey() + "=" + secret.getValue()); } ``` ### Managing Secrets #### Get a Single Secret ```java import dev.keyenv.types.SecretWithValue; SecretWithValue secret = client.getSecret("project-id", "production", "DATABASE_URL"); System.out.println(secret.getValue()); System.out.println(secret.getDescription()); ``` #### Set a Secret Creates or updates a secret: ```java client.setSecret("project-id", "production", "API_KEY", "sk_live_..."); // With description client.setSecret("project-id", "production", "API_KEY", "sk_live_...", "Production API key"); ``` #### Delete a Secret ```java client.deleteSecret("project-id", "production", "OLD_KEY"); ``` ### Bulk Operations #### Bulk Import Import multiple secrets at once: ```java import dev.keyenv.types.SecretInput; import dev.keyenv.types.BulkImportOptions; import dev.keyenv.types.BulkImportResult; import java.util.List; List secrets = List.of( new SecretInput("DATABASE_URL", "postgres://localhost/mydb", null), new SecretInput("REDIS_URL", "redis://localhost:6379", null), new SecretInput("API_KEY", "sk_test_...", "Test API key") ); BulkImportResult result = client.bulkImport( "project-id", "development", secrets, new BulkImportOptions(true) // overwrite existing ); System.out.println("Created: " + result.getCreated() + ", Updated: " + result.getUpdated()); ``` #### Generate .env File ```java import java.nio.file.Files; import java.nio.file.Paths; String envContent = client.generateEnvFile("project-id", "production"); Files.writeString(Paths.get(".env"), envContent); ``` ### Projects & Environments #### List Projects ```java import dev.keyenv.types.Project; import java.util.List; List projects = client.listProjects(); for (Project project : projects) { System.out.println(project.getName() + " (" + project.getId() + ")"); } ``` #### Get Project Details ```java import dev.keyenv.types.Project; Project project = client.getProject("project-id"); System.out.println("Project: " + project.getName()); for (var env : project.getEnvironments()) { System.out.println(" - " + env.getName()); } ``` #### List Environments ```java import dev.keyenv.types.Environment; import java.util.List; List environments = client.listEnvironments("project-id"); for (Environment env : environments) { System.out.println(env.getName()); } ``` ### Async Operations All methods have async variants that return `CompletableFuture`: ```java import java.util.concurrent.CompletableFuture; // Async secret loading CompletableFuture future = client.loadEnvAsync("project-id", "production"); future.thenAccept(count -> { System.out.println("Loaded " + count + " secrets"); }); // Async secrets retrieval client.exportSecretsAsMapAsync("project-id", "production") .thenAccept(secrets -> { System.out.println(secrets.get("DATABASE_URL")); }); ``` ### Caching Enable caching for better performance in high-throughput applications: ```java import java.time.Duration; KeyEnv client = KeyEnv.builder() .token(System.getenv("KEYENV_TOKEN")) .cacheTtl(Duration.ofMinutes(5)) .build(); // First call fetches from API client.exportSecrets("project-id", "production"); // Subsequent calls within TTL use cache client.exportSecrets("project-id", "production"); // Clear cache when needed client.clearCache("project-id", "production"); client.clearAllCache(); ``` ### Error Handling ```java import dev.keyenv.KeyEnv; import dev.keyenv.KeyEnvException; try { var secret = client.getSecret("project-id", "production", "MISSING_KEY"); } catch (KeyEnvException e) { System.err.println("Error " + e.getStatus() + ": " + e.getMessage()); if (e.getStatus() == 401) { System.err.println("Invalid or expired token"); } else if (e.getStatus() == 403) { System.err.println("Access denied"); } else if (e.getStatus() == 404) { System.err.println("Secret not found"); } } ``` ### API Reference #### Constructor Options (Builder) | Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | `token` | `String` | Yes | - | Service token | | `timeout` | `Duration` | No | `30s` | Request timeout | | `cacheTtl` | `Duration` | No | `0` | Cache TTL (0 disables) | | `baseUrl` | `String` | No | `https://api.keyenv.dev` | API base URL | #### Methods | Method | Description | |--------|-------------| | `getCurrentUser()` | Get current user/token info | | `listProjects()` | List all accessible projects | | `getProject(id)` | Get project with environments | | `listEnvironments(projectId)` | List environments in a project | | `listSecrets(projectId, env)` | List secret keys (no values) | | `exportSecrets(projectId, env)` | Export secrets with values | | `exportSecretsAsMap(projectId, env)` | Export as key-value map | | `getSecret(projectId, env, key)` | Get single secret | | `setSecret(projectId, env, key, value)` | Create or update secret | | `deleteSecret(projectId, env, key)` | Delete secret | | `bulkImport(projectId, env, secrets, options)` | Bulk import secrets | | `loadEnv(projectId, env)` | Load secrets into System properties | | `generateEnvFile(projectId, env)` | Generate .env file content | | `listPermissions(projectId, env)` | List permissions for an environment | | `setPermission(projectId, env, userId, role)` | Set user's permission | | `deletePermission(projectId, env, userId)` | Delete user's permission | | `getMyPermissions(projectId)` | Get current user's permissions | | `getProjectDefaults(projectId)` | Get default permissions | | `setProjectDefaults(projectId, defaults)` | Set default permissions | ### Examples #### Spring Boot Application ```java import dev.keyenv.KeyEnv; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import jakarta.annotation.PostConstruct; @SpringBootApplication public class Application { @PostConstruct public void loadSecrets() { String token = System.getenv("KEYENV_TOKEN"); if (token != null) { KeyEnv client = KeyEnv.create(token); client.loadEnv(System.getenv("KEYENV_PROJECT"), "production"); } } public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` #### Micronaut Application ```java import dev.keyenv.KeyEnv; import io.micronaut.context.event.ApplicationEventListener; import io.micronaut.context.event.StartupEvent; import jakarta.inject.Singleton; @Singleton public class SecretsLoader implements ApplicationEventListener { @Override public void onApplicationEvent(StartupEvent event) { String token = System.getenv("KEYENV_TOKEN"); if (token != null) { KeyEnv client = KeyEnv.create(token); int count = client.loadEnv(System.getenv("KEYENV_PROJECT"), "production"); System.out.println("Loaded " + count + " secrets from KeyEnv"); } } } ``` #### Quarkus Application ```java import dev.keyenv.KeyEnv; import io.quarkus.runtime.Startup; import jakarta.annotation.PostConstruct; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped @Startup public class SecretsConfig { @PostConstruct void loadSecrets() { String token = System.getenv("KEYENV_TOKEN"); if (token != null) { KeyEnv client = KeyEnv.create(token); client.loadEnv(System.getenv("KEYENV_PROJECT"), "production"); } } } ``` ## PHP SDK Official PHP SDK for KeyEnv. Source: https://keyenv.dev/docs/sdks/php/ Official PHP SDK for KeyEnv with PHP 8.0+ support. > **Warning** > > **Beta Release**: This SDK is currently in development. APIs may change before the stable 1.0 release. ### Installation Install via Composer: ```bash composer require keyenv/keyenv ``` ### Quick Start ```php loadEnv('your-project-id', 'production'); echo "Loaded {$count} secrets\n"; // Access secrets echo $_ENV['DATABASE_URL']; echo getenv('DATABASE_URL'); ``` ### Initialize the Client #### Simple Initialization ```php loadEnv('project-id', 'production'); echo "Loaded {$count} secrets\n"; // Access via $_ENV echo $_ENV['DATABASE_URL']; echo $_ENV['API_KEY']; // Or via getenv() echo getenv('DATABASE_URL'); echo getenv('API_KEY'); ``` #### Get Secrets as Array Get secrets as a key-value associative array: ```php exportSecretsAsArray('project-id', 'production'); echo $secrets['DATABASE_URL']; echo $secrets['API_KEY']; ``` #### Export as List Get secrets with metadata: ```php exportSecrets('project-id', 'production'); foreach ($secrets as $secret) { echo "{$secret->key}={$secret->value}\n"; } ``` ### Managing Secrets #### Get a Single Secret ```php getSecret('project-id', 'production', 'DATABASE_URL'); echo $secret->value; echo $secret->description; ``` #### Set a Secret Creates or updates a secret: ```php setSecret('project-id', 'production', 'API_KEY', 'sk_live_...'); // With description $client->setSecret( 'project-id', 'production', 'API_KEY', 'sk_live_...', description: 'Production API key' ); ``` #### Create a Secret Explicitly create a new secret (fails if exists): ```php createSecret( 'project-id', 'production', 'NEW_KEY', 'new_value', description: 'New secret' ); ``` #### Update a Secret Explicitly update an existing secret (fails if not exists): ```php updateSecret( 'project-id', 'production', 'EXISTING_KEY', 'updated_value' ); ``` #### Delete a Secret ```php deleteSecret('project-id', 'production', 'OLD_KEY'); ``` ### Environments #### List Environments ```php listEnvironments('project-id'); foreach ($environments as $env) { echo $env->name . "\n"; } ``` ### Generate .env File ```php generateEnvFile('project-id', 'production'); file_put_contents('.env', $envContent); ``` ### Error Handling ```php getSecret('project-id', 'production', 'MISSING_KEY'); } catch (KeyEnvException $e) { echo "Error {$e->getCode()}: {$e->getMessage()}\n"; if ($e->isNotFound()) { echo "Secret not found\n"; } elseif ($e->isUnauthorized()) { echo "Invalid or expired token\n"; } elseif ($e->isForbidden()) { echo "Access denied\n"; } // Access error details if ($e->getDetails()) { print_r($e->getDetails()); } } ``` ### API Reference #### Constructor Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `token` | `string` | Yes | - | Service token | | `timeout` | `int` | No | `30` | Request timeout (seconds) | #### Methods | Method | Description | |--------|-------------| | `validateToken()` | Validate token and get user info | | `listEnvironments(projectId)` | List environments in a project | | `listSecrets(projectId, env)` | List secret keys (no values) | | `exportSecrets(projectId, env)` | Export secrets with values | | `exportSecretsAsArray(projectId, env)` | Export as associative array | | `getSecret(projectId, env, key)` | Get single secret | | `createSecret(projectId, env, key, value)` | Create new secret | | `updateSecret(projectId, env, key, value)` | Update existing secret | | `setSecret(projectId, env, key, value)` | Create or update secret | | `deleteSecret(projectId, env, key)` | Delete secret | | `loadEnv(projectId, env)` | Load secrets into $_ENV | | `generateEnvFile(projectId, env)` | Generate .env file content | ### Examples #### Laravel Application ```php loadEnv(env('KEYENV_PROJECT'), env('APP_ENV', 'production')); } ``` #### Laravel Service Provider ```php app->singleton(KeyEnv::class, function () { return KeyEnv::create(config('services.keyenv.token')); }); } public function boot(): void { if ($token = config('services.keyenv.token')) { $client = app(KeyEnv::class); $client->loadEnv( config('services.keyenv.project'), app()->environment() ); } } } ``` #### Symfony Application ```php ['loadSecrets', 255], ]; } public function loadSecrets(): void { static $loaded = false; if ($loaded) { return; } if ($token = $_ENV['KEYENV_TOKEN'] ?? null) { $client = KeyEnv::create($token); $client->loadEnv($_ENV['KEYENV_PROJECT'], $_ENV['APP_ENV'] ?? 'prod'); $loaded = true; } } } ``` #### WordPress Plugin ```php exportSecretsAsArray(KEYENV_PROJECT, KEYENV_ENV ?? 'production'); foreach ($secrets as $key => $value) { if (!defined($key)) { define($key, $value); } } }, 1); ``` #### CLI Script ```php #!/usr/bin/env php exportSecrets('my-project', 'production'); foreach ($secrets as $secret) { echo "{$secret->key}={$secret->value}\n"; } } catch (KeyEnvException $e) { fwrite(STDERR, "KeyEnv Error: {$e->getMessage()}\n"); exit(1); } ``` #### PHPUnit Test Setup ```php loadEnv(getenv('KEYENV_PROJECT'), 'test'); } } } ``` ## Ruby SDK Official Ruby SDK for KeyEnv. Source: https://keyenv.dev/docs/sdks/ruby/ Official Ruby SDK for KeyEnv with Ruby 3.0+ support. > **Warning** > > **Beta Release**: This SDK is currently in development. APIs may change before the stable 1.0 release. ### Installation Add to your Gemfile: ```ruby gem 'keyenv' ``` Then run: ```bash bundle install ``` Or install directly: ```bash gem install keyenv ``` ### Quick Start ```ruby require 'keyenv' client = KeyEnv::Client.new(token: ENV['KEYENV_TOKEN']) # Load secrets into ENV count = client.load_env(project_id: 'your-project-id', environment: 'production') puts "Loaded #{count} secrets" # Access secrets puts ENV['DATABASE_URL'] ``` ### Initialize the Client #### Simple Initialization ```ruby require 'keyenv' client = KeyEnv::Client.new(token: ENV['KEYENV_TOKEN']) ``` #### With Options ```ruby client = KeyEnv::Client.new( token: ENV['KEYENV_TOKEN'], timeout: 60, # Request timeout in seconds (default: 30) cache_ttl: 300 # Cache TTL in seconds (default: 0 = disabled) ) ``` ### Loading Secrets #### Load into ENV The simplest way to use secrets in your application: ```ruby count = client.load_env(project_id: 'project-id', environment: 'production') puts "Loaded #{count} secrets" # Access via ENV puts ENV['DATABASE_URL'] puts ENV['API_KEY'] ``` #### Get Secrets as Hash Get secrets as a key-value hash: ```ruby secrets = client.export_secrets_as_hash(project_id: 'project-id', environment: 'production') puts secrets['DATABASE_URL'] puts secrets['API_KEY'] ``` #### Export as Array Get secrets with metadata: ```ruby secrets = client.export_secrets(project_id: 'project-id', environment: 'production') secrets.each do |secret| puts "#{secret.key}=#{secret.value}" end ``` ### Managing Secrets #### Get a Single Secret ```ruby secret = client.get_secret(project_id: 'project-id', environment: 'production', key: 'DATABASE_URL') puts secret.value puts secret.description ``` #### Set a Secret Creates or updates a secret: ```ruby client.set_secret( project_id: 'project-id', environment: 'production', key: 'API_KEY', value: 'sk_live_...' ) # With description client.set_secret( project_id: 'project-id', environment: 'production', key: 'API_KEY', value: 'sk_live_...', description: 'Production API key' ) ``` #### Create a Secret Explicitly create a new secret: ```ruby secret = client.create_secret( project_id: 'project-id', environment: 'production', key: 'NEW_KEY', value: 'new_value', description: 'New secret' ) ``` #### Update a Secret Explicitly update an existing secret: ```ruby secret = client.update_secret( project_id: 'project-id', environment: 'production', key: 'EXISTING_KEY', value: 'updated_value' ) ``` #### Delete a Secret ```ruby client.delete_secret(project_id: 'project-id', environment: 'production', key: 'OLD_KEY') ``` ### Bulk Operations #### Bulk Import Import multiple secrets at once: ```ruby result = client.bulk_import( project_id: 'project-id', environment: 'development', secrets: [ { key: 'DATABASE_URL', value: 'postgres://localhost/mydb' }, { key: 'REDIS_URL', value: 'redis://localhost:6379' }, { key: 'API_KEY', value: 'sk_test_...', description: 'Test API key' } ], overwrite: true ) puts "Created: #{result.created}, Updated: #{result.updated}" ``` #### Generate .env File ```ruby env_content = client.generate_env_file(project_id: 'project-id', environment: 'production') File.write('.env', env_content) ``` ### Projects & Environments #### List Projects ```ruby projects = client.list_projects projects.each do |project| puts "#{project.name} (#{project.id})" end ``` #### Get Project Details ```ruby project = client.get_project(project_id: 'project-id') puts "Project: #{project.name}" project.environments.each do |env| puts " - #{env.name}" end ``` #### List Environments ```ruby environments = client.list_environments(project_id: 'project-id') environments.each do |env| puts env.name end ``` ### Environment Permissions #### List Permissions ```ruby permissions = client.list_permissions(project_id: 'project-id', environment: 'production') permissions.each do |perm| puts "#{perm.user_email}: #{perm.role}" end ``` #### Set Permission ```ruby client.set_permission( project_id: 'project-id', environment: 'production', user_id: 'user-id', role: 'write' ) ``` #### Get My Permissions ```ruby permissions, is_team_admin = client.get_my_permissions(project_id: 'project-id') permissions.each do |perm| puts "#{perm.environment_name}: #{perm.role} (can_write: #{perm.can_write})" end ``` ### Caching Enable caching for better performance in high-throughput applications: ```ruby client = KeyEnv::Client.new( token: ENV['KEYENV_TOKEN'], cache_ttl: 300 # 5 minutes ) # First call fetches from API client.export_secrets(project_id: 'project-id', environment: 'production') # Subsequent calls within TTL use cache client.export_secrets(project_id: 'project-id', environment: 'production') # Clear cache when needed client.clear_cache(project_id: 'project-id', environment: 'production') client.clear_cache # Clear all ``` ### Error Handling ```ruby require 'keyenv' begin secret = client.get_secret(project_id: 'project-id', environment: 'production', key: 'MISSING_KEY') rescue KeyEnv::AuthenticationError => e puts "Invalid or expired token: #{e.message}" rescue KeyEnv::NotFoundError => e puts "Secret not found: #{e.message}" rescue KeyEnv::ValidationError => e puts "Validation error: #{e.message}" puts "Details: #{e.details}" if e.details rescue KeyEnv::RateLimitError => e puts "Rate limited, try again later" rescue KeyEnv::Error => e puts "Error #{e.status}: #{e.message}" end ``` ### API Reference #### Constructor Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `token` | `String` | Yes | - | Service token | | `timeout` | `Integer` | No | `30` | Request timeout (seconds) | | `cache_ttl` | `Integer` | No | `0` | Cache TTL (0 disables) | #### Methods | Method | Description | |--------|-------------| | `get_current_user` | Get current user/token info | | `validate_token` | Validate token and get user info | | `list_projects` | List all accessible projects | | `get_project(project_id:)` | Get project with environments | | `list_environments(project_id:)` | List environments in a project | | `list_secrets(project_id:, environment:)` | List secret keys (no values) | | `export_secrets(project_id:, environment:)` | Export secrets with values | | `export_secrets_as_hash(project_id:, environment:)` | Export as hash | | `get_secret(project_id:, environment:, key:)` | Get single secret | | `create_secret(project_id:, environment:, key:, value:)` | Create new secret | | `update_secret(project_id:, environment:, key:, value:)` | Update existing secret | | `set_secret(project_id:, environment:, key:, value:)` | Create or update secret | | `delete_secret(project_id:, environment:, key:)` | Delete secret | | `bulk_import(project_id:, environment:, secrets:)` | Bulk import secrets | | `load_env(project_id:, environment:)` | Load secrets into ENV | | `generate_env_file(project_id:, environment:)` | Generate .env file content | | `list_permissions(project_id:, environment:)` | List permissions | | `set_permission(project_id:, environment:, user_id:, role:)` | Set permission | | `delete_permission(project_id:, environment:, user_id:)` | Delete permission | | `get_my_permissions(project_id:)` | Get current user's permissions | | `get_project_defaults(project_id:)` | Get default permissions | | `set_project_defaults(project_id:, defaults:)` | Set default permissions | | `clear_cache` | Clear secrets cache | ### Examples #### Rails Application ```ruby # config/initializers/keyenv.rb if ENV['KEYENV_TOKEN'].present? client = KeyEnv::Client.new(token: ENV['KEYENV_TOKEN']) client.load_env( project_id: ENV['KEYENV_PROJECT'], environment: Rails.env ) end ``` #### Rails with Credentials ```ruby # config/initializers/keyenv.rb if Rails.application.credentials.keyenv&.token client = KeyEnv::Client.new(token: Rails.application.credentials.keyenv.token) client.load_env( project_id: Rails.application.credentials.keyenv.project, environment: Rails.env ) end ``` #### Sinatra Application ```ruby require 'sinatra' require 'keyenv' # Load secrets at startup if ENV['KEYENV_TOKEN'] client = KeyEnv::Client.new(token: ENV['KEYENV_TOKEN']) client.load_env(project_id: ENV['KEYENV_PROJECT'], environment: 'production') end get '/' do { status: 'ok' }.to_json end get '/config' do content_type :json { api_url: ENV['API_URL'] # Don't expose sensitive secrets! }.to_json end ``` #### Sidekiq Worker ```ruby # config/initializers/sidekiq.rb Sidekiq.configure_server do |config| if ENV['KEYENV_TOKEN'] client = KeyEnv::Client.new( token: ENV['KEYENV_TOKEN'], cache_ttl: 300 # Cache for 5 minutes ) client.load_env(project_id: ENV['KEYENV_PROJECT'], environment: Rails.env) end end ``` #### Rake Task ```ruby # lib/tasks/secrets.rake namespace :secrets do desc 'Sync secrets from KeyEnv to .env file' task :sync do require 'keyenv' token = ENV['KEYENV_TOKEN'] || raise('KEYENV_TOKEN not set') project = ENV['KEYENV_PROJECT'] || raise('KEYENV_PROJECT not set') env = ENV['RAILS_ENV'] || 'development' client = KeyEnv::Client.new(token: token) content = client.generate_env_file(project_id: project, environment: env) File.write('.env', content) puts "Synced secrets to .env" end end ``` #### RSpec Test Setup ```ruby # spec/support/keyenv.rb RSpec.configure do |config| config.before(:suite) do if ENV['KEYENV_TOKEN'] client = KeyEnv::Client.new(token: ENV['KEYENV_TOKEN']) client.load_env(project_id: ENV['KEYENV_PROJECT'], environment: 'test') end end end ``` #### AWS Lambda with Ruby ```ruby require 'keyenv' # Initialize client outside handler for reuse across invocations $keyenv_client = KeyEnv::Client.new( token: ENV['KEYENV_TOKEN'], cache_ttl: 300 # Cache for 5 minutes in warm lambdas ) def handler(event:, context:) # Load secrets (cached after first invocation) $keyenv_client.load_env( project_id: ENV['KEYENV_PROJECT'], environment: 'production' ) # Your handler logic here { statusCode: 200, body: JSON.generate({ message: 'Success' }) } end ``` ## .NET SDK Official .NET SDK for KeyEnv. Source: https://keyenv.dev/docs/sdks/dotnet/ Official .NET SDK for KeyEnv with .NET 6.0+ support and async/await patterns. > **Warning** > > **Beta Release**: This SDK is currently in development. APIs may change before the stable 1.0 release. ### Installation #### NuGet Package Manager ```bash dotnet add package KeyEnv ``` #### Package Manager Console ```powershell Install-Package KeyEnv ``` #### PackageReference Add to your `.csproj`: ```xml ``` ### Quick Start ```csharp using KeyEnv; var client = KeyEnvClient.Create(Environment.GetEnvironmentVariable("KEYENV_TOKEN")!); // Load secrets into environment variables int count = await client.LoadEnvAsync("your-project-id", "production"); Console.WriteLine($"Loaded {count} secrets"); // Access secrets Console.WriteLine(Environment.GetEnvironmentVariable("DATABASE_URL")); ``` ### Initialize the Client #### Simple Initialization ```csharp using KeyEnv; var client = KeyEnvClient.Create(Environment.GetEnvironmentVariable("KEYENV_TOKEN")!); ``` #### With Options For more control, use `KeyEnvOptions`: ```csharp using KeyEnv; var client = KeyEnvClient.Create(new KeyEnvOptions { Token = Environment.GetEnvironmentVariable("KEYENV_TOKEN")!, Timeout = TimeSpan.FromSeconds(60), CacheTtl = TimeSpan.FromMinutes(5) }); ``` ### Loading Secrets #### Load into Environment Variables The simplest way to use secrets in your application: ```csharp int count = await client.LoadEnvAsync("project-id", "production"); Console.WriteLine($"Loaded {count} secrets"); // Access via Environment.GetEnvironmentVariable var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL"); var apiKey = Environment.GetEnvironmentVariable("API_KEY"); ``` #### Get Secrets as Dictionary Get secrets as a key-value dictionary: ```csharp var secrets = await client.GetSecretsAsDictionaryAsync("project-id", "production"); Console.WriteLine(secrets["DATABASE_URL"]); Console.WriteLine(secrets["API_KEY"]); ``` #### Export as List Get secrets with metadata: ```csharp var secrets = await client.GetSecretsAsync("project-id", "production"); foreach (var secret in secrets) { Console.WriteLine($"{secret.Key}={secret.Value}"); } ``` ### Managing Secrets #### Get a Single Secret ```csharp var secret = await client.GetSecretAsync("project-id", "production", "DATABASE_URL"); Console.WriteLine(secret.Value); Console.WriteLine(secret.Description); ``` #### Set a Secret Creates or updates a secret: ```csharp await client.SetSecretAsync("project-id", "production", "API_KEY", "sk_live_..."); // With description await client.SetSecretAsync( "project-id", "production", "API_KEY", "sk_live_...", description: "Production API key" ); ``` #### Delete a Secret ```csharp await client.DeleteSecretAsync("project-id", "production", "OLD_KEY"); ``` ### Bulk Operations #### Bulk Import Import multiple secrets at once: ```csharp using KeyEnv.Types; var secrets = new List { new SecretInput { Key = "DATABASE_URL", Value = "postgres://localhost/mydb" }, new SecretInput { Key = "REDIS_URL", Value = "redis://localhost:6379" }, new SecretInput { Key = "API_KEY", Value = "sk_test_...", Description = "Test API key" } }; var result = await client.BulkImportAsync( "project-id", "development", secrets, new BulkImportOptions { Overwrite = true } ); Console.WriteLine($"Created: {result.Created}, Updated: {result.Updated}"); ``` #### Generate .env File ```csharp var envContent = await client.GenerateEnvFileAsync("project-id", "production"); await File.WriteAllTextAsync(".env", envContent); ``` ### Projects & Environments #### List Projects ```csharp var projects = await client.ListProjectsAsync(); foreach (var project in projects) { Console.WriteLine($"{project.Name} ({project.Id})"); } ``` #### Get Project Details ```csharp var project = await client.GetProjectAsync("project-id"); Console.WriteLine($"Project: {project.Name}"); foreach (var env in project.Environments) { Console.WriteLine($" - {env.Name}"); } ``` #### List Environments ```csharp var environments = await client.ListEnvironmentsAsync("project-id"); foreach (var env in environments) { Console.WriteLine(env.Name); } ``` ### Environment Permissions #### List Permissions ```csharp var permissions = await client.ListPermissionsAsync("project-id", "production"); foreach (var perm in permissions) { Console.WriteLine($"{perm.UserEmail}: {perm.Role}"); } ``` #### Set Permission ```csharp await client.SetPermissionAsync("project-id", "production", "user-id", "write"); ``` #### Get My Permissions ```csharp var response = await client.GetMyPermissionsAsync("project-id"); foreach (var perm in response.Permissions) { Console.WriteLine($"{perm.EnvironmentName}: {perm.Role} (CanWrite: {perm.CanWrite})"); } Console.WriteLine($"Is Team Admin: {response.IsTeamAdmin}"); ``` ### Caching Enable caching for better performance in high-throughput applications: ```csharp var client = KeyEnvClient.Create(new KeyEnvOptions { Token = Environment.GetEnvironmentVariable("KEYENV_TOKEN")!, CacheTtl = TimeSpan.FromMinutes(5) }); // First call fetches from API await client.GetSecretsAsync("project-id", "production"); // Subsequent calls within TTL use cache await client.GetSecretsAsync("project-id", "production"); // Clear cache when needed client.ClearCache("project-id", "production"); client.ClearAllCache(); ``` ### Error Handling ```csharp using KeyEnv; try { var secret = await client.GetSecretAsync("project-id", "production", "MISSING_KEY"); } catch (KeyEnvException ex) { Console.WriteLine($"Error {ex.StatusCode}: {ex.Message}"); if (ex.IsNotFound) { Console.WriteLine("Secret not found"); } else if (ex.IsUnauthorized) { Console.WriteLine("Invalid or expired token"); } else if (ex.IsForbidden) { Console.WriteLine("Access denied"); } } ``` ### Cancellation Support All async methods support cancellation tokens: ```csharp using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); try { var secrets = await client.GetSecretsAsync("project-id", "production", cts.Token); } catch (OperationCanceledException) { Console.WriteLine("Operation was cancelled"); } ``` ### IDisposable Pattern The client implements `IDisposable` for proper resource cleanup: ```csharp using (var client = KeyEnvClient.Create(token)) { await client.LoadEnvAsync("project-id", "production"); // Client is disposed when leaving the using block } // Or with using declaration (C# 8+) using var client = KeyEnvClient.Create(token); await client.LoadEnvAsync("project-id", "production"); ``` ### API Reference #### Constructor Options | Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | `Token` | `string` | Yes | - | Service token | | `Timeout` | `TimeSpan?` | No | `30s` | Request timeout | | `CacheTtl` | `TimeSpan?` | No | `0` | Cache TTL (0 disables) | | `BaseUrl` | `string?` | No | `https://api.keyenv.dev` | API base URL | #### Methods | Method | Description | |--------|-------------| | `GetCurrentUserAsync()` | Get current user/token info | | `ValidateTokenAsync()` | Validate token and get user info | | `ListProjectsAsync()` | List all accessible projects | | `GetProjectAsync(projectId)` | Get project with environments | | `CreateProjectAsync(teamId, name)` | Create a new project | | `DeleteProjectAsync(projectId)` | Delete a project | | `ListEnvironmentsAsync(projectId)` | List environments in a project | | `CreateEnvironmentAsync(projectId, name)` | Create a new environment | | `DeleteEnvironmentAsync(projectId, env)` | Delete an environment | | `ListSecretsAsync(projectId, env)` | List secret keys (no values) | | `GetSecretsAsync(projectId, env)` | Export secrets with values | | `GetSecretsAsDictionaryAsync(projectId, env)` | Export as dictionary | | `GetSecretAsync(projectId, env, key)` | Get single secret | | `SetSecretAsync(projectId, env, key, value)` | Create or update secret | | `DeleteSecretAsync(projectId, env, key)` | Delete secret | | `BulkImportAsync(projectId, env, secrets)` | Bulk import secrets | | `LoadEnvAsync(projectId, env)` | Load secrets into environment | | `GenerateEnvFileAsync(projectId, env)` | Generate .env file content | | `GetSecretHistoryAsync(projectId, env, key)` | Get secret version history | | `ListPermissionsAsync(projectId, env)` | List permissions | | `SetPermissionAsync(projectId, env, userId, role)` | Set permission | | `DeletePermissionAsync(projectId, env, userId)` | Delete permission | | `GetMyPermissionsAsync(projectId)` | Get current user's permissions | | `GetProjectDefaultsAsync(projectId)` | Get default permissions | | `SetProjectDefaultsAsync(projectId, defaults)` | Set default permissions | | `ClearCache(projectId, environment)` | Clear specific cache | | `ClearAllCache()` | Clear all cached data | ### Examples #### ASP.NET Core Web API ```csharp // Program.cs using KeyEnv; var builder = WebApplication.CreateBuilder(args); // Load secrets at startup var keyEnvToken = Environment.GetEnvironmentVariable("KEYENV_TOKEN"); if (!string.IsNullOrEmpty(keyEnvToken)) { using var client = KeyEnvClient.Create(keyEnvToken); await client.LoadEnvAsync( Environment.GetEnvironmentVariable("KEYENV_PROJECT")!, builder.Environment.EnvironmentName.ToLower() ); } // Now secrets are available via Environment.GetEnvironmentVariable builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run(); ``` #### ASP.NET Core with Dependency Injection ```csharp // Program.cs using KeyEnv; var builder = WebApplication.CreateBuilder(args); // Register KeyEnvClient as a singleton builder.Services.AddSingleton(sp => { return KeyEnvClient.Create(new KeyEnvOptions { Token = Environment.GetEnvironmentVariable("KEYENV_TOKEN")!, CacheTtl = TimeSpan.FromMinutes(5) }); }); var app = builder.Build(); app.Run(); // In a controller [ApiController] [Route("api/[controller]")] public class SecretsController : ControllerBase { private readonly KeyEnvClient _keyEnv; public SecretsController(KeyEnvClient keyEnv) { _keyEnv = keyEnv; } [HttpGet] public async Task Get() { var secrets = await _keyEnv.GetSecretsAsDictionaryAsync("project-id", "production"); return Ok(new { count = secrets.Count }); } } ``` #### Console Application ```csharp using KeyEnv; class Program { static async Task Main(string[] args) { var token = Environment.GetEnvironmentVariable("KEYENV_TOKEN") ?? throw new InvalidOperationException("KEYENV_TOKEN not set"); using var client = KeyEnvClient.Create(token); // Load all secrets into environment var count = await client.LoadEnvAsync("my-project", "production"); Console.WriteLine($"Loaded {count} secrets"); // Use the secrets var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL"); Console.WriteLine($"Database: {dbUrl}"); } } ``` #### Azure Functions ```csharp using KeyEnv; using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; var host = new HostBuilder() .ConfigureFunctionsWebApplication() .ConfigureServices(services => { services.AddSingleton(sp => { return KeyEnvClient.Create(new KeyEnvOptions { Token = Environment.GetEnvironmentVariable("KEYENV_TOKEN")!, CacheTtl = TimeSpan.FromMinutes(5) }); }); }) .Build(); // Load secrets at startup using (var scope = host.Services.CreateScope()) { var client = scope.ServiceProvider.GetRequiredService(); await client.LoadEnvAsync( Environment.GetEnvironmentVariable("KEYENV_PROJECT")!, "production" ); } host.Run(); ``` #### AWS Lambda with .NET ```csharp using Amazon.Lambda.Core; using KeyEnv; [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] namespace MyLambda; public class Function { private static readonly KeyEnvClient _client; private static bool _secretsLoaded = false; static Function() { _client = KeyEnvClient.Create(new KeyEnvOptions { Token = Environment.GetEnvironmentVariable("KEYENV_TOKEN")!, CacheTtl = TimeSpan.FromMinutes(5) }); } public async Task FunctionHandler(object input, ILambdaContext context) { // Load secrets once per cold start if (!_secretsLoaded) { await _client.LoadEnvAsync( Environment.GetEnvironmentVariable("KEYENV_PROJECT")!, "production" ); _secretsLoaded = true; } // Your handler logic here var apiKey = Environment.GetEnvironmentVariable("API_KEY"); return $"Success with API key: {apiKey?.Substring(0, 10)}..."; } } ``` #### Background Service ```csharp using KeyEnv; public class SecretsRefreshService : BackgroundService { private readonly KeyEnvClient _client; private readonly ILogger _logger; public SecretsRefreshService(KeyEnvClient client, ILogger logger) { _client = client; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { try { var count = await _client.LoadEnvAsync("project-id", "production", stoppingToken); _logger.LogInformation("Refreshed {Count} secrets", count); } catch (Exception ex) { _logger.LogError(ex, "Failed to refresh secrets"); } await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); } } } ``` ## Bun Using KeyEnv with Bun applications. Source: https://keyenv.dev/docs/sdks/bun/ Load KeyEnv secrets in your Bun applications using the CLI or the Node.js SDK. ### Using the CLI (Recommended) The simplest way to use KeyEnv with Bun is via the CLI's `run` command. #### Install the CLI ```bash curl -fsSL https://keyenv.dev/install.sh | bash ``` #### Configure package.json ```json { "scripts": { "dev": "keyenv run -p YOUR_PROJECT_ID -e development -- bun run src/index.ts", "start": "keyenv run -p YOUR_PROJECT_ID -e production -- bun run src/index.ts" } } ``` #### Run with Secrets ```bash # Set your token export KEYENV_TOKEN=your-service-token # Run with secrets injected bun run dev ``` ### Using the Node.js SDK Bun is Node.js-compatible, so you can use the official KeyEnv Node.js SDK. #### Install ```bash bun add @keyenv/node ``` #### Basic Usage ```typescript import { KeyEnv } from '@keyenv/node'; const client = new KeyEnv({ token: process.env.KEYENV_TOKEN!, }); // Load secrets into process.env await client.loadEnv('your-project-id', 'production'); console.log(process.env.DATABASE_URL); ``` #### Full Example ```typescript // src/index.ts import { KeyEnv } from '@keyenv/node'; const client = new KeyEnv({ token: process.env.KEYENV_TOKEN!, }); // Load secrets before starting server await client.loadEnv(process.env.KEYENV_PROJECT!, 'production'); const server = Bun.serve({ port: process.env.PORT || 3000, fetch(req) { return new Response('OK'); }, }); console.log(`Server running at http://localhost:${server.port}`); ``` ### Using Bun's Built-in fetch You can also call the KeyEnv API directly: ```typescript const KEYENV_TOKEN = process.env.KEYENV_TOKEN; const PROJECT_ID = 'your-project-id'; const ENVIRONMENT = 'production'; async function loadSecrets() { const response = await fetch( `https://api.keyenv.dev/api/v1/projects/${PROJECT_ID}/environments/${ENVIRONMENT}/secrets/export`, { headers: { 'Authorization': `Bearer ${KEYENV_TOKEN}`, }, } ); if (!response.ok) { throw new Error(`Failed to fetch secrets: ${response.status}`); } const { data: secrets } = await response.json(); for (const secret of secrets) { process.env[secret.key] = secret.value; } console.log(`Loaded ${secrets.length} secrets`); } await loadSecrets(); ``` ### Elysia Framework For Elysia applications: ```typescript import { Elysia } from 'elysia'; import { KeyEnv } from '@keyenv/node'; const client = new KeyEnv({ token: process.env.KEYENV_TOKEN!, }); // Load secrets before creating app await client.loadEnv(process.env.KEYENV_PROJECT!, 'production'); const app = new Elysia() .get('/', () => 'Hello World') .get('/config', () => ({ apiUrl: process.env.API_URL, })) .listen(3000); console.log(`Elysia running at http://${app.server?.hostname}:${app.server?.port}`); ``` ### Hono Framework For Hono applications running on Bun: ```typescript import { Hono } from 'hono'; import { KeyEnv } from '@keyenv/node'; const client = new KeyEnv({ token: process.env.KEYENV_TOKEN!, }); await client.loadEnv(process.env.KEYENV_PROJECT!, 'production'); const app = new Hono(); app.get('/', (c) => c.text('Hello World')); export default { port: process.env.PORT || 3000, fetch: app.fetch, }; ``` ### Scripts and One-off Commands For scripts that need secrets: ```typescript // scripts/migrate.ts import { KeyEnv } from '@keyenv/node'; const client = new KeyEnv({ token: process.env.KEYENV_TOKEN!, }); // Load production secrets for migration await client.loadEnv(process.env.KEYENV_PROJECT!, 'production'); // Now run your migration const databaseUrl = process.env.DATABASE_URL; console.log('Running migrations...'); // ... migration code ``` Run with: ```bash KEYENV_TOKEN=xxx KEYENV_PROJECT=xxx bun run scripts/migrate.ts ``` Or using the CLI: ```bash keyenv run -p PROJECT -e production -- bun run scripts/migrate.ts ``` ### Testing Load test secrets in your test setup: ```typescript // test/setup.ts import { KeyEnv } from '@keyenv/node'; import { beforeAll } from 'bun:test'; beforeAll(async () => { const client = new KeyEnv({ token: process.env.KEYENV_TOKEN!, }); await client.loadEnv(process.env.KEYENV_PROJECT!, 'test'); }); ``` Configure in `bunfig.toml`: ```toml [test] preload = ["./test/setup.ts"] ``` ### Best Practices 1. **Load secrets once at startup** - Don't fetch on every request: ```typescript // Good - load once await client.loadEnv(projectId, 'production'); const server = Bun.serve({ ... }); // Bad - fetching on every request app.get('/', async () => { await client.loadEnv(projectId, 'production'); // Don't do this! }); ``` 2. **Use environment-specific configs**: ```typescript const env = process.env.NODE_ENV || 'development'; await client.loadEnv(projectId, env); ``` 3. **Handle errors at startup**: ```typescript try { await client.loadEnv(projectId, 'production'); } catch (error) { console.error('Failed to load secrets:', error); process.exit(1); } ``` 4. **Use caching in serverless** - Enable cache TTL for warm function reuse: ```typescript const client = new KeyEnv({ token: process.env.KEYENV_TOKEN!, cacheTtl: 300, // 5 minutes }); ``` ## Deno Using KeyEnv with Deno applications. Source: https://keyenv.dev/docs/sdks/deno/ Load KeyEnv secrets in your Deno applications using the CLI or direct API calls. ### Using the CLI (Recommended) The simplest way to use KeyEnv with Deno is via the CLI's `run` command. #### Install the CLI ```bash curl -fsSL https://keyenv.dev/install.sh | bash ``` #### Configure deno.json ```json { "tasks": { "dev": "keyenv run -p YOUR_PROJECT_ID -e development -- deno run -A src/main.ts", "start": "keyenv run -p YOUR_PROJECT_ID -e production -- deno run -A src/main.ts" } } ``` #### Run with Secrets ```bash # Set your token export KEYENV_TOKEN=your-service-token # Run with secrets injected deno task dev ``` ### Direct API Usage Deno has built-in `fetch`, so you can call the KeyEnv API directly without any dependencies. #### Fetch and Load Secrets ```typescript const KEYENV_TOKEN = Deno.env.get("KEYENV_TOKEN"); const PROJECT_ID = "your-project-id"; const ENVIRONMENT = "production"; interface Secret { key: string; value: string; } interface ExportResponse { data: Secret[]; } async function loadSecrets(): Promise { const response = await fetch( `https://api.keyenv.dev/api/v1/projects/${PROJECT_ID}/environments/${ENVIRONMENT}/secrets/export`, { headers: { "Authorization": `Bearer ${KEYENV_TOKEN}`, "Content-Type": "application/json", }, } ); if (!response.ok) { throw new Error(`Failed to fetch secrets: ${response.status}`); } const data: ExportResponse = await response.json(); for (const secret of data.secrets) { Deno.env.set(secret.key, secret.value); } console.log(`Loaded ${data.secrets.length} secrets`); } // Load at startup await loadSecrets(); // Use secrets console.log(Deno.env.get("DATABASE_URL")); ``` #### Create a Reusable Module ```typescript // keyenv.ts const API_BASE = "https://api.keyenv.dev/api/v1"; export interface KeyEnvConfig { token: string; projectId: string; environment: string; } export interface Secret { key: string; value: string; description?: string; } export class KeyEnv { private token: string; private projectId: string; private environment: string; constructor(config: KeyEnvConfig) { this.token = config.token; this.projectId = config.projectId; this.environment = config.environment; } private async fetch(path: string, options?: RequestInit): Promise { const response = await fetch(`${API_BASE}${path}`, { ...options, headers: { "Authorization": `Bearer ${this.token}`, "Content-Type": "application/json", ...options?.headers, }, }); if (!response.ok) { const error = await response.json().catch(() => ({})); throw new Error(error.message || `HTTP ${response.status}`); } return response.json(); } async exportSecrets(): Promise { const data = await this.fetch<{ data: Secret[] }>( `/projects/${this.projectId}/environments/${this.environment}/secrets/export` ); return data.data; } async loadEnv(): Promise { const secrets = await this.exportSecrets(); for (const secret of secrets) { Deno.env.set(secret.key, secret.value); } return secrets.length; } async getSecret(key: string): Promise { return this.fetch( `/projects/${this.projectId}/environments/${this.environment}/secrets/${key}` ); } async setSecret(key: string, value: string, description?: string): Promise { await this.fetch( `/projects/${this.projectId}/environments/${this.environment}/secrets/${key}`, { method: "PUT", body: JSON.stringify({ value, description }), } ); } } ``` #### Using the Module ```typescript // main.ts import { KeyEnv } from "./keyenv.ts"; const keyenv = new KeyEnv({ token: Deno.env.get("KEYENV_TOKEN")!, projectId: "your-project-id", environment: "production", }); // Load all secrets into environment await keyenv.loadEnv(); // Or get specific secrets const dbUrl = await keyenv.getSecret("DATABASE_URL"); console.log(dbUrl.value); ``` ### Deno Deploy For Deno Deploy, use environment variables or the direct API approach. #### Using Environment Variables Set `KEYENV_TOKEN` in your Deno Deploy project settings, then fetch secrets at startup: ```typescript // main.ts const TOKEN = Deno.env.get("KEYENV_TOKEN"); const PROJECT = Deno.env.get("KEYENV_PROJECT"); // Cache secrets in module scope let secrets: Map | null = null; async function getSecrets(): Promise> { if (secrets) return secrets; const response = await fetch( `https://api.keyenv.dev/api/v1/projects/${PROJECT}/environments/production/secrets/export`, { headers: { "Authorization": `Bearer ${TOKEN}` } } ); const data = await response.json(); secrets = new Map(data.secrets.map((s: any) => [s.key, s.value])); return secrets; } Deno.serve(async (_req) => { const env = await getSecrets(); // Use env.get("DATABASE_URL"), etc. return new Response("OK"); }); ``` ### Fresh Framework For Fresh applications, load secrets in a plugin or middleware: ```typescript // plugins/keyenv.ts import { Plugin } from "$fresh/server.ts"; const TOKEN = Deno.env.get("KEYENV_TOKEN"); const PROJECT = Deno.env.get("KEYENV_PROJECT"); export default { name: "keyenv", async configureServer() { const response = await fetch( `https://api.keyenv.dev/api/v1/projects/${PROJECT}/environments/production/secrets/export`, { headers: { "Authorization": `Bearer ${TOKEN}` } } ); const data = await response.json(); for (const secret of data.secrets) { Deno.env.set(secret.key, secret.value); } console.log(`Loaded ${data.secrets.length} secrets from KeyEnv`); }, } satisfies Plugin; ``` ### Best Practices 1. **Use the CLI for local development** - It handles token management and environment switching. 2. **Cache secrets in production** - Avoid fetching on every request: ```typescript let cachedSecrets: Map | null = null; let cacheExpiry = 0; async function getSecrets() { if (cachedSecrets && Date.now() < cacheExpiry) { return cachedSecrets; } // Fetch and cache for 5 minutes cachedSecrets = await fetchSecrets(); cacheExpiry = Date.now() + 5 * 60 * 1000; return cachedSecrets; } ``` 3. **Handle errors gracefully**: ```typescript try { await loadSecrets(); } catch (error) { console.error("Failed to load secrets:", error); Deno.exit(1); } ``` 4. **Don't commit tokens** - Use environment variables or a `.env` file (gitignored). ## Next.js Integration Use KeyEnv secrets securely in Next.js applications. Source: https://keyenv.dev/docs/sdks/nextjs/ Integrate KeyEnv secrets into your Next.js application with proper security practices for server and client components. ### Features - Load secrets at build time or runtime - Server Components and Server Actions support - API Routes and Route Handlers support - Middleware integration - Environment-aware configuration - Works with App Router and Pages Router - Vercel deployment ready ### Installation ```bash npm install @keyenv/node ``` Or with other package managers: ```bash yarn add @keyenv/node pnpm add @keyenv/node ``` ### Quick Start The recommended approach is to load secrets once at application startup. #### App Router (Recommended) Create a secrets loader in your app: ```typescript // lib/secrets.ts import { KeyEnv } from '@keyenv/node'; let secretsLoaded = false; export async function loadSecrets() { if (secretsLoaded) return; const client = new KeyEnv({ token: process.env.KEYENV_TOKEN!, }); await client.loadEnv( process.env.KEYENV_PROJECT_ID!, process.env.NODE_ENV === 'production' ? 'production' : 'development' ); secretsLoaded = true; } ``` Then call it in your layout or specific routes: ```typescript // app/layout.tsx import { loadSecrets } from '@/lib/secrets'; export default async function RootLayout({ children, }: { children: React.ReactNode; }) { await loadSecrets(); return ( {children} ); } ``` --- ### next.config.js Integration For build-time secret injection, use a custom webpack configuration: ```javascript // next.config.js const { KeyEnv } = require('@keyenv/node'); /** @type {import('next').NextConfig} */ const nextConfig = { // Other config options... }; // Load secrets at build time async function loadBuildSecrets() { if (!process.env.KEYENV_TOKEN) { console.warn('KEYENV_TOKEN not set, skipping secret loading'); return {}; } const client = new KeyEnv({ token: process.env.KEYENV_TOKEN, }); const env = process.env.VERCEL_ENV || process.env.NODE_ENV; const environment = env === 'production' ? 'production' : 'development'; return await client.exportSecretsAsObject( process.env.KEYENV_PROJECT_ID, environment ); } module.exports = async () => { const secrets = await loadBuildSecrets(); return { ...nextConfig, env: { ...secrets, }, }; }; ``` > **Warning** > > Build-time secrets are embedded in the bundle. Only use this for non-sensitive configuration. For sensitive secrets, load them at runtime in Server Components. --- ### Server Components vs Client Components Next.js has different execution contexts. Understanding where secrets are safe is critical. | Context | Secrets Safe? | Access Method | |---------|---------------|---------------| | Server Components | Yes | `process.env.SECRET` | | Server Actions | Yes | `process.env.SECRET` | | API Routes / Route Handlers | Yes | `process.env.SECRET` | | Middleware | Yes | `process.env.SECRET` | | Client Components | No | Never expose secrets | #### Server Components (Safe) Server Components run only on the server. Secrets are never sent to the browser: ```typescript // app/dashboard/page.tsx import { KeyEnv } from '@keyenv/node'; export default async function DashboardPage() { // Safe: This code only runs on the server const client = new KeyEnv({ token: process.env.KEYENV_TOKEN!, }); const secrets = await client.exportSecretsAsObject( process.env.KEYENV_PROJECT_ID!, 'production' ); // Use the secret to fetch data const data = await fetch('https://api.example.com/data', { headers: { Authorization: `Bearer ${secrets.API_KEY}`, }, }); return
{/* Render data, not secrets */}
; } ``` #### Client Components (Never Use Secrets) Client Components run in the browser. Never access secrets here: ```typescript // components/UserProfile.tsx 'use client'; // WRONG: Never do this in a client component // const apiKey = process.env.API_KEY; // This would be undefined or leaked // CORRECT: Fetch data through an API route instead export function UserProfile() { const [data, setData] = useState(null); useEffect(() => { fetch('/api/user-data') .then(res => res.json()) .then(setData); }, []); return
{/* Render data */}
; } ``` #### Public Environment Variables For non-sensitive values needed in the browser, use the `NEXT_PUBLIC_` prefix: ```bash # In KeyEnv, create these secrets: NEXT_PUBLIC_APP_URL=https://myapp.com NEXT_PUBLIC_ANALYTICS_ID=UA-123456789 ``` ```typescript // components/Analytics.tsx 'use client'; export function Analytics() { // Safe: These are intentionally public const analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID; return