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

# Search Viral Library

> Search Genviral's curated corpus of organic viral posts by idea, category, platform, account, or publish date

Search the viral library: Genviral's curated corpus of organic TikTok, Instagram,
and Threads posts that passed 10,000 views, kept with their metrics, slide
images, hook, and hook analysis.

Every post in the corpus is organic content from a real account, selected and
classified by Genviral. Nothing in it is generated, sponsored, or paid.

## Searching by idea, not by keyword

`q` is a natural-language idea, not a keyword match. The corpus is indexed by
what a post is *about*, so `cooking app` finds meal-planning walkthroughs whose
captions never use either word, and `skincare founder story` finds a founder
talking about her product without saying "founder".

Ordering follows from that: results are ordered by relevance whenever `q` is
present, and by `newest` otherwise. Set `sort` explicitly to override either
default.

Filters compose with `q` rather than replacing it. Narrowing by `category`,
`business_type`, `platform`, `account`, or the publish dates is almost always a
better way to find a specific post than paging deep into a broad query.

## Query Parameters

<ParamField query="q" type="string">
  A natural-language idea to search for, 1-200 characters — for example
  `cooking app`, `productivity app onboarding`, `skincare founder story`. When
  set, results are ordered by relevance.
</ParamField>

<ParamField query="category" type="string">
  One content category, for example `Food & Cooking`, `Technology`, or
  `Health & Wellness`. See the enum in the OpenAPI schema for the full list.
</ParamField>

<ParamField query="business_type" type="string">
  What the post is selling: `app`, `ecommerce`, `saas`, `service`, `affiliate`,
  `creator`, `game`, `book`, `other`, or `unclassified`.
</ParamField>

<ParamField query="platform" type="string">
  `tiktok`, `instagram`, or `threads`.
</ParamField>

<ParamField query="content_type" type="string">
  `slideshow`, `video`, or `mixed`.
</ParamField>

<ParamField query="account" type="string">
  Match posts whose account handle contains this text.
</ParamField>

<ParamField query="product" type="string">
  Match posts whose product name contains this text.
</ParamField>

<ParamField query="max_slides" type="number">
  Return only posts with at most this many slides. Range: `1-100`.
</ParamField>

<ParamField query="published_since" type="string">
  Earliest publish date, as a calendar date `YYYY-MM-DD`. This is the day the
  post went out, not the day Genviral captured it.
</ParamField>

<ParamField query="published_until" type="string">
  Latest publish date, as a calendar date `YYYY-MM-DD`. Must be at or after
  `published_since`.
</ParamField>

<ParamField query="sort" type="string">
  `relevance`, `newest`, `latest_posted`, `most_views`, `most_likes`,
  `most_comments`, or `most_slides`. Defaults to `relevance` when `q` is set and
  `newest` otherwise.
</ParamField>

<ParamField query="limit" type="number" default="12">
  Posts per page. Range: `1-40`.
</ParamField>

<ParamField query="offset" type="number" default="0">
  Posts to skip. Range: `0-960`.
</ParamField>

## Response

<ResponseField name="posts" type="array">
  Matching posts, ordered by `sort`.

  <Expandable title="Viral Library Post Object">
    <ResponseField name="id" type="string" />

    <ResponseField name="platform" type="string" />

    <ResponseField name="content_type" type="string" />

    <ResponseField name="link" type="string | null" />

    <ResponseField name="category" type="string" />

    <ResponseField name="business_type" type="string | null" />

    <ResponseField name="product_name" type="string | null" />

    <ResponseField name="product_website" type="string | null" />

    <ResponseField name="account" type="string | null" />

    <ResponseField name="caption" type="string | null" />

    <ResponseField name="hashtags" type="string[]" />

    <ResponseField name="date_published" type="string | null">
      Calendar date, `YYYY-MM-DD`.
    </ResponseField>

    <ResponseField name="metrics" type="object">
      `views`, `likes`, `comments`, `shares`, and `bookmarks`, each a number or
      `null` where the platform did not report it.
    </ResponseField>

    <ResponseField name="slide_count" type="number | null" />

    <ResponseField name="preview_image_url" type="string | null" />

    <ResponseField name="image_urls" type="string[]">
      Slide images, in order.
    </ResponseField>

    <ResponseField name="video_url" type="string | null">
      Present only when the clip is available for playback.
    </ResponseField>

    <ResponseField name="hook_text" type="string | null">
      The opening line the post leads with.
    </ResponseField>

    <ResponseField name="hook_analysis" type="object | null">
      `content_class`, `format_template`, and `psychology_tags`.
    </ResponseField>

    <ResponseField name="search_summary" type="string | null" />

    <ResponseField name="niche" type="string | null" />

    <ResponseField name="topics" type="string[]" />

    <ResponseField name="search_keywords" type="string[]" />

    <ResponseField name="slide_texts" type="null">
      Always `null` here. Read one post with
      [Get Viral Library Post](/api-reference/get-viral-library-post) for its
      slide texts.
    </ResponseField>

    <ResponseField name="relevance" type="null">
      Reserved. The ranking score is not exposed.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="total" type="number">
  Total posts matching the query, not the size of this page.
</ResponseField>

<ResponseField name="has_more" type="boolean" />

<ResponseField name="next_offset" type="number | null">
  The `offset` for the next page, or `null` on the last page and once the
  reachable window is exhausted. At the reach cap a response can carry
  `has_more: true` alongside `next_offset: null`: more posts match, but none of
  them are reachable on this query — narrow it with filters instead.
</ResponseField>

## Reach and rate limits

One query reaches at most **1,000 results**: `offset` is capped at `960` and
`limit` at `40`. `total` still reports the full match count, so a query whose
`total` runs into the thousands is a query to narrow, not a query to page
through. Add a `category`, a `business_type`, a `platform`, an `account`, or a
`published_since`/`published_until` window and search again.

Reads are rate limited per authenticated customer:

| Operation    | Per minute | Per day | Results per day |
| ------------ | ---------- | ------- | --------------- |
| Search       | 20         | 200     | 2,000           |
| Get one post | 60         | 300     | —               |

The results budget is the one to plan around. A search is charged its requested
`limit` before it runs — asking for 40 spends 40 whether or not 40 come back —
so the 2,000 rows a day are yours to spend on wide pages or on many narrow ones.
Request an honest `limit` rather than the maximum, and the budget lasts.

A ceiling also applies across all callers together — 300 searches and 900
single-post reads per minute — so a burst can be throttled even while your own
budget is intact. Every limit answers `429` with `error_code: "rate_limited"`
and a `retry_after_seconds` value. Wait that long before retrying; retrying
sooner consumes budget without returning results.

A `503 provider_unavailable` means the limiter itself could not be reached.
The daily budget is counted centrally, so a request that cannot be counted is
refused rather than served. It is transient, and carries its own
`retry_after_seconds`.

<RequestExample>
  ```bash cURL theme={null}
  curl --request GET \
    --url 'https://www.genviral.io/api/partner/v1/viral-library?q=cooking%20app&category=Food%20%26%20Cooking&limit=12' \
    --header 'Authorization: Bearer <token>'
  ```
</RequestExample>

<ResponseExample>
  ```json Response theme={null}
  {
    "ok": true,
    "code": 200,
    "message": "Viral library posts retrieved",
    "data": {
      "posts": [
        {
          "id": "viral-post-1",
          "platform": "tiktok",
          "content_type": "slideshow",
          "link": "https://www.tiktok.com/@chefdesk/video/1",
          "category": "Food & Cooking",
          "business_type": "app",
          "product_name": "Chef Desk",
          "product_website": "https://chefdesk.example",
          "account": "chefdesk",
          "caption": "The 3 meals I batch on Sunday",
          "hashtags": ["mealprep", "cookingapp"],
          "date_published": "2026-07-14",
          "metrics": {
            "views": 412000,
            "likes": 38000,
            "comments": 900,
            "shares": 1200,
            "bookmarks": 5400
          },
          "slide_count": 6,
          "preview_image_url": "https://cdn.example.com/viral/preview-1.jpg",
          "image_urls": ["https://cdn.example.com/viral/slide-1.jpg"],
          "video_url": null,
          "hook_text": "I stopped cooking every night",
          "hook_analysis": {
            "content_class": "listicle",
            "format_template": "problem-solution",
            "psychology_tags": ["relief", "time-saving"]
          },
          "search_summary": "A batch-cooking app walkthrough told as a Sunday routine",
          "niche": "meal planning",
          "topics": ["batch cooking"],
          "search_keywords": ["meal prep app"],
          "slide_texts": null,
          "relevance": null
        }
      ],
      "total": 137,
      "has_more": true,
      "next_offset": 12
    }
  }
  ```
</ResponseExample>

## Error Responses

* `401` - authentication failed (missing, invalid, or revoked token)
* `402 subscription_required` - an active eligible subscription is required
* `403 tier_not_allowed` - the Scheduler tier cannot use the Partner API
* `422` - invalid query parameters, for example an unknown `category`, an
  `offset` above `960`, or a `published_until` before `published_since`
* `429 rate_limited` - the per-customer request limit, the daily results budget,
  or the shared ceiling was reached; wait `retry_after_seconds` and retry
* `503 provider_unavailable` - the rate limiter was unreachable, so the request
  was refused rather than served uncounted; wait `retry_after_seconds` and retry
