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

# Search a platform

> One page of items or accounts matching a phrase on one platform.

`GET /v1/search` returns one page of search results inside the request. `platform` says where to search, and `type` chooses item rows or account rows.

<CodeGroup>
  ```bash curl theme={null}
  curl -G https://api.refmatter.com/v1/search \
    -H "Authorization: Bearer $REFMATTER_API_KEY" \
    --data-urlencode "platform=youtube" \
    --data-urlencode "q=solar eclipse" \
    --data-urlencode "type=items" \
    --data-urlencode "sort=relevance" \
    --data-urlencode "maxAge=15m"
  ```

  ```ts TypeScript theme={null}
  const items = await refmatter.search.items({
    platform: 'youtube',
    q: 'solar eclipse',
  });
  const advertisers = await refmatter.search.accounts({
    platform: 'meta_ad_library',
    q: 'Dot & Key',
    country: 'IN',
  });
  const ads = await refmatter.items.list({ account: advertisers.data[0]!.id });
  ```
</CodeGroup>

The SDK sets `type` itself: `search.items` asks for item rows and `search.accounts` for account rows.

| Parameter    | Required | Values                                                          |
| ------------ | -------- | --------------------------------------------------------------- |
| `platform`   | yes      | `youtube`, `meta_ad_library`                                    |
| `q`          | no\*     | Search phrase, 1 to 200 characters                              |
| `type`       | no       | `items`, `accounts`. Default `items` on YouTube                 |
| `uploadDate` | no       | `today`, `week`, `month`, `year`; YouTube item searches only    |
| `duration`   | no       | `short`, `medium`, `long`; YouTube item searches only           |
| `sort`       | no       | `relevance`, `popular`; YouTube only. Default `relevance`       |
| `country`    | no       | `ALL`, or a two-letter country code; Ad Library only            |
| `cursor`     | no       | Opaque cursor from a previous page                              |
| `maxAge`     | no       | `0`, or whole minutes, hours, or days up to `1d`. Default `15m` |

`platform` is always required, on a first page and on a cursor page alike. `q` is required when `cursor` is absent. A cursor excludes `q`, `type`, and every filter; pass it with `platform` and nothing else, optionally with `maxAge`. A cursor that continues another platform's search is refused with `400 invalid_input` and `details.reason: "platform_mismatch"`.

## What each platform offers

| Platform          | `type`                        | Filters it reads                 | Rows per page      | Paging | `total`                                       | Rows carry                                                                                                     | Rows lack                                                                      |
| ----------------- | ----------------------------- | -------------------------------- | ------------------ | ------ | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `youtube`         | `items`, `accounts` (`items`) | `uploadDate`, `duration`, `sort` | YouTube's own page | Cursor | An estimate, `totalApproximate: true`         | Items: title, `publishedText`, duration, views. Accounts: handle, name, description, avatar, followers         | `publishedAt`, likes and comments on items; banner, links, country on accounts |
| `meta_ad_library` | `accounts` only               | `country`                        | Up to 15           | None   | The number of rows, `totalApproximate: false` | Page id, page alias, name, picture, like count, page category, the Instagram profile Meta shows, `pageDeleted` | Page description, ad counts, spend                                             |
| `instagram`       | Not available                 | —                                | —                  | —      | —                                             | —                                                                                                              | —                                                                              |
| `tiktok`          | Not available                 | —                                | —                  | —      | —                                             | —                                                                                                              | —                                                                              |

Instagram and TikTok answer `422 unsupported_object`: neither publishes a search to a visitor who is not signed in. The Ad Library answers about advertisers only, so `type=items` on it is refused, and `uploadDate`, `duration`, and `sort` are refused there as well. `country` on YouTube is refused the same way. Every one of those refusals is `400 invalid_input` with `details.reason: "not_available_for_platform"`, `details.platform`, and a plain `details.hint` such as `sort=popular is not available for meta_ad_library.` — never a silently ignored parameter.

## Item results

When `type=items`, the response is an `ItemSummaryList`.

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "object": "item_summary",
      "id": "youtube:nA9UZF-SZoQ",
      "platform": "youtube",
      "kind": "video",
      "url": "https://www.youtube.com/watch?v=nA9UZF-SZoQ",
      "author": {
        "object": "account",
        "id": "youtube:UCLA_DiR1FfKNvjuUpBHmylQ",
        "platform": "youtube",
        "handle": "nasa",
        "name": "NASA",
        "url": "https://www.youtube.com/channel/UCLA_DiR1FfKNvjuUpBHmylQ"
      },
      "title": "What is a solar eclipse?",
      "text": null,
      "publishedAt": null,
      "publishedText": "1 day ago",
      "durationMs": 845000,
      "stats": {
        "views": 8881337,
        "likes": null,
        "comments": null,
        "shares": null,
        "saves": null
      },
      "viewsApproximate": false,
      "thumbnails": [],
      "live": false,
      "ad": null
    }
  ],
  "page": { "nextCursor": null, "hasMore": false },
  "total": 2436237,
  "totalApproximate": true,
  "account": null,
  "observedAt": "2026-09-17T12:00:03.120Z",
  "cached": false,
  "usage": { "creditsCharged": 1, "lines": [{ "part": "fetch", "credits": 1 }] }
}
```

## Account results

When `type=accounts`, the response is an `AccountList`. A YouTube search answers with channels.

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "object": "account",
      "id": "youtube:UCLA_DiR1FfKNvjuUpBHmylQ",
      "platform": "youtube",
      "handle": "nasa",
      "name": "NASA",
      "url": "https://www.youtube.com/channel/UCLA_DiR1FfKNvjuUpBHmylQ",
      "description": "NASA's official YouTube channel: live mission coverage, spacewalks, and educational videos about space exploration.",
      "avatar": [
        {
          "url": "https://yt3.ggpht.com/nasa-avatar=s88",
          "width": 88,
          "height": 88,
          "expiresAt": null
        }
      ],
      "verified": true,
      "stats": { "followers": 13400000 },
      "meta_ad_library": null
    }
  ],
  "page": { "nextCursor": null, "hasMore": false },
  "total": 24909,
  "totalApproximate": true,
  "account": null,
  "observedAt": "2026-09-17T12:00:03.120Z",
  "cached": false,
  "usage": { "creditsCharged": 1, "lines": [{ "part": "fetch", "credits": 1 }] }
}
```

An Ad Library search answers with advertisers, and fills the `meta_ad_library` block.

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "object": "account",
      "id": "meta_ad_library:100200300400500",
      "platform": "meta_ad_library",
      "handle": "dotandkey",
      "name": "Dot & Key",
      "url": "https://www.facebook.com/ads/library/?view_all_page_id=100200300400500",
      "description": null,
      "avatar": [
        {
          "url": "https://scontent.xx.fbcdn.net/v/t39.30808-1/100200300400500.jpg",
          "width": null,
          "height": null,
          "expiresAt": null
        }
      ],
      "verified": true,
      "stats": { "followers": 412903 },
      "meta_ad_library": {
        "category": "Beauty, cosmetic & personal care",
        "likes": 412903,
        "instagramHandle": "dotandkey.skincare",
        "instagramFollowers": 688400,
        "instagramVerified": true,
        "pageDeleted": false
      }
    }
  ],
  "page": { "nextCursor": null, "hasMore": false },
  "total": 1,
  "totalApproximate": false,
  "account": null,
  "observedAt": "2026-09-17T12:00:03.120Z",
  "cached": false,
  "usage": { "creditsCharged": 1, "lines": [{ "part": "fetch", "credits": 1 }] }
}
```

Pass a row's `id` straight to [List an account's items](/guides/list-items) as `account`.

## What you get and what you do not

`totalApproximate` sits next to `total` and says what the number is. A YouTube search reports `true`, because YouTube states an estimate that can change between pages. An Ad Library search reports `false`: the total is the number of rows in the answer. `totalApproximate` is `null` whenever `total` is `null`.

Item search rows include `viewsApproximate`. The example reports `false`, so that value is typically exact for that result. The contract still allows `true` or `null`, so read the flag instead of assuming precision. Search rows provide relative `publishedText`; `publishedAt` is `null` in the normal search projection.

YouTube account rows include `id`, `platform`, `handle`, `name`, `url`, `description`, `avatar`, `verified`, and `stats.followers`. They do not include `stats.following` or the full account response fields such as banners, links, country, or joined date, and their `meta_ad_library` is `null`.

An Ad Library row names one advertiser page: its id, the page alias as `handle`, its name, its Ad Library link, its picture, and its like count as `stats.followers`. The `meta_ad_library` block adds the page category, the same like count, the Instagram profile Meta shows beside the page, and `pageDeleted` for a page that no longer exists but whose ads are still archived. `description` is `null`: the suggestion list carries no page description. `handle` is `null` for a page that has set no alias. A picture the Ad Library serves from an address we do not read costs that row its avatar, never the page.

## Paging

Pass `page.nextCursor` back as `cursor`, with the same `platform` and nothing else, optionally with `maxAge`. Cursors expire after 24 hours. A cursor is not tied to your workspace, but each workspace pays for the pages it is served.

The Ad Library has no cursor. Its own search box offers a short list of suggestions — up to fifteen — and no way to ask for more, so `page.nextCursor` is always `null` and `page.hasMore` is always `false`. Narrow `q`, or set `country`, to see different advertisers.

## Freshness

`maxAge` defaults to `15m` and accepts up to `1d`. A stored search page confirmed within the window can be served without contacting the platform. Observations are immutable.

| `maxAge` | Behavior                          |
| -------- | --------------------------------- |
| `0`      | Always search live                |
| `15m`    | Default freshness window          |
| `1d`     | Maximum accepted freshness window |

## Credits

| Situation                                            | Credits | `cached` |
| ---------------------------------------------------- | ------- | -------- |
| Live page with at least one row                      | 1       | `false`  |
| First serving of a stored page with at least one row | 1       | `false`  |
| Repeat within `maxAge`, served from a stored page    | 0       | `true`   |
| Empty page                                           | 0       | —        |
| `maxAge=0`: a fresh page every time                  | 1       | `false`  |
| Every error                                          | 0       | —        |
| A successful read whose result could not be written  | 0       | `false`  |

`usage.creditsCharged` is the number charged in this response. Errors are always free.

At a zero balance a live page answers `402 insufficient_credits` before the platform is contacted; a repeat within your `maxAge` and an empty page are free and keep answering.

## Headers

| Header                        | On                   | Value                                     |
| ----------------------------- | -------------------- | ----------------------------------------- |
| `Refmatter-Credits-Charged`   | Every response       | Integer, `0` whenever nothing was charged |
| `Refmatter-Credits-Remaining` | Every response       | The workspace balance after this response |
| `Server-Timing`               | Successful responses | Durations for phases that ran             |
| `Cache-Control`               | Successful responses | `no-store`                                |
| `Retry-After`                 | `429`                | Whole seconds                             |

## Rate limits

If no platform request can start, the endpoint returns `429 rate_limited` with `details.retryAfterSeconds`. Wait that many seconds before trying again. Errors are free.

## Errors

| Code                                                             | HTTP      | Means                                                               |
| ---------------------------------------------------------------- | --------- | ------------------------------------------------------------------- |
| `invalid_input`                                                  | 400       | Missing `platform` or `q`, bad `maxAge`, conflicting cursor fields  |
| `invalid_input` with `details.reason=not_available_for_platform` | 400       | The platform does not read that parameter or that value             |
| `invalid_input` with `details.reason=platform_mismatch`          | 400       | The cursor continues another platform's search                      |
| `invalid_input` with `details.reason=expired_cursor`             | 400       | Cursor expired or its page is no longer available                   |
| `invalid_input` with `details.reason=malformed_cursor`           | 400       | Cursor format is invalid                                            |
| `invalid_input` with `details.reason=invalid_signature`          | 400       | Cursor signature is invalid                                         |
| `invalid_input` with `details.reason=unsupported_version`        | 400       | Cursor version is not supported                                     |
| `invalid_input` with `details.reason=no_next_page`               | 400       | The cursor has no next page                                         |
| `unauthenticated` / `invalid_credential`                         | 401       | Missing or invalid credential                                       |
| `not_found` / `unavailable_removed`                              | 404 / 410 | The search page is unavailable                                      |
| `unsupported_object`                                             | 422       | That platform publishes no search to a visitor who is not signed in |
| `unavailable_restricted`                                         | 422       | The platform will not serve the result publicly                     |
| `rate_limited`                                                   | 429       | Try again after `details.retryAfterSeconds`                         |
| `source_changed` / `network_transport`                           | 502       | The source or upstream connection failed                            |
| `route_disabled` / `route_degraded`                              | 503       | The route is unavailable                                            |
| `network_timeout`                                                | 504       | The search took too long                                            |

A phrase that matches nothing is an empty page, not an error, and costs nothing.

## From an agent

Use `search_items` for item rows and `search_accounts` for account rows; both take `platform` and `q`. Pass `page.nextCursor` as `cursor` with the same `platform`. Then pass an advertiser row's `id` to `list_items` as `account`. All of them return one page synchronously.

## Not in this endpoint

This endpoint does not return full item or account responses, an advertiser's ads, comments, transcripts, or media bytes.

## Next steps

* [List an account's items](/guides/list-items)
* [Look up an account](/guides/look-up-an-account)
* [Look up an item](/guides/look-up-an-item)
* [List comments](/guides/list-comments)
* [Sources](/concepts/sources)
