WeChat-Search-V2-API

微信综合搜索(搜一搜)/WeChat Universal Search

Changed on

[中文]

用途:

  • 微信「搜一搜」综合搜索,垂类由 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.
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)
  • Auth
    HTTP bearer

Request body

keywordstring 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)

offsetinteger

首页传 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)

cursorstring nullable

翻页游标:首页留空;翻页时把上一页响应返回的 cursor 原样传回/Pagination cursor: leave empty for the first page; for the next page pass back the cursor returned in the previous response

rawboolean

True=原始搜索响应;False=精简解析结构/True=raw search response; False=simplified parsed structure

Example request

{
  "keyword": "人民日报",
  "business_type": "account"
}

Response

Successful Response

codeinteger

HTTP status code | HTTP状态码

request_idstring nullable

Unique request identifier | 唯一请求标识符

messagestring

Response message (EN-US) | 响应消息 (English)

message_zhstring

Response message (ZH-CN) | 响应消息 (中文)

supportstring

Support message | 支持消息

timestring

The time the response was generated | 生成响应的时间

time_stampinteger

The timestamp the response was generated | 生成响应的时间戳

time_zonestring

The timezone of the response time | 响应时间的时区

docsstring nullable

Link to the API Swagger documentation for this endpoint | 此端点的 API Swagger 文档链接

cache_messagestring nullable

Cache message (EN-US) | 缓存消息 (English)

cache_message_zhstring nullable

Cache message (ZH-CN) | 缓存消息 (中文)

cache_urlstring nullable

The URL to access the cached result | 访问缓存结果的 URL

routerstring

The endpoint that generated this response | 生成此响应的端点

paramsunknown
dataunknown

Changes

    • info

      added the optional property // to the response with the status

    • info

      added the optional property // to the response with the status

    • info

      added the new ai_search enum value to the request property

    • info

      added the new comment enum value to the request property

    • info

      added the new mini_game enum value to the request property

    • info

      added the new photos enum value to the request property

    • info

      added the new sticker enum value to the request property

    • info

      added the new underline enum value to the request property