Skip to main content

Overview

Manifest supports three authentication methods, implemented through a guard chain that runs on every request:
  1. Session Authentication - Cookie-based sessions via Better Auth (for web dashboard)
  2. API Key Authentication - X-API-Key header for programmatic access
  3. Bearer Token Authentication - Agent API keys for OTLP ingestion and LLM proxy
The API evaluates authentication in this order:
  1. SessionGuard - Validates Better Auth session cookies
  2. ApiKeyGuard - Falls through if no session, checks X-API-Key header
  3. OtlpAuthGuard - Used only for /otlp/* and /v1/chat/completions endpoints

Session Authentication

Session authentication uses Better Auth with cookie-based sessions. This is the primary method for the web dashboard.

How It Works

1

Login

User authenticates via email/password or OAuth providers (Google, GitHub, Discord)
2

Session Cookie

Better Auth creates a secure session cookie: better-auth.session_token
3

Request Validation

The SessionGuard validates the cookie on each request via auth.api.getSession()
4

User Context

On success, request.user and request.session are populated with user data

Session Endpoints

GET /api/auth/get-session
Returns the current session and user information
POST /api/auth/sign-in/email
Sign in with email and password
POST /api/auth/sign-up/email
Register a new account with email and password
POST /api/auth/sign-out
Sign out and invalidate the session
GET /api/auth/sign-in/{provider}
Initiate OAuth flow (google, github, discord)

Session Response

{
  "session": {
    "id": "session-abc123",
    "userId": "user-456",
    "token": "session-token-xyz",
    "expiresAt": "2024-12-31T23:59:59Z",
    "createdAt": "2024-03-15T10:30:00Z",
    "updatedAt": "2024-03-15T10:30:00Z"
  },
  "user": {
    "id": "user-456",
    "name": "John Doe",
    "email": "[email protected]",
    "emailVerified": true,
    "image": null,
    "createdAt": "2024-03-01T08:00:00Z",
    "updatedAt": "2024-03-15T10:30:00Z"
  }
}

Example Request

cURL
curl -X GET 'https://your-domain.com/api/v1/overview?range=24h' \
  -H 'Cookie: better-auth.session_token=your-session-token' \
  -H 'Content-Type: application/json'
Fetch (Browser)
// Cookies are automatically included with credentials: 'include'
const response = await fetch('https://your-domain.com/api/v1/overview?range=24h', {
  method: 'GET',
  credentials: 'include', // Important: sends cookies
  headers: {
    'Content-Type': 'application/json'
  }
});

const data = await response.json();

API Key Authentication

API key authentication provides programmatic access to analytics and management endpoints using the X-API-Key header.

Types of API Keys

Manifest supports two types of API keys:

Database API Keys

Multi-tenant keys stored in the api_keys table. Each key is associated with a user_id for data isolation. Keys are hashed using scrypt KDF and compared with timing-safe equality.

Environment API Key

Single shared key from the API_KEY environment variable. Used for simple single-tenant setups. Falls back after database lookup fails.

How It Works

1

Header Check

The ApiKeyGuard checks for the X-API-Key header
2

Database Lookup

Key is hashed and compared against api_keys.key_hash using timing-safe comparison
3

Fallback

If not found, compares against API_KEY environment variable
4

Context

On success, request.apiKeyUserId is set to the associated user ID for tenant filtering
5

Usage Tracking

last_used_at timestamp is updated asynchronously

Creating API Keys

API keys are created through the dashboard or by setting the API_KEY environment variable.
# In packages/backend/.env
API_KEY=your-secret-api-key-here

Example Request

cURL
curl -X GET 'https://your-domain.com/api/v1/agents' \
  -H 'X-API-Key: your-api-key' \
  -H 'Content-Type: application/json'
Fetch
const response = await fetch('https://your-domain.com/api/v1/agents', {
  method: 'GET',
  headers: {
    'X-API-Key': 'your-api-key',
    'Content-Type': 'application/json'
  }
});

const agents = await response.json();
Python
import requests

url = 'https://your-domain.com/api/v1/agents'
headers = {
    'X-API-Key': 'your-api-key',
    'Content-Type': 'application/json'
}

response = requests.get(url, headers=headers)
agents = response.json()

Security Features

Timing-Safe Comparison
security
API keys are compared using timingSafeEqual() to prevent timing attacks. Buffers are padded to the same length before comparison.
Key Hashing
security
Database API keys are hashed using scrypt KDF with the following parameters:
  • Cost factor (N): 16384
  • Block size (r): 8
  • Parallelization (p): 1
  • Key length: 32 bytes
  • Salt: Derived from the key itself

Bearer Token Authentication

Bearer token authentication is used exclusively for OTLP ingestion and the LLM routing proxy. Tokens are agent-specific API keys with the mnfst_ prefix.

Agent API Keys

Agent API keys are generated when creating an agent and stored in the agent_api_keys table.
Format
string
mnfst_ followed by a cryptographically secure random string
Storage
security
Keys are hashed using scrypt KDF. The plaintext key is only shown once during creation.
Scope
tenant
Each key is scoped to a specific tenant, agent, and user for strict data isolation.
Caching
performance
Valid keys are cached in-memory for 5 minutes (300 seconds) to reduce database load.

How It Works

1

Header Check

The OtlpAuthGuard extracts the token from the Authorization: Bearer {token} header
2

Prefix Validation

Validates the token starts with mnfst_ (unless in local mode with loopback IP)
3

Cache Lookup

Checks the in-memory cache for recently validated tokens
4

Database Lookup

If not cached, queries agent_api_keys table with key hash, includes joins to agents and tenants tables
5

Expiration Check

Validates expires_at if set (most keys have no expiration)
6

Context Injection

On success, request.ingestionContext is populated with tenant, agent, and user IDs

Ingestion Context

Successful authentication populates the ingestion context:
interface IngestionContext {
  tenantId: string;   // Tenant UUID
  agentId: string;    // Agent UUID
  agentName: string;  // Agent display name
  userId: string;     // User ID (tenant.name)
}

Example Request

OTLP Traces
curl -X POST 'https://your-domain.com/otlp/v1/traces' \
  -H 'Authorization: Bearer mnfst_your-agent-key-here' \
  -H 'Content-Type: application/json' \
  -d '{
    "resourceSpans": [
      {
        "resource": {
          "attributes": [
            {"key": "service.name", "value": {"stringValue": "my-agent"}}
          ]
        },
        "scopeSpans": [...]
      }
    ]
  }'
LLM Proxy
curl -X POST 'https://your-domain.com/v1/chat/completions' \
  -H 'Authorization: Bearer mnfst_your-agent-key-here' \
  -H 'Content-Type: application/json' \
  -d '{
    "model": "gpt-4",
    "messages": [
      {"role": "user", "content": "Hello!"}
    ]
  }'
Node.js
const response = await fetch('https://your-domain.com/otlp/v1/traces', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer mnfst_your-agent-key-here',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    resourceSpans: [/* ... */]
  })
});

Managing Agent Keys

GET /api/v1/agents/:name/key
Retrieve the current agent API key (returns prefix only, not full key)
POST /api/v1/agents/:name/rotate-key
Rotate the agent API key. Returns the new plaintext key (only time it’s shown).
Agent API keys are only shown in full once during creation or rotation. The dashboard only displays the key prefix (mnfst_abc...) after that. Store keys securely.

Local Mode Authentication

In local mode (MANIFEST_MODE=local), authentication is simplified for single-user development:
Session Auth
local
Better Auth is disabled. The LocalAuthGuard trusts all requests from loopback IPs (127.0.0.1, ::1, ::ffff:127.0.0.1) and injects a static local user.
OTLP Auth
local
The OtlpAuthGuard bypasses Bearer token validation for loopback connections. Accepts any token (including dev-mode dummy tokens like Bearer dev-no-auth).
Local User
constants
{
  id: 'local-user-00000000-0000-0000-0000-000000000001',
  name: 'Local User',
  email: '[email protected]'
}

Local Session Endpoint

Request
curl http://127.0.0.1:3001/api/auth/get-session
Response
{
  "session": {
    "id": "local-session",
    "userId": "local-user-00000000-0000-0000-0000-000000000001",
    "token": "local-token",
    "expiresAt": "2025-03-15T10:30:00Z",
    "createdAt": "2024-03-15T10:30:00Z",
    "updatedAt": "2024-03-15T10:30:00Z"
  },
  "user": {
    "id": "local-user-00000000-0000-0000-0000-000000000001",
    "name": "Local User",
    "email": "[email protected]",
    "emailVerified": true,
    "image": null,
    "createdAt": "2024-03-15T10:30:00Z",
    "updatedAt": "2024-03-15T10:30:00Z"
  }
}

Public Endpoints

Some endpoints skip authentication using the @Public() decorator:
GET /api/v1/health
Health check endpoint
GET /api/v1/github/stars
GitHub star count (public badge endpoint)
ALL /api/auth/*
Better Auth endpoints (login, register, OAuth callbacks)

Error Responses

Missing Authentication

X-API-Key Required
{
  "statusCode": 401,
  "message": "X-API-Key header required",
  "error": "Unauthorized"
}
Bearer Token Required
{
  "statusCode": 401,
  "message": "Authorization header required",
  "error": "Unauthorized"
}

Invalid Credentials

Invalid API Key
{
  "statusCode": 401,
  "message": "Invalid API key",
  "error": "Unauthorized"
}
Expired Key
{
  "statusCode": 401,
  "message": "API key expired",
  "error": "Unauthorized"
}

Forbidden Access

Local Mode (Non-Loopback)
{
  "statusCode": 403,
  "message": "Forbidden",
  "error": "Forbidden"
}

Security Best Practices

1

Secure Storage

Never commit API keys or agent tokens to version control. Use environment variables or secure secret management.
2

HTTPS Only

Always use HTTPS in production. Sessions and API keys are transmitted in headers.
3

Key Rotation

Rotate agent API keys regularly, especially after team member changes or suspected exposure.
4

Least Privilege

In multi-tenant setups, create separate API keys per service or application for easier revocation and auditing.
5

Monitor Usage

Check last_used_at timestamps on API keys to identify unused or compromised keys.

Environment Variables

Authentication-related environment variables:
BETTER_AUTH_SECRET
string
required
Secret for Better Auth session signing. Must be at least 32 characters. Generate with: openssl rand -hex 32
BETTER_AUTH_URL
string
Base URL for Better Auth callbacks. Defaults to http://localhost:{PORT}
API_KEY
string
Shared API key for programmatic access (fallback if database key not found)
MANIFEST_MODE
string
Set to local to enable local mode authentication (loopback bypass)
GOOGLE_CLIENT_ID
string
Google OAuth client ID (optional, enables Google sign-in)
GOOGLE_CLIENT_SECRET
string
Google OAuth client secret (required if client ID is set)
GITHUB_CLIENT_ID
string
GitHub OAuth client ID (optional, enables GitHub sign-in)
GITHUB_CLIENT_SECRET
string
GitHub OAuth client secret (required if client ID is set)
DISCORD_CLIENT_ID
string
Discord OAuth client ID (optional, enables Discord sign-in)
DISCORD_CLIENT_SECRET
string
Discord OAuth client secret (required if client ID is set)

Next Steps

API Overview

Return to API introduction and base concepts

Agent Management

Create and manage agents with API keys

OTLP Ingestion

Send telemetry data using agent API keys

LLM Routing

Use the LLM proxy with Bearer authentication

Build docs developers (and LLMs) love