# EnvVault — Secure Secrets for AI Agents

> **You are reading this because a user gave you this URL and an API key.**
> With these two things you can fetch any secret you need (API tokens, database credentials, deploy keys) without the user having to paste them into chat.

---

## What Is EnvVault?

EnvVault is a secrets vault. It stores encrypted credentials (API keys, passwords, tokens, connection strings) and exposes them through a simple REST API. Think of it as a lightweight alternative to AWS Secrets Manager that works anywhere — especially in sandboxed AI coding environments where you don't have access to AWS, cloud CLIs, or `.env` files.

**Base URL:** `https://envvault.fly.dev`

---

## Quick Start — Get a Secret in One Call

You already have an API key (the user gave it to you). Use it like this:

```bash
# Fetch a single secret
curl -s "https://envvault.fly.dev/api/v1/variables/SECRET_NAME?application=APP_NAME" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Response:

```json
{
  "name": "SECRET_NAME",
  "application": "APP_NAME",
  "value": "the-actual-secret-value",
  "description": "What this secret is for"
}
```

That's it. One HTTP call, and you have the secret.

---

## Typical Workflow

Here's how this usually works in practice:

1. **User gives you two things:** this URL and an API key (or session token)
2. **You fetch the secrets you need** for whatever task you're working on
3. **You export them as environment variables** so CLIs and SDKs can use them
4. **You do your work** (create PRs, deploy, call APIs, etc.)

### Example: GitHub PR Creation

The most common use case — you need a GitHub token to create PRs because the sandboxed git proxy only handles `git push`/`pull`, not the GitHub API.

```bash
# Step 1: Fetch the GitHub token
GH_TOKEN=$(curl -s "https://envvault.fly.dev/api/v1/variables/GITHUB_MASTER_KEY?application=github" \
  -H "Authorization: Bearer $ENVVAULT_KEY" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['value'])")

# Step 2: Export it
export GH_TOKEN

# Step 3: Use gh CLI normally
gh pr create --title "My changes" --body "Description"
gh pr merge 42 --merge
```

### Example: Fly.io Deployment

```bash
FLY_API_TOKEN=$(curl -s "https://envvault.fly.dev/api/v1/variables/FLY_API_TOKEN?application=flyio" \
  -H "Authorization: Bearer $ENVVAULT_KEY" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['value'])")

export FLY_API_TOKEN
flyctl deploy --remote-only
```

### Example: Fetch Multiple Secrets at Once

```bash
curl -s -X POST https://envvault.fly.dev/api/v1/variables/bulk \
  -H "Authorization: Bearer $ENVVAULT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"names": ["GITHUB_MASTER_KEY", "FLY_API_TOKEN", "OPENAI_API_KEY"]}'
```

Response:

```json
{
  "application": "default",
  "variables": {
    "GITHUB_MASTER_KEY": "github_pat_...",
    "FLY_API_TOKEN": "fo1_...",
    "OPENAI_API_KEY": "sk-..."
  }
}
```

Export them all at once:

```bash
eval $(curl -s -X POST https://envvault.fly.dev/api/v1/variables/bulk \
  -H "Authorization: Bearer $ENVVAULT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"names": ["GITHUB_MASTER_KEY", "FLY_API_TOKEN"]}' \
  | python3 -c "
import sys, json
data = json.load(sys.stdin)['variables']
mapping = {'GITHUB_MASTER_KEY': 'GH_TOKEN', 'FLY_API_TOKEN': 'FLY_API_TOKEN'}
for k, v in data.items():
    env_name = mapping.get(k, k)
    print(f'export {env_name}={chr(39)}{v}{chr(39)}')
")
```

---

## Authentication

Every API call (except health check) needs your API key in a header:

```
Authorization: Bearer YOUR_API_KEY
```

or:

```
X-API-Key: YOUR_API_KEY
```

The key the user gives you may be either:
- A **master API key** — permanent, full access
- A **session token** (starts with `sess_`) — temporary, scoped to specific apps, auto-expires

Both work the same way in the Authorization header. Session tokens are preferred for AI agents because they expire automatically and limit blast radius if leaked.

### Roles

| Role | What You Can Do |
|------|----------------|
| `read` | List and retrieve secrets |
| `write` | Everything in read + create, update, delete secrets |
| `admin` | Everything in write + manage API keys |

Most AI agent sessions use `read` or `write`.

---

## API Reference

### List All Secrets (Metadata Only)

```
GET /api/v1/variables/
```

Optional query params: `application` (filter by app)

Returns names, applications, descriptions, and timestamps — but **not** values. Use this to discover what's available.

### Get a Single Secret (With Value)

```
GET /api/v1/variables/{name}
```

Optional query params: `application` (default: `"default"`)

### Bulk Retrieve Secrets (With Values)

```
POST /api/v1/variables/bulk
```

Body:

```json
{
  "names": ["SECRET_1", "SECRET_2"],
  "application": "default"
}
```

Missing names are silently omitted from the response.

### Create or Update a Secret

```
POST /api/v1/variables/
```

Requires `write` role.

Body:

```json
{
  "name": "SECRET_NAME",
  "value": "secret-value",
  "application": "default",
  "environment": "prod",
  "description": "What this is for"
}
```

| Field | Required | Default | Notes |
|-------|----------|---------|-------|
| `name` | yes | — | Must match `^[A-Za-z_][A-Za-z0-9_.\-]*$` |
| `value` | yes | — | Non-empty string |
| `application` | no | `"default"` | Groups secrets by project/service |
| `environment` | no | `""` | Optional tag (prod, staging, dev) |
| `description` | no | `null` | Human-readable note |

POSTing the same name+application overwrites the existing value (secret rotation).

### Update a Secret (Partial)

```
PATCH /api/v1/variables/{name}
```

Requires `write` role. Query param: `application` (default: `"default"`).

Body (all fields optional):

```json
{
  "value": "new-value",
  "description": "Updated description",
  "new_name": "RENAMED_SECRET",
  "new_application": "other-app"
}
```

### Delete a Secret

```
DELETE /api/v1/variables/{name}
```

Requires `write` role. Query param: `application` (default: `"default"`).

### Health Check (No Auth)

```
GET /api/v1/health
```

---

## How Secrets Are Organized

Secrets are organized by **application** (like a folder/namespace). A secret's identity is `(name, application)`, so you can have `DATABASE_URL` in both `myapp` and `backend` as separate secrets.

Common patterns:
- `github` — GitHub tokens
- `flyio` — Fly.io deploy tokens
- `openai` — OpenAI API keys
- `default` — general-purpose secrets

Use `?application=APP_NAME` on GET requests to target a specific app. Use the list endpoint to see what applications exist.

---

## Error Handling

All errors return JSON:

```json
{"error": "Description of the problem."}
```

| Status | Meaning |
|--------|---------|
| 400 | Bad request (missing/invalid fields) |
| 401 | Missing or invalid API key |
| 403 | Insufficient permissions or wrong application scope |
| 404 | Secret not found |
| 500 | Server error |

---

## Security Rules

1. **Never write secrets to files or commit them.** Only export as environment variables in the current shell.
2. **Never log or echo raw secret values.** Use them programmatically.
3. **Fetch secrets fresh each session.** Don't cache or store them.
4. **If using a session token**, it will expire automatically (typically 24 hours). No cleanup needed.
5. All values are encrypted at rest with AES-256-GCM.
6. Each user's secrets are fully isolated — you can only access secrets belonging to the user whose key you have.

---

## Session Tokens (For Reference)

If the user gives you a session token (starts with `sess_`), it works exactly like an API key but:
- **Expires automatically** (1 hour to 7 days)
- **May be scoped** to specific applications (e.g., only `github` and `flyio`)
- **Cannot perform admin operations** (managing users, creating API keys)

If you try to access an application outside the token's scope, you'll get:

```json
{"error": "Access denied. Session token scoped to: [\"github\", \"flyio\"]"}
```

---

## Complete Example: AI Agent Session

Here's the full pattern from start to finish:

```bash
# The user gave you an API key
ENVVAULT_KEY="evk_abc123..."

# 1. See what secrets are available
curl -s https://envvault.fly.dev/api/v1/variables/ \
  -H "Authorization: Bearer $ENVVAULT_KEY"

# 2. Fetch what you need for GitHub
GH_TOKEN=$(curl -s "https://envvault.fly.dev/api/v1/variables/GITHUB_MASTER_KEY?application=github" \
  -H "Authorization: Bearer $ENVVAULT_KEY" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['value'])")
export GH_TOKEN

# 3. Verify GitHub access
gh auth status

# 4. Do your work
git push origin my-branch
gh pr create --title "Feature X" --body "Description"

# 5. If you need to deploy
FLY_API_TOKEN=$(curl -s "https://envvault.fly.dev/api/v1/variables/FLY_API_TOKEN?application=flyio" \
  -H "Authorization: Bearer $ENVVAULT_KEY" \
  | python3 -c "import sys,json; print(json.load(sys.stdin)['value'])")
export FLY_API_TOKEN
flyctl deploy --remote-only
```

---

## For Project Maintainers: Adding EnvVault to Your Project

Add this to your project's `CLAUDE.md` (or equivalent AI instruction file):

```markdown
## Credentials (via EnvVault)

This project uses EnvVault for secure credential access.
Full tutorial: https://envvault.fly.dev/tutorial.md

At the start of each session, ask the user for their EnvVault API key.
Then fetch tokens as needed:

    curl -s "https://envvault.fly.dev/api/v1/variables/VARIABLE_NAME?application=APP" \
      -H "Authorization: Bearer $ENVVAULT_KEY"

For GitHub PRs: fetch GITHUB_MASTER_KEY (application=github), export as GH_TOKEN.
For Fly.io deploys: fetch FLY_API_TOKEN (application=flyio), export as FLY_API_TOKEN.
```

---

## Summary

| You Need To... | Do This |
|----------------|---------|
| Fetch a secret | `GET /api/v1/variables/{name}?application={app}` |
| List available secrets | `GET /api/v1/variables/` |
| Fetch multiple secrets | `POST /api/v1/variables/bulk` with `{"names": [...]}` |
| Store a secret | `POST /api/v1/variables/` with name, value, application |
| Delete a secret | `DELETE /api/v1/variables/{name}?application={app}` |
| Check API is up | `GET /api/v1/health` |

**Remember:** You have the URL (`https://envvault.fly.dev`) and an API key. That's all you need.
