> ## 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.

# List an account's items

> One page of the items an account published, on any platform, named by one selector.

`GET /v1/items` returns one page of the items an account published. The `account` selector names the platform, so one endpoint covers YouTube channels, Instagram profiles, TikTok accounts, and Meta Ad Library advertisers. It returns summaries inside the request. For full item data, see [Look up an item](/guides/look-up-an-item).

<CodeGroup>
  ```bash curl theme={null}
  curl -G https://api.refmatter.com/v1/items \
    -H "Authorization: Bearer $REFMATTER_API_KEY" \
    --data-urlencode "account=youtube:@NASA" \
    --data-urlencode "type=videos" \
    --data-urlencode "sort=latest" \
    --data-urlencode "maxAge=15m"
  ```

  ```ts TypeScript theme={null}
  const firstPage = await refmatter.items.list({
    account: 'youtube:@NASA',
    type: 'videos',
    sort: 'latest',
  });
  const nextPage =
    firstPage.page.nextCursor === null
      ? null
      : await refmatter.items.list({ cursor: firstPage.page.nextCursor });
  ```
</CodeGroup>

| Parameter   | Required | Values                                                             |
| ----------- | -------- | ------------------------------------------------------------------ |
| `account`   | no\*     | Natural key, `<platform>:@handle`, or an account URL               |
| `type`      | no       | YouTube `videos`, `shorts`, `streams`; Instagram `posts`, `reels`  |
| `sort`      | no       | YouTube `latest`, `popular`; Ad Library `relevance`, `impressions` |
| `status`    | no       | `active`, `inactive`, `all`; Ad Library only. Default `active`     |
| `country`   | no       | `ALL`, or a two-letter country code; Ad Library only               |
| `mediaType` | no       | `all`, `video`, `image`; Ad Library only. Default `all`            |
| `since`     | no       | Earliest ad start date, `yyyy-mm-dd`; Ad Library only              |
| `until`     | no       | Latest ad start date, `yyyy-mm-dd`; 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`    |

`account` is required when `cursor` is absent. A cursor excludes `account` and every filter; pass the cursor alone, optionally with `maxAge`. `until` must not be earlier than `since`.

## Naming the account

The selector carries the platform, in one of three forms.

| Form                 | Examples                                                                                                                                                                     |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Natural key          | `youtube:UCLA_DiR1FfKNvjuUpBHmylQ`, `instagram:528817151`, `tiktok:6620168522134503430`, `meta_ad_library:100200300400500`                                                   |
| `<platform>:@handle` | `youtube:@NASA`, `instagram:@nasa`, `tiktok:@nasa`                                                                                                                           |
| Account URL          | `https://www.youtube.com/@NASA`, `https://www.instagram.com/nasa/`, `https://www.tiktok.com/@nasa`, `https://www.facebook.com/ads/library/?view_all_page_id=100200300400500` |

A natural key's id has to match the platform's own shape: `UC` and 22 more characters on YouTube, 1 to 32 digits on Instagram, TikTok, and the Ad Library. A Meta page has no handle, so `meta_ad_library:@dotandkey` is refused; name the page id or the Ad Library link instead.

A handle always carries its `@`. `youtube:mrbeast` is refused with `400 invalid_input`, because without the `@` a handle cannot be told apart from an id. A bare number or handle that names no platform — `528817151`, `nasa`, `@nasa` — is refused for the same reason: nothing in it says which platform to read.

Instagram and TikTok list an account by handle, so `instagram:<id>` and `tiktok:<id>` are served from the handle last observed for that id. When no observation has named the id yet, the answer is `400 invalid_input` with `details.reason: "handle_required"`, asking for the handle or the profile URL. `not_found` means something else: the platform says the account is gone.

## What each platform offers

| Platform          | `type`                                   | `sort`                                   | Other filters                                      | Rows per page        | Paging | `total`             | Rows carry                                                  | Rows lack                                              |
| ----------------- | ---------------------------------------- | ---------------------------------------- | -------------------------------------------------- | -------------------- | ------ | ------------------- | ----------------------------------------------------------- | ------------------------------------------------------ |
| `youtube`         | `videos`, `shorts`, `streams` (`videos`) | `latest`, `popular` (`latest`)           | —                                                  | YouTube's own page   | Cursor | `null`              | Title, `publishedText`, duration, views, thumbnails         | `publishedAt`, likes, comments, shares, saves          |
| `instagram`       | `posts`, `reels` (`posts`)               | —                                        | —                                                  | 12                   | Cursor | `null`              | Caption on posts; views, likes and comments on reels        | The other tab's fields, shares, saves, `publishedText` |
| `tiktok`          | —                                        | —                                        | —                                                  | The 10 newest        | None   | `null`              | Description, cover image, view count                        | Likes, comments, shares, saves, duration               |
| `meta_ad_library` | —                                        | `relevance`, `impressions` (`relevance`) | `status`, `country`, `mediaType`, `since`, `until` | First page, about 30 | None   | Matching ads, exact | Ad text, creative kind, run dates, variant group, platforms | Publication date, every public engagement count        |

A parameter the selected platform does not read, and a value outside the enum it does read, are both refused with `400 invalid_input`, never silently ignored:

```json theme={null}
{
  "details": {
    "field": "type",
    "reason": "not_available_for_platform",
    "platform": "instagram",
    "hint": "type=shorts is not available for instagram."
  }
}
```

## The page

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "object": "item_summary",
      "id": "youtube:21X5lGlDOfg",
      "platform": "youtube",
      "kind": "video",
      "url": "https://www.youtube.com/watch?v=21X5lGlDOfg",
      "author": {
        "object": "account",
        "id": "youtube:UCLA_DiR1FfKNvjuUpBHmylQ",
        "platform": "youtube",
        "handle": "nasa",
        "name": "NASA",
        "url": "https://www.youtube.com/channel/UCLA_DiR1FfKNvjuUpBHmylQ"
      },
      "title": "Artemis II crew returns to Earth",
      "text": null,
      "publishedAt": null,
      "publishedText": "1 day ago",
      "durationMs": 1143000,
      "stats": {
        "views": 35000000,
        "likes": null,
        "comments": null,
        "shares": null,
        "saves": null
      },
      "viewsApproximate": true,
      "thumbnails": [
        {
          "url": "https://i.ytimg.com/vi/21X5lGlDOfg/hqdefault.jpg",
          "width": 480,
          "height": 360,
          "expiresAt": null
        }
      ],
      "live": false,
      "ad": null
    }
  ],
  "page": {
    "nextCursor": "v2.eyJjaCI6IlVDTEFfRGlSMUZmS052anVVcEJIbXlsUSIsInAiOjJ9.9f1c2b6a7e4d3c8f5a0b6d2e1f7c4a9b3d6e8f0a1c5b7d9e2f4a6c8b0d3e5f7a",
    "hasMore": true
  },
  "total": null,
  "totalApproximate": null,
  "account": {
    "object": "account",
    "id": "youtube:UCLA_DiR1FfKNvjuUpBHmylQ",
    "platform": "youtube",
    "handle": "nasa",
    "name": "NASA",
    "url": "https://www.youtube.com/channel/UCLA_DiR1FfKNvjuUpBHmylQ"
  },
  "observedAt": "2026-09-17T12:00:03.120Z",
  "cached": false,
  "usage": { "creditsCharged": 1, "lines": [{ "part": "fetch", "credits": 1 }] }
}
```

`totalApproximate` sits next to `total` and says what the number is: `false` for an exact count, `true` for a platform's own estimate, and `null` whenever `total` is `null`. On this endpoint only the Ad Library states a total, and it is exact.

A row's `id` is the same natural key [Look up an item](/guides/look-up-an-item) gives that item, so a row and a looked-up item are the same object.

## What you get and what you do not

Rows are summaries. Use [Look up an item](/guides/look-up-an-item) for the full item response.

### YouTube

Views can be rounded, which is marked by `viewsApproximate`. Channel lists provide relative `publishedText`; `publishedAt` is `null` on these rows.

### Instagram

The two tabs carry different fields, because Instagram publishes different fields on each.

| Field            | `type=posts`                    | `type=reels`      |
| ---------------- | ------------------------------- | ----------------- |
| `text`           | The caption                     | `null`            |
| `stats.views`    | `null`                          | The play count    |
| `stats.likes`    | `null`                          | The like count    |
| `stats.comments` | `null`                          | The comment count |
| `kind`           | `image`, `carousel`, or `video` | `video`           |

`stats.shares` and `stats.saves` are `null` on both tabs. When the author turned off like and view counts, a reel row reports `null` views and likes and still counts comments. `publishedText` is `null`: the tabs carry no relative date, so `publishedAt` is decoded from the media id instead.

### TikTok

Rows carry the description, the cover image, and the view count. `stats.likes`, `stats.comments`, `stats.shares`, and `stats.saves` are `null`: the public page this endpoint reads states none of them. `durationMs` is `null`, and `publishedText` is `null` because the page carries no relative date; `publishedAt` is derived from the video id. A video the author marked private is left out of the page.

### Meta Ad Library

One row stands for one variant group, not one ad: `ad.collationId` names the group and `ad.collationCount` says how many near-identical ads it holds. `total` counts the ads that match your filters, so a page of 30 rows under a `total` of 882 is the expected shape, not a truncated answer.

An ad has a run start, not a publication date. `ad.startedAt` is when it began running and `ad.endedAt` is when it stopped, `null` while it is still running. `publishedAt`, `durationMs`, and every field in `stats` are `null`: the Ad Library publishes no public engagement counts for an ad.

`ad.spend`, `ad.impressions`, `ad.reach`, and `ad.currency` appear only where the Ad Library publishes them, which is political and issue ads and EU transparency. Everywhere else they are `null`. `spend` and `impressions` are the bands the library writes, such as `$200K - $250K` and `>1M`; a banded reach is not a count and reads as `null`.

The thumbnail is the ad's video poster, else its first image, else the first card of its carousel. `ad.mediaKind` is `video`, `image`, `carousel`, or `text`, and `ad.platforms` lists where the ad runs. `author.handle` is `null`, because a Meta page has no handle.

Find an advertiser's page id by name with [Search](/guides/search).

## Paging

Pass `page.nextCursor` back as `cursor`, alone, 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.

Instagram serves twelve rows per page; asking for more returns twelve.

TikTok and the Ad Library have no cursor. The public TikTok page publishes the ten newest videos and nothing behind them, and the Ad Library will not page its own ad list for a visitor who is not signed in, so `page.nextCursor` is always `null` and `page.hasMore` is always `false`. To follow a TikTok account over time, call the endpoint again later with `maxAge=0` and keep the rows you have not seen; the ids are stable. On the Ad Library, narrow instead: `since` and `until` cut the list into date ranges, `mediaType` splits video from image creatives, `country` limits it to one market, `status` picks running or stopped ads, and `sort=impressions` puts the most-seen ads first. When a way to continue either list appears, the endpoint gains a cursor and nothing else changes.

## Freshness

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

| `maxAge` | Behavior                          |
| -------- | --------------------------------- |
| `0`      | Always read the page 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`  |

Each distinct set of filters is its own page and its own charge. `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.

Naming a YouTube account by handle (or an `@handle` URL) and by its channel id creates two different pages for freshness and credits, so pick one form and use it consistently.

## 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 account, bad `maxAge`, conflicting cursor fields, or invalid cursor            |
| `invalid_input` with `details.reason=invalid_selector`           | 400       | The `account` value names no platform, or its id does not fit the platform             |
| `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=handle_required`            | 400       | An Instagram or TikTok id no observation has named; pass the handle                    |
| `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                                                            |
| `unsupported_object`                                             | 422       | The URL is a page this API does not turn into an account, such as a video or post link |
| `unauthenticated` / `invalid_credential`                         | 401       | Missing or invalid credential                                                          |
| `not_found` / `unavailable_removed`                              | 404 / 410 | The platform says the account or page is gone                                          |
| `unavailable_private` / `unavailable_restricted`                 | 422       | The platform will not serve the account 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 page took too long                                                                 |

An account with no matching items is an empty page, not an error, and costs nothing.

## From an agent

Use `list_items` with `account` for the first page. Pass `page.nextCursor` as `cursor` alone for the next page. The tool returns one page synchronously, and refuses a parameter the selected platform does not read rather than ignoring it.

## Not in this endpoint

This endpoint does not return full item fields, comments, transcripts, stories, highlights, tagged posts, every creative of an ad, or media bytes. Call [Look up an item](/guides/look-up-an-item) on a row's `url` for the full item, and [Import a URL](/guides/import-a-url) to keep its media.

## Next steps

* [Look up an account](/guides/look-up-an-account)
* [Look up an item](/guides/look-up-an-item)
* [Search](/guides/search)
* [List comments](/guides/list-comments)
* [Resolve a URL](/guides/resolve-a-url)
