API Security Model
This document explains how each server handles authentication for:
- Authenticated users (after login)
- Admin operations (from aventora-admin or automated scripts)
1. aventora-admin (Next.js Frontend)
Authenticated Users (After Login)
How it works:
- Users log in via domain-chatbot API (
POST /auth/login) - Receive JWT token in response
- JWT token stored in cookies (accessible via
getCookie()) - All authenticated API calls include:
Authorization: Bearer <jwt-token>
Used for:
- domain-chatbot API: JWT tokens (domain management, user management, academy, etc.)
- CMS API: JWT tokens (tenant management, pages, media, etc.) - CMS validates JWT by calling domain-chatbot
- phone-server API: JWT tokens (billing, calls, accounts, etc.) - if phone-server supports JWT, otherwise API keys
Token lifecycle:
- Tokens expire after configured time (default: session-based)
- Frontend intercepts 401 errors and redirects to login
- Token validation happens on the backend:
- domain-chatbot validates its own JWT tokens
- CMS validates JWT tokens by calling domain-chatbot's
/auth/validate-tokenendpoint
Admin Operations
How it works:
- Uses admin API keys from
.env.local:DOMAIN_CHATBOT_API_KEY- for domain-chatbot admin operationsHUB_API_KEY- for phone-server admin operations
- NOTE: No CMS API key needed - CMS uses JWT tokens, not API keys
- These are platform-wide admin keys (not domain-specific)
Used for:
- Cross-domain operations (domain-chatbot and phone-server)
- Creating/managing API keys (domain-chatbot and phone-server)
- System-wide admin tasks
- Automated scripts (e.g.,
ensure-domain-cms-api-keys.ts)
Important: aventora-admin is the ONLY component that stores admin API keys in environment variables. CMS does NOT use API keys - it validates JWT tokens from domain-chatbot.
2. CMS Backend (aventora-cms-backend)
Authenticated Users (After Login)
How it works:
- Users log in via domain-chatbot API
- Receive JWT token from domain-chatbot
- CMS validates JWT token by calling domain-chatbot's
/auth/validate-tokenendpoint - Endpoints use:
Depends(get_current_user)orDepends(require_super_admin()) - Request includes:
Authorization: Bearer <jwt-token>
Used for:
- Managing tenant content (pages, media, listings, etc.)
- Tenant-specific operations
Token validation:
- CMS does NOT validate JWT tokens directly
- CMS calls domain-chatbot API to validate tokens:
POST /auth/validate-token - Domain-chatbot returns user data if token is valid
- CMS does NOT have its own JWT secret - it delegates to domain-chatbot
Key point: CMS uses JWT tokens from domain-chatbot, NOT API keys. There is no CMS API key.
Admin Operations
How it works:
- Super admins use JWT tokens with
is_admin=trueand no domain assigned - Endpoints use:
Depends(require_super_admin()) - No API key authentication for admin operations - only JWT tokens
Special endpoint: /tenants/by-domain/{domain_name}/api-keys
- Authenticated via platform-wide admin API key from domain-chatbot
- This is the ONLY CMS endpoint that accepts API keys (not JWT)
- Used by automated scripts to update tenant API keys
- Validates API key by calling domain-chatbot API (no CMS env vars needed)
Server-to-Server Calls
When CMS needs to call other servers:
- Calls domain-chatbot: Uses
tenant.chatbot_api_keyfrom database (per tenant) - Calls phone-server: Uses
tenant.phone_api_keyfrom database (per tenant) - No environment variables - keys must be set in database or operation fails
3. Domain-Chatbot (domain-chatbot)
Authenticated Users (After Login)
How it works:
- Users log in via
POST /auth/loginwith username/password - Receive JWT token in response
- Endpoints use:
Depends(get_current_user)orDepends(get_current_active_admin) - Request includes:
Authorization: Bearer <jwt-token>
Token validation:
- JWT token signed with
JWT_SECRET - Validates signature, expiration, user exists and is active
- Token payload contains username (
subfield)
Used for:
- Domain management
- User management
- Knowledge base operations
- Academy operations
Admin Operations
How it works:
- Two methods:
- JWT tokens: Admin users (with
is_admin=true) log in and use JWT tokens - Platform-wide admin API keys: API keys with no domain assigned (platform-wide)
- JWT tokens: Admin users (with
- Endpoints use:
Depends(get_current_active_admin)orDepends(get_admin_user_or_api_key)
API key authentication:
- Platform-wide admin API keys stored in
domain_api_keystable - Keys have no domain assigned (domain is NULL or empty)
- Keys have
adminpermissions - Used by
aventora-adminfor cross-domain operations
Server-to-Server Calls
When domain-chatbot needs to call phone-server:
- Uses
domain.phone_api_keyfrom database (per domain) - Retrieved via
chat_db.get_domain_by_name(domain_name) - No environment variables - key must be set in database or operation fails (e.g., phone account creation skipped)
4. Phone-Server (AIventora-Phone)
Authenticated Users (After Login)
How it works:
- Users authenticate using API keys (not JWT tokens)
- API keys stored in
api_keystable (hashed) - Endpoints use:
Depends(require_call_management()),Depends(require_admin()), etc. - Request includes:
Authorization: Bearer <api-key>
API key validation:
- API key is hashed and looked up in
api_keystable - Checks if key is active, not expired, has required permissions
- Returns
APIKeyobject with permissions, account_id, etc.
Used for:
- Initiating calls (
/startendpoint). For informational email, optionalemail_template_id,email_template_name, andemail_template_params— see ENGAGEMENT_HUB_OUTBOUND_EMAIL.md. - Calendar operations
- Call logs
- Account management
- Billing operations
Note: Phone-server does NOT use JWT tokens - it uses API key authentication exclusively.
Admin Operations
How it works:
- Uses API keys with
adminpermission - Endpoints use:
Depends(require_admin()) - API key must have
permissions: ["admin"]in database
Used for:
- Creating/managing API keys
- Account management operations
- System-wide admin tasks
Server-to-Server Calls
When phone-server needs to call domain-chatbot:
- Uses
account.domain_chatbot_api_keyfrom database (per account) - Retrieved via
AccountManager.get_account(account_id) - No environment variables - key must be set in database or operation fails
When internal services need to call phone-server API:
bulk_call_serviceandemail_pull_serviceuseaccount.internal_api_keyfrom database- Retrieved via
AccountManager.get_account(account_id) - No environment variables - key must be set in database or operation fails
5. Aventora CRM (twenty-server + twenty-front)
Authenticated users (workspace members)
How it works:
- Users log into CRM and receive a workspace JWT.
- Aventora REST routes use
JwtAuthGuard+WorkspaceAuthGuard(same as other/rest/...APIs). - Request includes:
Authorization: Bearer <workspace-jwt>.
Aventora engagement endpoints (examples):
| Method | Path | Backend behavior |
|---|---|---|
| GET | /rest/aventora/email-templates | Proxies Hub GET /account-settings/email-templates with workspace AVENTORA_API_KEY |
| POST | /rest/aventora/start-engagement | Proxies Hub POST /integration/start (phone, SMS, email) |
| GET | /rest/aventora/engagements | Lists engagements stored in CRM DB |
| POST | /rest/aventora/pause-engagement / cancel-engagement | Forwards to Hub |
Email-specific start body (camelCase JSON): emailTemplateId, emailTemplateName, emailSubject, emailTemplateParams, emailBodyHtml, clientName — see CRM_OUTBOUND_EMAIL_TEMPLATES.md.
Direct Hub integrators (snake_case): see ENGAGEMENT_HUB_START_API.md.
Hub credentials:
- Stored as application variables on the Twenty Standard app (
AVENTORA_API_KEY, assigned domain)—not in the CRM user’s JWT. - Server env:
AVENTORA_BASE_URLpoints at Engagement Hub.
Important: End users never send the Hub API key from the browser for start/list; twenty-server adds Authorization: Bearer <AVENTORA_API_KEY> when calling Hub.
Hub → CRM (webhooks)
- Hub posts engagement updates to CRM
POST /rest/aventora/webhookwithx-aventora-webhook-secret(server envAVENTORA_WEBHOOK_SECRET). - See Aventora-Assistant
docs/twenty-integration.md.
Summary Table
| Server | User Auth | Admin Auth (from aventora-admin) | Server-to-Server Calls |
|---|---|---|---|
| aventora-admin | JWT tokens (from domain-chatbot) | Admin API keys from .env.local (for domain-chatbot & phone-server only) | Uses admin API keys from .env.local for domain-chatbot & phone-server; JWT tokens for CMS |
| CMS Backend | JWT tokens (validated via domain-chatbot API) | JWT tokens (super admin) | Database-stored keys (tenant.chatbot_api_key, tenant.phone_api_key) |
| Domain-Chatbot | JWT tokens (own JWT_SECRET) | JWT tokens OR platform-wide admin API keys | Database-stored key (domain.phone_api_key) |
| Phone-Server | API keys (from api_keys table) | API keys with admin permission | Database-stored keys (account.domain_chatbot_api_key, account.internal_api_key) |
| Aventora CRM | Workspace JWT | N/A (members use CRM UI) | Workspace AVENTORA_API_KEY → Hub; AVENTORA_BASE_URL on server |
Key Principles
- aventora-admin is special: Only component that stores admin API keys in environment variables (for domain-chatbot & phone-server)
- CMS uses JWT tokens only: CMS does NOT use API keys - it validates JWT tokens via domain-chatbot
- Python servers use database-stored keys: No environment variable API keys, no fallbacks
- User authentication:
- JWT tokens for CMS/domain-chatbot (CMS delegates to domain-chatbot)
- API keys for phone-server
- Admin authentication:
- JWT tokens for CMS/domain-chatbot (CMS delegates to domain-chatbot)
- API keys for phone-server
- Admin API keys from
.env.localonly for domain-chatbot & phone-server
- Server-to-server calls: Always use database-stored keys, fail immediately if not found
Example Flows
Flow 1: User logs into aventora-admin and manages CMS content
- User logs in → domain-chatbot returns JWT token
- aventora-admin stores JWT token in cookies
- User clicks "CMS Settings" → aventora-admin calls CMS API
- Header:
Authorization: Bearer <jwt-token> - CMS receives request and calls domain-chatbot's
/auth/validate-tokenendpoint - Domain-chatbot validates JWT token and returns user data
- CMS uses validated user data to process request
- Header:
- User updates CMS content → CMS validates JWT via domain-chatbot and updates database
Flow 2: Super admin creates new tenant via aventora-admin
- Super admin logs into aventora-admin → receives JWT token
- Super admin clicks "Create Tenant" → aventora-admin calls CMS API
- Header:
Authorization: Bearer <jwt-token> - CMS validates JWT token (checks
is_admin=true, no domain)
- Header:
- CMS provision endpoint needs to call domain-chatbot and phone-server
- Uses
tenant.chatbot_api_keyfrom database (or creates new keys) - Calls domain-chatbot:
Authorization: Bearer <tenant.chatbot_api_key> - Calls phone-server:
Authorization: Bearer <tenant.phone_api_key>
- Uses
Flow 3: Automated script ensures API keys exist
- Script runs:
npm run ensure-cms-api-keys -- --domain example.com - Script uses
DOMAIN_CHATBOT_API_KEYandHUB_API_KEYfrom.env.local(admin keys) - Calls domain-chatbot API to create/get chatbot API key
- Header:
Authorization: Bearer <DOMAIN_CHATBOT_API_KEY>
- Header:
- Calls phone-server API to create/get phone API key
- Header:
Authorization: Bearer <HUB_API_KEY>
- Header:
- Updates CMS database with both keys
- Calls CMS API:
Authorization: Bearer <DOMAIN_CHATBOT_API_KEY>
- Calls CMS API:
Flow 4: Domain-chatbot creates phone account for user
- User activates phone_assistant service in domain-chatbot
create_phone_account()function runs- Gets domain's phone_api_key from database:
domain.phone_api_key - Calls phone-server API to create account
- Header:
Authorization: Bearer <domain.phone_api_key>
- Header:
- Phone-server validates API key and creates account
Flow 5: Bulk call service processes queued calls
bulk_call_servicepolls for queued calls- For each call with
account_id, gets account's internal API key - Calls phone-server
/startendpoint- Header:
Authorization: Bearer <account.internal_api_key>
- Header:
- Phone-server validates API key and initiates call
Error Handling
If database-stored API keys are missing, operations fail immediately with clear errors:
- CMS: "Phone API key not configured for domain X. Please set phone_api_key in CMS tenant settings."
- Domain-Chatbot: "Phone API key not configured for domain X - cannot create phone account"
- Phone-Server: "Domain-chatbot API key not configured for account X. Please set domain_chatbot_api_key in account settings."
- Phone-Server Internal Services: "No internal_api_key configured for account X - call will fail authentication"
No fallbacks - operations fail immediately if keys are not found in database.