Skip to content

Paging

Every endpoint that returns a list (/me/adaccounts, /campaigns, /adsets, /ads, /insights) is paginated with cursors. A page holds at most limit items; the response tells you how to get the next page. Single-object reads (/act_<id>, /<campaign_id>, ...) are not paginated.

Parameters

Parameter Type Required Description
limit int no Maximum number of items in this page. Default 25, maximum 500. Values above 500 are silently clamped to 500; 0, negative or non-numeric values are error 100.
after string no Opaque cursor taken from paging.cursors.after of the previous page. Returns the items that follow it.

Use the largest limit that suits you (500) to minimise the number of requests, since each request counts against the rate limit.

The paging object

{
  "data": [ "..." ],
  "paging": {
    "cursors": {
      "before": "b2Zmc2V0OjA",
      "after": "b2Zmc2V0OjI"
    },
    "next": "https://graph.huydata.com/v21.0/act_199454714457591/campaigns?fields=id%2Cname&limit=2&access_token=...&after=b2Zmc2V0OjI"
  }
}
Key Present when Description
cursors.before the page has items Cursor pointing at the start of this page.
cursors.after the page has items Cursor pointing just past the last item of this page. Pass it as after to get the next page.
next there are more items after this page A complete, ready-to-use URL for the next page. It repeats all your original parameters (including access_token) and adds after.
previous this is not the first page URL for the previous page (same limit, before cursor). Requesting it returns the page that preceded this one.

The rule to follow is simple: request paging.next until it is absent. When paging.next is missing, you have the last page. Do not compare len(data) with limit to decide; a last page can be exactly limit long.

When data is empty (for example an insights request for a day with no delivery), the paging key may be omitted entirely:

{ "data": [] }

Cursors are opaque strings. Do not construct or modify them; only pass back what the API gave you. A tampered cursor gives error 100 ((#100) The after cursor provided is invalid).

Example: walking through campaigns two at a time

Page 1

curl -G "https://graph.huydata.com/v21.0/act_199454714457591/campaigns" \
  -d "access_token=$META_ACCESS_TOKEN" \
  -d "fields=id,name" \
  -d "limit=2"
{
  "data": [
    { "id": "12020064400866904", "name": "DMS_FB_AND_T1_INSTALL_202604" },
    { "id": "12025898431825415", "name": "DMS_FB_AND_T1_VALUE_202607" }
  ],
  "paging": {
    "cursors": {
      "before": "b2Zmc2V0OjA",
      "after": "b2Zmc2V0OjI"
    },
    "next": "https://graph.huydata.com/v21.0/act_199454714457591/campaigns?fields=id%2Cname&limit=2&access_token=...&after=b2Zmc2V0OjI"
  }
}

Page 2 — either request paging.next as is, or repeat your call with after=b2Zmc2V0OjI:

curl -G "https://graph.huydata.com/v21.0/act_199454714457591/campaigns" \
  -d "access_token=$META_ACCESS_TOKEN" \
  -d "fields=id,name" \
  -d "limit=2" \
  -d "after=b2Zmc2V0OjI"
{
  "data": [
    { "id": "12025985543471152", "name": "DMS_FB_AND_T1_VALUE_202608" },
    { "id": "12027158065922978", "name": "DMS_FB_AND_LATAM_VALUE_202608" }
  ],
  "paging": {
    "cursors": {
      "before": "b2Zmc2V0OjI",
      "after": "b2Zmc2V0OjQ"
    },
    "previous": "https://graph.huydata.com/v21.0/act_199454714457591/campaigns?fields=id%2Cname&limit=2&access_token=...&before=b2Zmc2V0OjI"
  }
}

There is no next: this account has exactly 4 campaigns and this was the last page.

Paging insights

Insights pages are counted in rows, not objects. At level=ad with breakdowns=country and time_increment=1, one ad produces one row per country per day: for act_199454714457591 that is 82 rows for one day, and more than 500 rows (two pages at limit=500) for a 7-day range. Always follow paging.next.

curl -G "https://graph.huydata.com/v21.0/act_199454714457591/insights" \
  -d "access_token=$META_ACCESS_TOKEN" \
  -d "level=account" \
  -d "fields=spend" \
  --data-urlencode 'time_range={"since":"2026-10-20","until":"2026-10-27"}' \
  -d "time_increment=1" \
  -d "limit=2"
{
  "data": [
    { "spend": "5148.93", "date_start": "2026-10-20", "date_stop": "2026-10-20" },
    { "spend": "4910.77", "date_start": "2026-10-21", "date_stop": "2026-10-21" }
  ],
  "paging": {
    "cursors": { "before": "b2Zmc2V0OjA", "after": "b2Zmc2V0OjI" },
    "next": "https://graph.huydata.com/v21.0/act_199454714457591/insights?level=account&fields=spend&time_range=%7B%22since%22%3A+%222026-10-20%22%2C+%22until%22%3A+%222026-10-27%22%7D&time_increment=1&limit=2&access_token=...&after=b2Zmc2V0OjI"
  }
}

Following paging.next in Python

import os
import time
import requests

BASE = "https://graph.huydata.com/v21.0"
TOKEN = os.environ["META_ACCESS_TOKEN"]


def get_all(path, **params):
    """Return every item of a paginated edge by following paging.next until it is absent."""
    params = {"access_token": TOKEN, "limit": 500, **params}
    url = f"{BASE}/{path}"
    items = []
    while url:
        resp = requests.get(url, params=params, timeout=60)
        body = resp.json()
        if "error" in body:
            if body["error"]["code"] == 17:          # rate limit: wait a minute and retry the same URL
                time.sleep(60)
                continue
            raise RuntimeError(body["error"])
        items.extend(body["data"])
        url = body.get("paging", {}).get("next")      # None on the last page
        params = None                                 # `next` already contains every parameter
    return items


ads = get_all("act_199454714457591/ads", fields="id,name,adset_id,creative{id,object_type}")
print(len(ads), "ads")

Two details matter in that loop: once you switch to paging.next, send no extra parameters (the URL already has them, and your access_token); and a rate-limit error (code 17) should retry the same URL after waiting, not skip the page.

Security note

paging.next and paging.previous embed your access_token in the query string, exactly as the Graph API does. Do not print these URLs in logs or commit them to a repository.