openapi: 3.1.0
info:
  title: lili520.com OpenAPI
  version: 2.6.0
  description: Single source of truth for implemented BFF routes.
servers:
  - url: https://api.lili520.com
    description: Production
  - url: https://api-test.lili520.com
    description: Test
tags:
  - { name: metrics, x-displayName: 统一指标, description: Shared metrics }
  - { name: review, x-displayName: 审核, description: Review queue and bulk actions }
  - { name: customers, x-displayName: 客户, description: Customer APIs }
  - { name: outreach, x-displayName: 开发信触达, description: Send and receipt }
  - { name: crm, x-displayName: 客户管理, description: Archive APIs }
  - { name: export, x-displayName: 导出, description: Export APIs }
  - { name: channels, x-displayName: 触达通道, description: Gmail、WhatsApp、通道发送与回写 }
  - { name: sender_pools, x-displayName: 发送池, description: Sender pool management }
  - { name: routing_rules, x-displayName: 路由规则, description: Routing rules and recommendations }
  - { name: channel_health, x-displayName: 通道健康, description: Channel health summary }
  - { name: channel_logs, x-displayName: 通道日志, description: Delivery and reply logs }
  - { name: reply_console, x-displayName: 回复控制台, description: Reply AI and approval workflow }
  - { name: audit_logs, x-displayName: 审计日志, description: Channel audit trail }
  - { name: quality, x-displayName: 数据质量, description: Quality issues and fixes }
  - { name: training, x-displayName: 训练中心, description: Training models and jobs }
  - { name: scheduler, x-displayName: 任务调度, description: Scheduler jobs and runs }
  - { name: search, x-displayName: 高级搜索, description: Search queries and saved searches }
  - { name: realtime, x-displayName: 实时通信, description: Channels and events }
  - { name: databoard, x-displayName: 数据大屏, description: Metrics and charts }
  - { name: ai_insights, x-displayName: 智能洞察, description: Insight generation and history }
  - { name: compliance, x-displayName: 合规, description: Compliance cases and logs }
  - { name: permissions, x-displayName: 权限, description: Roles, users, grants }
  - { name: i18n, x-displayName: 国际化, description: Locales and translations }
  - { name: themes, x-displayName: 主题, description: Theme settings }
  - { name: mobile, x-displayName: 移动端, description: Devices, releases, push }
  - { name: shortcuts, x-displayName: 快捷键, description: Shortcut configuration }
  - { name: onboarding, x-displayName: 新手引导, description: Onboarding tasks }
  - { name: feedback, x-displayName: 用户反馈, description: Feedback items and votes }
  - { name: webhooks, x-displayName: 回调管理, description: Managed webhooks and logs }
  - { name: meta, x-displayName: 元信息, description: Version and capability APIs }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Bearer auth or dev fallback session context.
    oAuth2:
      type: oauth2
      description: OAuth 2.0 authorization code flow for Gmail and third-party integrations.
      flows:
        authorizationCode:
          authorizationUrl: https://accounts.google.com/o/oauth2/v2/auth
          tokenUrl: https://oauth2.googleapis.com/token
          refreshUrl: https://oauth2.googleapis.com/token
          scopes:
            read: Read access to workspace data
            write: Write access to workspace data
paths:
  /api/metrics/overview:
    get: { tags: [metrics], summary: Get unified metrics, responses: { "200": { description: OK } } }
  /api/review/queue:
    get: { tags: [review], summary: Get review queue, responses: { "200": { description: OK } } }
  /api/review/bulk:
    post: { tags: [review], summary: Bulk review, responses: { "200": { description: OK }, "422": { description: Invalid request } } }
  /api/customers:
    get: { tags: [customers], summary: Get customers, responses: { "200": { description: OK } } }
  /api/capabilities:
    get: { tags: [meta], summary: Get capability registry, responses: { "200": { description: OK } } }
  /api/version:
    get: { tags: [meta], summary: Get version and runtime capabilities, responses: { "200": { description: OK } } }
  /api/channel-accounts:
    get: { tags: [channels], summary: Get all channel accounts, responses: { "200": { description: OK } } }
  /api/channel-accounts/gmail/connect:
    post: { tags: [channels], summary: Start Gmail OAuth connect, responses: { "200": { description: Authorization URL created }, "409": { description: Gmail unavailable } } }
  /api/channel-accounts/gmail/callback:
    get: { tags: [channels], summary: Complete Gmail OAuth callback, responses: { "200": { description: Connected }, "422": { description: Missing callback params } } }
  /api/channel-accounts/gmail/status:
    get: { tags: [channels], summary: Get Gmail account pool status, responses: { "200": { description: OK } } }
  /api/channel-accounts/whatsapp/connect:
    post: { tags: [channels], summary: Connect WhatsApp business channel, responses: { "200": { description: Connected }, "409": { description: WhatsApp unavailable }, "422": { description: Invalid payload } } }
  /api/channel-accounts/whatsapp/callback:
    get: { tags: [channels], summary: Verify WhatsApp callback, responses: { "200": { description: Verified }, "403": { description: Verify failed } } }
    post: { tags: [channels], summary: Write back WhatsApp callback event, responses: { "200": { description: Callback processed }, "422": { description: Invalid callback payload } } }
  /api/channel-accounts/whatsapp/status:
    get: { tags: [channels], summary: Get WhatsApp channel pool status, responses: { "200": { description: OK } } }
  /api/channel-send/prepare:
    post: { tags: [channels], summary: Prepare integrated channel send, responses: { "200": { description: Recommendation generated }, "409": { description: Policy blocked }, "422": { description: Invalid payload } } }
  /api/channel-send/execute:
    post: { tags: [channels], summary: Execute integrated channel send, responses: { "200": { description: Sent }, "409": { description: Blocked or unavailable }, "422": { description: Invalid payload } } }
  /api/channel-send/test:
    post: { tags: [channels], summary: Execute channel test send, responses: { "200": { description: Sent }, "409": { description: Blocked or unavailable }, "422": { description: Invalid payload } } }
  /api/channel-send/gmail/test:
    post: { tags: [channels], summary: Test Gmail pool send, responses: { "200": { description: Sent }, "409": { description: Blocked or unavailable }, "422": { description: Invalid payload } } }
  /api/channel-send/gmail/execute:
    post: { tags: [channels], summary: Execute Gmail pool send, responses: { "200": { description: Sent }, "409": { description: Blocked or unavailable }, "422": { description: Invalid payload } } }
  /api/channel-send/whatsapp/test:
    post: { tags: [channels], summary: Test WhatsApp pool send, responses: { "200": { description: Sent }, "409": { description: Blocked or unavailable }, "422": { description: Invalid payload } } }
  /api/channel-send/whatsapp/execute:
    post: { tags: [channels], summary: Execute WhatsApp pool send, responses: { "200": { description: Sent }, "409": { description: Blocked or unavailable }, "422": { description: Invalid payload } } }
  /api/channel-routing/recommendation:
    get: { tags: [routing_rules], summary: Get routing recommendation, responses: { "200": { description: OK }, "422": { description: Invalid filters } } }
  /api/channel-health/summary:
    get: { tags: [channel_health], summary: Get channel health summary, responses: { "200": { description: OK } } }
  /api/channel-events/writeback:
    post: { tags: [channels], summary: Write back normalized channel event, responses: { "200": { description: Written }, "422": { description: Invalid payload } } }
  /api/channel-logs/delivery:
    get: { tags: [channel_logs], summary: Get delivery logs, responses: { "200": { description: OK } } }
  /api/channel-logs/reply:
    get: { tags: [channel_logs], summary: Get reply logs, responses: { "200": { description: OK } } }
  /api/audit-logs:
    get: { tags: [audit_logs], summary: Get channel audit logs, responses: { "200": { description: OK } } }
  /api/sender-pools:
    get: { tags: [sender_pools], summary: Get sender pools, responses: { "200": { description: OK } } }
    post: { tags: [sender_pools], summary: Create sender pool, responses: { "200": { description: Created }, "422": { description: Invalid payload } } }
  /api/routing-rules:
    get: { tags: [routing_rules], summary: Get routing rules, responses: { "200": { description: OK } } }
    post: { tags: [routing_rules], summary: Create routing rule, responses: { "200": { description: Created }, "422": { description: Invalid payload } } }
  /api/reply-console/threads:
    get: { tags: [reply_console], summary: Get reply console threads, responses: { "200": { description: OK } } }
  /api/reply-console/threads/{id}:
    get: { tags: [reply_console], summary: Get reply thread detail, responses: { "200": { description: OK }, "404": { description: Thread not found } } }
  /api/reply-console/threads/{id}/classify:
    post: { tags: [reply_console], summary: Classify reply intent, responses: { "200": { description: Classified }, "404": { description: Thread not found } } }
  /api/reply-console/threads/{id}/draft-reply:
    post: { tags: [reply_console], summary: Generate AI reply draft, responses: { "200": { description: Drafted }, "404": { description: Thread not found } } }
  /api/reply-console/threads/{id}/approve-reply:
    post: { tags: [reply_console], summary: Approve or reject reply draft, responses: { "200": { description: Updated }, "404": { description: Thread not found }, "422": { description: Invalid payload } } }
  /api/channels/health:
    get: { tags: [channels], summary: Get legacy channel health, responses: { "200": { description: OK }, "500": { description: Read failed } } }
  /api/channels/gmail/connect:
    post: { tags: [channels], summary: Start legacy Gmail connect, responses: { "200": { description: Authorization URL created }, "409": { description: Gmail connect unavailable } } }
  /api/channels/gmail/callback:
    get: { tags: [channels], summary: Complete legacy Gmail callback, responses: { "302": { description: Redirect back to outreach } } }
  /api/channels/gmail/status:
    get: { tags: [channels], summary: Get legacy Gmail status, responses: { "200": { description: OK } } }
  /api/channels/gmail/send:
    post: { tags: [channels], summary: Send via legacy Gmail path, responses: { "200": { description: Sent }, "409": { description: Gmail send blocked } } }
  /api/channels/whatsapp/connect:
    post: { tags: [channels], summary: Refresh legacy WhatsApp status, responses: { "200": { description: Status refreshed }, "409": { description: WhatsApp unavailable } } }
  /api/channels/whatsapp/callback:
    get: { tags: [channels], summary: Verify legacy WhatsApp callback, responses: { "200": { description: Verify success }, "403": { description: Verify failed }, "409": { description: Verify token missing } } }
    post: { tags: [channels], summary: Write legacy WhatsApp callback, responses: { "200": { description: Callback processed }, "422": { description: Invalid callback payload } } }
  /api/channels/whatsapp/status:
    get: { tags: [channels], summary: Get legacy WhatsApp status, responses: { "200": { description: OK } } }
  /api/channels/whatsapp/send:
    post: { tags: [channels], summary: Send via legacy WhatsApp path, responses: { "200": { description: Sent }, "409": { description: WhatsApp send blocked } } }
  /api/outreach/send:
    post:
      tags: [outreach]
      summary: Send outreach
      requestBody:
        required: true
        content:
          application/json:
            examples:
              live: { value: { lead_id: lead_approved_001, mode: live } }
              sandbox: { value: { lead_id: lead_approved_001, mode: sandbox } }
              manual: { value: { lead_id: lead_approved_001, mode: manual, provider: gmail, provider_message_id: gmail_001 } }
      responses:
        "200": { description: Sent }
        "409": { description: Guard blocked or provider unavailable }
        "422": { description: Invalid payload }
  /api/outreach/receipt:
    post:
      tags: [outreach]
      summary: Write receipt event
      parameters:
        - in: header
          name: x-lili-signature
          required: false
          schema: { type: string }
          description: HMAC signature derived from EMAIL_WEBHOOK_SECRET.
      requestBody:
        required: true
        content:
          application/json:
            examples:
              live: { value: { message_id: resend_msg_001, provider: resend, provider_event_id: resend_evt_001, event_type: delivered, occurred_at: "2026-04-06T02:35:00.000Z" } }
              sandbox: { value: { simulate: true, message_id: sandbox_msg_001, provider: sandbox, provider_event_id: sandbox_evt_001, event_type: replied, occurred_at: "2026-04-06T02:36:00.000Z" } }
      responses:
        "200": { description: Accepted }
        "202": { description: Ignored unknown or old event }
        "401": { description: Invalid signature }
        "403": { description: Forbidden }
        "422": { description: Invalid payload }
        "503": { description: Webhook secret not configured }
  /api/crm/archive:
    post: { tags: [crm], summary: Archive lead to CRM, responses: { "200": { description: Archived }, "409": { description: Guard blocked }, "422": { description: Invalid payload } } }
  /api/export/crm:
    get: { tags: [export], summary: Export CRM data, responses: { "200": { description: Export stream }, "422": { description: Invalid export request } } }
  /api/quality/issues:
    get: { tags: [quality], summary: Get quality issues, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/quality/issues/{id}/fix:
    post: { tags: [quality], summary: Fix quality issue, responses: { "200": { description: Fixed }, "404": { description: Issue not found }, "409": { description: Fix not supported } } }
  /api/ai-training/models:
    get: { tags: [training], summary: Get training models, responses: { "200": { description: OK } } }
  /api/ai-training/jobs:
    get: { tags: [training], summary: Get training jobs, responses: { "200": { description: OK } } }
  /api/ai-training/models/{id}/start:
    post: { tags: [training], summary: Start model, responses: { "200": { description: Started }, "404": { description: Model not found }, "409": { description: Action not allowed } } }
  /api/ai-training/models/{id}/pause:
    post: { tags: [training], summary: Pause model, responses: { "200": { description: Paused }, "404": { description: Model not found }, "409": { description: Action not allowed } } }
  /api/ai-training/models/{id}/retrain:
    post: { tags: [training], summary: Retrain model, responses: { "200": { description: Retraining queued }, "404": { description: Model not found }, "409": { description: Action not allowed } } }
  /api/scheduler/jobs:
    get: { tags: [scheduler], summary: Get scheduler jobs, responses: { "200": { description: OK } } }
    post: { tags: [scheduler], summary: Create scheduler job, responses: { "200": { description: Created }, "422": { description: Invalid payload } } }
  /api/scheduler/jobs/{id}:
    patch: { tags: [scheduler], summary: Update scheduler job, responses: { "200": { description: Updated }, "404": { description: Job not found }, "422": { description: Invalid payload } } }
    delete: { tags: [scheduler], summary: Delete scheduler job, responses: { "200": { description: Deleted }, "404": { description: Job not found } } }
  /api/scheduler/jobs/{id}/run:
    post: { tags: [scheduler], summary: Run scheduler job, responses: { "200": { description: Run completed }, "404": { description: Job not found } } }
  /api/scheduler/jobs/{id}/pause:
    post: { tags: [scheduler], summary: Pause scheduler job, responses: { "200": { description: Paused }, "404": { description: Job not found } } }
  /api/scheduler/jobs/{id}/resume:
    post: { tags: [scheduler], summary: Resume scheduler job, responses: { "200": { description: Resumed }, "404": { description: Job not found } } }
  /api/scheduler/runs:
    get: { tags: [scheduler], summary: Get scheduler runs, responses: { "200": { description: OK } } }
  /api/scheduler/health:
    get: { tags: [scheduler], summary: Get scheduler health, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/search/query:
    get: { tags: [search], summary: Run search query (GET), responses: { "200": { description: OK }, "422": { description: Invalid filters } } }
    post: { tags: [search], summary: Run search query and persist history, responses: { "200": { description: OK }, "422": { description: Invalid filters } } }
  /api/search/saved:
    get: { tags: [search], summary: Get saved searches, responses: { "200": { description: OK } } }
    post: { tags: [search], summary: Save search, responses: { "200": { description: Created }, "422": { description: Invalid payload } } }
  /api/search/saved/{id}:
    delete: { tags: [search], summary: Delete saved search, responses: { "200": { description: Deleted }, "404": { description: Saved search not found } } }
  /api/search/history:
    get: { tags: [search], summary: Get search history, responses: { "200": { description: OK } } }
  /api/search/health:
    get: { tags: [search], summary: Get search health, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/realtime/channels:
    get: { tags: [realtime], summary: Get realtime channels, responses: { "200": { description: OK } } }
  /api/realtime/connect:
    post: { tags: [realtime], summary: Connect channel, responses: { "200": { description: Connected }, "404": { description: Channel not found } } }
  /api/realtime/disconnect:
    post: { tags: [realtime], summary: Disconnect channel, responses: { "200": { description: Disconnected }, "404": { description: Channel not found } } }
  /api/realtime/events:
    get: { tags: [realtime], summary: Get realtime events, responses: { "200": { description: OK } } }
  /api/realtime/test:
    post: { tags: [realtime], summary: Send realtime test event, responses: { "200": { description: Event written }, "404": { description: Channel not found } } }
  /api/realtime/health:
    get: { tags: [realtime], summary: Get realtime health, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/databoard/metrics:
    get: { tags: [databoard], summary: Get databoard metrics, responses: { "200": { description: OK } } }
  /api/databoard/charts:
    get: { tags: [databoard], summary: Get databoard charts, responses: { "200": { description: OK } } }
  /api/databoard/health:
    get: { tags: [databoard], summary: Get databoard health, responses: { "200": { description: OK }, "503": { description: Upstream unavailable } } }
  /api/ai-insights/summary:
    get: { tags: [ai_insights], summary: Get latest AI insight summary, responses: { "200": { description: OK } } }
  /api/ai-insights/generate:
    post: { tags: [ai_insights], summary: Generate AI insight, responses: { "200": { description: Generated }, "422": { description: Invalid payload }, "503": { description: Provider not configured } } }
  /api/ai-insights/history:
    get: { tags: [ai_insights], summary: Get AI insight history, responses: { "200": { description: OK } } }
  /api/ai-insights/health:
    get: { tags: [ai_insights], summary: Get AI insight health, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/compliance/cases:
    get: { tags: [compliance], summary: Get compliance cases, responses: { "200": { description: OK } } }
    post: { tags: [compliance], summary: Create compliance case, responses: { "200": { description: Created }, "401": { description: Authentication required }, "403": { description: Forbidden }, "422": { description: Invalid payload } } }
  /api/compliance/audit-logs:
    get: { tags: [compliance], summary: Get compliance audit logs, responses: { "200": { description: OK } } }
  /api/compliance/health:
    get: { tags: [compliance], summary: Get compliance health, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/permissions/roles:
    get: { tags: [permissions], summary: Get roles, responses: { "200": { description: OK } } }
  /api/permissions/users:
    get: { tags: [permissions], summary: Get users, responses: { "200": { description: OK } } }
  /api/permissions/grants:
    get: { tags: [permissions], summary: Get permission grants, responses: { "200": { description: OK } } }
    post: { tags: [permissions], summary: Create permission grant, responses: { "200": { description: Created }, "401": { description: Authentication required }, "403": { description: Forbidden }, "422": { description: Invalid payload } } }
  /api/permissions/grants/{id}:
    delete: { tags: [permissions], summary: Delete permission grant, responses: { "200": { description: Deleted }, "401": { description: Authentication required }, "403": { description: Forbidden }, "404": { description: Grant not found } } }
  /api/permissions/audit-logs:
    get: { tags: [permissions], summary: Get permission audit logs, responses: { "200": { description: OK } } }
  /api/permissions/health:
    get: { tags: [permissions], summary: Get permissions health, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/i18n/locales:
    get: { tags: [i18n], summary: Get locales, responses: { "200": { description: OK } } }
  /api/i18n/locales/{locale}/publish:
    post: { tags: [i18n], summary: Publish locale, responses: { "200": { description: Published }, "401": { description: Authentication required }, "403": { description: Forbidden }, "404": { description: Locale not found } } }
  /api/i18n/translations:
    get: { tags: [i18n], summary: Get translations, responses: { "200": { description: OK } } }
  /api/i18n/publish-logs:
    get: { tags: [i18n], summary: Get publish logs, responses: { "200": { description: OK } } }
  /api/i18n/health:
    get: { tags: [i18n], summary: Get i18n health, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/themes/current:
    get: { tags: [themes], summary: Get current theme, responses: { "200": { description: OK } } }
    post: { tags: [themes], summary: Update current theme, responses: { "200": { description: Updated }, "401": { description: Authentication required }, "403": { description: Forbidden }, "422": { description: Invalid payload } } }
  /api/themes/logs:
    get: { tags: [themes], summary: Get theme logs, responses: { "200": { description: OK } } }
  /api/themes/health:
    get: { tags: [themes], summary: Get themes health, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/mobile/devices:
    get: { tags: [mobile], summary: Get mobile devices, responses: { "200": { description: OK } } }
  /api/mobile/push/test:
    post: { tags: [mobile], summary: Send mobile push test, responses: { "200": { description: Sent }, "422": { description: Invalid payload } } }
  /api/mobile/push/logs:
    get: { tags: [mobile], summary: Get mobile push logs, responses: { "200": { description: OK } } }
  /api/mobile/releases:
    get: { tags: [mobile], summary: Get mobile releases, responses: { "200": { description: OK } } }
  /api/mobile/health:
    get: { tags: [mobile], summary: Get mobile health, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/shortcuts:
    get: { tags: [shortcuts], summary: Get shortcuts, responses: { "200": { description: OK } } }
    post: { tags: [shortcuts], summary: Create shortcut, responses: { "200": { description: Created }, "422": { description: Invalid payload } } }
  /api/shortcuts/{id}:
    delete: { tags: [shortcuts], summary: Delete shortcut, responses: { "200": { description: Deleted }, "404": { description: Shortcut not found } } }
  /api/shortcuts/health:
    get: { tags: [shortcuts], summary: Get shortcuts health, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/onboarding/tasks:
    get: { tags: [onboarding], summary: Get onboarding tasks, responses: { "200": { description: OK } } }
  /api/onboarding/tasks/{id}/complete:
    post: { tags: [onboarding], summary: Complete onboarding task, responses: { "200": { description: Completed }, "404": { description: Task not found } } }
  /api/onboarding/health:
    get: { tags: [onboarding], summary: Get onboarding health, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/feedback/items:
    get: { tags: [feedback], summary: Get feedback items, responses: { "200": { description: OK } } }
    post: { tags: [feedback], summary: Create feedback item, responses: { "200": { description: Created }, "401": { description: Authentication required }, "403": { description: Forbidden }, "422": { description: Invalid payload } } }
  /api/feedback/items/{id}/vote:
    post: { tags: [feedback], summary: Vote feedback item, responses: { "200": { description: Voted }, "404": { description: Item not found } } }
  /api/feedback/health:
    get: { tags: [feedback], summary: Get feedback health, responses: { "200": { description: OK }, "503": { description: Storage unavailable } } }
  /api/webhooks:
    get: { tags: [webhooks], summary: Get managed webhooks, responses: { "200": { description: OK } } }
    post: { tags: [webhooks], summary: Create webhook, responses: { "200": { description: Created }, "422": { description: Invalid payload } } }
  /api/webhooks/{id}:
    delete: { tags: [webhooks], summary: Delete webhook, responses: { "200": { description: Deleted }, "404": { description: Webhook not found } } }
  /api/webhooks/logs:
    get: { tags: [webhooks], summary: Get webhook logs, responses: { "200": { description: OK } } }
  /api/webhooks/test:
    post: { tags: [webhooks], summary: Send webhook test request, responses: { "200": { description: Test sent }, "404": { description: Webhook not found }, "422": { description: Invalid payload } } }
