Bundles API
title: Bundles API audience: Developer difficulty: Intermediate estimated_read_time: 8 min prerequisites:
- Authentication setup
- Understanding of bundles concept related_pages:
- ../concepts/bundles.md
- ../api-reference/keys.md
Manage bundles — named collections of environment variables (keys).
Overview
Bundles are the core organizational unit in EnvCat. Each bundle:
- Has a unique name within an organization (for example
dev/api,prod/database) - Contains attachments to reusable keys from the Keys library
- Can be requested by the CLI for secret delivery (approval flow)
- Supports import/export in ENV and JSON formats
Base URL: https://env.cat/api/v1/bundles (BFF). For local development use http://localhost:8888/api/v1/bundles.
Authentication: All endpoints require authentication (session cookie forwarded by the BFF).
Endpoints
Create Bundle
Create a new bundle.
Endpoint:
POST /api/v1/bundles
Authorization: Organization members
Request Body:
{
"name": "dev/api",
"description": "Development API secrets",
"tags": "development,api"
}
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Bundle name (unique per organization) |
description | string | No | Human-readable description |
tags | string | No | Comma-separated tags |
Response (201 Created):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "dev/api",
"description": "Development API secrets",
"tags": "development,api",
"created_by": "user-uuid",
"created_at": "2025-10-22T10:00:00Z",
"updated_at": "2025-10-22T10:00:00Z"
}
Notes:
- The API records
created_by(the user who created the bundle) for access control. - If a bundle with the same name exists in the organization the endpoint returns 409 Conflict.
Errors:
| Code | Description |
|---|---|
| 400 | Name required or invalid |
| 403 | Forbidden (not a member) |
| 409 | Bundle with this name already exists |
Example:
curl -s -X POST http://localhost:8888/api/v1/bundles \
-H "Content-Type: application/json" \
-H "Cookie: __session=your_clerk_session" \
-d '{"name":"dev/api","description":"Development API secrets"}' | jq
List Bundles
List bundles accessible to the authenticated user in the organization.
Endpoint:
GET /api/v1/bundles
Authorization: Organization members (role-based access)
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | No | Search by name (case-insensitive) |
tag | string | No | Filter by tag |
Response (200 OK):
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "dev/api",
"description": "Development API secrets",
"tags": "development,api",
"created_at": "2025-10-22T10:00:00Z",
"updated_at": "2025-10-22T10:00:00Z"
}
]
Example:
# List all bundles
curl -s "http://localhost:8888/api/v1/bundles" -H "Cookie: __session=your_clerk_session" | jq
# Search bundles
curl -s "http://localhost:8888/api/v1/bundles?query=dev" -H "Cookie: __session=your_clerk_session" | jq
Get Bundle
Get bundle metadata and the list of attached keys. Values are masked in this endpoint to avoid accidental exposure in the UI.
Endpoint:
GET /api/v1/bundles/:bundle_id
Authorization: Member or Owner
Response (200 OK):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "dev/api",
"description": "Development API secrets",
"tags": "development,api",
"created_by": "user-uuid",
"created_at": "2025-10-22T10:00:00Z",
"updated_at": "2025-10-22T10:00:00Z",
"items": [
{
"id": "key-uuid-1",
"name": "DATABASE_URL",
"value": "*****",
"is_secret": true,
"description": "Database connection",
"order": 0,
"required": false
},
{
"id": "key-uuid-2",
"name": "API_KEY",
"value": "*****",
"is_secret": true,
"description": null,
"order": 1,
"required": false
}
]
}
Notes:
- Values are masked (
"*****") for all attached keys on this endpoint. The approval UI uses this endpoint to show key names and metadata without revealing values.
Errors:
| Code | Description |
|---|---|
| 403 | Forbidden (not a member) |
| 404 | Bundle not found |
Example:
curl -s "http://localhost:8888/api/v1/bundles/550e8400-e29b-41d4-a716-446655440000" \
-H "Cookie: __session=your_clerk_session" | jq
Delete Bundle
Delete a bundle. Keys remain in the key library.
Endpoint:
DELETE /api/v1/bundles/:bundle_id
Authorization: Admin only
Response (200 OK):
{
"ok": true,
"message": "Bundle 'dev/api' deleted successfully"
}
Errors:
| Code | Description |
|---|---|
| 403 | Forbidden (not admin) |
| 404 | Bundle not found |
Example:
curl -s -X DELETE "http://localhost:8888/api/v1/bundles/550e8400-e29b-41d4-a716-446655440000" \
-H "Cookie: __session=your_clerk_session" | jq
List Bundle Keys
List keys attached to a bundle (values masked).
Endpoint:
GET /api/v1/bundles/:bundle_id/keys
Authorization: Member or Owner
Response (200 OK):
[
{
"id": "key-uuid-1",
"name": "DATABASE_URL",
"value": "*****",
"is_secret": true,
"order": 0
},
{
"id": "key-uuid-2",
"name": "API_KEY",
"value": "*****",
"is_secret": true,
"order": 1
}
]
Example:
curl -s "http://localhost:8888/api/v1/bundles/550e8400-e29b-41d4-a716-446655440000/keys" \
-H "Cookie: __session=your_clerk_session" | jq
Attach Key to Bundle
Attach an existing key from the library to a bundle.
Endpoint:
POST /api/v1/bundles/:bundle_id/keys/:key_id
Authorization: Admin only
Request Body (optional):
{ "order": 0, "required": false }
Response (201 Created):
{
"ok": true,
"message": "Key attached to bundle successfully"
}
Errors:
| Code | Description |
|---|---|
| 403 | Forbidden (not admin) |
| 404 | Bundle or key not found |
| 409 | Key already attached to bundle |
Example:
curl -s -X POST "http://localhost:8888/api/v1/bundles/bundle-uuid/keys/key-uuid" \
-H "Cookie: __session=your_clerk_session" | jq
Detach Key from Bundle
Remove a key from a bundle (key remains in library).
Endpoint:
DELETE /api/v1/bundles/:bundle_id/keys/:key_id
Authorization: Admin only
Response (200 OK):
{
"ok": true,
"message": "Key detached from bundle successfully"
}
Errors:
| Code | Description |
|---|---|
| 403 | Forbidden (not admin) |
| 404 | Bundle or key not found or key not attached |
Example:
curl -s -X DELETE "http://localhost:8888/api/v1/bundles/bundle-uuid/keys/key-uuid" \
-H "Cookie: __session=your_clerk_session" | jq
Reorder Key
Change the order of a key in the bundle.
Endpoint:
PUT /api/v1/bundles/:bundle_id/keys/:key_id/order
Authorization: Admin only
Request Body:
{ "order": 0 }
Response (200 OK):
{ "ok": true }
Errors:
| Code | Description |
|---|---|
| 400 | Invalid order value |
| 403 | Forbidden (not admin) |
| 404 | Bundle or key not found |
Example:
curl -s -X PUT "http://localhost:8888/api/v1/bundles/bundle-uuid/keys/key-uuid/order" \
-H "Content-Type: application/json" \
-H "Cookie: __session=your_clerk_session" \
-d '{"order":0}' | jq
Import Bundle (Legacy)
Import environment variables into a bundle (creates bundle items).
Note: This creates bundle items (deprecated). Use Keys API + Attach Key instead.
Endpoint:
POST /api/v1/bundles/:bundle_id/import
Authorization: Admin only
Request Body (ENV format):
{
"format": "env",
"payload": "DATABASE_URL=postgres://localhost:5432/db\nAPI_KEY=sk_live_abc123\nPORT=3000"
}
Request Body (JSON format):
{
"format": "json",
"data": {
"DATABASE_URL": "postgres://localhost:5432/db",
"API_KEY": "sk_live_abc123",
"PORT": "3000"
}
}
Response (200 OK):
{
"ok": true,
"imported": 3,
"updated": 0
}
Errors:
| Code | Description |
|---|---|
| 400 | Invalid format or payload |
| 403 | Forbidden (not admin) |
| 404 | Bundle not found |
Example:
curl -s -X POST "http://localhost:8888/api/v1/bundles/bundle-uuid/import" \
-H "Content-Type: application/json" \
-H "Cookie: __session=your_clerk_session" \
-d '{"format":"env","payload":"DATABASE_URL=postgres://localhost:5432/db\nAPI_KEY=sk_live_abc123"}' | jq
Export Bundle
Export bundle keys in ENV or JSON format.
Endpoint:
GET /api/v1/bundles/:bundle_id/export
Authorization: Member or Owner
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | env (default) or json |
reveal | string | No | 1 to reveal values (dev mode only) |
Behavior:
- By default secret values are masked as
*****. - Non-secret values are returned as stored (plain text).
reveal=1shows actual decrypted values, but only whenDEV_ALLOW_PLAINTEXT=1is set on the server (dev mode). The server returns403ifreveal=1is requested when plaintext reveal is not enabled.
Quoting and format details:
- ENV format follows
KEY=VALUElines with no surrounding quotes by default. Values that contain whitespace, quotes, or newlines are escaped according to POSIX shell rules where possible. When consuming witheval "$(envcat get ...)", the CLI output includes safe shell quoting (or use--write .envto write an on-disk file). - JSON format returns a simple key->value map (strings).
Response (200 OK) - ENV format:
DATABASE_URL=*****
API_KEY=*****
PORT=3000
Response (200 OK) - JSON format:
{
"DATABASE_URL": "*****",
"API_KEY": "*****",
"PORT": "3000"
}
Response (200 OK) - With reveal=1 (dev mode):
DATABASE_URL=postgres://localhost:5432/db
API_KEY=sk_live_abc123
PORT=3000
Errors:
| Code | Description |
|---|---|
| 403 | Forbidden (not a member) or reveal not allowed |
| 404 | Bundle not found |
Example:
# Export as ENV (masked)
curl -s "http://localhost:8888/api/v1/bundles/bundle-uuid/export?format=env" \
-H "Cookie: __session=your_clerk_session"
# Export as JSON
curl -s "http://localhost:8888/api/v1/bundles/bundle-uuid/export?format=json" \
-H "Cookie: __session=your_clerk_session"
# Export with revealed values (dev mode only)
curl -s "http://localhost:8888/api/v1/bundles/bundle-uuid/export?format=env&reveal=1" \
-H "Cookie: __session=your_clerk_session"
Legacy Endpoints (Bundle Items)
These endpoints manage bundle items (inline key-value pairs). They are deprecated in favor of the Keys API + key attachments.
Upsert Item (Deprecated)
Create or update a bundle item (inline key-value pair).
Endpoint:
POST /api/v1/bundles/:bundle_id/items
Authorization: Admin only
Request Body:
{
"key": "DATABASE_URL",
"value": "postgres://localhost:5432/db",
"is_secret": true
}
Response (200 OK):
{
"key": "DATABASE_URL",
"value": "*****",
"is_secret": true
}
Note: Use Keys API + Attach Key instead for reusable keys.
Delete Item (Deprecated)
Delete a bundle item.
Endpoint:
DELETE /api/v1/bundles/:bundle_id/items/:key
Authorization: Admin only
Response (200 OK):
{
"ok": true
}
Note: Use Detach Key instead.
Best Practices
Naming Conventions
Use hierarchical naming:
{environment}/{service}
dev/api
staging/frontend
prod/database
{team}/{project}
team-alpha/mobile
team-beta/analytics
Organizing Bundles
By environment:
dev/api,staging/api,prod/api
By service:
api/database,api/cache,api/auth
By purpose:
database/postgres,database/mongo,cache/redis
Security
Least privilege:
- Only attach keys that are needed
- Use specific bundles for specific purposes
- Don't create "all secrets" bundles
Regular audits:
- Review bundle contents regularly
- Remove unused bundles
- Rotate keys in the library (propagates to all bundles)
See Also
- Keys API - Manage reusable keys
- Requests API - Approval flow
- Authentication - Auth flow
- Errors - Error codes
Manage bundles - named collections of environment variables.
Overview
Bundles are the core organizational unit in env.cat. Each bundle:
- Has a unique name within a tenant (e.g.,
dev/api,prod/database) - Contains attached keys from the key library
- Can be requested via CLI for secret delivery
- Supports import/export in ENV and JSON formats
Base URL: https://env.cat/api/v1/bundles
Authentication: All endpoints require authentication (Clerk session → BFF signature)
Endpoints
Create Bundle
Create a new bundle.
Endpoint:
POST /api/v1/bundles
Authorization: Organization members
Request Body:
{
"name": "dev/api",
"description": "Development API secrets",
"tags": "development,api"
}
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Bundle name (unique per tenant) |
description | string | No | Human-readable description |
tags | string | No | Comma-separated tags |
Response (201 Created):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "dev/api",
"description": "Development API secrets",
"tags": "development,api",
"tenant_id": "tenant-uuid",
"created_at": "2025-10-22T10:00:00Z",
"updated_at": "2025-10-22T10:00:00Z"
}
Errors:
| Code | Description |
|---|---|
| 400 | Name required or invalid |
| 403 | Forbidden (not owner) |
| 409 | Bundle with this name already exists |
Example:
curl -X POST https://env.cat/api/v1/bundles \
-H "Content-Type: application/json" \
-H "Cookie: __session=your_clerk_session" \
-d '{
"name": "dev/api",
"description": "Development API secrets"
}'
List Bundles
List all bundles in current tenant.
Endpoint:
GET /api/v1/bundles
Authorization: Organization members (role-based access)
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | No | Search by name (case-insensitive) |
tag | string | No | Filter by tag |
Response (200 OK):
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "dev/api",
"description": "Development API secrets",
"tags": "development,api",
"tenant_id": "tenant-uuid",
"created_at": "2025-10-22T10:00:00Z",
"updated_at": "2025-10-22T10:00:00Z"
},
{
"id": "another-uuid",
"name": "prod/database",
"description": "Production database credentials",
"tags": "production,database",
"tenant_id": "tenant-uuid",
"created_at": "2025-10-22T11:00:00Z",
"updated_at": "2025-10-22T11:00:00Z"
}
]
Example:
# List all bundles
curl https://env.cat/api/v1/bundles \
-H "Cookie: __session=your_clerk_session"
# Search bundles
curl "https://env.cat/api/v1/bundles?query=dev" \
-H "Cookie: __session=your_clerk_session"
# Filter by tag
curl "https://env.cat/api/v1/bundles?tag=production" \
-H "Cookie: __session=your_clerk_session"
Get Bundle
Get bundle details with attached keys (values masked).
Endpoint:
GET /api/v1/bundles/:bundle_id
Authorization: Member or Owner
Response (200 OK):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "dev/api",
"description": "Development API secrets",
"tags": "development,api",
"tenant_id": "tenant-uuid",
"created_at": "2025-10-22T10:00:00Z",
"updated_at": "2025-10-22T10:00:00Z",
"items": [
{
"key": "DATABASE_URL",
"value": "*****",
"is_secret": true
},
{
"key": "API_KEY",
"value": "*****",
"is_secret": true
},
{
"key": "PORT",
"value": "3000",
"is_secret": false
}
]
}
Errors:
| Code | Description |
|---|---|
| 403 | Forbidden (not member) |
| 404 | Bundle not found |
Example:
curl https://env.cat/api/v1/bundles/550e8400-e29b-41d4-a716-446655440000 \
-H "Cookie: __session=your_clerk_session"
Delete Bundle
Delete a bundle (keys remain in library).
Endpoint:
DELETE /api/v1/bundles/:bundle_id
Authorization: Admin only
Response (200 OK):
{
"ok": true
}
Errors:
| Code | Description |
|---|---|
| 403 | Forbidden (not owner) |
| 404 | Bundle not found |
Example:
curl -X DELETE https://env.cat/api/v1/bundles/550e8400-e29b-41d4-a716-446655440000 \
-H "Cookie: __session=your_clerk_session"
List Bundle Keys
List all keys attached to a bundle (with masked values).
Endpoint:
GET /api/v1/bundles/:bundle_id/keys
Authorization: Member or Owner
Response (200 OK):
[
{
"id": "key-uuid-1",
"name": "DATABASE_URL",
"value": "*****",
"is_secret": true,
"order": 0
},
{
"id": "key-uuid-2",
"name": "API_KEY",
"value": "*****",
"is_secret": true,
"order": 1
},
{
"id": "key-uuid-3",
"name": "PORT",
"value": "3000",
"is_secret": false,
"order": 2
}
]
Errors:
| Code | Description |
|---|---|
| 403 | Forbidden (not member) |
| 404 | Bundle not found |
Example:
curl https://env.cat/api/v1/bundles/550e8400-e29b-41d4-a716-446655440000/keys \
-H "Cookie: __session=your_clerk_session"
Attach Key to Bundle
Attach an existing key from the library to a bundle.
Endpoint:
POST /api/v1/bundles/:bundle_id/keys/:key_id
Authorization: Admin only
Response (200 OK):
{
"ok": true
}
Errors:
| Code | Description |
|---|---|
| 403 | Forbidden (not owner) |
| 404 | Bundle or key not found |
| 409 | Key already attached to bundle |
Example:
curl -X POST https://env.cat/api/v1/bundles/bundle-uuid/keys/key-uuid \
-H "Cookie: __session=your_clerk_session"
Detach Key from Bundle
Remove a key from a bundle (key remains in library).
Endpoint:
DELETE /api/v1/bundles/:bundle_id/keys/:key_id
Authorization: Admin only
Response (200 OK):
{
"ok": true
}
Errors:
| Code | Description |
|---|---|
| 403 | Forbidden (not owner) |
| 404 | Bundle or key not found |
Example:
curl -X DELETE https://env.cat/api/v1/bundles/bundle-uuid/keys/key-uuid \
-H "Cookie: __session=your_clerk_session"
Reorder Key
Change the order of a key in the bundle.
Endpoint:
PUT /api/v1/bundles/:bundle_id/keys/:key_id/order
Authorization: Admin only
Request Body:
{
"order": 0
}
Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
order | integer | Yes | New position (0-based index) |
Response (200 OK):
{
"ok": true
}
Errors:
| Code | Description |
|---|---|
| 400 | Invalid order value |
| 403 | Forbidden (not owner) |
| 404 | Bundle or key not found |
Example:
curl -X PUT https://env.cat/api/v1/bundles/bundle-uuid/keys/key-uuid/order \
-H "Content-Type: application/json" \
-H "Cookie: __session=your_clerk_session" \
-d '{"order": 0}'
Import Bundle (Legacy)
Import environment variables into a bundle (creates bundle items).
Note: This creates bundle items (deprecated). Use Keys API + Attach Key instead.
Endpoint:
POST /api/v1/bundles/:bundle_id/import
Authorization: Admin only
Request Body (ENV format):
{
"format": "env",
"payload": "DATABASE_URL=postgres://localhost:5432/db\nAPI_KEY=sk_live_abc123\nPORT=3000"
}
Request Body (JSON format):
{
"format": "json",
"data": {
"DATABASE_URL": "postgres://localhost:5432/db",
"API_KEY": "sk_live_abc123",
"PORT": "3000"
}
}
Response (200 OK):
{
"ok": true,
"imported": 3,
"updated": 0
}
Errors:
| Code | Description |
|---|---|
| 400 | Invalid format or payload |
| 403 | Forbidden (not owner) |
| 404 | Bundle not found |
Example:
curl -X POST https://env.cat/api/v1/bundles/bundle-uuid/import \
-H "Content-Type: application/json" \
-H "Cookie: __session=your_clerk_session" \
-d '{
"format": "env",
"payload": "DATABASE_URL=postgres://localhost:5432/db\nAPI_KEY=sk_live_abc123"
}'
Export Bundle
Export bundle keys in ENV or JSON format.
Endpoint:
GET /api/v1/bundles/:bundle_id/export
Authorization: Member or Owner
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | env (default) or json |
reveal | string | No | 1 to reveal values (dev mode only) |
Response (200 OK) - ENV format:
DATABASE_URL=*****
API_KEY=*****
PORT=3000
Response (200 OK) - JSON format:
{
"DATABASE_URL": "*****",
"API_KEY": "*****",
"PORT": "3000"
}
Response (200 OK) - With reveal=1 (dev mode):
DATABASE_URL=postgres://localhost:5432/db
API_KEY=sk_live_abc123
PORT=3000
Errors:
| Code | Description |
|---|---|
| 403 | Forbidden (not member) or reveal not allowed |
| 404 | Bundle not found |
Example:
# Export as ENV (masked)
curl "https://env.cat/api/v1/bundles/bundle-uuid/export?format=env" \
-H "Cookie: __session=your_clerk_session"
# Export as JSON
curl "https://env.cat/api/v1/bundles/bundle-uuid/export?format=json" \
-H "Cookie: __session=your_clerk_session"
# Export with revealed values (dev mode only)
curl "https://env.cat/api/v1/bundles/bundle-uuid/export?format=env&reveal=1" \
-H "Cookie: __session=your_clerk_session"
Legacy Endpoints (Bundle Items)
These endpoints manage bundle items (inline key-value pairs). They are deprecated in favor of the Keys API + key attachments.
Upsert Item (Deprecated)
Create or update a bundle item (inline key-value pair).
Endpoint:
POST /api/v1/bundles/:bundle_id/items
Authorization: Admin only
Request Body:
{
"key": "DATABASE_URL",
"value": "postgres://localhost:5432/db",
"is_secret": true
}
Response (200 OK):
{
"key": "DATABASE_URL",
"value": "*****",
"is_secret": true
}
Note: Use Keys API + Attach Key instead for reusable keys.
Delete Item (Deprecated)
Delete a bundle item.
Endpoint:
DELETE /api/v1/bundles/:bundle_id/items/:key
Authorization: Admin only
Response (200 OK):
{
"ok": true
}
Note: Use Detach Key instead.
Best Practices
Naming Conventions
Use hierarchical naming:
{environment}/{service}
dev/api
staging/frontend
prod/database
{team}/{project}
team-alpha/mobile
team-beta/analytics
Organizing Bundles
By environment:
dev/api,staging/api,prod/api
By service:
api/database,api/cache,api/auth
By purpose:
database/postgres,database/mongo,cache/redis
Security
Least privilege:
- Only attach keys that are needed
- Use specific bundles for specific purposes
- Don't create "all secrets" bundles
Regular audits:
- Review bundle contents regularly
- Remove unused bundles
- Rotate keys in the library (propagates to all bundles)
See Also
- Keys API - Manage reusable keys
- Requests API - Approval flow
- Authentication - Auth flow
- Errors - Error codes