Skip to main content

Overview

CEMS uses Bearer token authentication for all API requests. Each user is issued a unique API key that must be included in the Authorization header.

Authentication Methods

Bearer Token

All requests must include an Authorization header with a Bearer token:
Authorization: Bearer <api_key>
Example:
curl -X GET https://cems.example.com/api/memory/status \
  -H "Authorization: Bearer cems_ak_abc123def456..."

API Key Format

API keys follow this format:
cems_ak_<48_hex_characters>
  • Prefix: cems_ak_ (user API key)
  • Random part: 48 hexadecimal characters (24 bytes)
  • Total length: 56 characters
Example: cems_ak_1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x

API Key Lookup Process

When you make a request, CEMS validates your API key through the following process:
  1. Extract token from Authorization: Bearer <token> header
  2. Calculate key prefix (first 8 characters of random part)
  3. Look up user in PostgreSQL by key prefix
  4. Verify hash using bcrypt comparison
  5. Check user status (must be active)
  6. Set user context for request scope
From server.py:104-149:
# Validate user API key from database
if not auth_header.startswith("Bearer "):
    return JSONResponse(
        {"error": "Authorization: Bearer <api_key> header required"},
        status_code=401,
    )
provided_key = auth_header[7:]

# Look up user by API key
user = service.get_user_by_api_key(provided_key)

if not user:
    return JSONResponse(
        {"error": "Invalid API key"},
        status_code=401,
    )

if not user.is_active:
    return JSONResponse(
        {"error": "User account is deactivated"},
        status_code=403,
    )

Security Features

Bcrypt Hashing

API keys are hashed using bcrypt before storage. The full key is only shown once during generation. From auth.py:36-45:
def hash_api_key(api_key: str) -> str:
    """Hash an API key for storage.
    
    Returns:
        bcrypt hash of the key.
    """
    return bcrypt.hashpw(api_key.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")

Key Prefix Indexing

Only the first 8 characters after the prefix are stored for lookup, enabling fast database queries while maintaining security. From auth.py:65-80:
def get_key_prefix(api_key: str) -> str:
    """Extract the prefix from an API key for lookup.
    
    Returns:
        Key prefix for database lookup (e.g., "cems_ak_abc12345")
    """
    parts = api_key.split("_")
    if len(parts) >= 3:
        prefix = "_".join(parts[:2])
        random_part = parts[2] if len(parts) == 3 else "_".join(parts[2:])
        return f"{prefix}_{random_part[:8]}"
    return api_key[:16]  # Fallback

Team Context

Optionally, you can specify a team context using the X-Team-ID header:
curl -X POST https://cems.example.com/api/memory/search \
  -H "Authorization: Bearer cems_ak_abc123..." \
  -H "X-Team-ID: team-uuid" \
  -H "Content-Type: application/json" \
  -d '{"query": "API conventions"}'
This allows you to scope operations to a specific team when you belong to multiple teams.

Credential Storage

The CEMS CLI stores credentials in ~/.cems/credentials (chmod 600) and checks them in order:
  1. CLI flags: --api-url, --api-key
  2. Environment variables: CEMS_API_URL, CEMS_API_KEY
  3. Credentials file: ~/.cems/credentials
Example credentials file:
CEMS_API_URL=https://cems.example.com
CEMS_API_KEY=cems_ak_1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x

Obtaining an API Key

API keys are generated by your CEMS administrator using the Admin API:
curl -X POST http://localhost:8765/admin/users \
  -H "Authorization: Bearer $CEMS_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"username": "yourname"}'
Response:
{
  "id": "user-uuid",
  "username": "yourname",
  "api_key": "cems_ak_1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x",
  "is_active": true,
  "created_at": "2026-02-28T10:30:00Z"
}
Important: Save this API key immediately. It cannot be retrieved later.

Resetting an API Key

If your API key is compromised, ask your administrator to reset it:
curl -X POST http://localhost:8765/admin/users/{user_id}/reset-key \
  -H "Authorization: Bearer $CEMS_ADMIN_KEY"
This invalidates the old key and generates a new one.

Unauthenticated Endpoints

These endpoints do not require authentication:
  • GET /health - Health check for Docker/monitoring
  • GET /api/config/setup - Setup discovery for client configuration
  • GET /ping - Simple ping endpoint
All other endpoints require a valid Bearer token.

Error Responses

401 Unauthorized

Missing or invalid API key:
{
  "error": "Authorization: Bearer <api_key> header required"
}
{
  "error": "Invalid API key"
}

403 Forbidden

User account is deactivated:
{
  "error": "User account is deactivated"
}

503 Service Unavailable

Database connection issues:
{
  "error": "Database not initialized"
}
{
  "error": "Database unavailable"
}

Build docs developers (and LLMs) love