> ## Documentation Index
> Fetch the complete documentation index at: https://aisa.one/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# WeChat Universal Search

> WeChat Universal Search

WeChat Universal Search

**Pricing** — billed per successful call; the final charge scales by your plan multiplier. Charged only when the upstream response body `code` is `200`.

<Note>
  Response structure is not yet fully documented — **refer to the actual API response**.
</Note>

## Example

```bash theme={null}
curl -X POST "https://api.aisa.one/apis/v1/tikhub/wechat_search/v2/fetch_search" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'  # see request schema below
```


## OpenAPI

````yaml openapi/tikhub.json POST /tikhub/wechat_search/v2/fetch_search
openapi: 3.0.0
info:
  title: TikHub API
  description: >-
    Multi-platform social media data — Douyin, TikTok, Bilibili, Kuaishou,
    Weibo, Xiaohongshu, Zhihu, and commercial (Xingtu/Huahuo/Billboard)
    endpoints.
  version: 1.0.0
servers:
  - url: https://api.aisa.one/apis/v1
security:
  - bearerAuth: []
paths:
  /tikhub/wechat_search/v2/fetch_search:
    post:
      tags:
        - TikHub - Wechat_search
      summary: WeChat Universal Search
      description: >-
        WeChat Search (v2). Pricing and billing notes describe the upstream
        provider. Use the AIsa price quote for gateway calls.


        ### Purpose

        - WeChat universal 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.

        - ⏱️ 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). For example, an
        account display name

        - 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
        `/tikhub/wechat_search/v2/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
        `/tikhub/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
        `/tikhub/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: `$.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.


        API references: `fetch_account_articles` = `POST
        /tikhub/wechat_mp/v2/fetch_account_articles` (operationId:
        `post_tikhub_wechat_mp_v2_fetch_account_articles`);
        `fetch_account_profile` = `POST
        /tikhub/wechat_mp/v2/fetch_account_profile` (operationId:
        `post_tikhub_wechat_mp_v2_fetch_account_profile`); `fetch_search_videos`
        = `POST /tikhub/wechat_search/v2/fetch_search_videos` (operationId:
        `post_tikhub_wechat_search_v2_fetch_search_videos`);
        `fetch_video_detail` = `POST
        /tikhub/wechat_channels/v2/fetch_video_detail` (operationId:
        `post_tikhub_wechat_channels_v2_fetch_video_detail`).
      operationId: post_tikhub_wechat_search_v2_fetch_search
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FetchSearchRequest'
        required: true
      responses:
        '200':
          description: >-
            Successful response. The outer object follows `ResponseModel`; the
            endpoint-specific payload is in `data`.


            ### Return

            - Search result list (structure varies slightly by vertical)


            ### 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: `$.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.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ResponseModel'
components:
  schemas:
    FetchSearchRequest:
      properties:
        keyword:
          type: string
          maxLength: 100
          minLength: 1
          title: Keyword
          description: >-
            Search keyword, 1-100 characters after trimming. For account
            searches, use the account display name.
          example: People's Daily
        business_type:
          type: string
          enum:
            - all
            - account
            - article
            - video
            - sticker
            - underline
            - encyclopedia
            - live_stream
            - comment
            - listen
            - news
            - photos
            - book
            - moments
            - image
            - mini_game
            - weixin_index
            - ai_search
          title: Business Type
          description: >-
            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


            Usage: 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).
          default: all
          example: account
        sort:
          anyOf:
            - type: integer
            - type: string
          title: Sort
          description: >-
            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).
          default: default
          example: hot
        publish_time:
          anyOf:
            - type: integer
            - type: string
          title: Publish Time
          description: >-
            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).
          default: all
          example: week
        offset:
          type: integer
          minimum: 0
          title: Offset
          description: >-
            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).
          default: 0
        cursor:
          title: Cursor
          description: >-
            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.
          nullable: true
          type: string
        raw:
          type: boolean
          title: Raw
          description: True=raw search response; False=simplified parsed structure
          default: true
      type: object
      required:
        - keyword
      title: FetchSearchRequest
    ResponseModel:
      properties:
        code:
          type: integer
          title: Code
          description: HTTP status code
          default: 200
        request_id:
          title: Request Id
          description: Unique request identifier
          nullable: true
          type: string
        message:
          type: string
          title: Message
          description: Response message (EN-US)
          default: Request successful. This request will incur a charge.
        message_zh:
          type: string
          title: Message Zh
          description: Response message (ZH-CN)
          default: 请求成功，本次请求将被计费。
        support:
          type: string
          title: Support
          description: Support message
          default: 'Discord: https://discord.gg/aMEAS8Xsvz'
        time:
          type: string
          title: Time
          description: The time the response was generated
        time_stamp:
          type: integer
          title: Time Stamp
          description: The timestamp the response was generated
        time_zone:
          type: string
          title: Time Zone
          description: The timezone of the response time
          default: America/Los_Angeles
        docs:
          title: Docs
          description: Link to the API Swagger documentation for this endpoint
          nullable: true
          type: string
        cache_message:
          title: Cache Message
          description: Cache message (EN-US)
          default: >-
            This response is cached and accessible via the URL below for 24
            hours at no extra cost. The cache is for request tracing only — it
            doesn't affect the API's data freshness and won't be returned
            through the API again.
          nullable: true
          type: string
        cache_message_zh:
          title: Cache Message Zh
          description: Cache message (ZH-CN)
          default: >-
            本次响应已缓存，可通过下方 URL 直接查看，有效期 24
            小时，访问缓存链接无额外费用。缓存仅用于请求溯源，不影响接口数据的时效性，也不会再次通过接口返回。
          nullable: true
          type: string
        cache_url:
          title: Cache Url
          description: The URL to access the cached result
          nullable: true
          type: string
        router:
          type: string
          title: Router
          description: The endpoint that generated this response
          default: ''
        params:
          title: Params
          description: The parameters used in the request
          default: {}
        data:
          title: Data
          description: >-
            Endpoint-specific response payload. The official shared model does
            not declare a fixed type or field set. Use the operation's
            documented return fields or response example; an operation-specific
            schema, when present, describes the documented fields. Undocumented
            fields must be read from the actual response rather than assumed.
          nullable: true
      type: object
      title: ResponseModel
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: AIsa API key. Get yours at https://aisa.one

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.