Error Codes
title: API Errors audience: Developer difficulty: Beginner estimated_read_time: 3 min prerequisites:
- Basic HTTP knowledge related_pages:
- ../api-reference/index.md
- ../troubleshooting/index.md
HTTP status codes and error responses used by the env.cat API.
Overview
All API errors return JSON responses with consistent structure:
{
"error": "Human-readable error message"
}
HTTP Status Codes
2xx Success
| Code | Name | Description |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | Created | Resource created successfully |
4xx Client Errors
| Code | Name | Description | Common Causes |
|---|---|---|---|
| 400 | Bad Request | Invalid request body or parameters | Missing required fields, invalid format |
| 401 | Unauthorized | Authentication required or invalid | Invalid/expired session, missing BFF signature |
| 403 | Forbidden | Insufficient permissions | Not owner when owner required, not member |
| 404 | Not Found | Resource doesn't exist | Invalid ID, expired request, deleted resource |
| 409 | Conflict | Resource already exists | Duplicate name, key already attached |
| 429 | Too Many Requests | Rate limit exceeded | Too many login attempts, request throttling |
5xx Server Errors
| Code | Name | Description |
|---|---|---|
| 500 | Internal Server Error | Server-side error |
Error Responses by Endpoint
Authentication Errors
401 Unauthorized - Invalid session:
{
"error": "Unauthorized"
}
401 Unauthorized - Invalid BFF signature:
{
"error": "Unauthorized"
}
403 Forbidden - Insufficient permissions:
{
"error": "Forbidden: owner role required"
}
403 Forbidden - Account locked:
{
"error": "Account locked until 2025-10-23T15:30:00Z"
}
Bundles Errors
400 Bad Request - Missing name:
{
"error": "name required"
}
404 Not Found - Bundle not found:
{
"error": "bundle not found"
}
409 Conflict - Duplicate name:
{
"error": "bundle with this name already exists"
}
Keys Errors
400 Bad Request - Missing required field:
{
"error": "Key name is required"
}
400 Bad Request - Missing value:
{
"error": "Key value is required"
}
404 Not Found - Key not found:
{
"error": "Key not found"
}
409 Conflict - Duplicate key name:
{
"error": "Key with this name already exists"
}
409 Conflict - Key already attached:
{
"error": "Key already attached to bundle"
}
Requests Errors
404 Not Found - Request expired:
{
"error": "request not found or expired"
}
400 Bad Request - Invalid bundle:
{
"error": "bundle not found"
}
Users Errors
400 Bad Request - Missing clerk_user_id:
{
"error": "clerk_user_id required"
}
400 Bad Request - Invalid format:
{
"error": "Invalid clerk_user_id format"
}
500 Internal Server Error - Failed to create:
{
"error": "Failed to create user: <details>"
}
Rate Limiting
429 Too Many Requests:
{
"error": "Too many requests"
}
Rate Limits:
| Endpoint | Limit | Window |
|---|---|---|
POST /api/v1/auth/login | 5 requests | 15 minutes |
POST /api/v1/auth/setup | 3 requests | 1 hour |
POST /api/v1/requests | 10 requests | 1 minute |
GET /api/v1/requests/:id/wait | 100 requests | 1 minute |
Headers (when rate limited):
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1634567890
Retry-After: 900
Common Error Scenarios
Scenario: Unauthorized Bundle Access
Request:
curl https://env.cat/api/v1/bundles/other-tenant-bundle-uuid \
-H "Cookie: __session=your_session"
Response (404):
{
"error": "bundle not found"
}
Why: Bundles are tenant-scoped. You can only access bundles in your tenant.
Scenario: Create Duplicate Bundle
Request:
curl -X POST https://env.cat/api/v1/bundles \
-H "Content-Type: application/json" \
-H "Cookie: __session=your_session" \
-d '{"name": "dev/api"}'
Response (409):
{
"error": "bundle with this name already exists"
}
Solution: Use a different name or delete the existing bundle.
Scenario: Expired Approval Request
Request:
curl https://env.cat/api/v1/requests/old-request-id/wait
Response (404):
{
"error": "request not found or expired"
}
Why: Requests expire after 5 minutes. Create a new request with envcat get.
Scenario: Member Tries to Create Bundle
Request:
curl -X POST https://env.cat/api/v1/bundles \
-H "Content-Type: application/json" \
-H "Cookie: __session=member_session" \
-d '{"name": "new/bundle"}'
Response (403):
{
"error": "Forbidden: owner role required"
}
Why: Only owners can create bundles. Members can only read.
Scenario: Invalid Clerk User ID Format
Request:
curl -X POST https://env.cat/api/v1/users/ensure \
-H "Content-Type: application/json" \
-d '{
"clerk_user_id": "invalid_format",
"email": "user@example.com"
}'
Response (400):
{
"error": "Invalid clerk_user_id format"
}
Why: Clerk user IDs must start with user_.
Error Handling Best Practices
Client-Side
Check status codes:
const response = await fetch('/api/v1/bundles', {
credentials: 'include'
});
if (response.status === 401) {
// Redirect to login
window.location.href = '/sign-in';
} else if (response.status === 403) {
// Show "insufficient permissions" message
alert('You do not have permission to perform this action');
} else if (response.status === 404) {
// Resource not found
alert('Resource not found');
} else if (response.status >= 500) {
// Server error
alert('Server error. Please try again later.');
}
Display error messages:
if (!response.ok) {
const error = await response.json();
alert(error.error || 'An error occurred');
}
CLI
Retry on network errors:
max_retries=3
retry_count=0
until envcat get --bundle dev/api; do
retry_count=$((retry_count + 1))
if [ $retry_count -ge $max_retries ]; then
echo "Failed after $max_retries attempts"
exit 1
fi
echo "Retry $retry_count/$max_retries..."
sleep 5
done
Check exit codes:
if ! envcat get --bundle dev/api --file .env; then
echo "Failed to get secrets (exit code: $?)"
exit 1
fi
Server-Side
Log errors (without sensitive data):
import logging
try:
bundle = service.get_bundle(bundle_id)
except Exception as e:
logging.error(f"Failed to get bundle {bundle_id}: {str(e)}")
return jsonify({"error": "internal server error"}), 500
Never log secrets:
# BAD: Logs secret value
logging.info(f"Created key: {key.name} = {key.value}")
# GOOD: Only logs key name
logging.info(f"Created key: {key.name}")
Debugging
Enable Verbose Logging
Flask API:
# Set log level in environment
export LOG_LEVEL=DEBUG
# Start API
docker compose up api
CLI:
# Future feature: verbose flag
envcat get --bundle dev/api --verbose
Check API Logs
# View Flask API logs
docker compose logs -f api
# View last 100 lines
docker compose logs --tail=100 api
Test API Directly
# Test public endpoint
curl -v https://env.cat/api/v1/requests \
-H "Content-Type: application/json" \
-d '{"client_pubkey":"test"}'
# Test authenticated endpoint
curl -v https://env.cat/api/v1/bundles \
-H "Cookie: __session=your_session"
See Also
- Authentication - Auth errors
- Bundles API - Bundle-specific errors
- Keys API - Key-specific errors
- Requests API - Approval flow errors
- Users API - User provisioning errors