Phase 5.14: API documentation - OpenAPI tags, response models, examples, docs

This commit is contained in:
Agent Zero
2026-07-23 22:44:54 +02:00
parent 3c1b2f227b
commit 66b6c32ed8
11 changed files with 716 additions and 28 deletions
+57 -1
View File
@@ -212,7 +212,63 @@ async def lifespan(app: FastAPI):
def create_app() -> FastAPI:
"""Create and configure the FastAPI application."""
settings = get_settings()
app = FastAPI(title="LeoCRM", version="1.0.0", lifespan=lifespan)
app = FastAPI(
title="LeoCRM",
version="1.0.0",
lifespan=lifespan,
description=(
"LeoCRM — Self-hosted CRM system for small sales teams.\n\n"
"## Authentication\n"
"All endpoints (except `/api/v1/auth/login` and `/api/v1/health`) require "
"a valid session cookie. Obtain a session by calling `POST /api/v1/auth/login`.\n\n"
"## Multi-Tenancy\n"
"All data is tenant-scoped. The tenant context is set automatically from the "
"authenticated session.\n\n"
"## Plugins\n"
"LeoCRM uses a plugin architecture. Core routes are always available. "
"Plugin routes (DMS, Mail, Calendar, Automation, etc.) are registered when "
"the corresponding plugin is active."
),
openapi_tags=[
{"name": "health", "description": "Health check endpoints — no auth required."},
{"name": "metrics", "description": "Prometheus metrics endpoint."},
{"name": "auth", "description": "Authentication: login, logout, session, password reset."},
{"name": "users", "description": "User management: CRUD, user-tenant assignments."},
{"name": "roles", "description": "Role management and permission definitions."},
{"name": "groups", "description": "User groups for contact assignment and filtering."},
{"name": "tenants", "description": "Tenant management and tenant switching."},
{"name": "notifications", "description": "User notifications and notification preferences."},
{"name": "contacts", "description": "Contact CRUD, contact persons, FTS search, export, soft-delete."},
{"name": "contact-folders", "description": "Contact folder management for organization."},
{"name": "entity-history", "description": "Audit trail and entity change history."},
{"name": "import-export", "description": "Bulk import and export of contacts and data."},
{"name": "plugins", "description": "Plugin management: list, install, activate, deactivate."},
{"name": "ai-copilot", "description": "AI copilot: chat, suggestions, conversation history."},
{"name": "workflows", "description": "Workflow definitions, instances, and execution."},
{"name": "user-preferences", "description": "Per-user preference settings."},
{"name": "currencies", "description": "Currency management for multi-currency support."},
{"name": "taxes", "description": "Tax rate management (VAT, sales tax)."},
{"name": "sequences", "description": "Number sequence management for invoices, quotes, etc."},
{"name": "system-settings", "description": "Tenant-level system settings (company info, theme)."},
{"name": "attachments", "description": "File attachments for contacts, companies, and entities."},
{"name": "addresses", "description": "Address management for contacts and companies."},
{"name": "audit", "description": "Audit log queries and compliance reporting."},
{"name": "automation", "description": "Automation engine: agents, automations, cron jobs, execution logs."},
{"name": "agents", "description": "AI agent definitions and agent runner endpoints."},
{"name": "dms", "description": "Document Management System: files, folders, sources, sharing."},
{"name": "mail", "description": "Email integration: IMAP accounts, folders, messages, send."},
{"name": "calendar", "description": "Calendar management: appointments, resources, recurrence."},
{"name": "search", "description": "Unified search across contacts, documents, emails, etc."},
{"name": "reports", "description": "Report generator: templates, rendering, scheduled reports."},
{"name": "entity-links", "description": "Entity linking: connect contacts to DMS documents and other entities."},
{"name": "kommunikation", "description": "Unified messaging: conversations, participants, messages."},
{"name": "ai-proactive", "description": "Proactive AI: insights, alerts, and recommendations."},
{"name": "ai-assistant", "description": "AI assistant: chat completions, tool calling, context awareness."},
{"name": "tags", "description": "Tag management: create, assign, search tags across entities."},
{"name": "permissions", "description": "Permission management: roles, field-level permissions, sharing."},
{"name": "public-share", "description": "Public sharing endpoints — no auth required, token-based access."},
],
)
app.add_middleware(
CORSMiddleware,
+1 -1
View File
@@ -26,7 +26,7 @@ from app.plugins.builtins.ai_ui_control.schemas import (
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter(tags=["ai-ui-control"])
# ─── REST endpoints (for AI agents) ───
+5 -3
View File
@@ -12,7 +12,9 @@ from app.core.auth import get_redis
from app.core.db import get_db
from app.core.rate_limit import check_rate_limit, get_client_ip, reset_rate_limit
from app.schemas.auth import (
AuthResponse,
LoginRequest,
MessageResponse,
PasswordResetConfirm,
PasswordResetRequest,
SwitchTenantRequest,
@@ -24,7 +26,7 @@ router = APIRouter(prefix="/api/v1/auth", tags=["auth"])
settings = get_settings()
@router.post("/login")
@router.post("/login", response_model=AuthResponse)
async def login(
request: Request,
body: LoginRequest,
@@ -91,7 +93,7 @@ async def login(
return resp
@router.post("/logout")
@router.post("/logout", response_model=MessageResponse)
async def logout(
request: Request,
db: AsyncSession = Depends(get_db),
@@ -112,7 +114,7 @@ async def logout(
return resp
@router.get("/me")
@router.get("/me", response_model=AuthResponse)
async def me(
request: Request,
db: AsyncSession = Depends(get_db),
+2 -1
View File
@@ -5,11 +5,12 @@ from __future__ import annotations
from fastapi import APIRouter
from app.core.monitoring import get_health_status
from app.schemas.common import HealthResponse
router = APIRouter(tags=["health"])
@router.get("/api/v1/health")
@router.get("/api/v1/health", response_model=HealthResponse)
async def health():
"""Health check — no auth required.
+2 -2
View File
@@ -19,7 +19,7 @@ from app.models.notification import (
NotificationPreference,
NotificationType,
)
from app.schemas.common import NotificationPreferenceUpdate
from app.schemas.common import NotificationPreferenceUpdate, UnreadCountResponse
router = APIRouter(prefix="/api/v1/notifications", tags=["notifications"])
@@ -67,7 +67,7 @@ async def mark_notification_read_endpoint(
}
@router.get("/unread-count")
@router.get("/unread-count", response_model=UnreadCountResponse)
async def unread_count_endpoint(
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_permission("notifications:read")),
+3 -3
View File
@@ -9,13 +9,13 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db
from app.deps import require_permission
from app.schemas.system_settings import SystemSettingsUpsert
from app.schemas.system_settings import SystemSettingsUpsert, SystemSettingsResponse
from app.services import system_settings_service
router = APIRouter(prefix="/api/v1/system-settings", tags=["system-settings"])
@router.get("")
@router.get("", response_model=SystemSettingsResponse)
async def get_system_settings(
db: AsyncSession = Depends(get_db),
current_user: dict = Depends(require_permission("settings:read")),
@@ -28,7 +28,7 @@ async def get_system_settings(
return result
@router.put("")
@router.put("", response_model=SystemSettingsResponse)
async def upsert_system_settings(
body: SystemSettingsUpsert,
db: AsyncSession = Depends(get_db),
+4 -4
View File
@@ -14,7 +14,7 @@ from app.core.db import get_db
from app.core.notifications import create_notification
from app.core.permissions import invalidate_permission_cache
from app.deps import require_permission
from app.schemas.user import UserCreate, UserUpdate
from app.schemas.user import UserCreate, UserUpdate, UserResponse, PaginatedUsers
from app.services.user_service import user_service, _UNSET
router = APIRouter(prefix="/api/v1/users", tags=["users"])
@@ -36,7 +36,7 @@ def _parse_role_id(raw: str | None) -> uuid.UUID | None:
) from None
@router.get("")
@router.get("", response_model=PaginatedUsers)
async def list_users(
page: int = Query(1, ge=1),
page_size: int = Query(25, ge=1, le=100),
@@ -49,7 +49,7 @@ async def list_users(
return await user_service.list_users(db, tenant_id, page, page_size, search)
@router.post("", status_code=status.HTTP_201_CREATED)
@router.post("", status_code=status.HTTP_201_CREATED, response_model=UserResponse)
async def create_user(
body: UserCreate,
db: AsyncSession = Depends(get_db),
@@ -103,7 +103,7 @@ async def create_user(
}
@router.get("/{user_id}")
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(
user_id: str,
db: AsyncSession = Depends(get_db),
+8 -8
View File
@@ -6,8 +6,8 @@ from pydantic import BaseModel, EmailStr, Field
class LoginRequest(BaseModel):
email: EmailStr
password: str = Field(..., min_length=1)
email: EmailStr = Field(..., examples=["admin@leocrm.local"])
password: str = Field(..., min_length=1, examples=["secure-password"])
class PasswordResetRequest(BaseModel):
@@ -24,12 +24,12 @@ class SwitchTenantRequest(BaseModel):
class AuthResponse(BaseModel):
user_id: str
email: str
name: str
role: str
tenant_id: str
tenant_name: str | None = None
user_id: str = Field(..., examples=["550e8400-e29b-41d4-a716-446655440000"])
email: str = Field(..., examples=["admin@leocrm.local"])
name: str = Field(..., examples=["Admin User"])
role: str = Field(..., examples=["admin"])
tenant_id: str = Field(..., examples=["550e8400-e29b-41d4-a716-446655440001"])
tenant_name: str | None = Field(None, examples=["Acme GmbH"])
class MessageResponse(BaseModel):
+5 -5
View File
@@ -6,11 +6,11 @@ from pydantic import BaseModel, EmailStr, Field
class UserCreate(BaseModel):
email: EmailStr
name: str = Field(..., min_length=1, max_length=200)
password: str = Field(..., min_length=8)
role: str = Field(default="viewer")
role_id: str | None = Field(default=None, description="UUID of a custom Role")
email: EmailStr = Field(..., examples=["user@leocrm.local"])
name: str = Field(..., min_length=1, max_length=200, examples=["John Doe"])
password: str = Field(..., min_length=8, examples=["secure-password"])
role: str = Field(default="viewer", examples=["viewer"])
role_id: str | None = Field(default=None, description="UUID of a custom Role", examples=["550e8400-e29b-41d4-a716-446655440000"])
is_active: bool = True
+500
View File
@@ -0,0 +1,500 @@
# LeoCRM API Documentation
> Auto-generated from FastAPI route enumeration. **295 endpoints** across **30 tag groups**.
## Overview
LeoCRM exposes a RESTful API under `/api/v1/`. All endpoints (except `/api/v1/health` and `/api/v1/auth/login`) require authentication via session cookie.
- **Swagger UI**: `/docs`
- **ReDoc**: `/redoc`
- **OpenAPI JSON**: `/openapi.json`
### Authentication
1. Call `POST /api/v1/auth/login` with `{email, password}` → receives session cookie.
2. Include the session cookie in all subsequent requests.
3. Call `POST /api/v1/auth/logout` to invalidate the session.
### Multi-Tenancy
All data is tenant-scoped. The tenant context is derived from the authenticated session. Users with multiple tenants can switch via `POST /api/v1/auth/switch-tenant`.
### Error Format
```json
{"detail": "Error message", "code": "error_code", "fields": {"field": "error"}}
```
### Pagination
List endpoints use `page` (1-based) and `page_size` (1-100) query parameters. Responses include `total`, `page`, `page_size`, and `items`.
---
## Core Routes
### health (1 endpoint)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/health` | Health check — no auth required. Returns status + DB/Redis/storage/worker checks. **Response model**: `HealthResponse` |
### metrics (1 endpoint)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/metrics` | Prometheus metrics endpoint. |
### auth (7 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/auth/login` | Login with email+password. Sets session cookie. **Request**: `LoginRequest`, **Response**: `AuthResponse` |
| POST | `/api/v1/auth/logout` | Logout — invalidate session, clear cookie. **Response**: `MessageResponse` |
| GET | `/api/v1/auth/me` | Get current user + active tenant. **Response**: `AuthResponse` |
| GET | `/api/v1/auth/me/permissions` | Get resolved permissions for current user. |
| POST | `/api/v1/auth/switch-tenant` | Switch active tenant. **Request**: `SwitchTenantRequest` |
| POST | `/api/v1/auth/password-reset/request` | Request password reset email. **Request**: `PasswordResetRequest` |
| POST | `/api/v1/auth/password-reset/confirm` | Confirm password reset with token. **Request**: `PasswordResetConfirm` |
### users (5 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/users` | List users with pagination. **Response**: `PaginatedUsers` |
| POST | `/api/v1/users` | Create a new user (admin only). **Request**: `UserCreate`, **Response**: `UserResponse` |
| GET | `/api/v1/users/{user_id}` | Get a single user. **Response**: `UserResponse` |
| PATCH | `/api/v1/users/{user_id}` | Update user fields. **Request**: `UserUpdate` |
| DELETE | `/api/v1/users/{user_id}` | Delete/deactivate a user. |
### roles (5 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/roles` | List all roles. |
| POST | `/api/v1/roles` | Create a custom role. |
| GET | `/api/v1/roles/permissions` | Get all available permission definitions. |
| PATCH | `/api/v1/roles/{role_id}` | Update role permissions. |
| DELETE | `/api/v1/roles/{role_id}` | Delete a custom role. |
### groups (9 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/groups` | List all groups. |
| POST | `/api/v1/groups` | Create a new group. |
| GET | `/api/v1/groups/{group_id}` | Get a single group. |
| PATCH | `/api/v1/groups/{group_id}` | Update group. |
| DELETE | `/api/v1/groups/{group_id}` | Delete group. |
| GET | `/api/v1/groups/{group_id}/members` | List group members. |
| POST | `/api/v1/groups/{group_id}/members` | Add member to group. |
| DELETE | `/api/v1/groups/{group_id}/members/{user_id}` | Remove member from group. |
| GET | `/api/v1/groups/user/{user_id}` | Get groups for a user. |
### tenants (4 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/tenants` | List tenants for current user. |
| POST | `/api/v1/tenants` | Create a new tenant. |
| GET | `/api/v1/tenants/{tenant_id}/users` | List users in a tenant. |
| POST | `/api/v1/tenants/{tenant_id}/users` | Add user to tenant. |
### notifications (6 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/notifications` | List notifications with pagination. |
| PATCH | `/api/v1/notifications/{notification_id}/read` | Mark notification as read. |
| GET | `/api/v1/notifications/unread-count` | Get unread count. **Response**: `UnreadCountResponse` |
| GET | `/api/v1/notifications/types` | List notification types. |
| GET | `/api/v1/notifications/preferences` | Get notification preferences. |
| PATCH | `/api/v1/notifications/preferences/{type_key}` | Update notification preference. |
### contacts (10 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/contacts` | List contacts with pagination, FTS search, type/folder filter, sorting. |
| POST | `/api/v1/contacts` | Create a contact (company or person). |
| GET | `/api/v1/contacts/export` | Stream contacts as CSV. |
| GET | `/api/v1/contacts/{contact_id}` | Get a single contact. |
| PUT | `/api/v1/contacts/{contact_id}` | Update contact. |
| DELETE | `/api/v1/contacts/{contact_id}` | Soft-delete contact. |
| GET | `/api/v1/contacts/{contact_id}/persons` | List contact persons. |
| POST | `/api/v1/contacts/{contact_id}/persons` | Add contact person. |
| PUT | `/api/v1/contacts/{contact_id}/persons/{person_id}` | Update contact person. |
| DELETE | `/api/v1/contacts/{contact_id}/persons/{person_id}` | Delete contact person. |
### contact-folders (6 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/contact-folders` | List contact folders. |
| POST | `/api/v1/contact-folders` | Create a folder. |
| PUT | `/api/v1/contact-folders/{folder_id}` | Update folder. |
| DELETE | `/api/v1/contact-folders/{folder_id}` | Delete folder. |
| PUT | `/api/v1/contact-folders/{folder_id}/reorder` | Reorder folder. |
| PUT | `/api/v1/contact-folders/contacts/{contact_id}/move` | Move contact to folder. |
### entity-history (3 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/entity-history/{entity_type}/{entity_id}` | Get change history for an entity. |
| POST | `/api/v1/entity-history/undo/{entity_type}/{entity_id}` | Undo last change. |
| POST | `/api/v1/entity-history/restore` | Restore entity to a specific version. |
### import-export (2 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/import` | Import data from file. |
| POST | `/api/v1/import/preview` | Preview import data. |
### plugins (11 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/plugins` | List all plugins. |
| GET | `/api/v1/plugins/manifest` | Get plugin manifest schema. |
| GET | `/api/v1/plugins/active-manifests` | Get manifests of all active plugins. |
| POST | `/api/v1/plugins/install-url` | Install plugin from URL. |
| POST | `/api/v1/plugins/upload` | Upload and install plugin ZIP. |
| POST | `/api/v1/plugins/{name}/install` | Install a discovered plugin. |
| POST | `/api/v1/plugins/{name}/activate` | Activate a plugin. |
| POST | `/api/v1/plugins/{name}/deactivate` | Deactivate a plugin. |
| GET | `/api/v1/plugins/{name}/config` | Get plugin configuration. |
| PATCH | `/api/v1/plugins/{name}/config` | Update plugin configuration. |
| DELETE | `/api/v1/plugins/{name}` | Uninstall a plugin. |
### ai-copilot (3 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/ai/copilot/query` | Ask the AI copilot a question. |
| POST | `/api/v1/ai/copilot/execute` | Execute an AI copilot action. |
| GET | `/api/v1/ai/copilot/history` | Get copilot conversation history. |
### workflows (10 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/workflows` | List workflow definitions. |
| POST | `/api/v1/workflows` | Create a workflow. |
| GET | `/api/v1/workflows/{workflow_id}` | Get a workflow. |
| PATCH | `/api/v1/workflows/{workflow_id}` | Update a workflow. |
| DELETE | `/api/v1/workflows/{workflow_id}` | Delete a workflow. |
| POST | `/api/v1/workflows/{workflow_id}/instances` | Start a workflow instance. |
| GET | `/api/v1/workflows/instances` | List workflow instances. |
| GET | `/api/v1/workflows/instances/{instance_id}` | Get a workflow instance. |
| POST | `/api/v1/workflows/instances/{instance_id}/advance` | Advance workflow to next step. |
| POST | `/api/v1/workflows/instances/{instance_id}/cancel` | Cancel a workflow instance. |
### user-preferences (4 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/user/preferences` | Get all user preferences. |
| GET | `/api/v1/user/preferences/{key}` | Get a specific preference. |
| PUT | `/api/v1/user/preferences/{key}` | Set a preference. |
| DELETE | `/api/v1/user/preferences/{key}` | Delete a preference. |
### currencies (4 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/currencies` | List currencies. |
| POST | `/api/v1/currencies` | Create a currency. |
| PATCH | `/api/v1/currencies/{currency_id}` | Update a currency. |
| DELETE | `/api/v1/currencies/{currency_id}` | Delete a currency. |
### taxes (4 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/taxes` | List tax rates. |
| POST | `/api/v1/taxes` | Create a tax rate. |
| PATCH | `/api/v1/taxes/{tax_id}` | Update a tax rate. |
| DELETE | `/api/v1/taxes/{tax_id}` | Delete a tax rate. |
### sequences (4 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/sequences` | List number sequences. |
| POST | `/api/v1/sequences` | Create a sequence. |
| PATCH | `/api/v1/sequences/{sequence_id}` | Update a sequence. |
| DELETE | `/api/v1/sequences/{sequence_id}` | Delete a sequence. |
### system-settings (2 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/system-settings` | Get system settings. **Response**: `SystemSettingsResponse` |
| PUT | `/api/v1/system-settings` | Upsert system settings. **Request**: `SystemSettingsUpsert`, **Response**: `SystemSettingsResponse` |
### attachments (4 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/attachments` | List attachments. |
| POST | `/api/v1/attachments` | Upload an attachment. |
| GET | `/api/v1/attachments/{attachment_id}` | Download an attachment. |
| DELETE | `/api/v1/attachments/{attachment_id}` | Delete an attachment. |
### addresses (4 endpoints)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/addresses` | List addresses. |
| POST | `/api/v1/addresses` | Create an address. |
| PATCH | `/api/v1/addresses/{address_id}` | Update an address. |
| DELETE | `/api/v1/addresses/{address_id}` | Delete an address. |
### audit (1 endpoint)
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/audit-log` | Query audit log entries. |
---
## Plugin Routes
### automation (Automation & Agents)
Agent Builder, Automation Builder, Cron-Scheduler, Agent Runner.
| Method | Path | Description |
|--------|------|-------------|
| GET/POST | `/api/v1/automation/*` | Automation definitions, triggers, execution logs. |
### agents (AI Agents)
| Method | Path | Description |
|--------|------|-------------|
| GET/POST/PUT/DELETE | `/api/v1/agents/*` | AI agent CRUD and runner endpoints. |
### dms (Document Management System)
19 endpoints for file and folder management.
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/dms/files` | List files. |
| POST | `/api/v1/dms/files/upload` | Upload a file. |
| GET | `/api/v1/dms/files/{file_id}` | Get file metadata. |
| GET | `/api/v1/dms/files/{file_id}/preview` | Preview a file. |
| POST | `/api/v1/dms/files/{file_id}/share` | Share a file. |
| GET/POST | `/api/v1/dms/folders` | Folder CRUD. |
| GET | `/api/v1/dms/search` | Search documents. |
| GET | `/api/v1/dms/shared-with-me` | Files shared with current user. |
### mail (Email Integration)
50 endpoints for IMAP/SMTP email management.
| Method | Path | Description |
|--------|------|-------------|
| GET/POST | `/api/v1/mail/accounts` | Email account management. |
| GET/POST | `/api/v1/mail/folders` | Folder management. |
| GET | `/api/v1/mail/{mail_id}` | Get a specific email. |
| POST | `/api/v1/mail/send` | Send an email. |
| POST | `/api/v1/mail/drafts` | Save draft. |
| GET | `/api/v1/mail/search` | Search emails. |
| GET | `/api/v1/mail/threads` | List email threads. |
| POST | `/api/v1/mail/pgp/keys` | PGP key management. |
| POST | `/api/v1/mail/rules` | Mail filter rules. |
| POST | `/api/v1/mail/vacation` | Vacation responder. |
### calendar (Calendar & Scheduling)
21 endpoints for calendar management.
| Method | Path | Description |
|--------|------|-------------|
| GET/POST | `/api/v1/calendar/entries` | Calendar entry CRUD. |
| GET/POST | `/api/v1/calendars` | Calendar CRUD. |
| GET | `/api/v1/calendar/kanban` | Kanban board view. |
| POST | `/api/v1/calendar/import` | Import ICS. |
| GET | `/api/v1/calendar/{calendar_id}/ics-feed` | ICS feed. |
| POST | `/api/v1/resources` | Resource booking. |
### search (Unified Search)
7 endpoints for cross-entity search.
| Method | Path | Description |
|--------|------|-------------|
| POST | `/api/v1/search` | Unified search across all entities. |
| GET | `/api/v1/search/providers` | List search providers. |
| POST | `/api/v1/search/reindex` | Rebuild search index. |
| POST | `/api/v1/search/similar` | Find similar entities. |
| GET | `/api/v1/search/suggest` | Search suggestions. |
| GET | `/api/v1/search/stats` | Search index statistics. |
### reports (Report Generator)
8 endpoints for report templates and generation.
| Method | Path | Description |
|--------|------|-------------|
| GET/POST | `/api/v1/reports/templates` | Report template CRUD. |
| POST | `/api/v1/reports/generate` | Generate a report. |
| GET | `/api/v1/reports/{report_id}` | Get report status. |
| GET | `/api/v1/reports/{report_id}/download` | Download generated report. |
### entity-links (Entity Linking)
4 endpoints for connecting entities.
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/contacts/{contact_id}/files` | Get files linked to a contact. |
| POST/DELETE | `/api/v1/dms/files/{file_id}/link` | Link/unlink a file to an entity. |
| GET | `/api/v1/dms/files/{file_id}/links` | Get all links for a file. |
### kommunikation (Unified Messaging)
23 endpoints for conversations and messages.
| Method | Path | Description |
|--------|------|-------------|
| GET/POST | `/api/v1/comm/conversations` | Conversation CRUD. |
| GET/POST | `/api/v1/comm/conversations/{id}/messages` | Message list and send. |
| POST | `/api/v1/comm/conversations/{id}/participants` | Manage participants. |
| POST | `/api/v1/comm/messages/{id}/reactions` | Message reactions. |
| GET | `/api/v1/comm/miniapps` | List mini-apps. |
### ai-proactive (Proactive AI)
8 endpoints for AI insights and suggestions.
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/v1/ai-proactive/suggestions` | Get AI suggestions. |
| GET | `/api/v1/ai-proactive/suggestions/stream` | SSE stream of suggestions. |
| POST | `/api/v1/ai-proactive/suggestions/{id}/act` | Act on a suggestion. |
| POST | `/api/v1/ai-proactive/suggestions/{id}/dismiss` | Dismiss a suggestion. |
| GET/PUT | `/api/v1/ai-proactive/settings` | Proactive AI settings. |
### ai-assistant (AI Assistant)
30 endpoints for AI chat sessions, providers, models, and tools.
| Method | Path | Description |
|--------|------|-------------|
| GET/POST | `/api/v1/ai/sessions` | Chat session CRUD. |
| GET | `/api/v1/ai/sessions/{id}/messages` | List messages in session. |
| POST | `/api/v1/ai/sessions/{id}/stream` | Stream chat completion (SSE). |
| GET/POST | `/api/v1/ai/providers` | AI provider CRUD. |
| GET/POST | `/api/v1/ai/models` | AI model CRUD. |
| GET/POST | `/api/v1/ai/presets` | Preset CRUD. |
| GET | `/api/v1/ai/tools` | List available AI tools. |
### ai-ui-control (AI UI Control)
WebSocket and REST endpoints for AI-driven UI control.
| Method | Path | Description |
|--------|------|-------------|
| WebSocket | `/ws/ai-ui-control` | Real-time UI command stream. |
| POST | `/api/v1/ai-ui-control/command` | Send UI command from AI agent. |
### tags (Tag Management)
8 endpoints for tag CRUD and assignment.
| Method | Path | Description |
|--------|------|-------------|
| GET/POST | `/api/v1/tags` | Tag CRUD. |
| POST/DELETE | `/api/v1/tags/assign` | Assign/remove tag from entity. |
| POST | `/api/v1/tags/bulk-assign` | Bulk assign tags. |
| GET | `/api/v1/tags/{tag_id}/entities` | Get entities with a tag. |
### permissions (Permission Management)
5 endpoints for file-level permissions and share links.
| Method | Path | Description |
|--------|------|-------------|
| GET/POST | `/api/v1/dms/files/{file_id}/permissions` | File permission CRUD. |
| POST | `/api/v1/dms/files/{file_id}/share-link` | Create public share link. |
| DELETE | `/api/v1/dms/share-links/{link_id}` | Revoke share link. |
### public-share (Public Sharing)
2 endpoints — no auth required, token-based access.
| Method | Path | Description |
|--------|------|-------------|
| GET | `/api/public/share/{token}` | Access shared resource via token. |
| POST | `/api/public/share/{token}` | Interact with shared resource. |
---
## Schema Examples
### LoginRequest
```json
{"email": "admin@leocrm.local", "password": "secure-password"}
```
### AuthResponse
```json
{
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"email": "admin@leocrm.local",
"name": "Admin User",
"role": "admin",
"tenant_id": "550e8400-e29b-41d4-a716-446655440001",
"tenant_name": "Acme GmbH"
}
```
### UserCreate
```json
{
"email": "user@leocrm.local",
"name": "John Doe",
"password": "secure-password",
"role": "viewer",
"is_active": true
}
```
### UserResponse
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@leocrm.local",
"name": "John Doe",
"role": "viewer",
"is_active": true,
"tenant_id": "550e8400-e29b-41d4-a716-446655440001"
}
```
### HealthResponse
```json
{"status": "healthy", "version": "1.0.0"}
```
### UnreadCountResponse
```json
{"count": 5}
```
---
## KI Integration Notes
### For AI Agents
1. **Health Check**: Run `python scripts/ai_health_check.py --base-url <url> --token <token>` before and after updates.
2. **Deploy**: Use `python scripts/ai_deploy.py --dry-run` to preview, then `python scripts/ai_deploy.py` for real deployment.
3. **OpenAPI**: The full OpenAPI spec is available at `/openapi.json` — use it for dynamic endpoint discovery.
4. **Safe Methods**: GET endpoints are safe to probe. POST/PUT/DELETE require careful payload construction.
5. **Session Auth**: AI agents should call `POST /api/v1/auth/login` first, then use the returned cookie for all subsequent requests.
+129
View File
@@ -0,0 +1,129 @@
"""Tests for OpenAPI documentation completeness (Task 5.14)."""
from __future__ import annotations
import os
import sys
import pytest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def test_openapi_tags_configured():
"""FastAPI app should have openapi_tags configured with descriptions."""
from app.main import app
assert app.openapi_tags is not None
assert len(app.openapi_tags) > 0
tag_names = {t["name"] for t in app.openapi_tags}
# Core tags should be present
assert "health" in tag_names
assert "auth" in tag_names
assert "users" in tag_names
assert "contacts" in tag_names
assert "system-settings" in tag_names
def test_openapi_tags_have_descriptions():
"""Each openapi_tag should have a description."""
from app.main import app
for tag in app.openapi_tags:
assert "description" in tag
assert len(tag["description"]) > 0
def test_app_has_description():
"""FastAPI app should have a description for OpenAPI docs."""
from app.main import app
assert app.description is not None
assert len(app.description) > 0
assert "LeoCRM" in app.description or "CRM" in app.description
def test_all_api_routes_have_tags():
"""All API routes should have at least one tag for OpenAPI grouping."""
from app.main import app
untagged = []
for route in app.routes:
if not hasattr(route, "methods") or not hasattr(route, "path"):
continue
if not route.path.startswith("/api/"):
continue
methods = route.methods - {"HEAD", "OPTIONS"} if route.methods else set()
if not methods:
continue
tags = getattr(route, "tags", []) or []
if not tags:
untagged.append(f"{route.path} [{','.join(sorted(methods))}]")
assert len(untagged) == 0, f"Routes without tags: {untagged}"
def test_key_routes_have_response_model():
"""Key routes should have response_model set for OpenAPI schema generation."""
from app.main import app
# Check that at least some routes have response_model
routes_with_response_model = 0
for route in app.routes:
if not hasattr(route, "methods") or not hasattr(route, "path"):
continue
if not route.path.startswith("/api/"):
continue
response_model = getattr(route, "response_model", None)
if response_model is not None:
routes_with_response_model += 1
assert routes_with_response_model >= 5, (
f"Expected at least 5 routes with response_model, got {routes_with_response_model}"
)
def test_auth_login_has_auth_response_model():
"""POST /api/v1/auth/login should have AuthResponse as response_model."""
from app.main import app
from app.schemas.auth import AuthResponse
for route in app.routes:
if (
hasattr(route, "path")
and route.path == "/api/v1/auth/login"
and hasattr(route, "response_model")
):
assert route.response_model == AuthResponse
return
pytest.fail("Route /api/v1/auth/login not found")
def test_health_has_health_response_model():
"""GET /api/v1/health should have HealthResponse as response_model."""
from app.main import app
from app.schemas.common import HealthResponse
for route in app.routes:
if (
hasattr(route, "path")
and route.path == "/api/v1/health"
and hasattr(route, "response_model")
):
assert route.response_model == HealthResponse
return
pytest.fail("Route /api/v1/health not found")
def test_api_documentation_md_exists():
"""docs/api-documentation.md should exist and have content."""
doc_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"docs",
"api-documentation.md",
)
assert os.path.isfile(doc_path), "docs/api-documentation.md not found"
with open(doc_path) as f:
content = f.read()
assert "LeoCRM API Documentation" in content
assert "auth" in content.lower()
assert "contacts" in content.lower()
assert "plugins" in content.lower()