openapi: 3.1.0 info: title: 番茄小说 API version: 2.0.0 description: | 番茄小说非官方 API 服务,支持书籍搜索、详情、目录、正文、批量接口、 段评/书评、漫画、音频/有声书、视频与分类浏览。 多数接口直接转发上游 JSON(结构随上游版本变化,`data` 字段以实际返回为准); 错误统一返回 `{"code": -1, "msg": "..."}`。 需要鉴权的端点(`/status`)通过查询参数 `key` 或请求头 `X-Access-Key` 携带访问密钥,未配置 `server.access_key` 时不启用验证。 license: name: MIT identifier: MIT servers: - url: http://localhost:40180 description: 本地开发 tags: - name: 系统 description: 服务状态监控 - name: 书籍 description: 搜索、详情、目录与批量详情 - name: 内容 description: 章节正文、批量正文与摘要 - name: 评论 description: 段落评论与书评 - name: 多媒体 description: 音频/有声书、视频、漫画及其代理 - name: 分类 description: 分类首页与分类详情 - name: 静态资源 description: 表情图片等静态文件 paths: /: get: tags: [系统] summary: 服务心跳 operationId: getRoot responses: "200": description: 服务存活 content: application/json: schema: type: object properties: code: { type: integer, example: 0 } message: { type: string, example: success } data: { nullable: true } /healthz: get: tags: [系统] summary: 存活探针 description: 供容器/反向代理探活,不校验访问密钥。 operationId: getHealthz responses: "200": description: 服务存活 content: text/plain: schema: { type: string } example: ok /status: get: tags: [系统] summary: 运行状态监控面板 description: HTML 状态页,包含运行时长、签名方法、数据库大小、设备密钥池、 请求统计与内置 API 文档;页面每 3 秒自动刷新。 operationId: getStatus security: - accessKeyQuery: [] - accessKeyHeader: [] parameters: - $ref: "#/components/parameters/AccessKeyQuery" responses: "200": description: HTML 状态页 content: text/html: schema: { type: string } "401": $ref: "#/components/responses/Unauthorized" /search: get: tags: [书籍] summary: 搜索书籍 operationId: searchBooks parameters: - name: query in: query required: true description: 搜索关键字(别名 `key`) schema: { type: string } example: 综漫 - name: key in: query description: "`query` 的别名,`query` 为空时生效" schema: { type: string } - name: offset in: query description: 结果偏移量,默认 0 schema: { type: integer, default: 0, minimum: 0 } example: 0 - name: tab_type in: query description: | 搜索类型,默认 3;仅返回匹配该值的 tab: * `1` 综合(混合:书籍 + 运营位 cell_data + 猜你喜欢 guess_you_like_data) * `2` 听书(book_data) * `3` 书籍(book_data,默认) * `4` 社区(topic_data) * `5` 全文(book_data) * `6` 用户(search_user_data) * `8` 漫画(book_data) * `11` 短剧(video_data) schema: { type: integer, default: 3, enum: [1, 2, 3, 4, 5, 6, 8, 11] } example: 3 responses: "200": description: 搜索结果(tab 已展平:结果块在顶层 data,并提升 has_more/next_offset 分页字段) content: application/json: schema: $ref: "#/components/schemas/SearchResponse" example: code: 0 message: SUCCESS data: - book_data: - book_id: "7668727260244872254" book_name: 综漫:今天的告白,我也很期待哦 author: 一念1浮生 abstract: (评分五天从7.5涨到9.0,真的不看看吗,前期有点青涩,往后会好一点)…… category: 动漫衍生 word_number: "357281" has_more: 1 next_offset: 10 "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /detail: get: tags: [书籍] summary: 获取书籍详情 operationId: getBookDetail parameters: - $ref: "#/components/parameters/BookId" responses: "200": description: 书籍详情(上游转发) content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /info: get: tags: [书籍] summary: 获取书籍详情(/detail 别名) operationId: getBookDetailAlias parameters: - $ref: "#/components/parameters/BookId" responses: "200": description: 同 /detail content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /catalog: get: tags: [书籍] summary: 获取章节目录(book_id → 每章 item_id) description: | 传入 book_id,返回全部章节列表 `data.item_data_list[]`, 每项含 `item_id`、`title`、`chapter_word_number`、`volume_name`。 item_id 用于调用 /content、/multi-content、/item-summary、/audio、/manga、/video 等 需要 item_id 的接口;漫画/短剧的每集(话)item_id 也从这里获取。 operationId: getBookCatalog parameters: - $ref: "#/components/parameters/BookId" responses: "200": description: 章节目录(上游转发,含每章 item_id) content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /directory/all_items: get: tags: [书籍] summary: 获取章节目录(/catalog 别名) operationId: getBookCatalogAlias parameters: - $ref: "#/components/parameters/BookId" responses: "200": description: 同 /catalog content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /multi-detail: get: tags: [书籍] summary: 批量获取书籍详情 operationId: getMultiBookDetail parameters: - name: book_id in: query required: true description: 书籍 ID 列表,逗号分隔 schema: { type: string } example: "7123456789012345678,7234567890123456789" responses: "200": description: 批量详情(上游转发) content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /content: get: tags: [内容] summary: 获取章节正文 description: | 返回章节正文。启用段评(`review=keep` 或配置 `fanqie.content.enable_review: true`)时, 正文 HTML 中会注入段评标记;纯文本输出(text_mode=plain 或配置 remove_html_tags: true) 时不注入段评标记( 为 XML 标签,纯文本无法承载)。 URL 参数优先于配置文件。 operationId: getItemContent parameters: - name: item_id in: query required: true description: 章节 ID schema: { type: string } example: "7271032589123412345" - name: text_mode in: query description: 文本模式,plain/1/true=纯文本,html/0/false=HTML;缺省用配置 remove_html_tags schema: { type: string, enum: ["plain", "html", "0", "1", "true", "false"] } example: "plain" - name: image_mode in: query description: 图片处理,keep/1/true=保留,remove/0/false=移除(别名 `img_mode`);缺省用配置 remove_images 取反 schema: { type: string, enum: ["keep", "remove", "0", "1", "true", "false"] } example: "keep" - name: review in: query description: 段评开关,keep/1=启用,remove/0=禁用(别名 `review_mode`);缺省用配置 enable_review schema: { type: string, enum: ["keep", "remove", "0", "1"] } responses: "200": description: 章节正文 content: application/json: schema: allOf: - $ref: "#/components/schemas/UpstreamResponse" - type: object properties: data: type: object properties: content: type: string description: 正文内容(HTML 或纯文本,取决于 text_mode) title: { type: string, description: 章节标题 } novel_data: type: object properties: book_id: { type: string, description: 所属书籍 ID } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /multi-content: get: tags: [内容] summary: 批量获取章节正文 operationId: getMultiItemContent parameters: - $ref: "#/components/parameters/BookId" - name: item_ids in: query required: true description: 章节 ID 列表,逗号分隔 schema: { type: string } example: "7271032589123412345,7271032589123412346" - name: text_mode in: query description: 文本模式,plain/1/true=纯文本,html/0/false=HTML;缺省用配置 remove_html_tags schema: { type: string, enum: ["plain", "html", "0", "1", "true", "false"] } - name: image_mode in: query description: 图片处理,keep/1/true=保留,remove/0/false=移除(别名 `img_mode`);缺省用配置 remove_images 取反 schema: { type: string, enum: ["keep", "remove", "0", "1", "true", "false"] } - name: ignore_failed in: query description: 忽略失败章节,1/true=在结果中跳过而非报错 schema: { type: string, enum: ["0", "1", "true", "false"] } responses: "200": description: 批量正文(并发拉取) content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /item-summary: get: tags: [内容] summary: 获取章节摘要 operationId: getItemSummary parameters: - $ref: "#/components/parameters/BookId" - name: item_ids in: query required: true description: 章节 ID 列表,逗号分隔 schema: { type: string } responses: "200": description: 章节摘要(上游转发) content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /chapter_review: get: tags: [评论] summary: 获取章节段评 description: 获取整个章节的段落评论数据。 operationId: getChapterReview parameters: - name: item_id in: query required: true description: 章节 ID schema: { type: string } - name: book_id in: query description: 书籍 ID schema: { type: string } responses: "200": description: 章节段评数据 content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /review: get: tags: [评论] summary: 获取段落评论/回复 description: 获取指定章节某一段落的评论;携带 `comment_id` 时返回该评论的回复。 operationId: getReview parameters: - name: item_id in: query required: true description: 章节 ID schema: { type: string } - name: para in: query description: 段落索引(从 0 开始) schema: { type: integer, minimum: 0 } example: 0 - name: book_id in: query description: 书籍 ID(实际必传,缺省时上游返回 101003 错误) schema: { type: string } - name: comment_id in: query description: 评论 ID(获取回复时需要) schema: { type: string } - name: sort in: query description: 排序方式,默认 1(热门) schema: { type: integer, default: 1 } - name: offset in: query description: 分页偏移量,默认 0 schema: { type: integer, default: 0, minimum: 0 } - name: count in: query description: 每页数量,默认 20 schema: { type: integer, default: 20, minimum: 1 } responses: "200": description: 评论/回复数据 content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /review_html: get: tags: [评论] summary: 获取段落评论 HTML 页面 description: 渲染好的评论 HTML 页面,可直接嵌入阅读器展示。 operationId: getReviewHtml parameters: - name: item_id in: query required: true description: 章节 ID schema: { type: string } - name: para in: query required: true description: 段落索引(从 0 开始) schema: { type: integer, minimum: 0 } example: 0 - name: book_id in: query description: 书籍 ID schema: { type: string } - name: page in: query description: 页码,默认 1 schema: { type: integer, default: 1, minimum: 1 } - name: page_size in: query description: 每页数量,默认 20 schema: { type: integer, default: 20, minimum: 1 } - name: sort in: query description: 排序方式,默认 1(热门) schema: { type: integer, default: 1 } responses: "200": description: 评论 HTML 页面 content: text/html: schema: { type: string } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /audio: get: tags: [多媒体] summary: 获取音频/有声书信息 description: 返回章节的有声书播放信息,含本地音频代理地址。 operationId: getAudio parameters: - name: item_ids in: query required: true description: 章节 ID schema: { type: string } - name: tone_id in: query description: 音色 ID,默认 1 schema: { type: string, default: "1" } - name: output in: query description: 输出模式 schema: { type: string } responses: "200": description: 音频信息(上游转发,含代理 URL) content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /audio/proxy/{key}: get: tags: [多媒体] summary: 音频代理 description: | 通过 /audio 返回的代理 key 拉取音频内容。 key 有效期 2 小时(与 CDN 链接有效期一致),过期后需重新调用 /audio。 operationId: getAudioProxy parameters: - name: key in: path required: true description: /audio 接口生成的代理 key schema: { type: string } example: a3f8c92e1b7d4f60 responses: "200": description: 音频二进制内容 content: audio/mpeg: schema: { type: string, format: binary } audio/aac: schema: { type: string, format: binary } "400": description: key 无效、过期或下载失败 content: text/plain: schema: { type: string } example: 音频信息已过期,请重新调用 /audio 接口 /video: get: tags: [多媒体] summary: 获取视频信息 description: | 短剧视频流为 CENC 加密(encrypt=true, cenc-aes-ctr, HEVC+AAC): 播放 URL 可直接下载,但文件本身加密,通用播放器无法解码。 响应透传加密状态字段:顶层 key_seed(Base64,每次请求变化)、 video_list.*.encrypt/encryption_method/kid,供需要对接官方播放器的调用方使用。 operationId: getVideo parameters: - name: item_ids in: query required: true description: 视频 ID,多个用逗号分隔;短剧每集的 item_id 从 /catalog 获取 schema: { type: string } responses: "200": description: 视频信息(上游转发) content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /manga: get: tags: [多媒体] summary: 获取漫画内容 description: | 返回漫画章节内容,图片经解密处理。 输出模式 `output` 优先于配置 `fanqie.manga.output_mode`: - `url`: 返回原始图片 URL 数组(不可用:链接 404 且内容加密,仅作上游原始信息展示) - `proxy`: 返回本地代理 URL(/manga/image/{item_id}/{index})——推荐,先调本接口再访问代理 URL(缓存 5 分钟) - `data_uri`: 返回 Base64 编码(data:image/jpeg;base64,...) CDN 链接说明(全接口通用):带 `x-signature` 签名的图片/音视频 URL 可直接 GET, 但不可修改 URL 任何部分(改尺寸模板/去参数即 403,签名覆盖整个 path)。 `x-expires` 为过期时间戳:图片约 30 天,音频约 2 小时,短剧视频约 6 小时, 过期后重新调用相应接口获取新 URL。无签名的 `audio_thumb_uri`/`horiz_thumb_url` 字段 为上游占位链接,不可用;封面请使用 `thumb_url`/`audio_thumb_url_hd`。 operationId: getManga parameters: - name: item_ids in: query required: true description: 章节 ID(别名 `item_id`) schema: { type: string } - name: item_id in: query description: "`item_ids` 的别名" schema: { type: string } - name: show_html in: query description: 1=返回 HTML 阅读页 schema: { type: string, enum: ["0", "1"] } - name: output in: query description: 输出模式,优先于配置文件 schema: { type: string, enum: [url, proxy, data_uri] } responses: "200": description: 漫画内容 content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } text/html: schema: { type: string } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /manga/image/{item_id}/{index}: get: tags: [多媒体] summary: 漫画图片代理 description: 返回解密后的漫画图片,响应带 1 小时缓存头。 operationId: getMangaImage parameters: - name: item_id in: path required: true description: 章节 ID schema: { type: string } - name: index in: path required: true description: 图片序号(从 0 开始) schema: { type: integer, minimum: 0 } responses: "200": description: 图片二进制内容 headers: Cache-Control: schema: { type: string } description: 固定为 public, max-age=3600 content: image/jpeg: schema: { type: string, format: binary } image/png: schema: { type: string, format: binary } image/webp: schema: { type: string, format: binary } "400": description: URL 格式或 index 参数错误 content: text/plain: schema: { type: string } "500": description: 图片获取/解密失败 content: text/plain: schema: { type: string } /category-front: get: tags: [分类] summary: 获取分类首页 operationId: getCategoryFront parameters: - name: tab in: query description: 标签页,默认 0 schema: { type: string, default: "0" } responses: "200": description: 分类首页数据(上游转发) content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /category-landing: get: tags: [分类] summary: 获取分类详情页 description: 支持按分类筛选、排序与分页的书籍列表(每页固定 20 条)。 operationId: getCategoryLanding parameters: - name: category_id in: query required: true description: 分类 ID(可从 /category-front 或搜索结果 book_data.category_schema 中解析) schema: { type: string } - name: offset in: query description: 偏移量,默认 0 schema: { type: integer, default: 0, minimum: 0 } - name: word_number in: query description: 字数筛选,透传上游(0=不限) schema: { type: integer, default: 0 } - name: book_status in: query description: 连载状态筛选,透传上游(0=不限) schema: { type: integer, default: 0 } - name: sort_by in: query description: 排序方式,透传上游(0=默认) schema: { type: integer, default: 0 } responses: "200": description: 分类书籍列表(上游转发) content: application/json: schema: { $ref: "#/components/schemas/UpstreamResponse" } "400": $ref: "#/components/responses/BadRequest" "500": $ref: "#/components/responses/ServerError" /emoji/{file}: get: tags: [静态资源] summary: 表情图片 description: 评论中使用的表情静态图片文件。 operationId: getEmoji parameters: - name: file in: path required: true description: 表情文件名 schema: { type: string } example: 1@2x.png responses: "200": description: 图片文件 content: image/png: schema: { type: string, format: binary } image/gif: schema: { type: string, format: binary } "404": description: 文件不存在 content: text/plain: schema: { type: string } components: securitySchemes: accessKeyQuery: type: apiKey in: query name: key description: 访问密钥,对应配置 server.access_key(或环境变量 FQ_SERVER_ACCESS_KEY) accessKeyHeader: type: apiKey in: header name: X-Access-Key description: 访问密钥(与查询参数 key 等效) parameters: BookId: name: book_id in: query required: true description: 书籍 ID schema: { type: string } example: "7123456789012345678" AccessKeyQuery: name: key in: query description: 访问密钥(仅配置了 server.access_key 时需要) schema: { type: string } responses: Unauthorized: description: 访问密钥缺失或无效(业务接口仅在 server.protect_all 开启时需要) content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: { code: -1, msg: "访问被拒绝:无效的访问密钥" } ServerError: description: 服务端错误(上游请求失败等) content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: { code: -1, msg: "获取内容失败: 请求超时" } BadRequest: description: 参数缺失或格式错误 content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" example: { code: -1, msg: "缺少 item_id 参数" } schemas: ErrorResponse: type: object required: [code, msg] properties: code: type: integer const: -1 msg: type: string description: 错误信息 SearchResponse: type: object description: | 搜索响应(已展平:原上游 `search_tabs[0]` 中的 `data` 提升为顶层 `data`, 并提升分页字段 `has_more`/`next_offset`;`search_tabs` 字段不再返回)。 required: [code, data] properties: code: type: integer description: 0=成功 example: 0 message: type: string example: SUCCESS data: type: array description: | 搜索结果块,单个元素的 *_data 字段由 tab_type 决定: `book_data`(tab_type 1/2/3/5/8:书籍/听书/全文/漫画,含 book_id/book_name/author/abstract 等)、 `topic_data`(tab_type 4 社区,含 title/comment)、 `search_user_data`(tab_type 6 用户,含 encode_user_id/is_author)、 `video_data`(tab_type 11 短剧)、 `cell_data`/`guess_you_like_data`(tab_type 1 的运营位与猜你喜欢)。 所有元素均附带 book_hot_content_data 等上游字段,以实际返回为准。 items: type: object properties: book_data: type: array description: 书籍结果(tab_type 1/2/3/5) items: { type: object } topic_data: type: array description: 话题结果(tab_type 4) items: { type: object } search_user_data: type: array description: 用户结果(tab_type 6) items: { type: object } video_data: type: array description: 短剧结果(tab_type 11) items: { type: object } cell_data: type: array description: 运营位(tab_type 1 混合出现) items: { type: object } guess_you_like_data: type: array description: 猜你喜欢(tab_type 1 混合出现) items: { type: object } has_more: type: integer description: 是否还有更多结果(1=有) example: 1 next_offset: type: integer description: 下一页 offset 值 example: 10 log_id: type: string UpstreamResponse: type: object description: | 通用响应包装。多数接口转发上游数据,`data` 的内部结构随上游接口变化, 以实际返回为准;此处仅约束外层字段。 required: [code, msg] properties: code: type: integer description: 0=成功,-1=失败 example: 0 msg: type: string example: success data: description: 业务数据,结构随接口不同