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.

On this page