openapi: 3.1.0
info:
  title: Yeehoo API
  version: 0.8.0-phase8
  description: Yeehoo 的用户端、管理端、创作台、计费与 OpenAI 兼容网关接口。
servers:
  - url: http://127.0.0.1:8080
security: []
tags:
  - name: Health
  - name: Auth
  - name: Playground
  - name: Conversation
  - name: API Keys
  - name: Gateway
  - name: Billing
  - name: Admin
  - name: System
paths:
  /api:
    get:
      tags: [System]
      summary: 获取 API 根信息
      responses:
        "200":
          description: API service metadata
  /metrics:
    get:
      tags: [System]
      summary: 获取 Prometheus 兼容指标
      responses:
        "200":
          description: Prometheus 文本格式指标
  /uploads-cache/{object}:
    get:
      tags: [System]
      summary: 读取本地开发环境缓存上传文件
      parameters:
        - name: object
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: 已上传对象
          content:
            image/*:
              schema:
                type: string
                format: binary
  /openapi.yaml:
    get:
      tags: [System]
      summary: 获取 OpenAPI 原始文档
      responses:
        "200":
          description: OpenAPI YAML 文档
          content:
            application/yaml:
              schema:
                type: string
  /docs:
    get:
      tags: [System]
      summary: 打开 ReDoc 文档首页
      responses:
        "302":
          description: 跳转到 ReDoc 页面
  /docs/redoc:
    get:
      tags: [System]
      summary: 打开 ReDoc 在线文档
      responses:
        "200":
          description: ReDoc HTML 页面
          content:
            text/html:
              schema:
                type: string
  /docs/swagger:
    get:
      tags: [System]
      summary: 打开 Swagger UI 在线文档
      responses:
        "200":
          description: Swagger UI HTML 页面
          content:
            text/html:
              schema:
                type: string
  /healthz:
    get:
      tags: [Health]
      summary: 存活探针
      responses:
        "200":
          description: OK
  /readyz:
    get:
      tags: [Health]
      summary: 依赖就绪探针
      responses:
        "200":
          description: 已就绪
        "503":
          description: 未就绪
  /api/auth/email/send-code:
    post:
      tags: [Auth]
      summary: 发送邮箱验证码
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email:
                  type: string
                  example: creator@example.com
      responses:
        "200":
          description: 验证码已发送
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  debug_code:
                    type: string
  /api/auth/email/verify:
    post:
      tags: [Auth]
      summary: 校验邮箱验证码并创建会话
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, code]
              properties:
                email:
                  type: string
                  example: creator@example.com
                code:
                  type: string
                  example: "123456"
      responses:
        "201":
          description: 会话创建成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DevLoginResponse"
  /api/auth/phone/send-code:
    post:
      tags: [Auth]
      summary: 发送手机验证码
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [phone, consent_accepted]
              properties:
                phone:
                  type: string
                  example: "+8613800138000"
                consent_accepted:
                  type: boolean
                  enum: [true]
                  description: 确认已同意服务端返回的条款与隐私协议版本
      responses:
        "200":
          description: 验证码已发送
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  debug_code:
                    type: string
  /api/auth/phone/verify:
    post:
      tags: [Auth]
      summary: 校验手机验证码并创建会话
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [phone, code]
              properties:
                phone:
                  type: string
                  example: "+8613800138000"
                code:
                  type: string
                  example: "123456"
      responses:
        "201":
          description: 会话创建成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DevLoginResponse"
  /api/auth/wechat/authorize:
    post:
      tags: [Auth]
      summary: 获取微信授权地址
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [consent_accepted]
              properties:
                consent_accepted:
                  type: boolean
                  enum: [true]
      responses:
        "200":
          description: 微信授权地址
          content:
            application/json:
              schema:
                type: object
                properties:
                  authorize_url:
                    type: string
  /api/auth/wechat/callback:
    get:
      tags: [Auth]
      summary: 完成微信开放平台网站扫码登录
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
        - {name: code, in: query, required: true, schema: {type: string}}
        - {name: state, in: query, required: true, schema: {type: string}}
      responses:
        "201":
          description: 未配置成功跳转地址时，直接返回已创建的会话
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DevLoginResponse"
        "302":
          description: 跳转到已配置的前端成功页
  /api/auth/oauth/{provider}/start:
    post:
      tags: [Auth]
      summary: 获取 OAuth 提供方授权地址
      parameters:
        - name: provider
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: 授权地址
          content:
            application/json:
              schema:
                type: object
                properties:
                  authorize_url:
                    type: string
  /api/auth/oauth/google/callback:
    get:
      tags: [Auth]
      summary: 使用单次 state 完成 Google OAuth 登录
      parameters:
        - {name: code, in: query, required: true, schema: {type: string}}
        - {name: state, in: query, required: true, schema: {type: string}}
        - {name: error, in: query, required: false, schema: {type: string}}
      responses:
        "201":
          description: 未配置成功跳转地址时，直接返回已创建的会话
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DevLoginResponse"
        "302":
          description: 跳转到已配置的前端成功页
        "400":
          description: OAuth state 无效、已过期或已被重放
  /api/me:
    get:
      tags: [Auth]
      security:
        - SessionToken: []
      summary: 获取当前会话用户
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      responses:
        "200":
          description: 当前用户信息
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
  /api/auth/logout:
    post:
      tags: [Auth]
      security: [{SessionToken: []}]
      summary: 注销当前会话
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      responses:
        "204": {description: 当前会话已注销}
  /api/auth/logout-all:
    post:
      tags: [Auth]
      security: [{SessionToken: []}]
      summary: 注销当前用户的所有会话
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      responses:
        "204": {description: 当前用户的所有会话已注销}
  /api/admin/auth/password/login:
    post:
      tags: [Admin]
      summary: 运营后台邮箱密码登录，返回 TOTP 挑战或首次设置二维码信息
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email:
                  type: string
                  example: admin@example.com
                password:
                  type: string
                  format: password
      responses:
        "200":
          description: 密码校验通过，必须继续完成 TOTP
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminPasswordLoginResponse"
  /api/admin/auth/totp/verify:
    post:
      tags: [Admin]
      summary: 使用 TOTP 动态码或恢复码完成后台登录
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [challenge_token, code]
              properties:
                challenge_token:
                  type: string
                code:
                  type: string
                  example: "123456"
      responses:
        "201":
          description: 后台会话创建成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminLoginResponse"
  /api/admin/auth/totp/activate:
    post:
      tags: [Admin]
      summary: 首次启用 TOTP 并创建后台会话
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [challenge_token, code]
              properties:
                challenge_token:
                  type: string
                code:
                  type: string
                  example: "123456"
      responses:
        "201":
          description: TOTP 已启用，后台会话创建成功，恢复码只返回一次
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminTOTPActivationLoginResponse"
  /api/admin/auth/me:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取当前运营后台账号
      responses:
        "200":
          description: 当前运营账号
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
  /api/admin/auth/logout:
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 注销当前运营后台会话
      responses:
        "204": {description: 当前后台会话已注销}
  /api/auth/bind/phone/send-code:
    post:
      tags: [Auth]
      security: [{SessionToken: []}]
      summary: 发送绑定手机号验证码
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [phone]
              properties:
                phone: {type: string, example: "+8613800138000"}
      responses:
        "200": {description: 验证码已发送}
  /api/auth/bind/phone/verify:
    post:
      tags: [Auth]
      security: [{SessionToken: []}]
      summary: 校验验证码并将手机号绑定到当前账号
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [phone, code]
              properties:
                phone: {type: string, example: "+8613800138000"}
                code: {type: string, example: "123456"}
      responses:
        "200":
          description: 更新后的用户信息
          content:
            application/json:
              schema: {$ref: "#/components/schemas/User"}
        "409": {description: 该手机号已归属其他账号}
  /api/auth/bind/wechat/authorize:
    post:
      tags: [Auth]
      security: [{SessionToken: []}]
      summary: 发起微信开放平台身份绑定
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      responses:
        "200":
          description: 微信授权地址
          content:
            application/json:
              schema:
                type: object
                properties:
                  authorize_url: {type: string}
        "409": {description: 该微信身份已归属其他账号}
  /api/capabilities:
    get:
      tags: [Playground]
      summary: 获取当前区域、认证、计费、分组与模型能力
      responses:
        "200":
          description: Capabilities
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Capabilities"
  /api/catalog/models:
    get:
      tags: [Playground]
      summary: 获取公开模型目录列表
      responses:
        "200":
          description: Model list
  /api/catalog/models/{model}/schema:
    get:
      tags: [Playground]
      summary: 获取公开模型参数结构
      parameters:
        - $ref: "#/components/parameters/Model"
      responses:
        "200":
          description: Model schema
  /api/catalog/quote:
    post:
      tags: [Playground]
      summary: 估算公开模型请求价格
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QuoteRequest"
      responses:
        "200":
          description: Quote
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Quote"
  /api/keys:
    get:
      tags: [API Keys]
      security:
        - SessionToken: []
      summary: 获取 API 密钥列表
      responses:
        "200":
          description: API key list
    post:
      tags: [API Keys]
      security:
        - SessionToken: []
      summary: 创建 API 密钥
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                scopes:
                  type: array
                  items:
                    type: string
                allowed_ips:
                  type: array
                  items: {type: string}
                allowed_models:
                  type: array
                  items: {type: string}
                expires_at: {type: string, format: date-time}
                rpm_limit: {type: integer, minimum: 0}
                tpm_limit: {type: integer, minimum: 0}
                daily_request_limit: {type: integer, minimum: 0}
                daily_credit_limit: {type: integer, minimum: 0}
                concurrent_limit: {type: integer, minimum: 0}
                webhook_url: {type: string, format: uri, description: Must be an https URL.}
                webhook_secret: {type: string, description: Shared secret for webhook signature verification.}
                webhook_events:
                  type: array
                  items: {type: string, enum: [task.completed, task.failed]}
                webhook_enabled: {type: boolean}
      responses:
        "201":
          description: API key created
  /api/keys/{id}:
    patch:
      tags: [API Keys]
      security: [{SessionToken: []}]
      summary: 更新 API 密钥限制
      parameters:
        - $ref: "#/components/parameters/ID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/APIKeyWrite"
      responses:
        "200":
          description: Updated API key
          content:
            application/json:
              schema: {$ref: "#/components/schemas/APIKey"}
    delete:
      tags: [API Keys]
      security: [{SessionToken: []}]
      summary: 撤销 API 密钥
      parameters:
        - $ref: "#/components/parameters/ID"
      responses:
        "204": {description: API key revoked}
  /api/keys/{id}/rotate:
    post:
      tags: [API Keys]
      security: [{SessionToken: []}]
      summary: 轮换 API 密钥并一次性返回新令牌
      parameters:
        - $ref: "#/components/parameters/ID"
      responses:
        "201":
          description: Rotated API key
          content:
            application/json:
              schema: {$ref: "#/components/schemas/CreatedAPIKey"}
  /api/keys/{id}/usage:
    get:
      tags: [API Keys]
      security: [{SessionToken: []}]
      summary: 获取 API 密钥用量分析
      parameters:
        - $ref: "#/components/parameters/ID"
        - {name: start, in: query, schema: {type: string, format: date-time}}
        - {name: end, in: query, schema: {type: string, format: date-time}}
      responses:
        "200":
          description: API key usage
          content:
            application/json:
              schema: {$ref: "#/components/schemas/APIKeyUsage"}
  /api/playground/models:
    get:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 获取创作台模型列表
      responses:
        "200":
          description: Model list
  /api/playground/models/{model}/schema:
    get:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 获取模型参数结构
      parameters:
        - $ref: "#/components/parameters/Model"
      responses:
        "200":
          description: Model schema
  /api/playground/quote:
    post:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 估算创作台请求价格
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QuoteRequest"
      responses:
        "200":
          description: Quote
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Quote"
  /api/playground/text/chat:
    post:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 提交创作台对话生成请求
      parameters: [{$ref: "#/components/parameters/IdempotencyKey"}]
      requestBody:
        $ref: "#/components/requestBodies/GenericJson"
      responses:
        "201":
          description: Playground run
  /api/playground/text/responses:
    post:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 提交创作台 Responses 请求
      parameters: [{$ref: "#/components/parameters/IdempotencyKey"}]
      requestBody:
        $ref: "#/components/requestBodies/GenericJson"
      responses:
        "201":
          description: Playground run
  /api/playground/images/generations:
    post:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 提交创作台图片生成请求
      parameters: [{$ref: "#/components/parameters/IdempotencyKey"}]
      requestBody:
        $ref: "#/components/requestBodies/GenericJson"
      responses:
        "201":
          description: Playground run
  /api/playground/videos/generations:
    post:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 提交创作台视频生成请求
      parameters: [{$ref: "#/components/parameters/IdempotencyKey"}]
      requestBody:
        $ref: "#/components/requestBodies/GenericJson"
      responses:
        "201":
          description: Playground run
  /api/playground/uploads/images:
    post:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 上传创作台参考图片
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
      responses:
        "201":
          description: Uploaded image metadata
  /api/playground/showcase/publish:
    post:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 将创作台成品发布到展示区
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [run_id]
              properties:
                run_id:
                  type: string
                candidate_index:
                  type: integer
                  minimum: 0
                title:
                  type: string
      responses:
        "201":
          description: Published Showcase work
  /api/playground/tasks/{task_id}:
    get:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 获取创作台生成任务
      parameters:
        - $ref: "#/components/parameters/TaskID"
      responses:
        "200":
          description: Generation task
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlaygroundTaskView"
  /api/playground/runs:
    get:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 获取创作台运行记录列表
      responses:
        "200":
          description: Playground run list
  /api/playground/runs/{run_id}:
    get:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 获取创作台运行记录
      parameters:
        - $ref: "#/components/parameters/RunID"
      responses:
        "200":
          description: Playground run
    delete:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 删除创作台运行记录
      parameters:
        - $ref: "#/components/parameters/RunID"
      responses:
        "204":
          description: Run deleted
  /api/playground/runs/{run_id}/fork:
    post:
      tags: [Playground]
      security:
        - SessionToken: []
      summary: 复制一条创作台运行记录
      parameters:
        - $ref: "#/components/parameters/RunID"
      responses:
        "201":
          description: Forked Playground run
  /api/conversations:
    get:
      tags: [Conversation]
      security:
        - SessionToken: []
      summary: 获取当前用户的创作会话列表
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [active, archived, deleted]
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
      responses:
        "200":
          description: Conversation list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationList"
    post:
      tags: [Conversation]
      security:
        - SessionToken: []
      summary: 创建新的图片或视频创作会话
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateConversationRequest"
      responses:
        "201":
          description: Conversation created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"
  /api/conversations/{id}:
    get:
      tags: [Conversation]
      security:
        - SessionToken: []
      summary: 获取单个创作会话
      parameters:
        - $ref: "#/components/parameters/ID"
      responses:
        "200":
          description: Conversation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"
        "404":
          description: Conversation not found
    delete:
      tags: [Conversation]
      security:
        - SessionToken: []
      summary: 删除当前用户的创作会话
      parameters:
        - $ref: "#/components/parameters/ID"
      responses:
        "204":
          description: Conversation deleted
        "404":
          description: Conversation not found
  /api/conversations/{id}/messages:
    get:
      tags: [Conversation]
      security:
        - SessionToken: []
      summary: 获取会话消息、关联任务与素材
      parameters:
        - $ref: "#/components/parameters/ID"
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
        - name: before_sequence
          in: query
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: Aggregated conversation messages
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationMessageList"
        "404":
          description: Conversation not found
    post:
      tags: [Conversation]
      security:
        - SessionToken: []
      summary: 在会话中发送一条创作消息并创建任务
      description: |
        第一阶段历史影响规则：
        - 默认仅使用当前消息 `content`
        - 不自动继承完整历史消息
        - 不自动继承上一轮结果
        - 只有显式传入 `references` 的历史素材或历史任务结果才会影响本轮生成
      parameters:
        - $ref: "#/components/parameters/ID"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendConversationMessageRequest"
      responses:
        "201":
          description: Message accepted and tasks created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SendConversationMessageResponse"
        "404":
          description: Conversation not found
        "409":
          description: Conversation archived or idempotency conflict
  /api/conversations/{id}/tasks:
    get:
      tags: [Conversation]
      security:
        - SessionToken: []
      summary: 获取会话任务列表
      parameters:
        - $ref: "#/components/parameters/ID"
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
      responses:
        "200":
          description: Conversation task list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationTaskList"
        "404":
          description: Conversation not found
  /api/showcase/feed:
    get:
      tags: [Showcase]
      summary: 获取当前区域公开展示作品列表
      parameters:
        - name: family
          in: query
          schema:
            type: string
        - name: cursor
          in: query
          schema:
            type: string
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
      responses:
        "200":
          description: Showcase feed
  /api/showcase/works/{id}/like:
    post:
      tags: [Showcase]
      summary: 点赞公开展示作品
      parameters:
        - $ref: "#/components/parameters/ID"
      responses:
        "200":
          description: Updated like count
  /api/billing/wallet:
    get:
      tags: [Billing]
      security:
        - SessionToken: []
      summary: 获取当前用户钱包
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      responses:
        "200":
          description: 钱包信息
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Wallet"
  /api/billing/plans:
    get:
      tags: [Billing]
      security:
        - SessionToken: []
      summary: 获取当前区域可购买套餐
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      responses:
        "200":
          description: 套餐列表
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlanList"
  /api/billing/checkout:
    post:
      tags: [Billing]
      security:
        - SessionToken: []
      summary: 创建支付下单会话
      description: |
        创建支付下单会话。

        `provider` 按区域受限：
        - `cn`：支持 `alipay`、`wechat_pay`
        - `global`：支持 `stripe`

        未传 `provider` 时，服务端会按区域自动选择默认支付渠道。
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            maxLength: 255
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [plan_id]
              properties:
                plan_id:
                  type: string
                  description: 套餐 ID，可通过 `/api/billing/plans` 获取
                provider:
                  type: string
                  description: 支付渠道
                  enum: [alipay, wechat_pay, stripe]
      responses:
        "201":
          description: 下单会话
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CheckoutSession"
        "409":
          description: 幂等键已被不同下单请求复用
  /api/billing/subscription:
    get:
      tags: [Billing]
      security:
        - SessionToken: []
      summary: 获取当前订阅与生效权益
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      responses:
        "200":
          description: 当前生效权益
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EffectiveEntitlements"
  /api/billing/subscription/cancel:
    post:
      tags: [Billing]
      security:
        - SessionToken: []
      summary: 取消当前订阅并在周期结束时生效
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            maxLength: 255
      responses:
        "200":
          description: 更新后的订阅信息
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Subscription"
        "404":
          description: 未找到有效订阅
        "409":
          description: 幂等键冲突或请求仍在处理中
  /api/billing/portal:
    post:
      tags: [Billing]
      security: [{SessionToken: []}]
      summary: 创建 Stripe 客户门户会话
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      responses:
        "201":
          description: 客户门户会话
          content:
            application/json:
              schema:
                type: object
                required: [url]
                properties:
                  url: {type: string, format: uri}
        "404": {description: 未找到有效订阅}
  /api/billing/orders:
    get:
      tags: [Billing]
      security:
        - SessionToken: []
      summary: 获取当前用户订单列表
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      responses:
        "200":
          description: 订单列表
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrderList"
  /api/billing/orders/{id}:
    get:
      tags: [Billing]
      security:
        - SessionToken: []
      summary: 获取当前用户单个订单详情
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
        - $ref: "#/components/parameters/ID"
      responses:
        "200":
          description: 订单详情
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Order"
        "404":
          description: 订单不存在或不属于当前用户
  /api/billing/orders/{id}/simulate-paid:
    post:
      tags: [Billing]
      security:
        - SessionToken: []
      summary: 在 local/dev/test 环境模拟支付成功
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
        - $ref: "#/components/parameters/ID"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                event_id:
                  type: string
                provider_payment_id:
                  type: string
                raw:
                  type: object
                  additionalProperties: true
      responses:
        "200":
          description: 模拟支付成功已应用
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SimulatedPaymentResult"
        "404":
          description: 订单不存在，或当前环境未开放该调试接口
  /api/billing/ledger:
    get:
      tags: [Billing]
      security:
        - SessionToken: []
      summary: 获取当前用户账本记录
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      responses:
        "200":
          description: 账本记录列表
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LedgerList"
  /api/billing/usage:
    get:
      tags: [Billing]
      security:
        - SessionToken: []
      summary: 获取当前用户用量记录
      parameters:
        - $ref: "#/components/parameters/RegionHeader"
      responses:
        "200":
          description: 用量记录列表
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UsageList"
  /api/billing/webhooks/{provider}:
    post:
      tags: [Billing]
      summary: 接收支付平台异步回调
      description: |
        接收支付平台异步回调。

        注意：不同支付渠道的真实回调协议不同：
        - `stripe`：官方事件 JSON，依赖 `Stripe-Signature`
        - `alipay`：表单回调，依赖 `sign`
        - `wechat_pay`：微信支付 v3 回调，依赖 `Wechatpay-*` 请求头和加密资源体

        当前文档中的 body schema 主要用于说明内部测试/兼容字段；正式对接时请以前述各支付平台官方回调协议为准。
      parameters:
        - $ref: "#/components/parameters/Provider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [region, order_id, status]
              properties:
                region:
                  type: string
                  enum: [cn, global]
                order_id:
                  type: string
                provider_payment_id:
                  type: string
                status:
                  type: string
                raw:
                  type: object
                  additionalProperties: true
      responses:
        "200":
          description: 回调处理成功
  /api/relay/callbacks/{provider}:
    post:
      tags: [Gateway]
      summary: 接收上游异步任务回调
      parameters:
        - $ref: "#/components/parameters/Provider"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "200":
          description: Callback applied
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GenerationTask"
  /api/admin/overview:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取管理后台概览指标
      responses:
        "200":
          description: Admin overview
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminOverview"
  /api/admin/queue:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取管理后台队列指标与告警
      responses:
        "200":
          description: Admin queue overview
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminQueueOverview"
  /api/admin/users:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取用户列表
      responses:
        "200":
          description: User list
  /api/admin/users/{id}/admin-password:
    post:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 设置管理员本地登录密码
      parameters:
        - $ref: "#/components/parameters/UserID"
      requestBody: {$ref: "#/components/requestBodies/GenericJson"}
      responses:
        "200":
          description: Updated user
  /api/admin/users/{id}/mfa/reset:
    post:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 重置管理员 MFA
      parameters:
        - $ref: "#/components/parameters/UserID"
      responses:
        "200":
          description: Admin MFA reset
  /api/admin/users/{id}/group:
    patch:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 更新用户分组
      parameters:
        - $ref: "#/components/parameters/UserID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [group_code]
              properties:
                group_code:
                  type: string
      responses:
        "200":
          description: Updated user
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
  /api/admin/users/{id}/credits:
    post:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 手动调整用户额度余额
      parameters:
        - $ref: "#/components/parameters/UserID"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amount, reason]
              properties:
                amount:
                  type: integer
                  format: int64
                reason:
                  type: string
                reference:
                  type: string
      responses:
        "200":
          description: Updated wallet
  /api/admin/groups:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取分组列表
      responses:
        "200":
          description: Group list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GroupList"
    post:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 创建或更新分组
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Group"
      responses:
        "201":
          description: Group created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Group"
  /api/admin/groups/{code}:
    put:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 更新分组
      parameters:
        - $ref: "#/components/parameters/GroupCode"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Group"
      responses:
        "200":
          description: Group updated
    delete:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 删除分组
      parameters:
        - $ref: "#/components/parameters/GroupCode"
      responses:
        "204":
          description: Group deleted
  /api/admin/channels:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取渠道列表
      responses:
        "200":
          description: Channel list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelList"
    post:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 创建供应渠道
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/Channel"}
      responses:
        "201":
          description: Channel created
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Channel"}
  /api/admin/channels/{id}:
    put:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 更新供应渠道
      parameters:
        - $ref: "#/components/parameters/ID"
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/Channel"}
      responses:
        "200":
          description: Channel updated
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Channel"}
  /api/admin/channel-probes:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取模型级渠道探测任务列表
      responses:
        "200": {description: Channel probe list}
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 创建模型级渠道探测任务
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/ChannelProbe"}
      responses:
        "201":
          description: Channel probe created
          content:
            application/json:
              schema: {$ref: "#/components/schemas/ChannelProbe"}
  /api/admin/provider-presets:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取结构化 Provider 接入预设
      responses:
        "200": {description: Provider preset list}
  /api/admin/model-specs:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取代码注册的系统模型规格
      responses:
        "200": {description: Model spec list}
  /api/admin/products:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取模型商品聚合列表
      responses:
        "200": {description: Product list}
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 创建模型商品及售价
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/ProductWrite"}
      responses:
        "201": {description: Product created}
  /api/admin/products/{id}:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取模型商品详情
      parameters: [{$ref: "#/components/parameters/ID"}]
      responses:
        "200": {description: Product detail}
    put:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 更新模型商品及售价
      parameters: [{$ref: "#/components/parameters/ID"}]
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/ProductWrite"}
      responses:
        "200": {description: Product updated}
  /api/admin/products/{id}/supplies:
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 绑定商品供货渠道
      parameters: [{$ref: "#/components/parameters/ID"}]
      requestBody: {$ref: "#/components/requestBodies/GenericJson"}
      responses:
        "200": {description: Product supply saved}
  /api/admin/products/{id}/supplies/{route_id}:
    put:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 更新商品供货渠道
      parameters: [{$ref: "#/components/parameters/ID"}, {name: route_id, in: path, required: true, schema: {type: string}}]
      requestBody: {$ref: "#/components/requestBodies/GenericJson"}
      responses:
        "200": {description: Product supply saved}
    delete:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 删除商品供货渠道
      parameters: [{$ref: "#/components/parameters/ID"}, {name: route_id, in: path, required: true, schema: {type: string}}]
      responses:
        "200": {description: Product supply deleted}
  /api/admin/channel-probes/{id}:
    put:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 更新模型级渠道探测任务
      parameters: [{$ref: "#/components/parameters/ID"}]
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/ChannelProbe"}
      responses:
        "200": {description: Channel probe updated}
    delete:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 删除模型级渠道探测任务
      parameters: [{$ref: "#/components/parameters/ID"}]
      responses:
        "204": {description: Channel probe deleted}
  /api/admin/channel-probes/{id}/run:
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 立即执行模型级渠道探测任务
      parameters: [{$ref: "#/components/parameters/ID"}]
      responses:
        "200":
          description: Channel probe run
          content:
            application/json:
              schema: {$ref: "#/components/schemas/ChannelProbeRun"}
  /api/admin/channel-probes/{id}/runs:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取模型级渠道探测历史
      parameters: [{$ref: "#/components/parameters/ID"}]
      responses:
        "200": {description: Channel probe run history}
  /api/admin/security/totp:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取当前管理员 MFA 状态
      responses:
        "200":
          description: MFA status
          content:
            application/json:
              schema: {$ref: "#/components/schemas/AdminMFAStatus"}
  /api/admin/security/totp/setup:
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 生成待激活的加密 TOTP 密钥
      responses:
        "201":
          description: Pending TOTP setup
          content:
            application/json:
              schema: {$ref: "#/components/schemas/AdminMFASetup"}
  /api/admin/security/totp/activate:
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 校验 TOTP 并启用 MFA，同时返回一次性备用码
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/AdminMFACode"}
      responses:
        "200": {description: MFA activated}
  /api/admin/security/totp/challenge:
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 使用 TOTP 或备用码换取短期管理员二次认证令牌
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/AdminMFACode"}
      responses:
        "200":
          description: Reauthentication token
          content:
            application/json:
              schema: {$ref: "#/components/schemas/AdminMFAChallenge"}
  /api/admin/security/totp/disable:
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 校验 TOTP 或备用码后关闭管理员 MFA
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/AdminMFACode"}
      responses:
        "200": {description: MFA disabled}
  /api/admin/subscriptions:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取订阅列表
      parameters:
        - {name: status, in: query, schema: {type: string}}
      responses:
        "200": {description: Subscription list}
  /api/admin/payment-events:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取支付回调事件列表
      parameters:
        - {name: status, in: query, schema: {type: string}}
      responses:
        "200": {description: Payment event list}
  /api/admin/payment-events/{provider}/{id}/retry:
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 重试失败的支付事件
      parameters:
        - $ref: "#/components/parameters/Provider"
        - $ref: "#/components/parameters/ID"
      responses:
        "200": {description: Payment event retried}
  /api/admin/analytics/summary:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取统计概览
      responses:
        "200":
          description: Analytics summary
  /api/admin/api-keys:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 管理端获取 API 密钥列表
      responses:
        "200":
          description: API key list
  /api/admin/plans:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取计费套餐列表
      responses:
        "200":
          description: Plan list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlanList"
    post:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 创建或更新计费套餐
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Plan"
      responses:
        "201":
          description: Plan created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Plan"
  /api/admin/plans/{id}:
    put:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 更新计费套餐
      parameters:
        - $ref: "#/components/parameters/ID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Plan"
      responses:
        "200":
          description: Plan updated
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Plan"
    delete:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 删除计费套餐
      parameters:
        - $ref: "#/components/parameters/ID"
      responses:
        "204":
          description: Plan deleted
  /api/admin/orders:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取订单列表
      responses:
        "200":
          description: Order list
  /api/admin/orders/{id}/provider:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 向支付渠道查询订单状态
      parameters:
        - $ref: "#/components/parameters/ID"
      responses:
        "200":
          description: Provider order state
  /api/admin/orders/{id}/close:
    post:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 关闭待支付订单
      parameters:
        - $ref: "#/components/parameters/ID"
      responses:
        "200":
          description: Closed order
  /api/admin/orders/{id}/refund:
    post:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 对已支付订单发起退款
      parameters:
        - $ref: "#/components/parameters/ID"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
      responses:
        "200":
          description: Refunded order
  /api/admin/payment-providers:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取支付渠道实例列表
      responses:
        "200":
          description: Payment provider instance list
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 创建支付渠道实例
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/PaymentProviderWrite"}
      responses:
        "201":
          description: Created payment provider instance
          content:
            application/json:
              schema: {$ref: "#/components/schemas/PaymentProviderInstance"}
  /api/admin/payment-providers/{id}:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取支付渠道实例详情
      parameters: [{$ref: "#/components/parameters/ID"}]
      responses:
        "200":
          description: Payment provider instance
          content:
            application/json:
              schema: {$ref: "#/components/schemas/PaymentProviderInstance"}
    put:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 更新支付渠道实例
      parameters: [{$ref: "#/components/parameters/ID"}]
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/PaymentProviderWrite"}
      responses:
        "200":
          description: Updated payment provider instance
          content:
            application/json:
              schema: {$ref: "#/components/schemas/PaymentProviderInstance"}
    delete:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 删除支付渠道实例
      parameters: [{$ref: "#/components/parameters/ID"}]
      responses:
        "204": {description: Deleted}
  /api/admin/payment-fulfillments:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取支付履约出站记录列表
      parameters:
        - name: status
          in: query
          schema: {type: string, enum: [pending, processing, processed, dead_letter]}
      responses:
        "200": {description: Payment fulfillment list}
  /api/admin/payment-fulfillments/{id}/retry:
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 重试死信支付履约记录
      parameters: [{$ref: "#/components/parameters/ID"}]
      responses:
        "200": {description: Fulfillment retry result}
  /api/admin/settings:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取系统设置列表
      responses:
        "200":
          description: Setting list
    post:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 创建或更新系统设置
      requestBody:
        $ref: "#/components/requestBodies/GenericJson"
      responses:
        "201":
          description: Setting saved
  /api/admin/settings/{key}:
    delete:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 删除系统设置
      parameters:
        - $ref: "#/components/parameters/SettingKey"
      responses:
        "204":
          description: Setting deleted
  /api/admin/logs:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取请求日志列表
      responses:
        "200":
          description: Request log list
  /api/admin/exports:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取异步导出任务列表
      responses:
        "200":
          description: Export job list
          content:
            application/json:
              schema:
                type: object
                properties:
                  object: {type: string, example: list}
                  data:
                    type: array
                    items: {$ref: "#/components/schemas/ExportJob"}
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 创建脱敏异步数据导出任务
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/ExportJobCreate"}
      responses:
        "201":
          description: Export job created
          content:
            application/json:
              schema: {$ref: "#/components/schemas/ExportJob"}
  /api/admin/exports/{id}/download:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 下载已完成的导出任务
      parameters: [{$ref: "#/components/parameters/ID"}]
      responses:
        "200":
          description: Export file
          content:
            application/json: {schema: {type: string, format: binary}}
            text/csv: {schema: {type: string, format: binary}}
        "409": {description: Export is not ready}
  /api/admin/alert-rules:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取持久化告警规则列表
      responses: {"200": {description: Alert rule list}}
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 创建告警规则
      requestBody: {required: true, content: {application/json: {schema: {$ref: "#/components/schemas/AlertRule"}}}}
      responses: {"200": {description: Saved alert rule}}
  /api/admin/alert-rules/{id}:
    put:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 更新告警规则
      parameters: [{$ref: "#/components/parameters/ID"}]
      requestBody: {required: true, content: {application/json: {schema: {$ref: "#/components/schemas/AlertRule"}}}}
      responses: {"200": {description: Saved alert rule}}
  /api/admin/alert-events:
    get:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 获取持久化告警事件列表
      parameters:
        - name: status
          in: query
          schema: {type: string, enum: [open, acknowledged, resolved]}
      responses: {"200": {description: Alert event list}}
  /api/admin/alert-events/{id}/acknowledge:
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 确认告警事件
      parameters: [{$ref: "#/components/parameters/ID"}]
      responses: {"200": {description: Acknowledged alert event}}
  /api/admin/alert-events/{id}/resolve:
    post:
      tags: [Admin]
      security: [{AdminSessionToken: []}]
      summary: 关闭告警事件
      parameters: [{$ref: "#/components/parameters/ID"}]
      responses: {"200": {description: Resolved alert event}}
  /api/admin/risk-events:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取风控事件列表
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
      responses:
        "200":
          description: Risk event list
          content:
            application/json:
              schema:
                type: object
                properties:
                  object:
                    type: string
                    example: list
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/AdminRiskEvent"
  /api/admin/tasks:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取管理端任务列表
      responses:
        "200":
          description: Task list
  /api/admin/usage:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取管理端用量列表
      responses:
        "200":
          description: Usage list
  /api/admin/ledger:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取管理端账本列表
      responses:
        "200":
          description: Ledger list
  /api/admin/audit-logs:
    get:
      tags: [Admin]
      security:
        - AdminSessionToken: []
      summary: 获取管理端审计日志列表
      responses:
        "200":
          description: Audit log list
  /v1/models:
    get:
      tags: [Gateway]
      security:
        - BearerAuth: []
      summary: 获取 OpenAI 兼容模型列表
      responses:
        "200":
          description: Model list
  /v1/chat/completions:
    post:
      tags: [Gateway]
      security:
        - BearerAuth: []
      summary: OpenAI 兼容对话补全接口
      parameters: [{$ref: "#/components/parameters/IdempotencyKey"}]
      requestBody:
        $ref: "#/components/requestBodies/GenericJson"
      responses:
        "200":
          description: Chat completion
  /v1/responses:
    post:
      tags: [Gateway]
      security:
        - BearerAuth: []
      summary: OpenAI 兼容 Responses 接口
      parameters: [{$ref: "#/components/parameters/IdempotencyKey"}]
      requestBody:
        $ref: "#/components/requestBodies/GenericJson"
      responses:
        "200":
          description: Response object
  /v1/generations:
    post:
      tags: [Gateway]
      security:
        - BearerAuth: []
      summary: 提交生成任务
      parameters: [{$ref: "#/components/parameters/IdempotencyKey"}]
      requestBody:
        $ref: "#/components/requestBodies/GenericJson"
      responses:
        "201":
          description: Generation task
  /v1/images/generations:
    post:
      tags: [Gateway]
      security:
        - BearerAuth: []
      summary: 提交图片生成任务
      parameters: [{$ref: "#/components/parameters/IdempotencyKey"}]
      requestBody:
        $ref: "#/components/requestBodies/GenericJson"
      responses:
        "201":
          description: Generation task
  /v1/images/generations/{task_id}:
    get:
      tags: [Gateway]
      security:
        - BearerAuth: []
      summary: 获取图片生成任务
      parameters:
        - $ref: "#/components/parameters/TaskID"
      responses:
        "200":
          description: Generation task
  /v1/videos/generations:
    post:
      tags: [Gateway]
      security:
        - BearerAuth: []
      summary: 提交视频生成任务
      parameters: [{$ref: "#/components/parameters/IdempotencyKey"}]
      requestBody:
        $ref: "#/components/requestBodies/GenericJson"
      responses:
        "201":
          description: Generation task
  /v1/videos/generations/{task_id}:
    get:
      tags: [Gateway]
      security:
        - BearerAuth: []
      summary: 获取视频生成任务
      parameters:
        - $ref: "#/components/parameters/TaskID"
      responses:
        "200":
          description: Generation task
  /v1/tasks/{task_id}:
    get:
      tags: [Gateway]
      security:
        - BearerAuth: []
      summary: 获取任意生成任务
      parameters:
        - $ref: "#/components/parameters/TaskID"
      responses:
        "200":
          description: Generation task
  /v1/tasks/{task_id}/cancel:
    post:
      tags: [Gateway]
      security:
        - BearerAuth: []
      summary: 取消排队中或执行中的生成任务
      parameters:
        - $ref: "#/components/parameters/TaskID"
      responses:
        "200":
          description: Generation task
  /v1/user/balance:
    get:
      tags: [Gateway]
      security:
        - BearerAuth: []
      summary: 获取 API 用户余额
      responses:
        "200":
          description: Balance
components:
  securitySchemes:
    SessionToken:
      type: apiKey
      in: header
      name: X-Session-Token
    AdminSessionToken:
      type: apiKey
      in: header
      name: X-Admin-Session-Token
    BearerAuth:
      type: http
      scheme: bearer
  parameters:
    RegionHeader:
      name: X-Yeehoo-Region
      in: header
      required: false
      description: 区域头。国内登录注册、绑定与当前会话查询请传 `cn`；未传时默认按 `global` 处理。
      schema:
        type: string
        enum: [cn, global]
      example: cn
    Model:
      name: model
      in: path
      required: true
      schema:
        type: string
    ID:
      name: id
      in: path
      required: true
      schema:
        type: string
    UserID:
      name: id
      in: path
      required: true
      schema:
        type: string
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        maxLength: 255
    GroupCode:
      name: code
      in: path
      required: true
      schema:
        type: string
    Alias:
      name: alias
      in: path
      required: true
      schema:
        type: string
    SettingKey:
      name: key
      in: path
      required: true
      schema:
        type: string
    Provider:
      name: provider
      in: path
      required: true
      schema:
        type: string
    TaskID:
      name: task_id
      in: path
      required: true
      schema:
        type: string
    RunID:
      name: run_id
      in: path
      required: true
      schema:
        type: string
  requestBodies:
    GenericJson:
      required: true
      content:
        application/json:
          schema:
            type: object
            additionalProperties: true
  schemas:
    ModelSpec:
      type: object
      required: [id, family, display_name, description, tags, schema, ui_hints, default_charge_type]
      properties:
        id: {type: string}
        family: {type: string, enum: [text, image, video]}
        display_name: {type: string}
        description: {type: string}
        tags: {type: array, items: {type: string}}
        schema:
          type: object
          required: [fields]
          properties:
            fields:
              type: array
              items:
                type: object
                required: [name, type, required, description]
                properties:
                  name: {type: string}
                  type: {type: string, enum: [string, number, integer, boolean, array]}
                  required: {type: boolean}
                  default: {}
                  options: {type: array, items: {type: string}}
                  minimum: {type: number}
                  maximum: {type: number}
                  description: {type: string}
        ui_hints:
          type: array
          items:
            type: object
            required: [field, control]
            properties:
              field: {type: string}
              label: {type: string}
              control: {type: string}
              placeholder: {type: string}
        default_charge_type: {type: string, enum: [per_token, per_second, per_request, per_image]}
    ProductWrite:
      type: object
      additionalProperties: false
      required: [model_spec_id, display_name, description, visibility, status, price]
      properties:
        model_spec_id: {type: string}
        region: {type: string, enum: [all, cn, global], description: 生效区域；all 表示中国区和 Global 共享}
        display_name: {type: string}
        description: {type: string}
        visibility: {type: string, enum: [public, hidden, internal, private]}
        status: {type: string, enum: [draft, active, paused, archived]}
        price: {type: number, exclusiveMinimum: 0}
    AlertRule:
      type: object
      required: [code, name, severity, metric, comparison, threshold, enabled]
      properties:
        id: {type: string}
        region: {type: string, enum: [global, cn]}
        code: {type: string}
        name: {type: string}
        severity: {type: string, enum: [info, warning, critical]}
        metric: {type: string, enum: [queue_backlog, task_failure_rate, outbox_dead_letter, export_failures, channel_unhealthy, redis_degraded]}
        comparison: {type: string, enum: [gte, gt, lte, lt]}
        threshold: {type: number}
        filters: {type: object, additionalProperties: true}
        enabled: {type: boolean}
    AlertEvent:
      type: object
      properties:
        id: {type: string}
        rule_id: {type: string}
        region: {type: string, enum: [global, cn]}
        code: {type: string}
        severity: {type: string, enum: [info, warning, critical]}
        status: {type: string, enum: [open, acknowledged, resolved]}
        message: {type: string}
        value: {type: number}
        threshold: {type: number}
        provider: {type: string}
        model_id: {type: string}
        channel_id: {type: string}
        group_code: {type: string}
        first_seen_at: {type: string, format: date-time}
        last_seen_at: {type: string, format: date-time}
    ExportJobCreate:
      type: object
      required: [kind, format]
      properties:
        kind: {type: string, enum: [logs, usage, ledger]}
        format: {type: string, enum: [csv, json]}
    ExportJob:
      type: object
      required: [id, region, kind, format, status, requested_by, row_count, created_at, updated_at, expires_at]
      properties:
        id: {type: string}
        region: {type: string, enum: [global, cn]}
        kind: {type: string, enum: [logs, usage, ledger]}
        format: {type: string, enum: [csv, json]}
        status: {type: string, enum: [pending, processing, completed, failed]}
        requested_by: {type: string}
        row_count: {type: integer, minimum: 0}
        content_type: {type: string}
        error_message: {type: string}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
        expires_at: {type: string, format: date-time}
        completed_at: {type: string, format: date-time}
    APIKey:
      type: object
      required: [id, user_id, name, key_prefix, key_last4, scopes, enabled, created_at]
      properties:
        id: {type: string}
        user_id: {type: string}
        name: {type: string}
        key_prefix: {type: string}
        key_last4: {type: string}
        scopes: {type: array, items: {type: string}}
        allowed_ips: {type: array, items: {type: string}}
        allowed_models: {type: array, items: {type: string}}
        rpm_limit: {type: integer, minimum: 0}
        tpm_limit: {type: integer, minimum: 0}
        daily_request_limit: {type: integer, minimum: 0}
        daily_credit_limit: {type: integer, minimum: 0}
        concurrent_limit: {type: integer, minimum: 0}
        enabled: {type: boolean}
        webhook_url: {type: string, format: uri}
        webhook_events:
          type: array
          items: {type: string, enum: [task.completed, task.failed]}
        webhook_enabled: {type: boolean}
        webhook_secret_configured: {type: boolean}
        webhook_last_delivered_at: {type: string, format: date-time}
        webhook_last_error: {type: string}
        expires_at: {type: string, format: date-time}
        revoked_at: {type: string, format: date-time}
        rotated_from_id: {type: string}
        last_used_at: {type: string, format: date-time}
        created_at: {type: string, format: date-time}
    APIKeyWrite:
      type: object
      properties:
        name: {type: string}
        scopes: {type: array, items: {type: string}}
        allowed_ips: {type: array, items: {type: string}}
        allowed_models: {type: array, items: {type: string}}
        expires_at: {type: string, format: date-time}
        rpm_limit: {type: integer, minimum: 0}
        tpm_limit: {type: integer, minimum: 0}
        daily_request_limit: {type: integer, minimum: 0}
        daily_credit_limit: {type: integer, minimum: 0}
        concurrent_limit: {type: integer, minimum: 0}
        webhook_url: {type: string, format: uri}
        webhook_secret: {type: string}
        webhook_events:
          type: array
          items: {type: string, enum: [task.completed, task.failed]}
        webhook_enabled: {type: boolean}
    CreatedAPIKey:
      type: object
      required: [api_key, token]
      properties:
        api_key: {$ref: "#/components/schemas/APIKey"}
        token:
          type: string
          description: Plaintext token shown only in this response.
    APIKeyUsage:
      type: object
      required: [api_key_id, period_start, period_end, requests, input_tokens, output_tokens, total_tokens, credits]
      properties:
        api_key_id: {type: string}
        period_start: {type: string, format: date-time}
        period_end: {type: string, format: date-time}
        requests: {type: integer}
        input_tokens: {type: integer}
        output_tokens: {type: integer}
        total_tokens: {type: integer}
        credits: {type: integer}
        last_used_at: {type: string, format: date-time}
    User:
      type: object
      properties:
        id:
          type: string
        region:
          type: string
          enum: [cn, global]
        email:
          type: string
        phone:
          type: string
        display_name:
          type: string
        role:
          type: string
        group_code:
          type: string
        status:
          type: string
    Capabilities:
      type: object
      properties:
        region:
          type: string
          enum: [cn, global]
        loginMethods:
          type: array
          items:
            type: string
        paymentProviders:
          type: array
          items:
            type: string
        billingMode:
          type: string
        features:
          type: object
          additionalProperties:
            type: boolean
        group:
          $ref: "#/components/schemas/CapabilityGroup"
        models:
          type: array
          items:
            type: object
            additionalProperties: true
    CapabilityGroup:
      type: object
      properties:
        code:
          type: string
        name:
          type: string
        policy:
          type: object
          additionalProperties: true
        limits:
          $ref: "#/components/schemas/PolicyLimits"
        rateLimit:
          type: integer
        queueClass:
          type: string
        watermark:
          type: boolean
        upgradeHint:
          type: string
    PolicyLimits:
      type: object
      properties:
        daily_tasks:
          type: integer
        daily_credits:
          type: integer
        concurrent_tasks:
          type: integer
    DevLoginResponse:
      type: object
      properties:
        user:
          $ref: "#/components/schemas/User"
        session_token:
          type: string
    AdminLoginResponse:
      type: object
      properties:
        user:
          $ref: "#/components/schemas/User"
        admin_session_token:
          type: string
        expires_at:
          type: string
          format: date-time
        admin_reauth_token:
          type: string
        admin_reauth_expires_at:
          type: string
          format: date-time
    AdminPasswordLoginResponse:
      type: object
      properties:
        next:
          type: string
          enum: [mfa_required, mfa_setup]
        user:
          $ref: "#/components/schemas/User"
        challenge_token:
          type: string
        mfa_setup:
          $ref: "#/components/schemas/AdminTOTPSetup"
    AdminTOTPSetup:
      type: object
      properties:
        otpauth_url:
          type: string
    AdminTOTPActivationLoginResponse:
      allOf:
        - $ref: "#/components/schemas/AdminLoginResponse"
        - type: object
          properties:
            backup_codes:
              type: array
              items:
                type: string
    QuoteRequest:
      type: object
      required: [model, family, input]
      properties:
        model:
          type: string
        family:
          type: string
          enum: [text, image, video]
        input:
          type: object
          additionalProperties: true
    Quote:
      type: object
      properties:
        model:
          type: string
        family:
          type: string
        estimated_credits:
          type: integer
        unit:
          type: string
        explanation:
          type: string
    CreateConversationRequest:
      type: object
      properties:
        mode:
          type: string
          enum: [image, video, mixed]
          description: 第一阶段前端建议只使用 `image` 或 `video`
        title:
          type: string
      example:
        mode: image
        title: Summer poster ideas
    Conversation:
      type: object
      required: [id, region, user_id, mode, status, message_count, created_at, updated_at]
      properties:
        id: {type: string}
        region: {type: string, enum: [cn, global]}
        user_id: {type: string}
        mode: {type: string, enum: [image, video, mixed]}
        title: {type: string}
        status: {type: string, enum: [active, archived, deleted]}
        message_count: {type: integer, minimum: 0}
        last_message_at: {type: string, format: date-time}
        metadata:
          type: object
          additionalProperties: true
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
    ConversationList:
      type: object
      required: [object, data]
      properties:
        object: {type: string, example: list}
        data:
          type: array
          items:
            $ref: "#/components/schemas/Conversation"
    ConversationReference:
      type: object
      properties:
        type:
          type: string
          enum: [task_result, asset]
        task_id:
          type: string
        asset_id:
          type: string
      required: [type]
    ConversationTaskSpec:
      type: object
      required: [kind]
      properties:
        kind:
          type: string
          enum: [image, video]
        model:
          type: string
        input:
          type: object
          additionalProperties: true
      example:
        kind: image
        model: gpt-image-2
        input:
          size: 1024x1024
          quality: high
    SendConversationMessageRequest:
      type: object
      properties:
        content:
          type: string
          description: 本轮用户创作指令
        task_specs:
          type: array
          items:
            $ref: "#/components/schemas/ConversationTaskSpec"
        references:
          type: array
          items:
            $ref: "#/components/schemas/ConversationReference"
      example:
        content: Keep the composition, change the style to watercolor
        task_specs:
          - kind: image
            model: gpt-image-2
            input:
              size: 1024x1024
        references:
          - type: task_result
            task_id: task_image_abc123
    ConversationMessage:
      type: object
      required: [id, conversation_id, region, user_id, role, message_type, status, sequence, request_id, created_at, updated_at]
      properties:
        id: {type: string}
        conversation_id: {type: string}
        region: {type: string, enum: [cn, global]}
        user_id: {type: string}
        role: {type: string, enum: [system, user, assistant]}
        message_type: {type: string, enum: [instruction, result, status]}
        content_text: {type: string}
        content_json:
          type: object
          additionalProperties: true
        status: {type: string, enum: [pending, completed, failed, deleted]}
        sequence: {type: integer, minimum: 1}
        request_id: {type: string}
        error_code: {type: string}
        error_message: {type: string}
        metadata:
          type: object
          additionalProperties: true
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
    ConversationMessageTask:
      type: object
      required: [id, conversation_id, message_id, task_id, kind, model, status, request_id, created_at, updated_at]
      properties:
        id: {type: string}
        conversation_id: {type: string}
        message_id: {type: string}
        task_id: {type: string}
        kind: {type: string, enum: [image, video]}
        model: {type: string}
        status: {type: string}
        request_id: {type: string}
        metadata:
          type: object
          additionalProperties: true
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
    ConversationMessageAsset:
      type: object
      required: [id, conversation_id, message_id, asset_role, asset_type, url, created_at]
      properties:
        id: {type: string}
        conversation_id: {type: string}
        message_id: {type: string}
        task_id: {type: string}
        asset_role: {type: string, enum: [input_reference, output_result]}
        asset_type: {type: string, enum: [image, video]}
        url: {type: string, format: uri}
        metadata:
          type: object
          additionalProperties: true
        created_at: {type: string, format: date-time}
    ConversationAggregatedMessage:
      type: object
      required: [message, tasks, assets]
      properties:
        message:
          $ref: "#/components/schemas/ConversationMessage"
        tasks:
          type: array
          items:
            $ref: "#/components/schemas/ConversationMessageTask"
        assets:
          type: array
          items:
            $ref: "#/components/schemas/ConversationMessageAsset"
    ConversationMessageList:
      type: object
      required: [object, data]
      properties:
        object: {type: string, example: list}
        data:
          type: array
          items:
            $ref: "#/components/schemas/ConversationAggregatedMessage"
    ConversationTaskList:
      type: object
      required: [object, data]
      properties:
        object: {type: string, example: list}
        data:
          type: array
          items:
            $ref: "#/components/schemas/ConversationMessageTask"
    SendConversationMessageResponse:
      type: object
      required: [conversation_id, user_message, tasks, references]
      properties:
        conversation_id: {type: string}
        user_message:
          $ref: "#/components/schemas/ConversationMessage"
        tasks:
          type: array
          items:
            $ref: "#/components/schemas/ConversationMessageTask"
        references:
          type: array
          items:
            $ref: "#/components/schemas/ConversationMessageAsset"
    Wallet:
      type: object
      properties:
        account_id:
          type: string
        region:
          type: string
          enum: [cn, global]
        user_id:
          type: string
        currency:
          type: string
        available_credits:
          type: integer
        authorized_credits:
          type: integer
    Plan:
      type: object
      properties:
        id:
          type: string
        region:
          type: string
          enum: [cn, global]
        code:
          type: string
        name:
          type: string
        group_code:
          type: string
        downgrade_group_code:
          type: string
        entitlement_mode:
          type: string
          enum: [permanent, subscription]
        currency:
          type: string
        amount:
          type: integer
        billing_cycle:
          type: string
        credit_amount:
          type: integer
        enabled:
          type: boolean
    PlanList:
      type: object
      properties:
        object:
          type: string
        data:
          type: array
          items:
            $ref: "#/components/schemas/Plan"
    CheckoutSession:
      type: object
      properties:
        order_id:
          type: string
          description: 订单ID
        provider:
          type: string
          description: 支付渠道
        checkout_url:
          type: string
          description: 跳转支付地址，适用于需要页面跳转的支付渠道
        qr_code_url:
          type: string
          description: 二维码原始内容，例如微信支付返回的 weixin:// 链接，需要前端自行渲染二维码时使用
        qr_code_data_url:
          type: string
          description: 后端直接生成的二维码图片 Data URL，可直接用于 img 展示
        client_secret:
          type: string
          description: 客户端确认支付所需的密钥，仅部分渠道返回
        replayed:
          type: boolean
          description: 是否为幂等重放返回
    Order:
      type: object
      required: [id, region, user_id, plan_id, provider, currency, amount, credit_amount, plan_snapshot, status, created_at, updated_at]
      properties:
        id: {type: string}
        region: {type: string, enum: [cn, global]}
        user_id: {type: string}
        plan_id: {type: string}
        provider: {type: string, enum: [alipay, wechat_pay, stripe]}
        provider_order_id: {type: string}
        currency: {type: string}
        amount: {type: integer}
        credit_amount: {type: integer}
        plan_snapshot:
          $ref: "#/components/schemas/Plan"
        status:
          type: string
          enum: [pending, paid, failed, refunded, canceled, expired]
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
    OrderList:
      type: object
      properties:
        object:
          type: string
        data:
          type: array
          items:
            $ref: "#/components/schemas/Order"
    Subscription:
      type: object
      required: [id, region, user_id, plan_id, order_id, provider, status, current_period_start, current_period_end, cancel_at_period_end]
      properties:
        id: {type: string}
        region: {type: string, enum: [cn, global]}
        user_id: {type: string}
        plan_id: {type: string}
        order_id: {type: string}
        provider: {type: string}
        provider_subscription_id: {type: string}
        status:
          type: string
          enum: [active, cancel_at_period_end, past_due, expired, refunded]
        current_period_start: {type: string, format: date-time}
        current_period_end: {type: string, format: date-time}
        cancel_at_period_end: {type: boolean}
        canceled_at: {type: string, format: date-time}
        ended_at: {type: string, format: date-time}
        metadata:
          type: object
          additionalProperties: true
    PaymentEvent:
      type: object
      required: [provider, event_id, order_id, status, incoming_status, received_at, attempt_count]
      properties:
        provider: {type: string}
        event_id: {type: string}
        order_id: {type: string}
        status: {type: string, enum: [processing, processed, failed]}
        incoming_status: {type: string}
        provider_payment_id: {type: string}
        payload: {type: object, additionalProperties: true}
        error_message: {type: string}
        received_at: {type: string, format: date-time}
        processed_at: {type: string, format: date-time}
        attempt_count: {type: integer}
        last_attempt_at: {type: string, format: date-time}
        next_retry_at: {type: string, format: date-time}
    Payment:
      type: object
      required: [id, order_id, provider, status, created_at]
      properties:
        id: {type: string}
        order_id: {type: string}
        provider: {type: string, enum: [alipay, wechat_pay, stripe]}
        provider_payment_id: {type: string}
        status: {type: string}
        raw:
          type: object
          additionalProperties: true
        created_at: {type: string, format: date-time}
    SimulatedPaymentResult:
      type: object
      required: [order, payment]
      properties:
        order:
          $ref: "#/components/schemas/Order"
        payment:
          $ref: "#/components/schemas/Payment"
    LedgerEntry:
      type: object
      properties:
        id: {type: string}
        region: {type: string, enum: [cn, global]}
        account_id: {type: string}
        user_id: {type: string}
        transaction_id: {type: string}
        order_id: {type: string}
        entry_type: {type: string}
        amount_credits: {type: integer}
        balance_after_credits: {type: integer}
        metadata:
          type: object
          additionalProperties: true
        created_at: {type: string, format: date-time}
    LedgerList:
      type: object
      properties:
        object:
          type: string
        data:
          type: array
          items:
            $ref: "#/components/schemas/LedgerEntry"
    UsageEvent:
      type: object
      properties:
        id: {type: string}
        region: {type: string, enum: [cn, global]}
        transaction_id: {type: string}
        request_id: {type: string}
        task_id: {type: string}
        user_id: {type: string}
        model_id: {type: string}
        family: {type: string}
        unit: {type: string}
        quantity: {type: integer}
        input_tokens: {type: integer}
        output_tokens: {type: integer}
        total_tokens: {type: integer}
        credits: {type: integer}
        raw:
          type: object
          additionalProperties: true
        created_at: {type: string, format: date-time}
    UsageList:
      type: object
      properties:
        object:
          type: string
        data:
          type: array
          items:
            $ref: "#/components/schemas/UsageEvent"
    Entitlement:
      type: object
      required: [id, region, user_id, source_type, source_id, group_code, status, starts_at]
      properties:
        id: {type: string}
        region: {type: string, enum: [cn, global]}
        user_id: {type: string}
        source_type: {type: string, enum: [subscription, permanent, legacy_grandfathered, admin]}
        source_id: {type: string}
        plan_id: {type: string}
        group_code: {type: string}
        status: {type: string, enum: [active, expired, revoked, refunded]}
        starts_at: {type: string, format: date-time}
        ends_at: {type: string, format: date-time}
        metadata:
          type: object
          additionalProperties: true
    EffectiveEntitlements:
      type: object
      required: [base_group, effective_group, entitlements]
      properties:
        base_group: {type: string}
        effective_group: {type: string}
        subscription:
          $ref: "#/components/schemas/Subscription"
        entitlements:
          type: array
          items:
            $ref: "#/components/schemas/Entitlement"
    Group:
      type: object
      properties:
        region:
          type: string
          enum: [cn, global]
        code:
          type: string
        name:
          type: string
        description:
          type: string
        enabled:
          type: boolean
        priority:
          type: integer
        policy:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    GroupList:
      type: object
      properties:
        object:
          type: string
        data:
          type: array
          items:
            $ref: "#/components/schemas/Group"
    ChannelCredentialRef:
      type: object
      readOnly: true
      required: [id, configured, fingerprint, last_rotated_at]
      properties:
        id:
          type: string
        configured:
          type: boolean
        fingerprint:
          type: string
        last_rotated_at:
          type: string
          format: date-time
    Channel:
      type: object
      required: [id, region, name, auth_type, health_status, status, priority, weight, failure_count]
      properties:
        id:
          type: string
        region:
          type: string
          enum: [all, cn, global]
          description: 生效区域；all 表示中国区和 Global 共享
        name:
          type: string
        base_url:
          type: string
        auth_type:
          type: string
        auth_ref:
          type: string
        credential:
          $ref: "#/components/schemas/ChannelCredentialRef"
        header_override:
          type: object
          additionalProperties: true
        rate_limit:
          type: object
          additionalProperties: true
        balance_config:
          type: object
          additionalProperties: true
        health_status:
          type: string
        status:
          type: string
        tags:
          type: array
          items:
            type: string
        priority:
          type: integer
        weight:
          type: integer
        failure_count:
          type: integer
        last_health_check_at:
          type: string
          format: date-time
        recover_at:
          type: string
          format: date-time
    ChannelList:
      type: object
      properties:
        object:
          type: string
        data:
          type: array
          items:
            $ref: "#/components/schemas/Channel"
        total:
          type: integer
        page:
          type: integer
        page_size:
          type: integer
    ChannelProbe:
      type: object
      required: [channel_id, sku_id, upstream_model]
      properties:
        id: {type: string}
        region: {type: string, enum: [cn, global]}
        channel_id: {type: string}
        sku_id: {type: string}
        upstream_model: {type: string}
        protocol: {type: string}
        endpoint_path: {type: string}
        request_template: {type: object, additionalProperties: true}
        interval_seconds: {type: integer, minimum: 30}
        jitter_seconds: {type: integer, minimum: 0}
        timeout_ms: {type: integer, minimum: 500, maximum: 60000}
        failure_threshold: {type: integer, minimum: 1}
        success_threshold: {type: integer, minimum: 1}
        daily_cost_budget: {type: number, minimum: 0}
        enabled: {type: boolean}
        last_run_at: {type: string, format: date-time}
        next_run_at: {type: string, format: date-time}
    ChannelProbeRun:
      type: object
      required: [id, probe_id, region, channel_id, sku_id, upstream_model, status, latency_ms, http_status, started_at]
      properties:
        id: {type: string}
        probe_id: {type: string}
        region: {type: string, enum: [cn, global]}
        channel_id: {type: string}
        sku_id: {type: string}
        upstream_model: {type: string}
        status: {type: string, enum: [running, succeeded, failed]}
        latency_ms: {type: integer}
        http_status: {type: integer}
        error_class: {type: string}
        error_message: {type: string}
        response_summary: {type: object, additionalProperties: true}
        estimated_cost: {type: number}
        started_at: {type: string, format: date-time}
        finished_at: {type: string, format: date-time}
    AdminMFAStatus:
      type: object
      required: [enabled, backup_codes_left]
      properties:
        enabled: {type: boolean}
        backup_codes_left: {type: integer, minimum: 0}
        updated_at: {type: string, format: date-time}
    PaymentProviderCredentialMetadata:
      type: object
      required: [configured]
      properties:
        id: {type: string}
        configured: {type: boolean}
        fingerprint: {type: string}
        last_rotated_at: {type: string, format: date-time}
    PaymentProviderInstance:
      type: object
      required: [id, region, provider, name, mode, enabled, config, credential, created_at, updated_at]
      properties:
        id: {type: string}
        region: {type: string, enum: [global, cn]}
        provider: {type: string, enum: [stripe, alipay, wechat_pay]}
        name: {type: string}
        mode: {type: string, enum: [sandbox, production]}
        enabled: {type: boolean}
        config: {type: object, additionalProperties: true}
        credential: {$ref: "#/components/schemas/PaymentProviderCredentialMetadata"}
        created_at: {type: string, format: date-time}
        updated_at: {type: string, format: date-time}
    PaymentProviderWrite:
      type: object
      required: [provider, name, mode, enabled, config]
      properties:
        id: {type: string}
        provider: {type: string, enum: [stripe, alipay, wechat_pay]}
        name: {type: string}
        mode: {type: string, enum: [sandbox, production]}
        enabled: {type: boolean}
        config: {type: object, additionalProperties: true}
        credentials:
          type: object
          additionalProperties: {type: string}
    AdminMFASetup:
      type: object
      required: [otpauth_url]
      properties:
        otpauth_url: {type: string, writeOnly: true}
    AdminMFACode:
      type: object
      required: [code]
      properties:
        code: {type: string}
    AdminMFAChallenge:
      type: object
      required: [token, expires_at]
      properties:
        token: {type: string, writeOnly: true}
        expires_at: {type: string, format: date-time}
    GenerationTask:
      type: object
      properties:
        id:
          type: string
        object:
          type: string
        kind:
          type: string
        model:
          type: string
        status:
          type: string
        progress:
          type: integer
        request_id:
          type: string
        billing_transaction_id:
          type: string
        result:
          type: object
          additionalProperties: true
    PlaygroundTaskView:
      type: object
      properties:
        task_id:
          type: string
        object:
          type: string
        kind:
          type: string
        model:
          type: string
        status:
          type: string
        progress:
          type: integer
        created_at:
          type: integer
          format: int64
        completed_at:
          type: integer
          format: int64
        result:
          type: object
          additionalProperties: true
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
    PublicGenerationTask:
      type: object
      properties:
        task_id:
          type: string
        object:
          type: string
        kind:
          type: string
        model:
          type: string
        status:
          type: string
        progress:
          type: integer
        created_at:
          type: integer
          format: int64
        completed_at:
          type: integer
          format: int64
        result:
          type: object
          additionalProperties: true
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
    AdminOverview:
      type: object
      properties:
        status:
          type: string
        metrics:
          type: object
          additionalProperties:
            type: integer
        task_statuses:
          type: array
          items:
            type: object
            properties:
              status:
                type: string
              count:
                type: integer
        generated_at:
          type: string
          format: date-time
    AdminQueueOverview:
      type: object
      properties:
        status:
          type: string
        metrics:
          type: array
          items:
            $ref: "#/components/schemas/AdminQueueMetric"
        alerts:
          type: array
          items:
            $ref: "#/components/schemas/AdminQueueAlert"
        generated_at:
          type: string
          format: date-time
    AdminQueueMetric:
      type: object
      properties:
        region:
          type: string
        queue_class:
          type: string
        status:
          type: string
        group_code:
          type: string
        model_id:
          type: string
        count:
          type: integer
        avg_queue_wait_ms:
          type: integer
        avg_execution_ms:
          type: integer
        failure_rate:
          type: number
        oldest_age_seconds:
          type: integer
    AdminQueueAlert:
      type: object
      properties:
        level:
          type: string
        code:
          type: string
        message:
          type: string
        queue_class:
          type: string
        group_code:
          type: string
        model_id:
          type: string
        status:
          type: string
        value:
          type: number
        threshold:
          type: number
        created_at:
          type: string
          format: date-time
    AdminRiskEvent:
      type: object
      properties:
        id:
          type: string
        region:
          type: string
        request_id:
          type: string
        user_id:
          type: string
        api_key_id:
          type: string
        group_code:
          type: string
        ip_address:
          type: string
        device_id:
          type: string
        dimension:
          type: string
        key:
          type: string
        limit:
          type: integer
        count:
          type: integer
        action:
          type: string
        reason:
          type: string
        created_at:
          type: string
          format: date-time
