Skip to main content

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

CodeNameDescription
200OKRequest succeeded
201CreatedResource created successfully

4xx Client Errors

CodeNameDescriptionCommon Causes
400Bad RequestInvalid request body or parametersMissing required fields, invalid format
401UnauthorizedAuthentication required or invalidInvalid/expired session, missing BFF signature
403ForbiddenInsufficient permissionsNot owner when owner required, not member
404Not FoundResource doesn't existInvalid ID, expired request, deleted resource
409ConflictResource already existsDuplicate name, key already attached
429Too Many RequestsRate limit exceededToo many login attempts, request throttling

5xx Server Errors

CodeNameDescription
500Internal Server ErrorServer-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:

EndpointLimitWindow
POST /api/v1/auth/login5 requests15 minutes
POST /api/v1/auth/setup3 requests1 hour
POST /api/v1/requests10 requests1 minute
GET /api/v1/requests/:id/wait100 requests1 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