# Pagination

Using the API

How list endpoints return items in pages, how to fetch the next page with a cursor, and how pages are priced.

Some endpoints return a list of items, a page at a time. Every list endpoint pages the same way, so you write the loop once.

## The response

A successful list response has three keys: `data`, an array of items in the resource's [shared schema](https://staging-fly.socialtap.dev/resources/index.md); `page`, which says whether more items exist and how to get them; and `meta`, as on every response:

Shape of a list response:

```json
{
  "data": [
    "…"
  ],
  "page": {
    "has_more": true,
    "next_cursor": "…",
    "next_cursor_expires_at": "…",
    "end_reason": "…"
  },
  "meta": {
    "request_id": "…",
    "platform": "…",
    "source": "…",
    "is_cached": true,
    "credits_charged": 0,
    "latency_ms": 0,
    "fetched_at": "…",
    "input": "…",
    "credits_remaining": 0,
    "client_request_id": "…",
    "warnings": "…",
    "mode": "…"
  }
}
```

200 OK (Facebook Ad Library Search API):

```json
{
  "data": [
    {
      "advertiser": {
        "avatar_url": "https://scontent-atl3-2.xx.fbcdn.net/v/t39.35426-6/739782028_1533198008291919_1079542453788497101_n.jpg?stp=dst-jpg_s60x60_tt6&_nc_cat=104&ccb=SCRUBBED&_nc_sid=SCRUBBED&_nc_ohc=SCRUBBED&_nc_oc=SCRUBBED&_nc_zt=14&_nc_ht=scontent-atl3-2.xx&_nc_gid=SCRUBBED&_nc_ss=7b289&oh=SCRUBBED&…",
        "display_name": "American Health Support Community",
        "id": "966175593248497",
        "is_verified": null,
        "url": "https://www.facebook.com/61588018263996",
        "username": null
      },
      "approximate_fields": [
        "first_shown_at"
      ],
      "audience_size": null,
      "country_codes": [],
      "creatives": [
        {
          "body": "My aunt called me a creep for putting a camera in Nana’s living room.\n\nNana called me practical.\n\nThen the camera caught something worse than a fall.\n\nI need to be clear about one thing before anyone decides what kind of grandson I am.\n\nNana asked for the camera.\n\nNot me.\n\nNot my…",
          "cta_text": "Learn more",
          "link_description": null,
          "link_url": "https://shop.pipitea.com/hbt/kd/sp-nm",
          "media": [
            {
              "alt_text": null,
              "duration_ms": null,
              "expires_at": "2026-10-25T18:00:00Z",
              "height": null,
              "thumbnail_url": "https://scontent-atl3-1.xx.fbcdn.net/v/t39.35426-6/742001725_2100792434176297_9060645049521596104_n.jpg?stp=dst-jpg_s600x600_tt6&_nc_cat=100&ccb=SCRUBBED&_nc_sid=SCRUBBED&_nc_ohc=SCRUBBED&_nc_oc=SCRUBBED&_nc_zt=14&_nc_ht=scontent-atl3-1.xx&_nc_gid=SCRUBBED&_nc_ss=7b289&oh=SCRUBBE…",
              "type": "image",
              "url": "https://scontent-atl3-1.xx.fbcdn.net/v/t39.35426-6/742501415_1706621370582971_4085631001896338504_n.jpg?_nc_cat=103&ccb=SCRUBBED&_nc_sid=SCRUBBED&_nc_ohc=SCRUBBED&_nc_oc=SCRUBBED&_nc_zt=14&_nc_ht=scontent-atl3-1.xx&_nc_gid=SCRUBBED&_nc_ss=7b289&oh=SCRUBBED&oe=F2A52380",
              "width": null
            }
          ],
          "title": "Natural Relief in Weeks"
        }
      ],
      "first_shown_at": "2026-07-18T07:00:00Z",
      "id": "2619351891814847",
      "impressions": null,
      "is_active": true,
      "is_political": false,
      "languages": [],
      "last_shown_at": null,
      "paid_by": null,
      "platform": "facebook",
      "platform_specific": {},
      "publisher_platforms": [
        "facebook",
        "instagram",
        "audience_network"
      ],
      "reach": null,
      "spend": null,
      "type": "image",
      "unknown_fields": [],
      "url": "https://www.facebook.com/ads/library/?id=2619351891814847"
    },
    {
      "advertiser": {
        "avatar_url": "https://scontent-atl3-1.xx.fbcdn.net/v/t39.35426-6/712623693_1599994994434095_1320701223312676781_n.jpg?stp=dst-jpg_s60x60_tt6&_nc_cat=110&ccb=SCRUBBED&_nc_sid=SCRUBBED&_nc_ohc=SCRUBBED&_nc_oc=SCRUBBED&_nc_zt=14&_nc_ht=scontent-atl3-1.xx&_nc_gid=SCRUBBED&_nc_ss=7b289&oh=SCRUBBED&…",
        "display_name": "American Health Support Community",
        "id": "966175593248497",
        "is_verified": null,
        "url": "https://www.facebook.com/61588018263996",
        "username": null
      },
      "approximate_fields": [
        "first_shown_at"
      ],
      "audience_size": null,
      "country_codes": [],
      "creatives": [
        {
          "body": "I almost looked past the woman who saved my husband's life. She was seventy-nine, sitting alone at the booth next to ours at the Cracker Barrel off Highway 31. A glass of milk in front of her. She kept stirring it. She wasn't eating.\n\nIf I'd known, the night I leaned over and ask…",
          "cta_text": "Learn more",
          "link_description": "\"I've tried everything to support my kidney function naturally. Fish oil, CoQ10, cutting protein, stressing about every meal. Nothing moved the needle enough to satisfy my doctor. Hibiscus tea is the first thing that actually showed up in my numbers. It's been 3 months and my eGF…",
          "link_url": "https://shop.pipitea.com/hbt/kd/sp",
          "media": [
            {
              "alt_text": null,
              "duration_ms": null,
              "expires_at": "2026-10-25T18:00:00Z",
              "height": null,
              "thumbnail_url": "https://scontent-atl3-2.xx.fbcdn.net/v/t39.35426-6/711729685_1717035472767079_186136568392095276_n.jpg?stp=dst-jpg_s600x600_tt6&_nc_cat=104&ccb=SCRUBBED&_nc_sid=SCRUBBED&_nc_ohc=SCRUBBED&_nc_oc=SCRUBBED&_nc_zt=14&_nc_ht=scontent-atl3-2.xx&_nc_gid=SCRUBBED&_nc_ss=7b289&oh=SCRUBBED…",
              "type": "image",
              "url": "https://scontent-atl3-1.xx.fbcdn.net/v/t39.35426-6/713729791_868879862958547_7409366389733397444_n.jpg?_nc_cat=107&ccb=SCRUBBED&_nc_sid=SCRUBBED&_nc_ohc=SCRUBBED&_nc_oc=SCRUBBED&_nc_zt=14&_nc_ht=scontent-atl3-1.xx&_nc_gid=SCRUBBED&_nc_ss=7b289&oh=SCRUBBED&oe=F2A52380",
              "width": null
            }
          ],
          "title": "Natural Relief in Weeks"
        }
      ],
      "first_shown_at": "2026-06-01T07:00:00Z",
      "id": "1923645134954720",
      "impressions": null,
      "is_active": true,
      "is_political": false,
      "languages": [],
      "last_shown_at": null,
      "paid_by": null,
      "platform": "facebook",
      "platform_specific": {},
      "publisher_platforms": [
        "facebook",
        "instagram",
        "audience_network"
      ],
      "reach": null,
      "spend": null,
      "type": "image",
      "unknown_fields": [],
      "url": "https://www.facebook.com/ads/library/?id=1923645134954720"
    },
    {
      "advertiser": {
        "avatar_url": "https://scontent-atl3-1.xx.fbcdn.net/v/t39.35426-6/712025330_1303417705276292_736671271727941526_n.jpg?stp=dst-jpg_s60x60_tt6&_nc_cat=103&ccb=SCRUBBED&_nc_sid=SCRUBBED&_nc_ohc=SCRUBBED&_nc_oc=SCRUBBED&_nc_zt=14&_nc_ht=scontent-atl3-1.xx&_nc_gid=SCRUBBED&_nc_ss=7b289&oh=SCRUBBED&o…",
        "display_name": "American Health Support Community",
        "id": "966175593248497",
        "is_verified": null,
        "url": "https://www.facebook.com/61588018263996",
        "username": null
      },
      "approximate_fields": [
        "first_shown_at"
      ],
      "audience_size": null,
      "country_codes": [],
      "creatives": [
        {
          "body": "I almost looked past the woman who saved my husband's life. She was seventy-nine, sitting alone at the booth next to ours at the Cracker Barrel off Highway 31. A glass of milk in front of her. She kept stirring it. She wasn't eating.\n\nIf I'd known, the night I leaned over and ask…",
          "cta_text": "Learn more",
          "link_description": "\"I've tried everything to support my kidney function naturally. Fish oil, CoQ10, cutting protein, stressing about every meal. Nothing moved the needle enough to satisfy my doctor. Hibiscus tea is the first thing that actually showed up in my numbers. It's been 3 months and my eGF…",
          "link_url": "https://shop.pipitea.com/hbt/kd/sp",
          "media": [
            {
              "alt_text": null,
              "duration_ms": null,
              "expires_at": "2026-10-25T18:00:00Z",
              "height": null,
              "thumbnail_url": "https://scontent-atl3-1.xx.fbcdn.net/v/t39.35426-6/711737708_4485707625086258_2353150069319301753_n.jpg?stp=dst-jpg_s600x600_tt6&_nc_cat=109&ccb=SCRUBBED&_nc_sid=SCRUBBED&_nc_ohc=SCRUBBED&_nc_oc=SCRUBBED&_nc_zt=14&_nc_ht=scontent-atl3-1.xx&_nc_gid=SCRUBBED&_nc_ss=7b289&oh=SCRUBBE…",
              "type": "image",
              "url": "https://scontent-atl3-2.xx.fbcdn.net/v/t39.35426-6/710755472_911440771966686_1736050619245168045_n.jpg?_nc_cat=111&ccb=SCRUBBED&_nc_sid=SCRUBBED&_nc_ohc=SCRUBBED&_nc_oc=SCRUBBED&_nc_zt=14&_nc_ht=scontent-atl3-2.xx&_nc_gid=SCRUBBED&_nc_ss=7b289&oh=SCRUBBED&oe=F2A52380",
              "width": null
            }
          ],
          "title": "Natural Relief in Weeks"
        }
      ],
      "first_shown_at": "2026-06-01T07:00:00Z",
      "id": "2140497416496021",
      "impressions": null,
      "is_active": true,
      "is_political": false,
      "languages": [],
      "last_shown_at": null,
      "paid_by": null,
      "platform": "facebook",
      "platform_specific": {},
      "publisher_platforms": [
        "facebook",
        "instagram",
        "audience_network"
      ],
      "reach": null,
      "spend": null,
      "type": "image",
      "unknown_fields": [],
      "url": "https://www.facebook.com/ads/library/?id=2140497416496021"
    }
  ],
  "page": {
    "has_more": true,
    "next_cursor": "c1.EXAMPLE_CURSOR",
    "next_cursor_expires_at": "2026-10-26T12:00:00Z",
    "end_reason": null
  },
  "meta": {
    "request_id": "req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f",
    "platform": "facebook",
    "source": "api",
    "is_cached": false,
    "credits_charged": 1,
    "latency_ms": 0,
    "fetched_at": "2026-10-25T12:00:00Z",
    "input": null,
    "credits_remaining": 9999,
    "client_request_id": null,
    "warnings": [],
    "mode": "live"
  }
}
```

Long arrays and strings are shortened for display.

| Field | Type | Description |
| --- | --- | --- |
| `page.has_more` | `boolean` | True when another page exists; then next_cursor is set. An empty page with has_more true is not the end. |
| `page.next_cursor` | `string` or `null` | Pass as cursor, with the same other parameters, to get the next page; null at the end. |
| `page.next_cursor_expires_at` | `string` (date-time) or `null` | When next_cursor stops working, RFC 3339 UTC; null at the end. Don't store cursors. |
| `page.end_reason` | `string` or `null` | Why there are no more pages; null while has_more is true. end: the list ended, or reached since/until. platform_limit: the platform stops serving further pages although the list goes on. max_pages: SocialTap's cap on pages in one walk. New values may be added. A short or empty page is not the end: only has_more false is.<br>One of `end`, `platform_limit`, `max_pages` |

There are no totals: platforms don't report them reliably, and counting would cost extra requests.

## Parameters

Every list endpoint accepts these on top of its own parameters and the [common options](https://staging-fly.socialtap.dev/docs/credits/index.md#caching):

| Parameter | Type | Description |
| --- | --- | --- |
| `limit` | `integer`<br>min 1 | Items per page, 1 to the endpoint's page size (its default and maximum; larger values are reduced to it). Omit it: every page costs the same, so a smaller limit means more pages to pay for. |
| `cursor` | `string` | page.next_cursor from the previous response. Send the same other parameters; omit to start from the first page. |

## Getting the next page

1. Send the first request without `cursor`.
2. If `page.has_more` is `true`, send **the same request again** with `cursor` set to `page.next_cursor` from the previous response.
3. Stop when `page.has_more` is `false`.

Keep every other parameter the same: only `limit` and the common options such as `max_age` may change between pages.

> [!WARNING]
>
> **A short or empty page is not the end.** A page can hold fewer items than `limit`, or none, and still have `page.has_more` set to `true`: keep going. Only `page.has_more` `false` ends the list.

Every list page with a next page also carries the next request ready to send, in the standard `link` header (`rel="next"`): your request with the cursor filled in. Generic HTTP clients can follow it.

## Why a list ended

When `page.has_more` is `false`, `page.end_reason` says why, so fewer items than you expected is never a mystery:

- `end`: the list really ended, or it reached the time window you asked for with `since`/`until`.
- `platform_limit`: the platform stops serving further pages although the list goes on (many platforms cap how far logged-out visitors can scroll). Repeating the walk later will not get further.
- `max_pages`: the walk reached SocialTap's cap on pages per list.

## Time windows

List endpoints ordered by time accept `since` (inclusive) and `until` (exclusive), as an RFC 3339 time or a date (see [Requests](https://staging-fly.socialtap.dev/docs/requests/index.md#param-since)). On a newest-first list, paging stops as soon as an item older than `since` appears: the last page has `page.has_more` `false` with `page.end_reason` `end`, and the older pages are neither fetched nor charged. `until` filters out newer items; the pages that hold them are still read, so they are charged.

JavaScript:

```javascript
const items = [];
let cursor = null;
do {
  const params = new URLSearchParams({ ...yourParams, ...(cursor ? { cursor: cursor } : {}) });
  const res = await fetch(`https://staging-fly.socialtap.dev/v1/<platform>/<endpoint>?${params}`, {
    headers: { "x-api-key": process.env.SOCIALTAP_API_KEY },
  });
  if (!res.ok) throw new Error((await res.json()).error.code);
  const body = await res.json();
  items.push(...body.data);
  cursor = body.page.next_cursor;
} while (cursor);
```

## Cursors

Cursors are opaque and short-lived: don't parse, build or store them. Each one stops working at `page.next_cursor_expires_at`. A cursor that is malformed, expired, from another endpoint, or sent with different parameters fails with [`invalid_cursor`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_cursor) (HTTP 400) and costs nothing; repeat the original request without a cursor to start again.

## Order and page size

Each list endpoint states the order of its items and its page size: the default and the largest `limit` it accepts (larger values are reduced). One request reads one page from the platform, so a list of N items takes about N ÷ page size requests when you leave `limit` out.

| Endpoint | Order | Page size | Cursor lifetime | Price per page |
| --- | --- | --- | --- | --- |
| [Facebook Ad Library Search API](https://staging-fly.socialtap.dev/docs/reference/facebook/search_ads/index.md) | most impressions first | up to 10 | 1 hour | 1 credit |
| [Instagram Posts API](https://staging-fly.socialtap.dev/docs/reference/instagram/posts/index.md) | newest first | up to 12 | 24 hours | 1 credit |
| [Rumble Comments API](https://staging-fly.socialtap.dev/docs/reference/rumble/comments/index.md) | newest first | up to 500 | 24 hours | 1 credit |
| [Rumble Channel Videos API](https://staging-fly.socialtap.dev/docs/reference/rumble/posts/index.md) | Rumble's upload order (newest uploads first, not strictly by published_at) | up to 25 | 1 hour | 1 credit |
| [Rumble Search API](https://staging-fly.socialtap.dev/docs/reference/rumble/search_posts/index.md) | Rumble's relevance order | up to 36 | 1 hour | 1 credit |
| [TikTok Comments API](https://staging-fly.socialtap.dev/docs/reference/tiktok/comments/index.md) | TikTok's comment ranking (the order its app shows) | up to 50 | 24 hours | 1 credit |
| [TikTok Posts API](https://staging-fly.socialtap.dev/docs/reference/tiktok/posts/index.md) | newest first | up to 15 | 24 hours | 1 credit |
| [YouTube Comments API](https://staging-fly.socialtap.dev/docs/reference/youtube/comments/index.md) | newest first | up to 20 | 1 hour | 1 credit |
| [YouTube Channel Videos API](https://staging-fly.socialtap.dev/docs/reference/youtube/posts/index.md) | newest first | up to 48 | 1 hour | 1 credit |

## Pricing

A list endpoint's price is **per page**: every successful page costs the same, however many items it holds and whatever `limit` you send (a smaller limit lowers neither the price of a page nor the walk's: it costs more pages, below). Empty pages are charged like any other successful page. Failed requests, including [`invalid_cursor`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_cursor), are free, and so are pages served from the cache. See [Credits and caching](https://staging-fly.socialtap.dev/docs/credits/index.md).

### A smaller limit costs more

A `limit` below the page size does not split one charge into parts. The request returns the first `limit` items of a platform page, and the next request, with its cursor, serves the rest of that same platform page: it is fetched again and charged as a page each time. Walking a page in parts therefore costs one page per part, and because the platform page is read again for every part, items can shift between those requests if the platform adds or removes items in the meantime, so an item may repeat or be skipped.

**Omitting `limit` is the cheapest way to walk a list:** every request returns a full page, and each platform page is read and charged once. Use a smaller `limit` only when you need fewer items than a page holds and will stop there.

## Errors

On top of the errors every endpoint can return, list endpoints have these:

| Code | HTTP | Retry | Meaning | What to do |
| --- | --- | --- | --- | --- |
| `invalid_cursor` | 400 | No | The cursor is malformed, expired, belongs to another endpoint, or the other parameters differ from the request that issued it. | Repeat the original request without cursor to start the list again. |

A rejected cursor costs nothing; repeat the original request without `cursor` to start again.

---

[HTML version](https://staging-fly.socialtap.dev/docs/pagination/) · [Docs index for AI agents](https://staging-fly.socialtap.dev/llms.txt) · [All docs in one file](https://staging-fly.socialtap.dev/llms-full.txt)
