{
  "openapi": "3.1.0",
  "info": {
    "title": "YoYoo Bot Open Platform",
    "version": "1.0.0",
    "description": "YoYoo Matrix Bot 开放平台开发者 API。\n\n## 快速开始\n\n1. 在 YoYoo App **设置 → 我的机器人** 创建 Bot，保存一次性 `secret`（前缀 `ybt_live_`）。\n2. **入站**：连接 WebSocket `wss://bot.yoyoo.org/v1/stream`，请求头 `Authorization: Bearer {secret}`。\n3. **出站**：`POST /v1/messages` 发送消息到 Matrix 房间。\n4. 入站事件为 [CloudEvents 1.0](https://cloudevents.io) 信封，见 [bot-platform-event.schema.json](/schemas/v1/bot-platform-event.schema.json)。\n\n## 域名\n\n| 用途 | 基址 |\n|------|------|\n| 开发者 API（本文档） | `https://bot.yoyoo.org` |\n| App 管理 Bot（JWT） | `https://api.yoyoo.org/app/bot` |\n\n## Webhook 模式\n\n创建 Bot 时选择 `webhook` 并填写 `webhookUrl`。平台 POST CloudEvent JSON，头：\n- `X-Yoyoo-Signature-Timestamp`：Unix 秒\n- `X-Yoyoo-Signature-256`：`sha256=` + HMAC-SHA256(secret, `{timestamp}.{body}`)\n",
    "contact": {
      "name": "YoYoo Bot Platform",
      "url": "https://bot.yoyoo.org/docs/"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://bot.yoyoo.org",
      "description": "生产环境（开发者入口）"
    }
  ],
  "tags": [
    {
      "name": "outbound",
      "description": "出站消息（Bot secret 鉴权）"
    },
    {
      "name": "inbound",
      "description": "入站事件（WebSocket / Webhook）"
    },
    {
      "name": "schemas",
      "description": "JSON Schema 契约（匿名可读）"
    }
  ],
  "security": [],
  "paths": {
    "/v1/messages": {
      "post": {
        "tags": [
          "outbound"
        ],
        "operationId": "sendMessage",
        "summary": "以 Bot 身份发送消息",
        "description": "向指定 Matrix 房间发送文本消息。`roomId` 通常来自入站事件的 `subject` 或 `data.matrix.room_id`。\n",
        "security": [
          {
            "botSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OutboundMessageRequest"
              },
              "examples": {
                "text": {
                  "summary": "普通文本",
                  "value": {
                    "roomId": "!abc123:yoyoo.org",
                    "body": "你好，我是 Bot",
                    "msgtype": "m.text"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "发送成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiSuccessBoolean"
                }
              }
            }
          },
          "401": {
            "description": "secret 无效或 Bot 已停用",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "400": {
            "description": "参数校验失败",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/stream": {
      "get": {
        "tags": [
          "inbound"
        ],
        "operationId": "connectStream",
        "summary": "WebSocket 入站事件流",
        "description": "升级为 WebSocket。鉴权方式（二选一）：\n- 请求头 `Authorization: Bearer {secret}`\n- 查询参数 `?access_token={secret}`\n\n可选 `last_event_id` 查询最近缓冲事件（重连回放）。\n\n服务端推送 JSON 文本帧，每条为 [BotPlatformEvent](/schemas/v1/bot-platform-event.schema.json)。\n",
        "security": [
          {
            "botSecret": []
          }
        ],
        "parameters": [
          {
            "name": "access_token",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "与 Authorization Bearer 等价的 secret"
          },
          {
            "name": "last_event_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "重连时传入上次收到的 CloudEvent id"
          }
        ],
        "responses": {
          "101": {
            "description": "Switching Protocols（WebSocket）"
          },
          "401": {
            "description": "secret 无效"
          }
        }
      }
    },
    "/v1/bots/{botId}/stream": {
      "get": {
        "tags": [
          "inbound"
        ],
        "operationId": "connectStreamLegacy",
        "summary": "WebSocket 入站（兼容路径）",
        "description": "与 `/v1/stream` 等价；Bot 身份由 secret 决定，路径中的 `botId` 仅作兼容。",
        "security": [
          {
            "botSecret": []
          }
        ],
        "parameters": [
          {
            "name": "botId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "101": {
            "description": "WebSocket"
          }
        }
      }
    },
    "/schemas/v1/bot-platform-event.schema.json": {
      "get": {
        "tags": [
          "schemas"
        ],
        "summary": "入站 CloudEvent 信封 Schema",
        "responses": {
          "200": {
            "description": "JSON Schema 2020-12"
          }
        }
      }
    },
    "/schemas/v1/message-received-data.schema.json": {
      "get": {
        "tags": [
          "schemas"
        ],
        "summary": "MessageReceived 事件 data Schema",
        "responses": {
          "200": {
            "description": "JSON Schema 2020-12"
          }
        }
      }
    },
    "/schemas/v1/bot-resource.schema.json": {
      "get": {
        "tags": [
          "schemas"
        ],
        "summary": "Bot 资源 Schema",
        "responses": {
          "200": {
            "description": "JSON Schema 2020-12"
          }
        }
      }
    },
    "/schemas/v1/bot-credential.schema.json": {
      "get": {
        "tags": [
          "schemas"
        ],
        "summary": "创建 Bot 凭证 Schema",
        "responses": {
          "200": {
            "description": "JSON Schema 2020-12"
          }
        }
      }
    },
    "/schemas/v1/outbound-message-request.schema.json": {
      "get": {
        "tags": [
          "schemas"
        ],
        "summary": "出站消息请求 Schema（snake_case 参考）",
        "responses": {
          "200": {
            "description": "JSON Schema 2020-12"
          }
        }
      }
    },
    "/openapi/v1.yaml": {
      "get": {
        "tags": [
          "schemas"
        ],
        "summary": "本 OpenAPI 文档（YAML）",
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 YAML"
          }
        }
      }
    },
    "/openapi/v1.json": {
      "get": {
        "tags": [
          "schemas"
        ],
        "summary": "本 OpenAPI 文档（JSON）",
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 JSON"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "botSecret": {
        "type": "http",
        "scheme": "bearer",
        "description": "创建 Bot 时返回的明文 secret，前缀 `ybt_live_`。\n示例：`Authorization: Bearer ybt_live_xxxxxxxx`\n"
      }
    },
    "schemas": {
      "OutboundMessageRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "roomId",
          "body"
        ],
        "properties": {
          "roomId": {
            "type": "string",
            "description": "Matrix 房间 ID，例如 `!abc123:yoyoo.org`",
            "example": "!abc123:yoyoo.org"
          },
          "body": {
            "type": "string",
            "maxLength": 48000,
            "description": "消息正文"
          },
          "msgtype": {
            "type": "string",
            "enum": [
              "m.text",
              "m.notice"
            ],
            "default": "m.text",
            "description": "Matrix 消息类型"
          }
        }
      },
      "ApiSuccessBoolean": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 0
          },
          "msg": {
            "type": "string",
            "nullable": true
          },
          "data": {
            "type": "boolean",
            "example": true
          }
        }
      },
      "ApiError": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 1
          },
          "msg": {
            "type": "string"
          },
          "data": {
            "nullable": true
          }
        }
      }
    }
  }
}
