---
title: "微信综合搜索（搜一搜）/WeChat Universal Search"
method: POST
path: "/api/v1/wechat_search/v2/fetch_search"
tags: ["WeChat-Search-V2-API"]
---

# 微信综合搜索（搜一搜）/WeChat Universal Search

`POST /api/v1/wechat_search/v2/fetch_search`

## Request

- Base URL: `https://api.tikhub.io` (the document declares no server; this is the origin it was published from)
- URL: `https://api.tikhub.io/api/v1/wechat_search/v2/fetch_search`
- Auth: HTTP bearer

# [中文]
### 用途:
- 微信「搜一搜」综合搜索，垂类由 `business_type` **字符串键**切换，传关键词即可。
- **实测可搜**：综合 / 公众号（含服务号、视频号）/ 文章 / 视频号视频 / 表情。
- ⚠️ 其余垂类（直播 / 朋友圈 / 新闻 / 读书 / 听书 / 图片 / 百科 / 微信指数 / 划线 / 评论 / 贴图 / 小游戏 / AI 搜索）由微信服务端按账号与地区下发，当前实测恒返回空结果（`no_more` 文案非空即为正常空结果，`raw=true` / `raw=false` 都在顶层；`raw=false` 时另有 `count=0` + `items=[]`。仍按一次正常调用计费）。
- 价格：0.01$/次
- ⏱️ 由于微信服务器原因，本接口响应较慢，请将客户端请求超时（timeout）设置为 30 秒；timeout 设置过小会造成已扣费但收不到响应的情况。
- ⚠️ 大整数 ID 精度：响应中的 `docID` / `feedNonceId` 等为 64 位大整数，超出 JavaScript 安全整数范围（2^53-1）。请始终以**字符串**方式接收 / 传递这类 ID（解析 JSON 用 json-bigint 或按文本取值），勿让其经过 JS `Number`。Swagger UI 文档页对超大整数会末位舍入显示，属正常现象，不影响接口实际返回的数据。
### 参数:
- keyword: 搜索关键词（去空白后 1-100 字）。示例 `人民日报`
- business_type: 可选，默认 `all`。垂类字符串键（共 18 个）。**实测有结果**：综合 `all` / 公众号·服务号·视频号 `account` / 文章 `article` / 视频 `video` / 表情 `sticker`。其余键 `underline` / `encyclopedia` / `live_stream` / `comment` / `listen` / `news` / `photos` / `book` / `moments` / `image` / `mini_game` / `weixin_index` / `ai_search` 由服务端按账号与地区下发，当前实测恒空。传列表外的值将直接报错（422）。
- sort: 可选，默认 `default`。排序（结果页「排序」下拉，综合与各垂类通用）—— 不限(相关性) `default`/0 / 最新(发布时间降序) `latest`/1 / 最热(点赞降序) `hot`/2，字符串键或整数均可，非法值报错（400）。
- publish_time: 可选，默认 `all`。发布时间（结果页「时间」下拉，通用）—— 不限 `all`/0 / 最近一天 `day`/1 / 最近七天 `week`/2 / 最近半年 `half_year`/3，字符串键或整数均可，非法值报错（400）。
- offset: 可选，默认 0（>=0）。首页传 `0`；**翻页请用 `cursor`，只传 offset 无效**（每页都回到第一页）。
- cursor: 可选。翻页游标。首页留空；翻页时把上一页响应里返回的 `cursor` 原样传回（须同时重复上一页的 `sort` / `publish_time`），配合 `continue_flag` 判断是否还有下一页。
- raw: 可选，默认 True。True=原始搜索响应；False=精简解析。

💡 视频垂类专属的「时长」筛选见 `/fetch_search_videos`（`business_type=video` + `duration`）。
### 返回:
- 搜索结果列表（结构随垂类略有差异）

### 典型链路:
- 💡 **拿视频号详情（媒体下载地址 / decode_key）**：搜索（`video` / `all`）的视频结果项**不直接含**媒体，只给 `exportId`（+ `jumpInfo.extInfo.feedNonceId`）。要下载 / 解密：取 `exportId` → 走视频号 V2 接口 `/api/v1/wechat_channels/v2/fetch_video_detail`（传 `export_id`）拿 `media`（`url` / `url_token` / `decode_key`）和明文 `username`。完整链路与解密说明见该接口文档。
- 公众号结果项的 `jumpInfo.userName`（`gh_…`）可喂给公众号 V2 接口 `/api/v1/wechat_mp/v2/fetch_account_profile` / `fetch_account_articles`。

### 响应结构与 JSON Path:
#### `raw=false`（精简解析，snake_case）:
- 关键词回填: `$.data.keyword`
- 实际生效的垂类代码（如公众号=33554499）: `$.data.business_type`
- 结果总数: `$.data.total` —— 响应里所有 box 与 subBox 的 `totalCount` **全扫取最大值**（垂类的 `totalCount` 挂在 `subBoxes` 上；综合搜索没有单一总数，给的是最大的那个 box）。**零结果时为 null**（没有任何正数 totalCount），恒空垂类恒为 null
- 是否还有下一页: `$.data.continue_flag`
- 翻页游标（原样回传接口的 `cursor` 参数取下一页）: `$.data.cursor`
- 服务端 offset（仅供参考，单独传回**无法**翻页）: `$.data.offset`
- 本页结果数: `$.data.count`
- 拍平的结果项列表（已把各 box / subBox 的 items 合并）: `$.data.items[]`
- 服务端下发的垂类 tab 清单（仅综合 `business_type=all` 时有值，其余为 null）: `$.data.categories[]` —— 每项 `.type` / `.word` / `.extra_kvs`，**这是当前账号真正可用垂类的权威来源**。注意 `.type` 是垂类的**整数码**（如 33554499），而本接口的 `business_type` 只收上面那 18 个**字符串键**，整数会被拒（422）—— 需自行按键表映射（如 33554499 → `account`）
- 零结果时服务端给的文案: `$.data.no_more`（有值 = 微信返回的正常空结果，**不是**调用失败）
- 单项（N 为下标，字段随垂类不同；以下为 `account` 公众号示例）:
    - 标题（含 `<em>` 高亮）: `$.data.items[N].title`
    - 描述: `$.data.items[N].desc`
    - 文档 id: `$.data.items[N].docID`
    - 类型名（如「公众号」）: `$.data.items[N].accTypeName`
    - 跳转信息: `$.data.items[N].jumpInfo`（`.userName` = `gh_…`、`.nickName`、`.signature`）
    - 视频垂类项另含 `exportId`（+ `jumpInfo.extInfo.feedNonceId`）→ 见上「典型链路」
#### `raw=true`（原始，本接口默认）:
- `data` 顶层: `keyword` / `business_type` / `results`（**不**做 items 拍平）/ `total` / `continue_flag` / `offset` / `cursor` / `categories` / `no_more`（`total` / `categories` / `no_more` 两种模式都在顶层，字段集已对齐）
- 完整结果集: `$.data.results`
- 结果盒子数组: `$.data.results.data[]`，每个 box 的结果在 `$.data.results.data[M].items[]` 与 `$.data.results.data[M].subBoxes[K].items[]` —— 即 `raw=false` 的 `items[]` 是把这些拍平后的结果。
- 是否还有下一页: `$.data.continue_flag`（`raw=true` / `raw=false` 均在顶层）
- 翻页游标: `$.data.cursor` —— 原样回传接口的 `cursor` 参数取下一页；`$.data.offset` 仅为服务端 offset，单独传回**无法**翻页
- 总数直接读顶层 `$.data.total`（raw 两种模式都有）—— 它是 `$.data.results.data[M].totalCount` 与 `$.data.results.data[M].subBoxes[K].totalCount` **全扫取的最大值**；垂类的数字挂在 `subBoxes` 上，只读顶层 box 的 `totalCount` 会拿到 null
- 做结果遍历建议直接用 `raw=false`（items 已拍平），路径更干净。

# [English]
### Purpose:
- WeChat "Search" (搜一搜) universal search; the vertical is switched by the `business_type` **string key** — just pass a keyword.
- **Verified to return data**: all / official accounts (incl. service accounts and Channels) / articles / Channels videos / stickers.
- ⚠️ The other verticals (live streams, Moments, news, books, listen, images, encyclopedia, WeChat Index, underline, comments, photos, mini games, AI search) are gated server-side per account & region and currently always come back empty. A non-empty `no_more` is the universal signal (top level in both `raw=true` and `raw=false`); `raw=false` additionally gives `count=0` + `items=[]`. Still billed as a normal call.
- Price: $0.01 per request
- ⏱️ Due to WeChat server latency, this endpoint responds slowly; please set your client request timeout to 30 seconds — a timeout that is too small may result in being billed without receiving the response.
- ⚠️ Large-integer ID precision: IDs such as `docID` / `feedNonceId` in the response are 64-bit big integers beyond JavaScript's safe-integer range (2^53-1). Always receive / pass such IDs as **strings** (parse JSON with json-bigint or read them as text), never through JS `Number`. Swagger UI rounds the trailing digits of huge integers in its docs view — this is expected and does not affect the actual data returned by the API.
### Parameters:
- keyword: Search keyword (1-100 chars after trimming). E.g. `人民日报`
- business_type: Optional, default `all`. Vertical string key (18 total). **Verified to return data**: `all` / `account` / `article` / `video` / `sticker`. The rest (`underline` / `encyclopedia` / `live_stream` / `comment` / `listen` / `news` / `photos` / `book` / `moments` / `image` / `mini_game` / `weixin_index` / `ai_search`) are gated server-side per account & region and currently always come back empty. Any value outside this list is rejected (422).
- sort: Optional, default `default`. Sort (result page "sort" dropdown, common to all verticals) — `default`/0 (relevance) / `latest`/1 (newest) / `hot`/2 (most liked); string key or integer, invalid values rejected (400).
- publish_time: Optional, default `all`. Publish time (result page "time" dropdown, common) — `all`/0 / `day`/1 / `week`/2 / `half_year`/3; string key or integer, invalid values rejected (400).
- offset: Optional, default 0 (>=0). Pass `0` for the first page; **use `cursor` to paginate — offset alone does not work** (it returns the first page every time).
- cursor: Optional. Pagination cursor. Leave empty for the first page; for the next page pass back the `cursor` returned in the previous response (repeat the same `sort` / `publish_time`), and use `continue_flag` to check whether there are more pages.
- raw: Optional, default True. True=raw search response; False=simplified parsing.

💡 The "duration" filter specific to the video vertical is available at `/fetch_search_videos` (`business_type=video` + `duration`).
### Return:
- Search result list (structure varies slightly by vertical)

### Typical chains:
- 💡 **To get Channels video detail (media download address / decode_key)**: video result items from search (`video` / `all`) do **not** contain media directly — only `exportId` (+ `jumpInfo.extInfo.feedNonceId`). To download / decrypt: take `exportId` → call the Channels V2 endpoint `/api/v1/wechat_channels/v2/fetch_video_detail` (pass `export_id`) to get `media` (`url` / `url_token` / `decode_key`) and the plain `username`. See that endpoint's docs for the full chain and decryption notes.
- The `jumpInfo.userName` (`gh_…`) of official account result items can be fed into the MP V2 endpoints `/api/v1/wechat_mp/v2/fetch_account_profile` / `fetch_account_articles`.

### Response structure & JSON Path:
#### `raw=false` (simplified, snake_case):
- Keyword echo: `$.data.keyword`
- Effective vertical code (e.g. account=33554499): `$.data.business_type`
- Total results: `$.data.total` — the **maximum** `totalCount` scanned across every box and subBox (a vertical's `totalCount` hangs off `subBoxes`; the All tab has no single total, so this is the largest contributing box). **null on a zero-result page** (no positive totalCount anywhere), so it is always null for the always-empty verticals
- Has next page: `$.data.continue_flag`
- Pagination cursor (pass back as the `cursor` parameter for the next page): `$.data.cursor`
- Server-side offset (informational only; passing it back alone does **not** paginate): `$.data.offset`
- Results on this page: `$.data.count`
- Flattened result item list (items of all boxes / subBoxes merged): `$.data.items[]`
- Server-issued vertical tab list (only present for `business_type=all`, null otherwise): `$.data.categories[]` — each entry has `.type` / `.word` / `.extra_kvs`. **This is the authoritative list of verticals actually available to the serving account.** Note `.type` is the vertical's **integer code** (e.g. 33554499) while this endpoint's `business_type` accepts only the 18 **string keys** above — an integer is rejected (422), so map it yourself (e.g. 33554499 → `account`).
- Server wording for a zero-result page: `$.data.no_more` (present = a legitimate empty result from WeChat, **not** a failed call)
- Single item (N is the index; fields vary by vertical; below is an `account` example):
    - Title (with `<em>` highlight): `$.data.items[N].title`
    - Description: `$.data.items[N].desc`
    - Document id: `$.data.items[N].docID`
    - Type name (e.g. "公众号"): `$.data.items[N].accTypeName`
    - Jump info: `$.data.items[N].jumpInfo` (`.userName` = `gh_…`, `.nickName`, `.signature`)
    - Video vertical items also carry `exportId` (+ `jumpInfo.extInfo.feedNonceId`) → see "Typical chains" above
#### `raw=true` (raw, default for this endpoint):
- `data` top level: `keyword` / `business_type` / `results` (items are **not** flattened) / `total` / `continue_flag` / `offset` / `cursor` / `categories` / `no_more` (`total` / `categories` / `no_more` are top level in both modes — the key sets are aligned)
- Full result set: `$.data.results`
- Result box array: `$.data.results.data[]`; each box's results are in `$.data.results.data[M].items[]` and `$.data.results.data[M].subBoxes[K].items[]` — the `items[]` of `raw=false` is the flattened merge of these.
- Has next page: `$.data.continue_flag` (top level for both `raw=true` / `raw=false`)
- Pagination cursor: `$.data.cursor` — pass it back as the `cursor` parameter for the next page; `$.data.offset` is only the server-side offset and passing it back alone does **not** paginate
- Read the total from the top level `$.data.total` (present in both raw modes) — it is the **maximum** of `$.data.results.data[M].totalCount` and `$.data.results.data[M].subBoxes[K].totalCount`; a vertical's number hangs off `subBoxes`, so reading only the top-level box's `totalCount` yields null
- For result iteration, prefer `raw=false` (items already flattened) for cleaner paths.

## Request body

- FetchSearchRequest
  - `keyword` string, required: 搜索关键词（1-100 字）/Search keyword (1-100 chars)
  - `business_type` 'all' | 'account' | 'article' | 'video' | 'sticker' | 'underline' | 'encyclopedia' | 'live_stream' | 'comment' | 'listen' | 'news' | 'photos' | 'book' | 'moments' | 'image' | 'mini_game' | 'weixin_index' | 'ai_search': 垂类字符串键（共 18 个）。**实测有结果**：综合 all / 公众号·服务号·视频号 account / 文章 article / 视频 video / 表情 sticker。其余键（underline / encyclopedia / live_stream / comment / listen / news / photos / book / moments / image / mini_game / weixin_index / ai_search）由微信服务端按账号与地区下发，当前实测恒返回空结果（count=0 + no_more 文案，仍按一次正常调用计费）。当前账号真正可用的 tab 以综合搜索响应里的 categories 为准；传列表外的值直接报错（422）/Vertical key (18 total). **Verified to return data**: all / account / article / video / sticker. The rest (underline / encyclopedia / live_stream / comment / listen / news / photos / book / moments / image / mini_game / weixin_index / ai_search) are gated server-side per account & region and currently always come back empty (count=0 plus a no_more message — still billed as a normal call). Use the `categories` field of an `all` search for the authoritative per-account tab list. Values outside this list are rejected (422)
  - `sort` union: 排序（结果页「排序」下拉，综合与各垂类通用）：不限(相关性) default/0 / 最新(发布时间降序) latest/1 / 最热(点赞降序) hot/2，字符串键或整数均可/Sort (result page 'sort' dropdown, common to all verticals): default/0 (relevance) / latest/1 (newest) / hot/2 (most liked); string key or integer
    - integer
    - string
  - `publish_time` union: 发布时间（结果页「时间」下拉，通用）：不限 all/0 / 最近一天 day/1 / 最近七天 week/2 / 最近半年 half_year/3，字符串键或整数均可/Publish time (result page 'time' dropdown, common): all/0 / day/1 / week/2 / half_year/3; string key or integer
    - integer
    - string
  - `offset` integer: 首页传 0；翻页请用 cursor（只传 offset 无效，每页都回到第一页）/Pass 0 for the first page; use cursor to paginate (offset alone does not work — it returns the first page every time)
  - `cursor` string, nullable: 翻页游标：首页留空；翻页时把上一页响应返回的 cursor 原样传回/Pagination cursor: leave empty for the first page; for the next page pass back the cursor returned in the previous response
  - `raw` boolean: True=原始搜索响应；False=精简解析结构/True=raw search response; False=simplified parsed structure

## Response `200`

Successful Response

- ResponseModel
  - `code` integer: HTTP status code | HTTP状态码
  - `request_id` string, nullable: Unique request identifier | 唯一请求标识符
  - `message` string: Response message (EN-US) | 响应消息 (English)
  - `message_zh` string: Response message (ZH-CN) | 响应消息 (中文)
  - `support` string: Support message | 支持消息
  - `time` string: The time the response was generated | 生成响应的时间
  - `time_stamp` integer: The timestamp the response was generated | 生成响应的时间戳
  - `time_zone` string: The timezone of the response time | 响应时间的时区
  - `docs` string, nullable: Link to the API Swagger documentation for this endpoint | 此端点的 API Swagger 文档链接
  - `cache_message` string, nullable: Cache message (EN-US) | 缓存消息 (English)
  - `cache_message_zh` string, nullable: Cache message (ZH-CN) | 缓存消息 (中文)
  - `cache_url` string, nullable: The URL to access the cached result | 访问缓存结果的 URL
  - `router` string: The endpoint that generated this response | 生成此响应的端点
  - `params` unknown
  - `data` unknown

## Other responses

- `422`: Validation Error

## Changes

- **2026-10-08** `96a122a5f220`: 2 info
  - added the optional property `detail/items/ctx` to the response with the `422` status
  - added the optional property `detail/items/input` to the response with the `422` status
- **2026-09-05** `eab99e8bfe5b`: 6 info
  - added the new `ai_search` enum value to the request property `business_type`
  - added the new `comment` enum value to the request property `business_type`
  - added the new `mini_game` enum value to the request property `business_type`
  - added the new `photos` enum value to the request property `business_type`
  - …2 more
- **2026-09-01** `50f9ec6e1cd3`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/tikhub/apis/tikhub-douyin-tiktok-xiaohongshu-lemon8-bilibili-kuaishou-pi/changes/api/v1/wechat_search/v2/fetch_search/post.md)

---

[API](https://skmtc.dev/tikhub/apis/tikhub-douyin-tiktok-xiaohongshu-lemon8-bilibili-kuaishou-pi.md) - [All operations](https://skmtc.dev/tikhub/apis/tikhub-douyin-tiktok-xiaohongshu-lemon8-bilibili-kuaishou-pi/llms.txt) - [OpenAPI document](https://skmtc.dev/tikhub/apis/tikhub-douyin-tiktok-xiaohongshu-lemon8-bilibili-kuaishou-pi/revisions/86a3483c705a?raw)
