Skip to main content

API Reference


title: API Reference audience: Developer difficulty: Intermediate estimated_read_time: 10 min prerequisites:

  • Basic REST API knowledge
  • EnvCat running locally or access to hosted service related_pages:
  • ../cli-reference/envcat-get.md
  • ../architecture/index.md

Complete API documentation for EnvCat backend endpoints.

Base URL

Local Development:

http://localhost:8888

All API endpoints are accessed through the Next.js BFF (Backend-for-Frontend) layer, which proxies requests to the Flask API on the internal Docker network.

Important: The Flask API runs on http://api:8080 internally but is not exposed to the host. All API calls should go through the Web UI at http://localhost:8888.

Authentication

Current (M7): No authentication required (local-first, single-user).

Future (M9+): Bearer token authentication will be added for multi-user deployments.

# Future: Include auth header
curl -H "Authorization: Bearer YOUR_TOKEN" \
http://localhost:8888/api/v1/endpoint

API Design Principles

EnvCat API follows these principles:

REST-ful

  • Resources: Bundles, bundle items, requests
  • HTTP Methods: GET (read), POST (create), PUT (update), DELETE (remove)
  • Status Codes: Semantic HTTP status codes

JSON

All requests and responses use JSON.

Request:

curl -X POST http://localhost:8888/api/v1/bundles \
-H "Content-Type: application/json" \
-d '{"name":"dev/example"}'

Response:

{
"id": "abc123",
"name": "dev/example",
"created_at": "2025-10-18T10:00:00Z"
}

Idempotency

Safe methods (GET, PUT, DELETE) are idempotent.

Error Handling

Consistent error responses:

{
"error": "Resource not found",
"code": "NOT_FOUND",
"details": {}
}

Response Formats

Success Response

Status: 200 OK, 201 Created, 204 No Content

{
"id": "resource-id",
"field": "value",
"created_at": "2025-10-18T10:00:00Z"
}

Error Response

Status: 400, 404, 500, etc.

{
"error": "Error message",
"code": "ERROR_CODE",
"details": {
"field": "Additional context"
}
}

Endpoint Categories

Health

Monitor service health.

  • GET /healthz - Health check

Requests (Device Approval Flow)

Manage approval requests for CLI.

  • POST /api/v1/requests - Create request
  • GET /api/v1/requests/:id/context - Get request context
  • GET /api/v1/requests/:id/wait - Poll for approval (long-poll)
  • PUT /api/v1/requests/:id/payload - Upload ciphertext (dev mode)
  • POST /api/v1/requests/:id/encrypt-from-bundle - Server-side re-encryption (recommended)

Documentation coming in M1-M3

Bundles

Manage bundles (named sets of environment variables).

  • POST /api/v1/bundles - Create bundle
  • GET /api/v1/bundles - List bundles
  • GET /api/v1/bundles/:id - Get bundle details
  • DELETE /api/v1/bundles/:id - Delete bundle

Documentation coming in M1-M3

Bundle Items

Manage individual environment variables within bundles.

  • POST /api/v1/bundles/:id/items - Upsert item
  • DELETE /api/v1/bundles/:id/items/:key - Delete item

Documentation coming in M1-M3

Import/Export

Import and export bundle data.

  • POST /api/v1/bundles/:id/import - Import from .env or JSON
  • GET /api/v1/bundles/:id/export - Export to .env or JSON

Documentation coming in M1-M3

Rate Limiting

Current (M7): Basic rate limiting implemented.

  • Requests endpoint: Min 3-second polling interval (enforced with 429 Too Many Requests)
  • Other endpoints: No limits (local-first, single-user)

Future (M9+): Configurable rate limits for multi-user deployments.

Common Headers

Request Headers

HeaderRequiredDescription
Content-TypeYes (for POST/PUT)application/json
AuthorizationNo (future: M9+)Bearer TOKEN

Response Headers

HeaderDescription
Content-TypeAlways application/json
X-Request-IDUnique request identifier (future)

HTTP Status Codes

CodeMeaningUsage
200OKSuccessful GET, PUT
201CreatedSuccessful POST (resource created)
204No ContentSuccessful DELETE
400Bad RequestInvalid input, validation error
404Not FoundResource not found, expired request
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer error

Pagination

Current (M7): No pagination (bundles typically < 100).

Future: Pagination for large datasets:

GET /api/v1/bundles?limit=50&offset=0

Response:

{
"data": [...],
"pagination": {
"limit": 50,
"offset": 0,
"total": 150,
"next": "/api/v1/bundles?limit=50&offset=50"
}
}

Bundles endpoint supports query parameters:

# Search by name
GET /api/v1/bundles?query=myproject

# Filter by tag
GET /api/v1/bundles?tag=dev

Security

Encryption

  • At-rest: Bundle values encrypted in SQLite (libsodium secretbox)
  • In-transit: HTTP (local dev), HTTPS (production)
  • End-to-end: Requests encrypted to CLI public key (sealed boxes)

No Plaintext Storage

Server never stores plaintext secrets:

  • Bundle values encrypted at rest
  • Ephemeral requests store only ciphertext in Redis
  • Decryption happens in-memory only

TTL & Expiration

  • Requests: 5-minute TTL (configurable via REQUEST_TTL)
  • Single-use: Requests deleted after retrieval
  • Redis TTL: Automatic expiration

Input Validation

All inputs validated:

  • Bundle names: alphanumeric + /, -, _
  • Keys: alphanumeric + _
  • Values: arbitrary strings (encrypted)

Examples

Create Bundle and Add Items

# Create bundle
RESPONSE=$(curl -s -X POST http://localhost:8888/api/v1/bundles \
-H "Content-Type: application/json" \
-d '{"name":"dev/example","description":"Development secrets"}')

BUNDLE_ID=$(echo $RESPONSE | jq -r '.id')

# Add item
curl -X POST "http://localhost:8888/api/v1/bundles/$BUNDLE_ID/items" \
-H "Content-Type: application/json" \
-d '{
"key": "API_KEY",
"value": "sk_test_123",
"is_secret": true
}'

Device Approval Flow

# 1. Create request (CLI does this)
RESPONSE=$(curl -s -X POST http://localhost:8888/api/v1/requests \
-H "Content-Type: application/json" \
-d '{
"client_pubkey": "BASE64_PUBKEY",
"bundle_id": "BUNDLE_ID"
}')

REQUEST_ID=$(echo $RESPONSE | jq -r '.id')

# 2. User approves in browser at /approve/$REQUEST_ID

# 3. Poll for result (CLI does this)
curl "http://localhost:8888/api/v1/requests/$REQUEST_ID/wait?interval=3"

Development

Local Testing

Using cURL:

# Health check
curl http://localhost:8888/healthz

# List bundles
curl http://localhost:8888/api/v1/bundles

Using httpie:

# Install httpie
pip install httpie

# Create bundle
http POST localhost:8888/api/v1/bundles \
name=dev/test \
description="Test bundle"

Using Postman:

  1. Import OpenAPI spec (future: auto-generated)
  2. Set base URL: http://localhost:8888
  3. Send requests

API Client Libraries

Python (requests):

import requests

response = requests.get('http://localhost:8888/api/v1/bundles')
bundles = response.json()

JavaScript (fetch):

const response = await fetch('http://localhost:8888/api/v1/bundles');
const bundles = await response.json();

Go:

resp, err := http.Get("http://localhost:8888/api/v1/bundles")

Future Enhancements

Planned for M8-M12:

  • M8: Secret connectors (AWS, GCP, Azure)
  • M9: Authentication & RBAC
  • M10: Audit logs API
  • M11: Webhooks for bundle changes
  • M12: Secret scanning API

OpenAPI Specification

Future: Auto-generated OpenAPI 3.0 spec will be available at:

http://localhost:8888/api/v1/openapi.json

This can be imported into Postman, Swagger UI, or other API tools.

Troubleshooting

Connection Refused

Issue: curl: (7) Failed to connect to localhost port 8888

Solution: Make sure services are running:

docker compose ps
docker compose up -d

404 Not Found

Issue: {"error": "Not found"}

Solution: Check endpoint path. All API endpoints start with /api/v1/.

500 Internal Server Error

Issue: Server error.

Solution: Check API logs:

docker compose logs api

CORS Errors (Browser)

Issue: CORS policy blocking request from browser.

Solution: Web UI should proxy requests through Next.js BFF. Don't call Flask API directly from browser.


Detailed endpoint documentation coming in M1-M3 implementation.

For now, refer to:

  • AGENTS.md - See root AGENTS.md for API specification
  • docs/SERVICES.md - See internal docs/SERVICES.md for service documentation
  • CLI Reference - CLI commands that use these endpoints