{
  "openapi": "3.1.0",
  "info": {
    "title": "NovaMeow Cloud API",
    "version": "1.0.0",
    "summary": "全球边缘多租户 Agent 平台",
    "description": "NovaMeow Cloud · 喵喵云的对外 API。机器侧端点(/v1/*)用 Bearer API key 鉴权;控制台端点(/auth/me、/console/*)用登录会话 cookie 鉴权,两套互不相通。Agent 对话为 SSE 流式(text/event-stream),事件契约见 /v1/agent 的响应说明。"
  },
  "servers": [
    {
      "url": "https://api.cloud.novameow.ai",
      "description": "生产(Cloudflare 全球边缘);自部署请替换为你的 workers.dev 域名"
    }
  ],
  "tags": [
    {
      "name": "platform",
      "description": "平台基础(健康检查)"
    },
    {
      "name": "agent",
      "description": "机器侧 · Agent 对话与会话历史(Bearer key)"
    },
    {
      "name": "voice",
      "description": "机器侧 · 语音(Bearer key)· TTS(文本→语音,同步 mp3 / 流式 PCM)、ASR(语音→文本)、**语音对话闭环 /v1/voice**(音频进 → asr→agent→tts → 智能音频出,一趟请求,继承 agent 全能力,ADR-0019)"
    },
    {
      "name": "kb",
      "description": "知识库 · RAG 检索(双面同形:/v1/kb/* 用 Bearer key,/console/kb/* 用会话 cookie)"
    },
    {
      "name": "apps",
      "description": "应用(App)· 租户多应用与 App 级 key(双面同形:/v1/apps* 用 Bearer 租户通用 key,/console/apps* 用会话 cookie)。App 级 key 调任何 /v1/apps* 一律 403「App 级 key 无管理权」(最小权限)"
    },
    {
      "name": "provider",
      "description": "自带模型通道(BYO,ADR-0016)· 租户级 OpenAI 兼容通道与显式故障转移(双面同形:/v1/provider* 用 Bearer 租户通用 key,/console/provider* 用会话 cookie;App 级 key 一律 403)。key AES-256-GCM 加密落库,明文绝不回显(GET 只回末 4 位);平台未注入 PROVIDER_ENC_KEY 时配置端点回 503「BYO 未启用」"
    },
    {
      "name": "devices",
      "description": "设备 · 登记设备 → 一次性激活码(15 分钟 TTL,明文只回显一次)→ 设备凭码 POST /v1/devices/activate 兑换设备级 key(继承设备绑定的 App = 设备说话即该 App 人设)。管理面双面同形:/v1/devices* 用 Bearer 租户通用 key(App/设备 key 一律 403),/console/devices* 用会话 cookie;activate 为全站唯一无鉴权端点(码即凭证,按 IP 限流 10 次/分),heartbeat 仅设备 key。固件字段只跟踪展示,不做 OTA 分发"
    },
    {
      "name": "logs",
      "description": "请求日志 · 观测性(W6,ADR-0017)· 机器侧每请求一行**元数据**(端点/状态/时延/归属/错误分类),**绝不含对话内容**(text_chars 只是长度,error_class 只是分类词)。保留 7 天,Cron 自动清理。双面同形:/v1/logs 用 Bearer 租户通用 key(App/设备 key 一律 403),/console/logs 用会话 cookie;响应附 summary{count,err_count,p50_ms,p95_ms}(SQL 对筛选范围聚合,p50/p95 为 lower nearest-rank 变体)"
    },
    {
      "name": "auth",
      "description": "人侧 · 登录(邮箱 OTP / GitHub OAuth)与会话"
    },
    {
      "name": "console",
      "description": "人侧 · 控制台管理 API(会话 cookie)"
    },
    {
      "name": "site",
      "description": "站点门面(ADR-0021)· 全部公开无鉴权:公开状态页 /status(依赖逐项探活,只显脱敏分类,绝不泄密钥/原始错误)、SEO 的 /robots.txt 与 /sitemap.xml(host 从请求派生,不硬编码域名)、站点图标 /favicon.svg"
    },
    {
      "name": "portable",
      "description": "配置导出/导入(ADR-0023)· 本租户 personas+apps+settings 可移植 JSON(对标 Dify DSL,换环境/备份/分享)。双面同形:/v1/export·/v1/import 用 Bearer 租户通用 key(App/设备 key 一律 403),/console/export·/console/import 用会话 cookie。**导出零密钥**(不导 API key/BYO 密文/KB 内容/设备);app 的 persona 以 name 引用(可移植,非 id),导入按 name 解析回本租户 persona id(解析不到置空回落)。导入 mode skip(默认,同名跳过)/merge(同名覆盖),逐项尽力而为(单项失败不中止,回 skipped/errors);写入恒带鉴权租户 tenant_id,绝不取 config 里的 tenant 字段"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "机器侧 API key(形如 sk-meow-…,控制台签发,明文只显示一次;服务端只存 sha256 哈希)。鉴权失败统一 401,不区分原因。"
      },
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "mb_session",
        "description": "控制台会话 cookie(登录后种下;HMAC 签名无状态 token,HttpOnly/Secure/SameSite=Lax,7 天有效)。"
      }
    },
    "schemas": {
      "AgentRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "用户消息,必填且不能为空白"
          },
          "session_id": {
            "type": "string",
            "description": "会话标识,决定多轮记忆归属;缺省 \"anon\"(本租户共享匿名会话)。租户间同名互不相通。建议只用字母数字与 - _"
          },
          "persona": {
            "type": "string",
            "description": "直传人格提示(system prompt),仅本次请求生效;缺省默认人格「喵喵」"
          },
          "persona_id": {
            "type": "string",
            "description": "人格库引用(控制台建的人格 id)。查得到则覆盖 persona;查不到静默回落默认。只查本租户"
          },
          "images": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "图片列表:https URL 或 data:image/…;base64 数据 URL;空 = 纯文本"
          },
          "kb": {
            "type": "boolean",
            "default": true,
            "description": "知识库检索注入开关。缺省 true:本租户有文档即自动检索引用(无文档零开销);false = 本轮显式关闭"
          }
        }
      },
      "SseEventStream": {
        "type": "string",
        "description": "SSE 事件流。每帧:event: <type>\\ndata: {\"type\":\"<type>\",…}\\n\\n。客户端按 data 内的 type 字段分发(event: 行仅为标准 SSE 装饰)。事件:kb_refs{refs:[{doc_id,title,seq,score}]} 本轮知识库引用清单(仅命中时发,先于一切 llm 事件);provider_fallback{from:\"byo\",reason} BYO 通道终败、平台通道接管重跑本轮(仅 BYO 回落时发,先于重跑的一切 llm 事件;reason 为脱敏分类词 retry_exhausted/upstream_4xx/upstream_5xx/channel_unavailable/provider_error;收到后应清空本轮已渲染增量);llm_delta{text} 文本增量;tool_call{name} 工具开始(只带名,无参数);tool_result{name,ok} 工具结束(无结果内容);stats{iterations,channel} 成功收尾(channel 为本轮实际通道:byo/platform/platform_fallback,additive 字段);llm_end{reply} 权威全文;error{message} 带内错误(友好话术)。成功顺序:[kb_refs] → [provider_fallback] → delta*/工具事件交错 → stats → llm_end;失败:已发 delta → error(无 llm_end,历史不存回)。流结束无显式终止事件。"
      },
      "Message": {
        "type": "object",
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "user",
              "assistant"
            ],
            "description": "消息角色"
          },
          "content": {
            "type": "string",
            "description": "消息文本(历史中的图片以 [图片] 占位,无 base64)"
          }
        }
      },
      "HistoryResponse": {
        "type": "object",
        "properties": {
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            },
            "description": "仍按原文保留的轮次(被压缩的旧轮在 summary 里)"
          },
          "summary": {
            "type": "string",
            "description": "滚动记忆摘要;未压缩过为空串"
          },
          "count": {
            "type": "integer",
            "description": "messages 条数"
          }
        }
      },
      "SessionListResponse": {
        "type": "object",
        "description": "会话索引列表(ADR-0022)。**纯元数据,绝不含对话内容**——要回看内容走 /v1/sessions/{id}/history",
        "properties": {
          "sessions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SessionRow"
            },
            "description": "按 last_at 倒序的会话(每条只含元数据)"
          }
        }
      },
      "SessionRow": {
        "type": "object",
        "description": "一个会话的索引元数据(无任何对话内容)",
        "properties": {
          "id": {
            "type": "string",
            "description": "session_id(寻址串;匿名请求落到 anon)"
          },
          "turn_count": {
            "type": "integer",
            "description": "累计对话轮数"
          },
          "first_at": {
            "type": "string",
            "description": "首次出现时刻(ISO 8601 UTC)"
          },
          "last_at": {
            "type": "string",
            "description": "最后活跃时刻(ISO 8601 UTC)"
          },
          "app_id": {
            "type": "string",
            "nullable": true,
            "description": "末次该会话请求所用 App 级 key 的 app_id;通用 key 为 null"
          },
          "device_id": {
            "type": "string",
            "nullable": true,
            "description": "末次该会话请求所用设备 key 的 device_id;非设备 key 为 null"
          }
        }
      },
      "TtsRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "要合成的文本,必填非空"
          },
          "voice": {
            "type": "string",
            "default": "female-shaonv",
            "description": "音色 id,缺省 female-shaonv"
          }
        }
      },
      "AsrResponse": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string",
            "description": "识别出的文本(已 trim;中文可用,专名可能同音错、无标点)"
          }
        }
      },
      "VoiceRequest": {
        "type": "object",
        "required": [
          "audio_b64"
        ],
        "properties": {
          "audio_b64": {
            "type": "string",
            "description": "base64 编码的音频(格式同 /v1/asr 接受的:wav/mp3/flac/ogg 等;解码后字节上限 25MB)。标准 base64 字母表,容忍换行/空白。"
          },
          "session_id": {
            "type": "string",
            "description": "会话 id(可选):同 /v1/agent——用于把请求路由到对应会话实例,继承多轮记忆。缺省 anon。"
          },
          "voice": {
            "type": "string",
            "default": "female-shaonv",
            "description": "回复语音音色 id(可选):同 /v1/tts,缺省 female-shaonv。"
          },
          "kb": {
            "type": "boolean",
            "description": "知识库检索开关(可选):透传给 agent 链路(同 /v1/agent 的 kb 语义)。"
          },
          "persona_id": {
            "type": "string",
            "description": "人格库引用 id(可选):透传给 agent 链路(同 /v1/agent 的 persona_id 语义,租户隔离)。"
          }
        }
      },
      "VoiceResponse": {
        "type": "object",
        "properties": {
          "asr_text": {
            "type": "string",
            "description": "ASR 识别出的用户语音文本(已 trim)。"
          },
          "reply_text": {
            "type": "string",
            "description": "Agent 的完整文字回复(worker 侧把 DO 的 SSE 流 drain 成完整文本,取 llm_end 的 reply)。"
          },
          "audio_b64": {
            "type": "string",
            "nullable": true,
            "description": "回复语音的 base64 mp3。**降级**:TTS 失败时为 null(文字仍在 reply_text,只是没出声)。"
          },
          "audio_format": {
            "type": "string",
            "enum": [
              "mp3"
            ],
            "description": "回复音频格式,恒为 mp3。"
          }
        }
      },
      "EmailStartRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "description": "收码邮箱(服务端 trim + 转小写)"
          }
        }
      },
      "EmailVerifyRequest": {
        "type": "object",
        "required": [
          "email",
          "code"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "code": {
            "type": "string",
            "description": "6 位数字验证码(5 分钟有效,错 5 次锁定)"
          }
        }
      },
      "Me": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "tenant_id": {
            "type": "string"
          }
        }
      },
      "ApiKeyMeta": {
        "type": "object",
        "description": "key 元数据(绝不含 key_hash / 明文)",
        "properties": {
          "id": {
            "type": "string"
          },
          "prefix": {
            "type": "string",
            "description": "明文前 8 字符(非密钥),用于辨认"
          },
          "created_at": {
            "type": "integer",
            "description": "签发时间(epoch ms)"
          },
          "revoked_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "撤销时间(epoch ms);null = 仍有效"
          },
          "rate_limit": {
            "type": "integer",
            "description": "每分钟请求上限;0 = 不限流"
          },
          "app_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "归属应用 id(W3 新增,additive);null = 租户通用 key"
          }
        }
      },
      "IssueKeyRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "展示用标签(当前接收但不落库)"
          },
          "rate_limit": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100000,
            "default": 60,
            "description": "每分钟请求上限;0 = 不限流;超 100000 钳到 100000"
          }
        }
      },
      "IssueKeyResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "prefix": {
            "type": "string"
          },
          "rate_limit": {
            "type": "integer"
          },
          "key": {
            "type": "string",
            "description": "key 明文(sk-meow-…)。只在本响应出现一次,之后无从取回"
          }
        }
      },
      "UsageResponse": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer",
            "description": "本租户累计调用数"
          },
          "daily": {
            "type": "array",
            "description": "近 7 天逐日计数(升序)",
            "items": {
              "type": "object",
              "properties": {
                "day": {
                  "type": "integer",
                  "description": "epoch 天序号(= floor(ts/86400000))"
                },
                "count": {
                  "type": "integer"
                }
              }
            }
          },
          "key_count": {
            "type": "integer",
            "description": "本租户 key 总数(含已撤销)"
          }
        }
      },
      "Persona": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          },
          "is_default": {
            "type": "integer",
            "description": "1 = 本租户默认人格;0 = 否(同租户至多一个 1)"
          },
          "created_at": {
            "type": "integer",
            "description": "epoch ms"
          }
        }
      },
      "CreatePersonaRequest": {
        "type": "object",
        "required": [
          "name",
          "prompt"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "人格名,trim 后非空,≤200 字"
          },
          "prompt": {
            "type": "string",
            "maxLength": 8000,
            "description": "system prompt,trim 后非空,≤8000 字"
          },
          "is_default": {
            "type": "boolean",
            "default": false,
            "description": "true 则先清掉本租户其它默认再置位"
          }
        }
      },
      "TenantSettings": {
        "type": "object",
        "description": "每租户 LLM 配置。null/缺省 = 平台默认(与不配置逐字节一致)",
        "properties": {
          "model": {
            "type": [
              "string",
              "null"
            ],
            "description": "模型名;null = 平台默认(MiniMax-M3)"
          },
          "thinking": {
            "type": "boolean",
            "description": "思考开关;默认 false(关,更快)"
          },
          "temperature": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 2,
            "description": "温度 0~2;null = 不传(provider 默认)"
          }
        }
      },
      "KbDoc": {
        "type": "object",
        "description": "知识库文档元数据(正文按块存于服务端,列表不回正文)",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "char_count": {
            "type": "integer",
            "description": "正文字符数(按 Unicode 字符计)"
          },
          "chunk_count": {
            "type": "integer",
            "description": "分块数(~600 字/块,相邻重叠 ~100 字)"
          },
          "created_at": {
            "type": "string",
            "description": "ISO 8601 UTC"
          }
        }
      },
      "KbCreateRequest": {
        "type": "object",
        "required": [
          "title",
          "text"
        ],
        "properties": {
          "title": {
            "type": "string",
            "maxLength": 200,
            "description": "文档标题(检索结果以〔标题#块号〕标注来源)"
          },
          "text": {
            "type": "string",
            "description": "正文(纯文本 / Markdown;单篇 ≤100KB)"
          }
        }
      },
      "KbCreateResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "文档 id(删除时用)"
          },
          "title": {
            "type": "string"
          },
          "char_count": {
            "type": "integer"
          },
          "chunk_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string"
          },
          "note": {
            "type": "string",
            "description": "「已入库;向量索引异步生效(约数秒),稍候可检索」"
          }
        }
      },
      "KbSearchRequest": {
        "type": "object",
        "required": [
          "q"
        ],
        "properties": {
          "q": {
            "type": "string",
            "description": "检索问题(单条嵌入后在本租户 namespace 内查 top-5)"
          }
        }
      },
      "KbSearchResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "description": "top-5 命中(score 降序;调试用不过滤阈值——agent 注入路径才按 score≥0.45 过滤)",
            "items": {
              "type": "object",
              "properties": {
                "doc_id": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "seq": {
                  "type": "integer",
                  "description": "块序号(0 起)"
                },
                "score": {
                  "type": "number",
                  "description": "余弦相似度(越大越相似)"
                },
                "text": {
                  "type": "string",
                  "description": "块文本预览(截断 ~200 字)"
                }
              }
            }
          }
        }
      },
      "App": {
        "type": "object",
        "description": "应用(App)= 一个可发布的喵喵分身(ADR-0014)。所有可空字段 null = 不覆盖,逐字段回落 租户设置 → 平台默认",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "maxLength": 60,
            "description": "应用名(≤60 字)"
          },
          "system_prompt": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 8000,
            "description": "直写 system prompt(最高优先);null = 用 persona_id / 回落"
          },
          "persona_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "人格库引用(personas.id);写入时不校验存在性,解析时查不到静默回落下一级"
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "description": "模型覆盖;null = 承租户「模型设置」→ 平台默认"
          },
          "thinking": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "思考开关覆盖;null = 承租户设置"
          },
          "temperature": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 2,
            "description": "温度覆盖;null = 承租户 → provider 默认"
          },
          "kb_enabled": {
            "type": "boolean",
            "description": "该 App 对话是否启用知识库注入;false = 恒关(与请求 kb:false 同效)"
          },
          "tools_enabled": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "工具白名单;null = 全部内置工具;[] = 全部禁用"
          },
          "created_at": {
            "type": "string",
            "description": "ISO 8601 UTC"
          }
        }
      },
      "AppUpsertRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "description": "POST(新建)与 PUT(整体更新)同体。**PUT 是整体替换**:缺省字段 = 清回「不覆盖」,非逐字段 PATCH",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 60,
            "description": "trim 后非空,≤60 字"
          },
          "system_prompt": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 8000,
            "description": "与 persona_id 同给时以本字段为准"
          },
          "persona_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "model": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "thinking": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "temperature": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 2
          },
          "kb_enabled": {
            "type": "boolean",
            "default": true
          },
          "tools_enabled": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "maxLength": 64
            },
            "maxItems": 16,
            "description": "≤16 项,每项 trim 后非空 ≤64 字;[] = 全禁;缺省/null = 全部"
          }
        }
      },
      "AppKeyIssueResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "prefix": {
            "type": "string"
          },
          "rate_limit": {
            "type": "integer"
          },
          "app_id": {
            "type": "string",
            "description": "归属应用 id"
          },
          "key": {
            "type": "string",
            "description": "App 级 key 明文(sk-meow-…)。只在本响应出现一次,之后无从取回"
          }
        }
      },
      "ProviderPutRequest": {
        "type": "object",
        "required": [
          "base_url",
          "api_key"
        ],
        "description": "配置 BYO 通道(W5,ADR-0016)。只读这三字段,体里任何 tenant_id 一概忽略。key 服务端 AES-256-GCM 加密落库,明文不回显",
        "properties": {
          "base_url": {
            "type": "string",
            "maxLength": 300,
            "description": "OpenAI 兼容端点根(如 https://api.openai.com/v1)。只许 http(s)://,host 不得是内网字面量(localhost / 127.* / 10.* / 192.168.* / 169.254.* 等)"
          },
          "api_key": {
            "type": "string",
            "maxLength": 500,
            "description": "该端点的 key(trim 后非空)。只在本请求体出现这一次,之后无从取回(GET 只回末 4 位)"
          },
          "failover": {
            "type": "boolean",
            "default": true,
            "description": "BYO 失败是否自动回落平台通道(回落会发 SSE provider_fallback 事件,绝不静默);false = 如实回错"
          }
        }
      },
      "ProviderConfig": {
        "type": "object",
        "description": "BYO 通道状态(GET 与 PUT 的响应同形)。**绝不含 key 明文 / 密文**",
        "properties": {
          "configured": {
            "type": "boolean",
            "description": "是否已配齐(base_url 与 key 都在)"
          },
          "base_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "已配置的端点;未配置为 null"
          },
          "key_tail": {
            "type": [
              "string",
              "null"
            ],
            "description": "key 末 4 位(非密钥,辨认用);未配置 / 密文暂不可解(如密钥轮换后)为 null"
          },
          "failover": {
            "type": "boolean",
            "description": "失败是否回落平台通道"
          }
        }
      },
      "ProviderTestResponse": {
        "type": "object",
        "description": "通道探针结果:用已保存配置真打一发最小补全(max_tokens:1,单次不重试)",
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "端点是否 200"
          },
          "latency_ms": {
            "type": "integer",
            "description": "本次探针耗时(毫秒)"
          },
          "error": {
            "type": "string",
            "description": "ok=false 时的脱敏错误短语(如「通道返回 HTTP 401」;不含 key/URL 细节)"
          }
        }
      },
      "Device": {
        "type": "object",
        "description": "设备(ADR-0015)。响应**绝不含**激活码明文或哈希;activated_at 非空 = 码已兑换作废",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "maxLength": 60,
            "description": "设备名(≤60 字)"
          },
          "app_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "绑定应用(apps.id);激活时继承给设备 key;null = 通用行为。写入时不校验存在性,App 已删则对话按通用行为降级"
          },
          "hw_model": {
            "type": [
              "string",
              "null"
            ],
            "description": "硬件型号(登记可给,心跳可更新;纯展示)"
          },
          "firmware": {
            "type": [
              "string",
              "null"
            ],
            "description": "固件版本(心跳上报;只跟踪展示,不做 OTA 分发)"
          },
          "activated_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "激活时刻(ISO 8601);null = 未激活(码未兑换)"
          },
          "last_seen_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "最后在线(ISO 8601;心跳与设备 key 调 /v1/agent 时尽力而为刷新)"
          },
          "code_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "当前激活码过期时刻(ISO 8601;非密钥)"
          },
          "created_at": {
            "type": "string",
            "description": "ISO 8601 UTC"
          }
        }
      },
      "DeviceRegisterRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 60,
            "description": "trim 后非空,≤60 字"
          },
          "app_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "绑定应用(可选;不做写时存在性校验,先例 ADR-0014 persona_id)"
          },
          "hw_model": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          }
        }
      },
      "DeviceRegisterResponse": {
        "type": "object",
        "description": "登记成功。activation_code 为**明文一次性激活码**,只在本响应出现一次(库里只存 sha256 哈希),15 分钟内有效",
        "properties": {
          "id": {
            "type": "string",
            "description": "设备 id"
          },
          "name": {
            "type": "string"
          },
          "app_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "hw_model": {
            "type": [
              "string",
              "null"
            ]
          },
          "activation_code": {
            "type": "string",
            "description": "16 位 base32 一次性激活码(明文只此一次)"
          },
          "code_expires_at": {
            "type": "string",
            "description": "码过期时刻(ISO 8601,15 分钟 TTL)"
          },
          "created_at": {
            "type": "string"
          }
        }
      },
      "DeviceActivateRequest": {
        "type": "object",
        "required": [
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "一次性激活码(登记/重发码响应里的 activation_code;大小写不敏感)"
          }
        }
      },
      "DeviceActivateResponse": {
        "type": "object",
        "description": "兑换成功(码即刻作废)。key 为设备 key 明文,只回这一次",
        "properties": {
          "device_id": {
            "type": "string"
          },
          "key": {
            "type": "string",
            "description": "设备 key 明文(sk-meow-…)。只在本响应出现一次;调 /v1/agent 即该设备身份(绑了 App 即该 App 人设)"
          },
          "app_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "该设备 key 继承的应用 id;null = 通用行为"
          }
        }
      },
      "DeviceRecodeResponse": {
        "type": "object",
        "description": "重发码成功(= 重新配对:旧码作废,设备旧 key 已撤销)。activation_code 明文只回这一次",
        "properties": {
          "id": {
            "type": "string"
          },
          "activation_code": {
            "type": "string",
            "description": "新的 16 位 base32 一次性激活码(明文只此一次)"
          },
          "code_expires_at": {
            "type": "string",
            "description": "新码过期时刻(ISO 8601,15 分钟 TTL)"
          }
        }
      },
      "DeviceHeartbeatRequest": {
        "type": "object",
        "description": "两字段皆可选(纯打点也合法);体可缺省",
        "properties": {
          "firmware": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "固件版本(更新展示;不触发任何分发)"
          },
          "hw_model": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          }
        }
      },
      "RequestLogEntry": {
        "type": "object",
        "description": "一条请求日志(纯元数据;**没有任何内容字段**——后端 schema 即如此,ADR-0017 红线)",
        "properties": {
          "id": {
            "type": "string"
          },
          "app_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "App 级 key 的请求带其 app_id;通用 key 为 null"
          },
          "device_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "设备 key 的请求带其 device_id;非设备 key 为 null"
          },
          "endpoint": {
            "type": "string",
            "description": "端点标签(固定小集合:/v1/agent、/v1/tts、/v1/tts/stream、/v1/asr、/v1/kb/docs、/v1/kb/docs/:id、/v1/kb/search)"
          },
          "status": {
            "type": "integer",
            "description": "最终 HTTP 状态码(SSE 流内 error 不改状态码)"
          },
          "latency_ms": {
            "type": [
              "integer",
              "null"
            ],
            "description": "处理时延 ms;agent/流式端点 = 到响应头就绪(非整流结束)"
          },
          "session_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "会话 id(仅 /v1/agent;寻址串,非内容)"
          },
          "text_chars": {
            "type": [
              "integer",
              "null"
            ],
            "description": "涉及文本的字符数(**只记长度,绝不记原文**;无文本端点为 null)"
          },
          "error_class": {
            "type": [
              "string",
              "null"
            ],
            "description": "错误分类词(4xx/5xx;成功为 null;绝不放上游错误原文)"
          },
          "channel": {
            "type": [
              "string",
              "null"
            ],
            "description": "通道(agent 路径:byo|platform,**配置口径**——本轮是否实际回落只在 SSE stats 事件;其余端点 null)"
          },
          "ts": {
            "type": "string",
            "description": "记录时刻(ISO 8601 UTC)"
          }
        }
      },
      "LogsResponse": {
        "type": "object",
        "description": "日志查询响应:行(ts 倒序,≤limit)+ summary(对满足筛选的**全部行** SQL 聚合,非仅返回页)",
        "properties": {
          "logs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RequestLogEntry"
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer",
                "description": "筛选范围内总行数"
              },
              "err_count": {
                "type": "integer",
                "description": "其中 status≥400 的行数"
              },
              "p50_ms": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "时延中位数(lower nearest-rank 变体;无可算行为 null)"
              },
              "p95_ms": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "时延 95 分位(lower nearest-rank 变体;无可算行为 null)"
              }
            }
          }
        }
      },
      "ErrorText": {
        "type": "string",
        "description": "错误响应体为简短中文文本(text/plain)"
      },
      "DeviceUpdateRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "description": "2.1 期改名/换绑体:只收 name / app_id 两字段(激活态/码/固件不可经 PUT 触碰,多给的字段被忽略);校验口径与登记一致",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 60,
            "description": "trim 后非空,≤60 字"
          },
          "app_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "换绑应用;null/空串 = 解绑。只影响之后的激活继承——已发设备 key 不动(要换人设走 recode 重配对);缺省不传同 null = 解绑,纯改名请回传现值"
          }
        }
      },
      "PersonaUpdateRequest": {
        "type": "object",
        "required": [
          "name",
          "prompt"
        ],
        "description": "2.1 期人格更新体(校验同 create;is_default 不经本端点改,走 /default 专职端点)",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "description": "trim 后非空,≤200 字"
          },
          "prompt": {
            "type": "string",
            "maxLength": 8000,
            "description": "trim 后非空,≤8000 字"
          }
        }
      },
      "KbChunksResponse": {
        "type": "object",
        "description": "2.1 期文档分块预览(行级 WHERE tenant_id;按 seq 升序,上限 50 块;只回块文本,不含向量/内部 id)",
        "properties": {
          "chunks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "seq": {
                  "type": "integer",
                  "description": "块序号(0 起)"
                },
                "text": {
                  "type": "string",
                  "description": "块文本"
                }
              }
            }
          }
        }
      },
      "ConfigExport": {
        "type": "object",
        "description": "导出的可移植配置(ADR-0023)。**绝不含任何密钥**(API key/BYO provider 密文都不导),也不含知识库内容与设备。app 的 persona 以 name 引用(persona_ref),非 id",
        "properties": {
          "format_version": {
            "type": "integer",
            "enum": [
              1
            ],
            "description": "格式版本,当前恒为 1;导入只认此版本,不认 → 400"
          },
          "exported_at": {
            "type": "string",
            "description": "导出时刻 ISO 8601 UTC"
          },
          "personas": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "prompt": {
                  "type": "string"
                },
                "is_default": {
                  "type": "boolean"
                }
              }
            }
          },
          "apps": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "system_prompt": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "persona_ref": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "引用的人格**名字**(非 id);导出时由 app.persona_id 解析而来,悬挂引用 → null"
                },
                "model": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "thinking": {
                  "type": [
                    "boolean",
                    "null"
                  ]
                },
                "temperature": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "minimum": 0,
                  "maximum": 2
                },
                "kb_enabled": {
                  "type": "boolean"
                },
                "tools_enabled": {
                  "type": [
                    "array",
                    "null"
                  ],
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "settings": {
            "type": "object",
            "description": "租户 LLM 设置三字段(**不含 BYO 密文**)",
            "properties": {
              "model": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "thinking": {
                "type": "boolean"
              },
              "temperature": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 2
              }
            }
          }
        }
      },
      "ImportRequest": {
        "type": "object",
        "required": [
          "config"
        ],
        "description": "导入请求。config 是导出产物;**其内任何 tenant 字段一概忽略**(租户身份恒取自鉴权)",
        "properties": {
          "config": {
            "$ref": "#/components/schemas/ConfigExport"
          },
          "mode": {
            "type": "string",
            "enum": [
              "skip",
              "merge"
            ],
            "default": "skip",
            "description": "同名冲突:skip(默认,跳过保留)/ merge(覆盖)。未知值按 skip 兜底"
          }
        }
      },
      "ImportResult": {
        "type": "object",
        "description": "导入结果(逐项尽力而为,ADR-0023)。imported 为各类成功写入计数;skipped/errors 为跳过/失败的逐项中文说明(单项失败不中止整批)",
        "properties": {
          "imported": {
            "type": "object",
            "properties": {
              "personas": {
                "type": "integer",
                "description": "成功写入的人格数(新建 + merge 覆盖)"
              },
              "apps": {
                "type": "integer",
                "description": "成功写入的应用数(新建 + merge 覆盖)"
              },
              "settings": {
                "type": "boolean",
                "description": "设置是否已 merge 写入"
              }
            }
          },
          "skipped": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "跳过项(同名 skip / 超配额)的中文说明"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "失败项(校验不过 / 写库失败)的中文说明"
          }
        }
      },
      "AppExchangeRequest": {
        "type": "object",
        "required": [
          "code",
          "code_verifier"
        ],
        "description": "原生 App 流换 key 请求(ADR-0025)。code 来自 GitHub 回调深链 meowbot://auth/callback?code=;code_verifier 是发起授权时本地生成的 PKCE 随机串(其 sha256 的 base64url 即发起时传的 code_challenge)",
        "properties": {
          "code": {
            "type": "string",
            "description": "GitHub 回调深链带回的一次性 code(KV 暂存 5 分钟、单次有效)"
          },
          "code_verifier": {
            "type": "string",
            "description": "PKCE code verifier(高熵随机串)。服务端校验 base64url-no-pad(sha256(code_verifier)) 等于发起时存的 code_challenge(RFC 7636,仅 S256)"
          }
        }
      },
      "AppExchangeResult": {
        "type": "object",
        "description": "换 key 成功响应(ADR-0025)。api_key 明文只在此返回一次,服务端只存 sha256 哈希",
        "properties": {
          "api_key": {
            "type": "string",
            "description": "新签发的 API key 明文(sk-meow- 前缀 + 64 位十六进制)。**只此一次**,之后任何接口不再返回"
          },
          "tenant_id": {
            "type": "string",
            "description": "该 key 所属租户 id(来自 GitHub 身份 upsert 的真租户)"
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "未授权:key/会话 缺失、无效或已撤销(统一 401,不区分原因,防探测)"
      },
      "ForbiddenAppKey": {
        "description": "「App 级 key 无管理权」:持 App 级 key(app_id 非空)调 /v1/apps* 管理面被拒(最小权限,ADR-0014 D3;W4 起设备 key 调 /v1/apps* 同样 403「设备 key 无管理权」)。注意与 401 的区别:401 = key 本身无效;403 = key 有效但无管理权"
      },
      "ForbiddenRuntimeKey": {
        "description": "运行凭证触碰管理面被拒(最小权限,ADR-0015 D5):持设备 key → 403「设备 key 无管理权」;持 App 级 key → 403「App 级 key 无管理权」。只有租户通用 key / 控制台会话能管理设备。注意与 401 的区别:401 = key 本身无效;403 = key 有效但无管理权"
      },
      "RateLimited": {
        "description": "限流:该 key 固定窗口(60s)内超额。0 = 不限流的 key 不会触发",
        "headers": {
          "Retry-After": {
            "description": "几秒后可重试(向上取整,≥1)",
            "schema": {
              "type": "integer"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/healthz": {
      "get": {
        "tags": [
          "platform"
        ],
        "summary": "健康检查",
        "operationId": "healthz",
        "responses": {
          "200": {
            "description": "服务在线,响应体为纯文本 ok",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "const": "ok"
                }
              }
            }
          }
        }
      }
    },
    "/v1/agent": {
      "post": {
        "tags": [
          "agent"
        ],
        "summary": "Agent 对话(SSE 流式)",
        "operationId": "agentTurn",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SSE 事件流(头含 Cache-Control: no-cache、X-Accel-Buffering: no)。注意:HTTP 200 之后的运行期错误走流内 error 事件,不再改状态码",
            "content": {
              "text/event-stream": {
                "schema": {
                  "$ref": "#/components/schemas/SseEventStream"
                }
              }
            }
          },
          "400": {
            "description": "请求体不是合法 JSON(bad request body)或 text 为空白(「text 不能为空」)"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/sessions/{id}/history": {
      "get": {
        "tags": [
          "agent"
        ],
        "summary": "读取会话历史与记忆摘要",
        "operationId": "sessionHistory",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "session_id(与 /v1/agent 写入侧同名即同会话;空回落 anon)。只能读到本租户的会话。特殊字符需 URL 编码且读写两侧处理不对称,建议只用字母数字与 - _"
          }
        ],
        "responses": {
          "200": {
            "description": "会话快照;从未用过的会话返回空 messages + count=0(不报 404)。绝不含 key 或 base64 图片",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoryResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/sessions": {
      "get": {
        "tags": [
          "agent"
        ],
        "summary": "列出本租户会话(机器侧;纯元数据,不含对话内容)",
        "operationId": "sessionsList",
        "description": "补 Durable Object 无中央枚举的缺口(ADR-0022):返回本租户会话索引,按 last_at 倒序。**只回元数据**(id/轮数/时间/归属),内容走 /v1/sessions/{id}/history。注意:本路径(/v1/sessions)与 /v1/sessions/{id}/history 是不同路径,互不冲突",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            },
            "description": "返回条数(默认 100,上限 500;非法值回落默认)"
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "只返回 last_at 不早于此刻的会话(ISO 8601,含;字典序比较)"
          }
        ],
        "responses": {
          "200": {
            "description": "last_at 倒序的会话索引(纯元数据)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenRuntimeKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/v1/sessions/{id}": {
      "delete": {
        "tags": [
          "agent"
        ],
        "summary": "删除一个会话(清记忆 + 删索引)",
        "operationId": "sessionDelete",
        "description": "删会话(ADR-0022 D4):① 清该会话 Durable Object 的 storage(逐轮历史 + 摘要,真清记忆)② 删 D1 会话索引行。**幂等**:会话不存在也回 200{deleted:true}。删后 /history 读到空、列表不含它。租户隔离两层(DO 名带 tenant_id + D1 行级 WHERE)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "要删除的 session_id(空回落 anon)。只能删本租户的会话"
          }
        ],
        "responses": {
          "200": {
            "description": "已删除(幂等:不存在也回此)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenRuntimeKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "内部错误(DO 记忆清除或索引删除失败,可整体重试;删除幂等)"
          }
        }
      }
    },
    "/v1/tts": {
      "post": {
        "tags": [
          "voice"
        ],
        "summary": "语音合成(同步,整段 mp3)",
        "operationId": "ttsSync",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TtsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "mp3 音频字节(32kHz / 128kbps)",
            "content": {
              "audio/mpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "请求体坏 / text 为空"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "「语音合成暂时不可用」(上游或配置问题)"
          }
        }
      }
    },
    "/v1/tts/stream": {
      "post": {
        "tags": [
          "voice"
        ],
        "summary": "语音合成(流式 PCM,边合成边吐)",
        "operationId": "ttsStream",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TtsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "裸 PCM 流:s16le / 32000Hz / 单声道,首块约 1.5s。注意:流开始(200)后上游失败 = 流提前结束,无带内错误",
            "headers": {
              "X-Audio-Format": {
                "description": "固定 pcm_s16le_32000_mono",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "请求体坏 / text 为空"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "配置缺失等启动期失败"
          }
        }
      }
    },
    "/v1/asr": {
      "post": {
        "tags": [
          "voice"
        ],
        "summary": "语音识别(音频字节进、文本出)",
        "description": "后端 Workers AI `whisper-large-v3-turbo`(边缘原生、**零外部 key**),中文固定 language=zh + 标点引导(REQ-0002/ADR-0026);出参归一为 `{text}`,与基础版契约一致、调用方零改动。",
        "operationId": "asr",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "请求体 = 原始音频字节(非 JSON):wav/mp3/flac/ogg 等 Whisper 可识别格式,上限 25MB",
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "识别结果",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AsrResponse"
                }
              }
            }
          },
          "400": {
            "description": "「音频为空」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "413": {
            "description": "「音频过大(上限 25MB)」"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "「语音识别暂时不可用」/「语音识别失败」"
          }
        }
      }
    },
    "/v1/voice": {
      "post": {
        "tags": [
          "voice"
        ],
        "summary": "语音对话闭环(音频进 → asr→agent→tts → 智能音频出,一趟请求)",
        "description": "ADR-0019:把「音频进 → 智能音频出」做成服务端一等公民。base64 音频 → Whisper 转文本(asr_text)→ 以该文本走与 /v1/agent **同一条** 链路(工具/RAG/人格/记忆/BYO/多租户全继承;kb、persona_id 透传)拿完整回复(reply_text)→ MiniMax 合成 mp3 → base64 回返。Bearer 鉴权(App/设备 key 照常可用)。延迟 = asr + agent + tts 串行之和(同步 JSON 版口径;流式版见 ADR-0019 D5 升级路径)。",
        "operationId": "voiceChat",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VoiceRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "闭环结果:asr_text + reply_text + audio_b64(base64 mp3)。**降级**:tts 失败时仍回 200,但 audio_b64 为 null(文字成功、只是没出声),客户端据此只读文字。",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceResponse"
                }
              }
            }
          },
          "400": {
            "description": "请求体坏 /「audio_b64 不是合法 base64」/「音频为空」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "413": {
            "description": "「音频过大(上限 25MB)」(base64 解码后字节超 /v1/asr 上限)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "ASR 段失败 →「听不清,请再说一遍喵」;agent 段失败 → 透传 DO 的喵口吻错误文案。(注:tts 段失败**不**走 502,而是 200 + audio_b64:null 降级。)"
          }
        }
      }
    },
    "/v1/kb/docs": {
      "get": {
        "tags": [
          "kb"
        ],
        "summary": "列出本租户知识库文档(机器侧)",
        "operationId": "kbListDocs",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "created_at 倒序;不含正文",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "docs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/KbDoc"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "内部错误"
          }
        }
      },
      "post": {
        "tags": [
          "kb"
        ],
        "summary": "上传文档入库(分块 → 嵌入 → 向量入索引;机器侧)",
        "operationId": "kbCreateDoc",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KbCreateRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "已入库。**向量索引异步生效(约数秒)**,刚传完立刻检索可能查不到,稍候重试",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KbCreateResponse"
                }
              }
            }
          },
          "400": {
            "description": "「title 不能为空」/「text 不能为空」/「标题过长(上限 200 字)」/「请求体格式错误」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "「文档数已达上限(每租户 50 篇),删几篇再传喵」"
          },
          "413": {
            "description": "「文档过大(单篇上限 100KB),拆小一点再传喵」"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(配额计数 / 生成 id / D1 落库失败;错误体中文明说)"
          },
          "502": {
            "description": "嵌入 / 向量写入暂时不可用(已脱敏)"
          }
        }
      }
    },
    "/v1/kb/docs/{id}": {
      "delete": {
        "tags": [
          "kb"
        ],
        "summary": "删除文档(同步删 D1 文本 + 删向量;机器侧)",
        "operationId": "kbDeleteDoc",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "文档 id(列表 / 上传响应里的 id)"
          }
        ],
        "responses": {
          "200": {
            "description": "{id, deleted:true}。删除后该文档立刻不再被检索/注入(文本行已删,残留向量查不出文本)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "「缺少文档 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该文档」(不存在或属别租户,不区分,防枚举)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / 取块清单 / 删行失败;错误体中文明说)"
          },
          "502": {
            "description": "向量删除暂时不可用(D1 未动,可整体重试)"
          }
        }
      }
    },
    "/v1/kb/search": {
      "post": {
        "tags": [
          "kb"
        ],
        "summary": "检索测试(看哪些块会被引用;机器侧)",
        "operationId": "kbSearch",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KbSearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "top-5 命中(score 降序)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KbSearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "「q 不能为空」/「请求体格式错误」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "嵌入 / 检索暂时不可用(已脱敏)"
          }
        }
      }
    },
    "/v1/apps": {
      "get": {
        "tags": [
          "apps"
        ],
        "summary": "列出本租户应用(机器侧;租户通用 key 限定)",
        "operationId": "appsList",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "created_at 倒序",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apps": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/App"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenAppKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(列表查询失败;错误体中文明说)"
          }
        }
      },
      "post": {
        "tags": [
          "apps"
        ],
        "summary": "新建应用(机器侧;租户通用 key 限定)",
        "operationId": "appsCreate",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppUpsertRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "建好的完整 App(含 id;之后用 id 签 App 级 key)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/App"
                }
              }
            }
          },
          "400": {
            "description": "「请求体格式错误」/「name 不能为空」/「名称过长(上限 60 字)」/「system_prompt 过长(上限 8000 字)」/「模型名过长」/「温度需在 0~2 之间」/「工具白名单过长(上限 16 项)」/「工具名不能为空」/「工具名过长(上限 64 字)」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "「App 级 key 无管理权」(最小权限)或「应用数已达上限(每租户 20 个),删几个再建喵」(配额)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(配额计数 / 生成 id / D1 落库失败;错误体中文明说)"
          }
        }
      }
    },
    "/v1/apps/{id}": {
      "get": {
        "tags": [
          "apps"
        ],
        "summary": "应用详情(机器侧;租户通用 key 限定)",
        "operationId": "appsGet",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "应用 id(列表 / 新建响应里的 id)"
          }
        ],
        "responses": {
          "200": {
            "description": "完整 App",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/App"
                }
              }
            }
          },
          "400": {
            "description": "「缺少应用 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenAppKey"
          },
          "404": {
            "description": "「未找到该应用」(不存在或属别租户,不区分,防枚举)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(详情查询失败;错误体中文明说)"
          }
        }
      },
      "put": {
        "tags": [
          "apps"
        ],
        "summary": "整体更新应用(机器侧;缺省字段 = 清回「不覆盖」,非 PATCH)",
        "operationId": "appsUpdate",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppUpsertRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新后的完整 App(created_at 不变)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/App"
                }
              }
            }
          },
          "400": {
            "description": "同 POST /v1/apps 的 400 枚举"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenAppKey"
          },
          "404": {
            "description": "「未找到该应用」(防枚举)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / 写 D1 失败;错误体中文明说)"
          }
        }
      },
      "delete": {
        "tags": [
          "apps"
        ],
        "summary": "删除应用(其全部 App key 同步撤销,立刻 401;会话历史保留)",
        "operationId": "appsDelete",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{id, deleted:true}。key 撤销在删行之前完成;撤销失败则中止不删行,可整体重试(撤销幂等)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "「缺少应用 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenAppKey"
          },
          "404": {
            "description": "「未找到该应用」(防枚举)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / key 撤销失败(应用未删,请重试)/ 删行失败;错误体中文明说)"
          }
        }
      }
    },
    "/v1/apps/{id}/keys": {
      "post": {
        "tags": [
          "apps"
        ],
        "summary": "签发 App 级 key(机器侧;明文只回显一次)",
        "operationId": "appsIssueKey",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "description": "体可缺省 / 空(全用默认值);rate_limit 口径同 POST /console/keys",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IssueKeyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "签发成功;key 字段为明文,只回这一次。该 key 调 /v1/agent 即此 App 的配置,调 /v1/apps* 一律 403",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppKeyIssueResponse"
                }
              }
            }
          },
          "400": {
            "description": "「缺少应用 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenAppKey"
          },
          "404": {
            "description": "「未找到该应用」(防枚举)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / 取随机 / 写 D1 失败;错误体中文明说)"
          }
        }
      }
    },
    "/v1/provider": {
      "get": {
        "tags": [
          "provider"
        ],
        "summary": "读 BYO 通道状态(机器侧;明文不回显,只 key_tail)",
        "operationId": "providerGet",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "未配置 → {configured:false,…}(200 而非 404——未配置是正常态)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderConfig"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenAppKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "内部错误"
          },
          "503": {
            "description": "「BYO 未启用」(平台未注入 PROVIDER_ENC_KEY)"
          }
        }
      },
      "put": {
        "tags": [
          "provider"
        ],
        "summary": "配置 BYO 通道(机器侧;key 加密落库)",
        "operationId": "providerPut",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProviderPutRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "已保存;响应同 GET 形状(含 key_tail,无明文)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderConfig"
                }
              }
            }
          },
          "400": {
            "description": "「请求体格式错误」/「base_url 不能为空」/「base_url 只支持 http:// 或 https://」/「base_url 过长(上限 300 字符)」/「base_url 不能指向内网地址」/「base_url 不支持 IPv6 字面量」/「base_url 缺少主机名」/「api_key 不能为空」/「api_key 过长(上限 500 字符)」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenAppKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "内部错误(取随机 / 加密 / 落库失败)"
          },
          "503": {
            "description": "「BYO 未启用」(平台未注入 PROVIDER_ENC_KEY)"
          }
        }
      },
      "delete": {
        "tags": [
          "provider"
        ],
        "summary": "清除 BYO 通道(机器侧;回平台通道,幂等)",
        "operationId": "providerDelete",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "{configured:false, deleted:true};未配置时同样 200(幂等)。本端点不依赖 PROVIDER_ENC_KEY(清理路径不可被锁死)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "configured": {
                      "type": "boolean"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenAppKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/v1/provider/test": {
      "post": {
        "tags": [
          "provider"
        ],
        "summary": "测试 BYO 通道(机器侧;用已保存配置打一发最小补全)",
        "operationId": "providerTest",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "探针结果(ok=false 也是 200——端点本身成功,结果是数据)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderTestResponse"
                }
              }
            }
          },
          "400": {
            "description": "「尚未配置 BYO 通道」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenAppKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "内部错误(密文不可用等,错误体中文明说)"
          },
          "503": {
            "description": "「BYO 未启用」(平台未注入 PROVIDER_ENC_KEY)"
          }
        }
      }
    },
    "/v1/logs": {
      "get": {
        "tags": [
          "logs"
        ],
        "summary": "本租户请求日志(机器侧;纯元数据,不含对话内容)",
        "operationId": "logsList",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            },
            "description": "返回条数(默认 100,上限 500;非法值回落默认)"
          },
          {
            "name": "endpoint",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "按端点精确筛(如 /v1/agent)"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "按状态码精确筛(100~599 之外丢弃)"
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "起始时刻(ISO 8601,含;ts 为 TEXT ISO 列,字典序比较)"
          }
        ],
        "responses": {
          "200": {
            "description": "ts 倒序的日志行 + summary 聚合",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LogsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenRuntimeKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/auth/email/start": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "邮箱登录第一步:发送验证码",
        "operationId": "emailStart",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailStartRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{\"ok\":true}。任何合法邮箱一律同响应(防注册枚举);绝不回显验证码",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "「邮箱格式不正确」/「请求体格式错误」"
          },
          "500": {
            "description": "发信或存储失败(已脱敏)"
          }
        }
      }
    },
    "/auth/email/verify": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "邮箱登录第二步:验码并登录(首登自动开租户)",
        "operationId": "emailVerify",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailVerifyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "登录成功:{\"tenant_id\":…} + Set-Cookie: mb_session(HttpOnly,7 天)",
            "headers": {
              "Set-Cookie": {
                "description": "mb_session 会话 cookie",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tenant_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "参数不正确"
          },
          "401": {
            "description": "「验证码无效或已过期」(无此邮箱的码 / 码错 / 过期,不区分)"
          },
          "429": {
            "description": "「尝试次数过多,请重新获取验证码」(≥5 次)"
          },
          "500": {
            "description": "服务暂时不可用"
          }
        }
      }
    },
    "/auth/github": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "发起 GitHub OAuth 登录(网页流 / 原生 App 流双模式)",
        "description": "不带任何参数 = 网页流(回调种 mb_session cookie 跳 /console,行为不变)。带 app=1 + PKCE 参数 = 原生 App 流(ADR-0025):回调改 302 深链 meowbot://auth/callback?code=<一次性 code>,App 再 POST /auth/app/exchange 用 code+code_verifier 换 API key。GitHub OAuth App 回调地址在两种模式下都不变。",
        "operationId": "githubStart",
        "parameters": [
          {
            "name": "app",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            },
            "description": "可选。app=1 启用原生 App 流(深链回跳 + code/PKCE 换 key)。缺省 = 网页 cookie 流,行为完全不变(ADR-0025)"
          },
          {
            "name": "code_challenge",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "可选(app=1 时必带)。PKCE code challenge = base64url-no-pad(sha256(code_verifier))。服务端随 state 暂存于 KV(TTL 10 分钟),回调时绑定到一次性 code,exchange 时校验(RFC 7636)"
          },
          {
            "name": "code_challenge_method",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "S256"
              ]
            },
            "description": "可选(app=1 时必带)。**仅支持 S256**(plain 不接受——绑定强度,ADR-0025 D1)"
          }
        ],
        "responses": {
          "302": {
            "description": "跳转 github.com 授权页,并种短期 CSRF state cookie(gh_oauth_state,HttpOnly,10 分钟)。app=1 时额外把 code_challenge 随 state 暂存 KV(回调走深链而非 cookie);两种模式都跳 GitHub 授权页",
            "headers": {
              "Location": {
                "description": "GitHub 授权 URL",
                "schema": {
                  "type": "string"
                }
              },
              "Set-Cookie": {
                "description": "gh_oauth_state(一次性 CSRF state,网页/app 流都种)",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "500": {
            "description": "「服务未正确配置」(GITHUB_CLIENT_ID 缺失)"
          }
        }
      }
    },
    "/auth/github/callback": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "GitHub OAuth 回调",
        "operationId": "githubCallback",
        "parameters": [
          {
            "name": "code",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "GitHub 授权码"
          },
          {
            "name": "state",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "CSRF state(与 cookie 常量时间比对)"
          },
          {
            "name": "error",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "GitHub 侧错误码(用户拒绝授权等)"
          }
        ],
        "responses": {
          "302": {
            "description": "网页流(无 app 上下文):成功跳 /console + Set-Cookie: mb_session(并清 state cookie),失败跳 /console?error=<稳定短语>(oauth_state_mismatch / oauth_token_exchange / oauth_missing_params 等,不泄内部细节)。原生 App 流(发起时带过 app=1,ADR-0025):**不种 session cookie**,成功跳深链 meowbot://auth/callback?code=<一次性 code>(KV 暂存 5 分钟、单次有效),失败跳 meowbot://auth/callback?error=<脱敏短语>;App 再用 code POST /auth/app/exchange 换 key",
            "headers": {
              "Location": {
                "description": "网页流:/console 或 /console?error=<code>;App 流:meowbot://auth/callback?code=<一次性 code> 或 ?error=<脱敏短语>",
                "schema": {
                  "type": "string"
                }
              },
              "Set-Cookie": {
                "description": "**仅网页流**成功时种 mb_session;两种模式都清掉 gh_oauth_state(App 流不种 session)",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/auth/app/exchange": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "原生 App 流 · 用一次性 code + PKCE verifier 换 API key(ADR-0025)",
        "description": "GitHub OAuth 原生 App 流的换取步骤。**无需鉴权——一次性 code 自身即凭据**。校验:① KV 中 code 存在且未过期;② pkce_s256_challenge(code_verifier) 等于发起时存的 code_challenge;通过则立即删除 code(单次有效,重放即 401)并为该租户签一把新 API key。失败一律 401 统一脱敏(不区分 不存在/过期/已用/verifier 错,防探测)。明文 key 只在此响应返回一次。",
        "operationId": "appExchange",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppExchangeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "换取成功:返回新签发的 API key 明文(只此一次)与租户 id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppExchangeResult"
                }
              }
            }
          },
          "400": {
            "description": "「请求体格式错误」(缺 code 或 code_verifier)"
          },
          "401": {
            "description": "换取失败(code 不存在 / 已过期 / 已使用 / code_verifier 与 challenge 不符)——**统一脱敏 401,刻意不区分原因,防探测**(ADR-0025 D3/D5)"
          }
        }
      }
    },
    "/auth/me": {
      "get": {
        "tags": [
          "auth"
        ],
        "summary": "当前登录用户",
        "operationId": "me",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "当前用户",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/auth/logout": {
      "post": {
        "tags": [
          "auth"
        ],
        "summary": "登出(清会话 cookie,幂等)",
        "operationId": "logout",
        "responses": {
          "200": {
            "description": "「已登出」+ 清除 mb_session(Max-Age=0)。无会话时调用同样 200。注意:无状态 token 在 7 天 exp 前密码学上仍有效",
            "headers": {
              "Set-Cookie": {
                "description": "mb_session=; Max-Age=0",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/console/keys": {
      "get": {
        "tags": [
          "console"
        ],
        "summary": "列出本租户的 API key",
        "operationId": "listKeys",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "created_at 倒序;绝不含 key_hash / 明文",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKeyMeta"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      },
      "post": {
        "tags": [
          "console"
        ],
        "summary": "签发新 API key",
        "operationId": "issueKey",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "description": "体可缺省 / 空(全用默认值)",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IssueKeyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "签发成功;key 字段为明文,只回这一次",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IssueKeyResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/console/keys/{id}": {
      "delete": {
        "tags": [
          "console"
        ],
        "summary": "撤销 key(置 revoked_at,立即生效,幂等)",
        "operationId": "revokeKey",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "key 行 id(列表里的 id,非明文)"
          }
        ],
        "responses": {
          "200": {
            "description": "{id, revoked:true};重复撤销幂等(revoked_at 不刷新)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "revoked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "「缺少 key id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该 key」(不存在或属别租户,不区分,防枚举)"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/console/usage": {
      "get": {
        "tags": [
          "console"
        ],
        "summary": "本租户用量统计(近 7 天)",
        "operationId": "usage",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "聚合统计",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/console/logs": {
      "get": {
        "tags": [
          "logs"
        ],
        "summary": "本租户请求日志(控制台;与 /v1/logs 同形同内核)",
        "operationId": "consoleLogsList",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            },
            "description": "返回条数(默认 100,上限 500;非法值回落默认)"
          },
          {
            "name": "endpoint",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "按端点精确筛(如 /v1/agent)"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "按状态码精确筛(100~599 之外丢弃)"
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "起始时刻(ISO 8601,含;ts 为 TEXT ISO 列,字典序比较)"
          }
        ],
        "responses": {
          "200": {
            "description": "ts 倒序的日志行 + summary 聚合",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LogsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/console/sessions": {
      "get": {
        "tags": [
          "console"
        ],
        "summary": "列出本租户会话(控制台;与 /v1/sessions 同形同内核)",
        "operationId": "consoleSessionsList",
        "description": "ADR-0022 D3/D5。会话 cookie 鉴权;响应同 /v1/sessions(纯元数据,不含对话内容)。控制台「观测 · 会话」页用",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            },
            "description": "返回条数(默认 100,上限 500)"
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "只返回 last_at 不早于此刻的会话(ISO 8601)"
          }
        ],
        "responses": {
          "200": {
            "description": "last_at 倒序的会话索引(纯元数据)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/console/sessions/{id}/history": {
      "get": {
        "tags": [
          "console"
        ],
        "summary": "读取会话历史(控制台;行点击查看用)",
        "operationId": "consoleSessionHistory",
        "description": "ADR-0022 D5。会话 cookie 鉴权;与 /v1/sessions/{id}/history 同一 DO 读路径,只读不写。控制台「会话」页行点击查看历史用",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "session_id(空回落 anon)。只能读本租户会话"
          }
        ],
        "responses": {
          "200": {
            "description": "会话快照(从未用过返回空 messages + count=0)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoryResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/console/sessions/{id}": {
      "delete": {
        "tags": [
          "console"
        ],
        "summary": "删除一个会话(控制台;清记忆 + 删索引)",
        "operationId": "consoleSessionDelete",
        "description": "ADR-0022 D4。会话 cookie 鉴权;与 /v1/sessions/{id} 同内核(清 DO storage + 删 D1 索引行,幂等)",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "要删除的 session_id(空回落 anon)。只能删本租户会话"
          }
        ],
        "responses": {
          "200": {
            "description": "已删除(幂等:不存在也回此)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean",
                      "const": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误(可整体重试;删除幂等)"
          }
        }
      }
    },
    "/console/personas": {
      "get": {
        "tags": [
          "console"
        ],
        "summary": "列出本租户人格库",
        "operationId": "listPersonas",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "created_at 倒序",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "personas": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Persona"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      },
      "post": {
        "tags": [
          "console"
        ],
        "summary": "新建人格",
        "operationId": "createPersona",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePersonaRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "{id}(机器侧调 /v1/agent 时作 persona_id 引用)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "「请求体格式错误」/「name 和 prompt 不能为空」/「name 或 prompt 过长」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/console/personas/{id}": {
      "delete": {
        "tags": [
          "console"
        ],
        "summary": "删除人格(引用该 id 的调用自动回落默认)",
        "operationId": "deletePersona",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{id, deleted:true}",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "「缺少人格 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该人格」(不存在或属别租户,不区分,防枚举)"
          },
          "500": {
            "description": "内部错误"
          }
        }
      },
      "put": {
        "tags": [
          "console"
        ],
        "summary": "更新人格 name/prompt(2.1 期;is_default 走 /default 专职端点)",
        "operationId": "updatePersona",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "人格 id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PersonaUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{id, name, prompt}",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "prompt": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "「缺少人格 id」/「请求体格式错误」/「name 和 prompt 不能为空」/「name 或 prompt 过长」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该人格」(不存在或属别租户,不区分,防枚举)"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/console/personas/{id}/default": {
      "post": {
        "tags": [
          "console"
        ],
        "summary": "设为本租户默认人格(先全清再置位,至多一个默认)",
        "operationId": "setDefaultPersona",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{id, is_default:true}",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "is_default": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "缺少人格 id"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该人格」(防枚举)"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/console/settings": {
      "get": {
        "tags": [
          "console"
        ],
        "summary": "读本租户 LLM 配置",
        "operationId": "getSettings",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "未配置过返回默认 {model:null, thinking:false, temperature:null}(200 而非 404——未配置是正常态)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantSettings"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      },
      "put": {
        "tags": [
          "console"
        ],
        "summary": "改本租户 LLM 配置(upsert;PUT 与 POST 等价)",
        "operationId": "putSettings",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "description": "只读 model/thinking/temperature 三字段(体里任何 tenant_id 一概忽略);model 空串 = 清回平台默认;空体 {} 合法 = 全部清回默认",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TenantSettings"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "返回归一化后的当前设置(同 GET 形状)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantSettings"
                }
              }
            }
          },
          "400": {
            "description": "「请求体格式错误」/「模型名过长」/「温度需在 0~2 之间」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      },
      "post": {
        "tags": [
          "console"
        ],
        "summary": "改本租户 LLM 配置(与 PUT 同一处理器)",
        "operationId": "postSettings",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TenantSettings"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "同 PUT",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TenantSettings"
                }
              }
            }
          },
          "400": {
            "description": "同 PUT"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/console/kb/docs": {
      "get": {
        "tags": [
          "kb"
        ],
        "summary": "列出本租户知识库文档(控制台)",
        "operationId": "consoleKbListDocs",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "同 GET /v1/kb/docs(共享内核,仅鉴权面不同)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "docs": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/KbDoc"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      },
      "post": {
        "tags": [
          "kb"
        ],
        "summary": "上传文档入库(控制台)",
        "operationId": "consoleKbCreateDoc",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KbCreateRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "同 POST /v1/kb/docs(向量索引异步生效,约数秒)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KbCreateResponse"
                }
              }
            }
          },
          "400": {
            "description": "同 POST /v1/kb/docs"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "「文档数已达上限(每租户 50 篇),删几篇再传喵」"
          },
          "413": {
            "description": "「文档过大(单篇上限 100KB),拆小一点再传喵」"
          },
          "500": {
            "description": "服务端内部错误(配额计数 / 生成 id / D1 落库失败;错误体中文明说)"
          },
          "502": {
            "description": "嵌入 / 向量写入暂时不可用(已脱敏)"
          }
        }
      }
    },
    "/console/kb/docs/{id}": {
      "delete": {
        "tags": [
          "kb"
        ],
        "summary": "删除文档(控制台)",
        "operationId": "consoleKbDeleteDoc",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "同 DELETE /v1/kb/docs/{id}",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "「缺少文档 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该文档」(防枚举)"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / 取块清单 / 删行失败;错误体中文明说)"
          },
          "502": {
            "description": "向量删除暂时不可用(可整体重试)"
          }
        }
      }
    },
    "/console/kb/search": {
      "post": {
        "tags": [
          "kb"
        ],
        "summary": "检索测试(控制台调试框)",
        "operationId": "consoleKbSearch",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KbSearchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "同 POST /v1/kb/search",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KbSearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "「q 不能为空」/「请求体格式错误」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "502": {
            "description": "嵌入 / 检索暂时不可用(已脱敏)"
          }
        }
      }
    },
    "/console/apps": {
      "get": {
        "tags": [
          "apps"
        ],
        "summary": "列出本租户应用(控制台)",
        "operationId": "consoleAppsList",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "同 GET /v1/apps(共享内核,仅鉴权面不同)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apps": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/App"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "服务端内部错误(列表查询失败;错误体中文明说)"
          }
        }
      },
      "post": {
        "tags": [
          "apps"
        ],
        "summary": "新建应用(控制台)",
        "operationId": "consoleAppsCreate",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppUpsertRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "同 POST /v1/apps",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/App"
                }
              }
            }
          },
          "400": {
            "description": "同 POST /v1/apps 的 400 枚举"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "「应用数已达上限(每租户 20 个),删几个再建喵」(配额)"
          },
          "500": {
            "description": "服务端内部错误(配额计数 / 生成 id / D1 落库失败;错误体中文明说)"
          }
        }
      }
    },
    "/console/apps/{id}": {
      "get": {
        "tags": [
          "apps"
        ],
        "summary": "应用详情(控制台)",
        "operationId": "consoleAppsGet",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "同 GET /v1/apps/{id}",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/App"
                }
              }
            }
          },
          "400": {
            "description": "「缺少应用 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该应用」(防枚举)"
          },
          "500": {
            "description": "服务端内部错误(详情查询失败;错误体中文明说)"
          }
        }
      },
      "put": {
        "tags": [
          "apps"
        ],
        "summary": "整体更新应用(控制台;缺省字段 = 清回「不覆盖」)",
        "operationId": "consoleAppsUpdate",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AppUpsertRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "同 PUT /v1/apps/{id}",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/App"
                }
              }
            }
          },
          "400": {
            "description": "同 POST /v1/apps 的 400 枚举"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该应用」(防枚举)"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / 写 D1 失败;错误体中文明说)"
          }
        }
      },
      "delete": {
        "tags": [
          "apps"
        ],
        "summary": "删除应用(控制台;其全部 App key 同步撤销)",
        "operationId": "consoleAppsDelete",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "同 DELETE /v1/apps/{id}",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "「缺少应用 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该应用」(防枚举)"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / key 撤销失败(应用未删,请重试)/ 删行失败;错误体中文明说)"
          }
        }
      }
    },
    "/console/provider": {
      "get": {
        "tags": [
          "provider"
        ],
        "summary": "读 BYO 通道状态(控制台)",
        "operationId": "consoleProviderGet",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "同 GET /v1/provider(共享内核,仅鉴权面不同)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderConfig"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          },
          "503": {
            "description": "「BYO 未启用」"
          }
        }
      },
      "put": {
        "tags": [
          "provider"
        ],
        "summary": "配置 BYO 通道(控制台)",
        "operationId": "consoleProviderPut",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProviderPutRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "同 PUT /v1/provider",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderConfig"
                }
              }
            }
          },
          "400": {
            "description": "同 PUT /v1/provider 的 400 枚举"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          },
          "503": {
            "description": "「BYO 未启用」"
          }
        }
      },
      "delete": {
        "tags": [
          "provider"
        ],
        "summary": "清除 BYO 通道(控制台;幂等)",
        "operationId": "consoleProviderDelete",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "同 DELETE /v1/provider",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "configured": {
                      "type": "boolean"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          }
        }
      }
    },
    "/console/provider/test": {
      "post": {
        "tags": [
          "provider"
        ],
        "summary": "测试 BYO 通道(控制台)",
        "operationId": "consoleProviderTest",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "同 POST /v1/provider/test",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderTestResponse"
                }
              }
            }
          },
          "400": {
            "description": "「尚未配置 BYO 通道」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "内部错误"
          },
          "503": {
            "description": "「BYO 未启用」"
          }
        }
      }
    },
    "/console/apps/{id}/keys": {
      "post": {
        "tags": [
          "apps"
        ],
        "summary": "签发 App 级 key(控制台;明文只回显一次)",
        "operationId": "consoleAppsIssueKey",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "description": "体可缺省 / 空(全用默认值)",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IssueKeyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "同 POST /v1/apps/{id}/keys",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppKeyIssueResponse"
                }
              }
            }
          },
          "400": {
            "description": "「缺少应用 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该应用」(防枚举)"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / 取随机 / 写 D1 失败;错误体中文明说)"
          }
        }
      }
    },
    "/v1/devices": {
      "get": {
        "tags": [
          "devices"
        ],
        "summary": "列出本租户设备(机器侧;租户通用 key 限定)",
        "operationId": "devicesList",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "created_at 倒序;绝不含激活码明文/哈希",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "devices": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Device"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenRuntimeKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(列表查询失败;错误体中文明说)"
          }
        }
      },
      "post": {
        "tags": [
          "devices"
        ],
        "summary": "登记设备(机器侧;响应含一次性激活码,只回显这一次)",
        "operationId": "devicesCreate",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceRegisterRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "登记成功;activation_code 明文只回这一次,15 分钟内拿它调 /v1/devices/activate 兑换设备 key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceRegisterResponse"
                }
              }
            }
          },
          "400": {
            "description": "「请求体格式错误」/「name 不能为空」/「名称过长(上限 60 字)」/「app_id 过长」/「hw_model 过长(上限 200 字)」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "「设备 key 无管理权」/「App 级 key 无管理权」(最小权限)或「设备数已达上限(每租户 100 台),删几台再登记喵」(配额)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(配额计数 / 取随机 / D1 落库失败;错误体中文明说)"
          }
        }
      }
    },
    "/v1/devices/activate": {
      "post": {
        "tags": [
          "devices"
        ],
        "summary": "一次性码兑换设备 key(无鉴权:码即凭证;按 IP 限流)",
        "operationId": "devicesActivate",
        "description": "全站唯一无鉴权端点(ADR-0015 D2/D3)。防线:16 位 base32 一次性码(~80bit,CSPRNG)+ 15 分钟 TTL + 服务端只存 sha256 + 按 IP 固定窗口限流(CF-Connecting-IP,10 次/分)+ 原子兑换(同码并发只有一个成功)。成功即签发设备 key(api_keys 行带 device_id 并继承设备绑定的 app_id),码同时作废",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceActivateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "兑换成功;key 为设备 key 明文,只回这一次(之后无从取回,丢了走「重发码」重新配对)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceActivateResponse"
                }
              }
            }
          },
          "400": {
            "description": "「请求体格式错误」/「缺少激活码」"
          },
          "401": {
            "description": "「未授权」:码过期 / 不存在 / 已被重发码作废(**不区分原因**,防枚举;服务端故障同样回 401 不泄底)"
          },
          "410": {
            "description": "「激活码已使用」:码已兑换过(一次性语义;含同码并发竞争失败方)"
          },
          "429": {
            "description": "「激活尝试过于频繁」:该 IP 固定窗口(60s)内超 10 次,带 Retry-After 头(防爆破)"
          },
          "500": {
            "description": "服务端内部错误(签 key / 认领写库失败;已签出的 key 会回滚撤销,可整体重试)"
          }
        }
      }
    },
    "/v1/devices/heartbeat": {
      "post": {
        "tags": [
          "devices"
        ],
        "summary": "设备心跳(仅设备 key;上报固件/型号 + 刷新最后在线)",
        "operationId": "devicesHeartbeat",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "description": "体可缺省 / 空(纯打点);firmware/hw_model 给了才更新",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceHeartbeatRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{device_id, last_seen_at}。另:设备 key 调 /v1/agent 也会尽力而为刷新 last_seen_at(无需显式心跳)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "device_id": {
                      "type": "string"
                    },
                    "last_seen_at": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "「firmware 过长(上限 200 字)」/「hw_model 过长(上限 200 字)」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "「仅设备 key 可上报心跳」:通用 / App 级 key 无设备身份"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "内部错误(写 D1 失败)"
          }
        }
      }
    },
    "/v1/devices/{id}": {
      "get": {
        "tags": [
          "devices"
        ],
        "summary": "设备详情(机器侧;租户通用 key 限定)",
        "operationId": "devicesGet",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "设备 id(列表 / 登记响应里的 id)"
          }
        ],
        "responses": {
          "200": {
            "description": "完整设备(不含激活码明文/哈希)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Device"
                }
              }
            }
          },
          "400": {
            "description": "「缺少设备 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenRuntimeKey"
          },
          "404": {
            "description": "「未找到该设备」(不存在或属别租户,不区分,防枚举)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(详情查询失败;错误体中文明说)"
          }
        }
      },
      "delete": {
        "tags": [
          "devices"
        ],
        "summary": "删除设备(其设备 key 同步撤销,立刻 401)",
        "operationId": "devicesDelete",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{id, deleted:true}。key 撤销在删行之前完成;撤销失败则中止不删行,可整体重试(撤销幂等)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "「缺少设备 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenRuntimeKey"
          },
          "404": {
            "description": "「未找到该设备」(防枚举)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / key 撤销失败(设备未删,请重试)/ 删行失败;错误体中文明说)"
          }
        }
      },
      "put": {
        "tags": [
          "devices"
        ],
        "summary": "改名 / 换绑应用(机器侧;租户通用 key 限定。2.1 期新增,ADR-0015 修订节)",
        "operationId": "devicesUpdate",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "设备 id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新后的完整设备(同 GET)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Device"
                }
              }
            }
          },
          "400": {
            "description": "「缺少设备 id」/「请求体格式错误」/「name 不能为空」/「名称过长(上限 60 字)」/「app_id 过长」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenRuntimeKey"
          },
          "404": {
            "description": "「未找到该设备」(不存在或属别租户,不区分,防枚举)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / 写 D1 / 回查失败;错误体中文明说)"
          }
        }
      }
    },
    "/v1/devices/{id}/recode": {
      "post": {
        "tags": [
          "devices"
        ],
        "summary": "重发激活码(= 重新配对:旧码作废 + 设备旧 key 撤销;新码只回显一次)",
        "operationId": "devicesRecode",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "新码明文只回这一次(15 分钟 TTL);该设备旧 key 已撤销(在用设备立刻 401),凭新码重新激活换新 key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceRecodeResponse"
                }
              }
            }
          },
          "400": {
            "description": "「缺少设备 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenRuntimeKey"
          },
          "404": {
            "description": "「未找到该设备」(防枚举)"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / key 撤销失败(码未重发,请重试)/ 取随机 / 写 D1 失败;错误体中文明说)"
          }
        }
      }
    },
    "/console/devices": {
      "get": {
        "tags": [
          "devices"
        ],
        "summary": "列出本租户设备(控制台)",
        "operationId": "consoleDevicesList",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "同 GET /v1/devices(共享内核,仅鉴权面不同)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "devices": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Device"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "服务端内部错误(列表查询失败;错误体中文明说)"
          }
        }
      },
      "post": {
        "tags": [
          "devices"
        ],
        "summary": "登记设备(控制台;响应含一次性激活码)",
        "operationId": "consoleDevicesCreate",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceRegisterRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "同 POST /v1/devices",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceRegisterResponse"
                }
              }
            }
          },
          "400": {
            "description": "同 POST /v1/devices 的 400 枚举"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "「设备数已达上限(每租户 100 台),删几台再登记喵」(配额)"
          },
          "500": {
            "description": "服务端内部错误(配额计数 / 取随机 / D1 落库失败;错误体中文明说)"
          }
        }
      }
    },
    "/console/devices/{id}": {
      "get": {
        "tags": [
          "devices"
        ],
        "summary": "设备详情(控制台)",
        "operationId": "consoleDevicesGet",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "同 GET /v1/devices/{id}",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Device"
                }
              }
            }
          },
          "400": {
            "description": "「缺少设备 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该设备」(防枚举)"
          },
          "500": {
            "description": "服务端内部错误(详情查询失败;错误体中文明说)"
          }
        }
      },
      "delete": {
        "tags": [
          "devices"
        ],
        "summary": "删除设备(控制台;其设备 key 同步撤销)",
        "operationId": "consoleDevicesDelete",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "同 DELETE /v1/devices/{id}",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "「缺少设备 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该设备」(防枚举)"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / key 撤销失败(设备未删,请重试)/ 删行失败;错误体中文明说)"
          }
        }
      },
      "put": {
        "tags": [
          "devices"
        ],
        "summary": "改名 / 换绑应用(控制台;与 PUT /v1/devices/{id} 同内核)",
        "operationId": "consoleDevicesUpdate",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "设备 id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceUpdateRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "同 PUT /v1/devices/{id}",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Device"
                }
              }
            }
          },
          "400": {
            "description": "同 PUT /v1/devices/{id} 的 400 枚举"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该设备」(防枚举)"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / 写 D1 / 回查失败;错误体中文明说)"
          }
        }
      }
    },
    "/console/devices/{id}/recode": {
      "post": {
        "tags": [
          "devices"
        ],
        "summary": "重发激活码(控制台;旧码作废 + 设备旧 key 撤销)",
        "operationId": "consoleDevicesRecode",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "同 POST /v1/devices/{id}/recode(新码明文只回显一次)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceRecodeResponse"
                }
              }
            }
          },
          "400": {
            "description": "「缺少设备 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该设备」(防枚举)"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / key 撤销失败(码未重发,请重试)/ 取随机 / 写 D1 失败;错误体中文明说)"
          }
        }
      }
    },
    "/console/kb/docs/{id}/chunks": {
      "get": {
        "tags": [
          "kb"
        ],
        "summary": "文档分块预览(控制台;2.1 期。按 seq 升序,上限 50 块)",
        "operationId": "consoleKbDocChunks",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "文档 id"
          }
        ],
        "responses": {
          "200": {
            "description": "{chunks:[{seq,text}...]}",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KbChunksResponse"
                }
              }
            }
          },
          "400": {
            "description": "「缺少文档 id」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "「未找到该文档」(不存在或属别租户,不区分,防枚举)"
          },
          "500": {
            "description": "服务端内部错误(归属校验 / 行查询失败;错误体中文明说)"
          }
        }
      }
    },
    "/status": {
      "get": {
        "tags": [
          "site"
        ],
        "summary": "公开状态页(依赖逐项探活;给人看的 HTML)",
        "description": "ADR-0021 D1。无鉴权公开页:对 D1 / KV / Vectorize / Workers AI 各做一次轻量探活(LLM Provider 仅查是否已配置,不真打,避免烧配额),逐项尽力而为 + 各自兜底——任一项探活失败只显该项「降级」,不影响整页与其它项。出于安全,只显示通/不通的脱敏分类(正常/降级/未配置),**绝不展示任何密钥或原始错误细节**。对照 /healthz(只回纯文本 ok 给机器)。",
        "operationId": "statusPage",
        "responses": {
          "200": {
            "description": "宣纸朱砂主题 HTML(零外部依赖);含品牌、整体健康度(运行中/部分降级)与各依赖逐项状态。探活异常已兜底,本页恒返回 200",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/robots.txt": {
      "get": {
        "tags": [
          "site"
        ],
        "summary": "爬虫规则(SEO)",
        "description": "ADR-0021 D2。允许收录官网与文档站(Allow: /),挡掉应用页(Disallow: /console)与机器侧 API(Disallow: /v1/);末行 Sitemap: 指向 /sitemap.xml。Sitemap 的绝对 URL 与 host 从请求派生,不硬编码域名(绑自定义域后照样对)。",
        "operationId": "robotsTxt",
        "responses": {
          "200": {
            "description": "纯文本爬虫规则",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "tags": [
          "site"
        ],
        "summary": "站点地图(SEO)",
        "description": "ADR-0021 D3。sitemap 0.9:列官网 /、文档索引 /docs、API 参考 /docs/api,以及每篇文档 /docs/:slug(slug 取自文档站单一真源,不手抄)。<loc> 的 host 从请求派生并做 XML 转义。",
        "operationId": "sitemapXml",
        "responses": {
          "200": {
            "description": "application/xml 的 urlset(sitemap 0.9)",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/favicon.svg": {
      "get": {
        "tags": [
          "site"
        ],
        "summary": "站点图标(朱砂方印 + 负空间白线猫)",
        "description": "ADR-0021 D4。复用三面统一 logo 的独立 SVG;Content-Type image/svg+xml,Cache-Control public, max-age=86400。/favicon.ico 不做(现代浏览器认 SVG favicon)。",
        "operationId": "faviconSvg",
        "responses": {
          "200": {
            "description": "SVG 图标字节",
            "content": {
              "image/svg+xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          }
        }
      }
    },
    "/v1/export": {
      "get": {
        "tags": [
          "portable"
        ],
        "summary": "导出本租户配置(机器侧;租户通用 key 限定;零密钥)",
        "operationId": "configExport",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "可移植配置 JSON;Content-Disposition attachment(下载 meowbot-config.json)。**零密钥**",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigExport"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenRuntimeKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(读取失败;错误体中文明说)"
          }
        }
      }
    },
    "/v1/import": {
      "post": {
        "tags": [
          "portable"
        ],
        "summary": "导入配置到本租户(机器侧;租户通用 key 限定)",
        "operationId": "configImport",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "导入结果(逐项尽力而为;含 imported/skipped/errors)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportResult"
                }
              }
            }
          },
          "400": {
            "description": "「请求体格式错误」/「缺少 config」/「不认识的配置格式版本(本服务支持 format_version=1)」/「单次导入条目过多(personas/apps 各上限 200)」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenRuntimeKey"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "服务端内部错误(建映射 / 配额计数失败;错误体中文明说)"
          }
        }
      }
    },
    "/console/export": {
      "get": {
        "tags": [
          "portable"
        ],
        "summary": "导出本租户配置(控制台;会话 cookie;零密钥)",
        "operationId": "consoleConfigExport",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "可移植配置 JSON;Content-Disposition attachment(下载 meowbot-config.json)。**零密钥**",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigExport"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "服务端内部错误(读取失败;错误体中文明说)"
          }
        }
      }
    },
    "/console/import": {
      "post": {
        "tags": [
          "portable"
        ],
        "summary": "导入配置到本租户(控制台;会话 cookie)",
        "operationId": "consoleConfigImport",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "导入结果(逐项尽力而为;含 imported/skipped/errors)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportResult"
                }
              }
            }
          },
          "400": {
            "description": "「请求体格式错误」/「缺少 config」/「不认识的配置格式版本(本服务支持 format_version=1)」/「单次导入条目过多(personas/apps 各上限 200)」"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "description": "服务端内部错误(建映射 / 配额计数失败;错误体中文明说)"
          }
        }
      }
    }
  }
}
