# Pagination

Page through list endpoints with page and limit, skip, or cursors, and keep a local copy in sync with updatedSince.

List endpoints return results a page at a time. Most use one of three styles: numbered pages, offsets, or opaque cursors. Each endpoint's parameters in the [API reference](https://pozy.ai/docs/api) tell you which one it takes. This page shows how to walk each style to the end.

## Numbered pages: page and limit

Posts, accounts, analytics and most ads lists take `page` (starting at 1) and `limit`, and return a `pagination` object:

```bash cURL
curl "https://api.pozy.ai/v1/posts?status=published&page=1&limit=100" \
  -H "Authorization: Bearer $POZY_API_KEY"
```
```ts Node
const all = [];
for (let page = 1; ; page++) {
  const res = await fetch(`https://api.pozy.ai/v1/posts?${new URLSearchParams({ status: "published", page: String(page), limit: "100" })}`, {
    headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error);
  all.push(...data.posts);
  if (page >= data.pagination.pages) break;
}
```
```python Python
import os, requests

headers = {"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"}
posts, page = [], 1
while True:
    resp = requests.get(
        "https://api.pozy.ai/v1/posts",
        headers=headers,
        params={"status": "published", "page": page, "limit": 100},
        timeout=30,
    ).json()
    posts += resp["posts"]
    if page >= resp["pagination"]["pages"]:
        break
    page += 1
```

```json
{
  "posts": [ { "_id": "6721a0c47b2e5f9d3a18c6e0", "status": "published" } ],
  "pagination": { "page": 1, "limit": 100, "total": 342, "pages": 4 }
}
```

`GET /v1/posts` defaults to 10 per page and accepts up to 500. `GET /v1/accounts` returns every account when you send no paging parameters at all, and up to 500 per page when you do.

Numbered pages can shift if items are added or removed while you read: a new post can push an old one onto the next page, so you see it twice. For a full export, prefer a cursor where the endpoint offers one, or dedupe by `_id`.

## Offsets: skip and limit

Webhook delivery logs, tracking windows, creator payouts and money endpoints take `skip` (how many to jump over) and `limit`. The response says whether more remain:

```bash Terminal
curl "https://api.pozy.ai/v1/webhooks/logs?status=failed&limit=100&skip=200" \
  -H "Authorization: Bearer $POZY_API_KEY"
```

```json
{
  "logs": [ { "_id": "dlv_0c1Fq8Lr2Zt7Wm4Kx9Pa", "event": "post.published", "status": "failed" } ],
  "pagination": { "total": 241, "skip": 200, "limit": 100, "hasMore": false }
}
```

Add `limit` to `skip` for the next page and stop when `hasMore` is `false`.

## Cursors: cursor and nextCursor

Inbox conversations, comments, leads, follower stats, the analytics change feed and most newer endpoints use cursors. Send `limit` alone for the first page, then pass the `nextCursor` you got back as `cursor`. Stop when `nextCursor` is `null`.

```bash cURL
curl "https://api.pozy.ai/v1/inbox/conversations?limit=100" \
  -H "Authorization: Bearer $POZY_API_KEY"

# next page
curl "https://api.pozy.ai/v1/inbox/conversations?limit=100&cursor=eyJpZCI6IjY3MjE..." \
  -H "Authorization: Bearer $POZY_API_KEY"
```
```ts Node
let cursor: string | undefined;
do {
  const query = new URLSearchParams({ limit: "100" });
  if (cursor) query.set("cursor", cursor);
  const res = await fetch(`https://api.pozy.ai/v1/inbox/conversations?${query}`, {
    headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
  });
  const data = await res.json();
  if (!res.ok) throw new Error(data.error);
  for (const c of data.data) handle(c);
  cursor = data.pagination.nextCursor ?? undefined;
} while (cursor);
```
```python Python
cursor = None
while True:
    params = {"limit": 100}
    if cursor:
        params["cursor"] = cursor
    resp = requests.get(
        "https://api.pozy.ai/v1/inbox/conversations",
        headers=headers, params=params, timeout=30,
    ).json()
    for c in resp["data"]:
        handle(c)
    cursor = resp["pagination"].get("nextCursor")
    if not cursor:
        break
```

Cursors are opaque. Pass them back exactly as you got them: do not parse, build or compare them. A page can hold fewer items than `limit`, or even none, while `nextCursor` is still set. Keep going until it is `null`.

`GET /v1/accounts` takes a cursor too: send `limit` without `page` and follow `pagination.nextCursor`. Cursor pages are ordered by account id, so accounts added or removed mid-walk never shift a page. You cannot combine `cursor` with `page`, `sort` or `perProfile`.

## Other styles you will meet

| Parameters | Used by | How to walk |
| --- | --- | --- |
| `before` + `limit` | Changelog, call history | Pass the previous response's `nextCursor` as `before`. It is a timestamp; stop when it is `null`. |
| `after` + `limit` | Reddit search, Discord members, WhatsApp groups, some ads reports | Pass the returned `after` (or `paging.after`) back until it is empty. |
| `pageToken` | Google Business reviews, media and keywords | Pass the returned `nextPageToken` as `pageToken`. |
| `since` + `until` | Account insights | Date ranges, not pages. Split long ranges into smaller windows. |

Some platform-backed lists inherit the platform's quirks, for example a cursor that must be reused with the same `limit`. The platform pages call these out.

## Keep a local copy in sync

To mirror posts into your own database, do one full walk, then ask only for what changed:

```bash Terminal
curl "https://api.pozy.ai/v1/posts?updatedSince=2026-10-30T09:00:00Z&limit=500" \
  -H "Authorization: Bearer $POZY_API_KEY"
```

Store the time you started each sync and use it as the next `updatedSince`, a minute or two earlier to cover clock drift. Better still, update your copy from [webhooks](https://pozy.ai/docs/webhooks) (`post.published`, `post.failed` and the rest) and run the `updatedSince` sync as a nightly safety net.

## Tips

- Use the largest `limit` the endpoint allows when you read everything. Fewer pages means fewer requests against your [rate limit](https://pozy.ai/docs/getting-started/rate-limits).
- Filter on the server (`profileId`, `accountId`, `platform`, `status`, date ranges) instead of fetching everything and filtering yourself.
- Out-of-range values are rejected with `400`, not silently clamped. Check the bounds in the reference.
