Skip to main content

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:

FieldTypeRequiredDescription
namestringYesBundle name (unique per organization)
descriptionstringNoHuman-readable description
tagsstringNoComma-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:

CodeDescription
400Name required or invalid
403Forbidden (not a member)
409Bundle 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:

ParameterTypeRequiredDescription
querystringNoSearch by name (case-insensitive)
tagstringNoFilter 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:

CodeDescription
403Forbidden (not a member)
404Bundle 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:

CodeDescription
403Forbidden (not admin)
404Bundle 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:

CodeDescription
403Forbidden (not admin)
404Bundle or key not found
409Key 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:

CodeDescription
403Forbidden (not admin)
404Bundle 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:

CodeDescription
400Invalid order value
403Forbidden (not admin)
404Bundle 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:

CodeDescription
400Invalid format or payload
403Forbidden (not admin)
404Bundle 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:

ParameterTypeRequiredDescription
formatstringNoenv (default) or json
revealstringNo1 to reveal values (dev mode only)

Behavior:

  • By default secret values are masked as *****.
  • Non-secret values are returned as stored (plain text).
  • reveal=1 shows actual decrypted values, but only when DEV_ALLOW_PLAINTEXT=1 is set on the server (dev mode). The server returns 403 if reveal=1 is requested when plaintext reveal is not enabled.

Quoting and format details:

  • ENV format follows KEY=VALUE lines with no surrounding quotes by default. Values that contain whitespace, quotes, or newlines are escaped according to POSIX shell rules where possible. When consuming with eval "$(envcat get ...)", the CLI output includes safe shell quoting (or use --write .env to 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:

CodeDescription
403Forbidden (not a member) or reveal not allowed
404Bundle 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

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:

FieldTypeRequiredDescription
namestringYesBundle name (unique per tenant)
descriptionstringNoHuman-readable description
tagsstringNoComma-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:

CodeDescription
400Name required or invalid
403Forbidden (not owner)
409Bundle 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:

ParameterTypeRequiredDescription
querystringNoSearch by name (case-insensitive)
tagstringNoFilter 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:

CodeDescription
403Forbidden (not member)
404Bundle 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:

CodeDescription
403Forbidden (not owner)
404Bundle 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:

CodeDescription
403Forbidden (not member)
404Bundle 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:

CodeDescription
403Forbidden (not owner)
404Bundle or key not found
409Key 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:

CodeDescription
403Forbidden (not owner)
404Bundle 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:

FieldTypeRequiredDescription
orderintegerYesNew position (0-based index)

Response (200 OK):

{
"ok": true
}

Errors:

CodeDescription
400Invalid order value
403Forbidden (not owner)
404Bundle 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:

CodeDescription
400Invalid format or payload
403Forbidden (not owner)
404Bundle 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:

ParameterTypeRequiredDescription
formatstringNoenv (default) or json
revealstringNo1 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:

CodeDescription
403Forbidden (not member) or reveal not allowed
404Bundle 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