Skip to main content

Keys API


title: Keys API audience: Developer difficulty: Intermediate estimated_read_time: 7 min prerequisites:

  • Authentication setup
  • Understanding of keys vs bundles related_pages:
  • ../concepts/bundles.md
  • ../api-reference/bundles.md

Manage keys - reusable environment variables in your library.

Overview

Keys are individual environment variables stored in a centralized library. They can be:

  • Attached to multiple bundles
  • Updated once (propagates to all bundles)
  • Marked as secret (values masked in UI) or non-secret
  • Searched and filtered

Base URL: https://env.cat/api/v1/keys

Authentication: All endpoints require authentication (Clerk session → BFF signature)

Endpoints

List Keys

List all keys in current tenant.

Endpoint:

GET /api/v1/keys

Authorization: Organization members (role-based access)

Query Parameters:

ParameterTypeRequiredDescription
querystringNoSearch by key name (case-insensitive)

Response (200 OK):

[
{
"id": "key-uuid-1",
"name": "DATABASE_URL",
"value": "*****",
"is_secret": true,
"description": "PostgreSQL connection string",
"created_at": "2025-10-22T10:00:00Z",
"updated_at": "2025-10-22T10:00:00Z",
"used_in_bundles": 3
},
{
"id": "key-uuid-2",
"name": "PORT",
"value": "3000",
"is_secret": false,
"description": "Application port",
"created_at": "2025-10-22T10:05:00Z",
"updated_at": "2025-10-22T10:05:00Z",
"used_in_bundles": 5
}
]

Notes:

  • Secret keys show value: "*****" (masked)
  • Non-secret keys show actual decrypted value
  • used_in_bundles shows how many bundles use this key

Example:

# List all keys
curl https://env.cat/api/v1/keys \
-H "Cookie: __session=your_clerk_session"

# Search keys
curl "https://env.cat/api/v1/keys?query=DATABASE" \
-H "Cookie: __session=your_clerk_session"

Create Key

Create a new key in the library.

Endpoint:

POST /api/v1/keys

Authorization: Organization members (all members can create keys)

Request Body:

{
"name": "DATABASE_URL",
"value": "postgres://user:pass@localhost:5432/db",
"is_secret": true,
"description": "PostgreSQL connection string"
}

Parameters:

FieldTypeRequiredDefaultDescription
namestringYes-Key name (unique per tenant, UPPERCASE_SNAKE_CASE recommended)
valuestringYes-Key value (encrypted at-rest)
is_secretbooleanNotrueIf true, value masked in UI
descriptionstringNo""Human-readable description

Response (201 Created):

{
"id": "key-uuid",
"name": "DATABASE_URL",
"value": "*****",
"is_secret": true,
"description": "PostgreSQL connection string",
"created_at": "2025-10-22T10:00:00Z",
"updated_at": "2025-10-22T10:00:00Z",
"used_in_bundles": 0
}

Errors:

CodeDescription
400Name or value required, or invalid format
403Forbidden (not owner)
409Key with this name already exists

Example:

curl -X POST https://env.cat/api/v1/keys \
-H "Content-Type: application/json" \
-H "Cookie: __session=your_clerk_session" \
-d '{
"name": "DATABASE_URL",
"value": "postgres://user:pass@localhost:5432/db",
"is_secret": true,
"description": "PostgreSQL connection string"
}'

Get Key

Get a single key by ID.

Endpoint:

GET /api/v1/keys/:key_id

Authorization: Organization members (role-based access)

Query Parameters:

ParameterTypeRequiredDescription
revealstringNo1 to show decrypted value (secrets always masked unless dev mode)

Response (200 OK):

{
"id": "key-uuid",
"name": "DATABASE_URL",
"value": "*****",
"is_secret": true,
"description": "PostgreSQL connection string",
"created_at": "2025-10-22T10:00:00Z",
"updated_at": "2025-10-22T10:00:00Z",
"used_in_bundles": 3,
"bundles": [
{
"id": "bundle-uuid-1",
"name": "dev/api"
},
{
"id": "bundle-uuid-2",
"name": "staging/api"
},
{
"id": "bundle-uuid-3",
"name": "prod/api"
}
]
}

Response (200 OK) - With reveal=1 (dev mode only):

{
"id": "key-uuid",
"name": "DATABASE_URL",
"value": "postgres://user:pass@localhost:5432/db",
"is_secret": true,
"description": "PostgreSQL connection string",
"created_at": "2025-10-22T10:00:00Z",
"updated_at": "2025-10-22T10:00:00Z",
"used_in_bundles": 3,
"bundles": [...]
}

Errors:

CodeDescription
403Forbidden (not member) or reveal not allowed
404Key not found

Example:

# Get key (masked)
curl https://env.cat/api/v1/keys/key-uuid \
-H "Cookie: __session=your_clerk_session"

# Get key (revealed - dev mode only)
curl "https://env.cat/api/v1/keys/key-uuid?reveal=1" \
-H "Cookie: __session=your_clerk_session"

Update Key

Update a key's value or description. Changes propagate to all bundles.

Endpoint:

PUT /api/v1/keys/:key_id

Authorization: Organization admins only

Request Body:

{
"value": "postgres://user:newpass@localhost:5432/db",
"description": "Updated PostgreSQL connection string"
}

Parameters:

FieldTypeRequiredDescription
valuestringNoNew value (if provided, encrypted at-rest)
descriptionstringNoNew description
is_secretbooleanNoUpdate secret flag

Response (200 OK):

{
"id": "key-uuid",
"name": "DATABASE_URL",
"value": "*****",
"is_secret": true,
"description": "Updated PostgreSQL connection string",
"created_at": "2025-10-22T10:00:00Z",
"updated_at": "2025-10-22T11:00:00Z",
"used_in_bundles": 3
}

Errors:

CodeDescription
403Forbidden (not owner)
404Key not found

Example:

curl -X PUT https://env.cat/api/v1/keys/key-uuid \
-H "Content-Type: application/json" \
-H "Cookie: __session=your_clerk_session" \
-d '{
"value": "postgres://user:newpass@localhost:5432/db",
"description": "Updated connection string"
}'

Important: Updating a key's value immediately affects all bundles using this key.


Delete Key

Delete a key from the library. Removes from all bundles.

Endpoint:

DELETE /api/v1/keys/:key_id

Authorization: Organization admins only

Response (200 OK):

{
"ok": true
}

Errors:

CodeDescription
403Forbidden (not owner)
404Key not found

Example:

curl -X DELETE https://env.cat/api/v1/keys/key-uuid \
-H "Cookie: __session=your_clerk_session"

Warning: Deleting a key removes it from all bundles using it. This action cannot be undone.


Key Propagation

Keys are reusable and propagate changes automatically.

How It Works

  1. Create key: DATABASE_URL = postgres://localhost:5432/db
  2. Attach to bundles: dev/api, staging/api, prod/api
  3. Update key value: DATABASE_URL = postgres://newhost:5432/db
  4. Automatic propagation: All 3 bundles now have new value

Benefits

  • Single source of truth: Update once, affects all bundles
  • Consistency: No risk of mismatched values across environments
  • Efficiency: No need to update each bundle individually

Use Cases

Rotating secrets:

# 1. Update key value
curl -X PUT https://env.cat/api/v1/keys/stripe-key-uuid \
-d '{"value":"sk_live_NEW_KEY"}'

# 2. All bundles using STRIPE_SECRET_KEY now have new value
# No need to update each bundle manually

Shared configuration:

# Create key used across all environments
curl -X POST https://env.cat/api/v1/keys \
-d '{
"name": "LOG_LEVEL",
"value": "info",
"is_secret": false
}'

# Attach to dev, staging, and prod bundles
# Update once to change all environments

Best Practices

Naming Conventions

Use SCREAMING_SNAKE_CASE:

✅ DATABASE_URL
✅ STRIPE_SECRET_KEY
✅ MAX_CONNECTIONS

❌ databaseUrl
❌ stripe-secret-key
❌ maxConnections

Be descriptive:

✅ POSTGRES_PRIMARY_DATABASE_URL
✅ STRIPE_LIVE_SECRET_KEY
✅ REDIS_SESSION_CACHE_URL

❌ DB_URL
❌ KEY
❌ CACHE

Organizing Keys

Group related keys:

POSTGRES_HOST
POSTGRES_PORT
POSTGRES_DATABASE
POSTGRES_USER
POSTGRES_PASSWORD

Use descriptions:

{
"name": "STRIPE_SECRET_KEY",
"description": "Production Stripe secret key (live mode). Rotate every 90 days."
}

Security

Mark secrets correctly:

  • API keys, passwords, tokens: is_secret: true
  • Ports, URLs, flags: is_secret: false

Rotate regularly:

  • Update key values periodically
  • Especially after team member departures
  • Use description to track rotation schedule

Audit usage:

  • Check used_in_bundles before deleting
  • Review which bundles use sensitive keys
  • Remove unused keys

See Also