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.