# Pagination

Adspy API · https://www.adspy.sh/docs/api/pagination · Updated October 24, 2026

Lists page with an opaque cursor: send it back with the same filters.

## A page

Response:

```json
{
  "data": [ … ],
  "has_more": true,
  "next_cursor": "eyJ2IjoxLCJsIjoic2VhcmNoQ3JlYXRpdmVzIi…"
}
```

- limit sets the page size (1 to 100, 20 by default; similar creatives up to 50).
- has_more is true when another page exists; next_cursor is then a string, else null.
- A page can hold fewer items than limit, even none, and still have more: Instagram items leave social pages after the page is read. Follow has_more, never the item count.

## Cursors

- Send next_cursor as cursor with the same filters as the first request. limit may change between pages.
- A cursor is signed: it is bound to its list, its filters and your organization, and valid 24 hours. A changed, expired or foreign cursor answers 400 invalid_cursor.
- Do not parse or build cursors; their content may change without notice.

```sh
# First page
curl "https://www.adspy.sh/api/v1/creatives?query=protein&limit=50" \
  -H "Authorization: Bearer $ADSPY_API_KEY"

# Next page: the same filters plus the cursor
curl "https://www.adspy.sh/api/v1/creatives?query=protein&limit=50&cursor=$NEXT_CURSOR" \
  -H "Authorization: Bearer $ADSPY_API_KEY"
```

```js
async function* creatives(query) {
  let cursor = null;
  do {
    const params = new URLSearchParams({ query, limit: "50" });
    if (cursor) params.set("cursor", cursor);
    const response = await fetch(`https://www.adspy.sh/api/v1/creatives?${params}`, {
      headers: { Authorization: `Bearer ${process.env.ADSPY_API_KEY}` },
    });
    const page = await response.json();
    if (!response.ok) throw new Error(`${page.code}: ${page.detail}`);
    yield* page.data;
    cursor = page.next_cursor;
  } while (cursor);
}

for await (const creative of creatives("protein")) console.log(creative.id);
```

```python
def creatives(query):
    cursor = None
    while True:
        params = {"query": query, "limit": 50}
        if cursor:
            params["cursor"] = cursor
        response = requests.get(
            "https://www.adspy.sh/api/v1/creatives",
            params=params,
            headers={"Authorization": f"Bearer {os.environ['ADSPY_API_KEY']}"},
            timeout=70,
        )
        page = response.json()
        if not response.ok:
            raise RuntimeError(f"{page['code']}: {page['detail']}")
        yield from page["data"]
        cursor = page["next_cursor"]
        if not cursor:
            break

for creative in creatives("protein"):
    print(creative["id"])
```

## Live lists

Pages read the library as it is when each page is requested, not a frozen snapshot. When the library changes between two pages, an item can appear twice or be skipped. For a complete, consistent copy, create an export.