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 requestGET /api/v1/requests/:id/context- Get request contextGET /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 bundleGET /api/v1/bundles- List bundlesGET /api/v1/bundles/:id- Get bundle detailsDELETE /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 itemDELETE /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 JSONGET /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
| Header | Required | Description |
|---|---|---|
Content-Type | Yes (for POST/PUT) | application/json |
Authorization | No (future: M9+) | Bearer TOKEN |
Response Headers
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-Request-ID | Unique request identifier (future) |
HTTP Status Codes
| Code | Meaning | Usage |
|---|---|---|
200 | OK | Successful GET, PUT |
201 | Created | Successful POST (resource created) |
204 | No Content | Successful DELETE |
400 | Bad Request | Invalid input, validation error |
404 | Not Found | Resource not found, expired request |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Server 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"
}
}
Filtering & Search
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:
- Import OpenAPI spec (future: auto-generated)
- Set base URL:
http://localhost:8888 - 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