Pagination and windows
Why the lists truncate instead of paginating, and how date windows work.
There is no cursor
Collection endpoints return a truncated, spend-ranked slice:
{
"campaigns": [ … ],
"total": 137,
"truncated": true,
"dataFreshness": { … }
}limit is clamped per endpoint (the reference gives each maximum). There is
no cursor, no page, no offset — and that's deliberate rather than
unfinished.
These lists rank by spend after joining daily metrics onto the dimension
rows, in application memory. A keyset cursor over that ordering would silently
skip or duplicate rows as spend shifts between requests, which is worse than
not offering one. So instead of a broken cursor you get an honest total and a
truncated flag.
To see more: raise limit, or narrow with campaignId / adGroupId. If you
genuinely need every row of a large account, use
POST /accounts/{projectId}/reports and let
Apple paginate.
The one exception
GET /accounts/{projectId}/actions — the change ledger — has a real,
stable cursor, because it orders by an immutable created_at:
curl ".../api/v1/accounts/current/actions?limit=50&before=2026-07-01T00:00:00Z"Date windows anchor at yesterday
Every metric endpoint takes ?days (1–90, default 30). The window ends
yesterday, not today: Apple's numbers for the current day are incomplete,
and quietly including them makes today's CPA look better than it is.
So days=30 means the 30 complete days ending yesterday.
Freshness beats recency
Account-scoped reads carry dataFreshness:
{
"dataFreshness": {
"lastSyncAt": "2026-07-26T04:12:00Z",
"ageHours": 15.2,
"coverage": "5 of 30 requested days",
"warning": "This account has 5 days of history. Treat 30-day averages as provisional."
}
}coverage is the one to watch: days=30 on an account we've only had for five
days is a five-day number wearing a 30-day label. When warning is present,
surface it rather than acting through it — and check
GET /accounts/{projectId}/syncs before any automated decision.