# API Pagination - Grep

[Developers](/developers)[API Reference](/developers/api)Pagination

# Pagination

Cursor-based pagination on /research, /research/{job\_id\_or\_slug}/timeline, and /billing/transactions. CursorPage shape, why keyset, and how to walk pages.

## Where it applies

Three v2 endpoints return paged results. All share the same CursorPage\[T] envelope.

| Endpoint                                         | Returns                                    |
| ------------------------------------------------ | ------------------------------------------ |
| `GET /api/v2/research`                           | Your tenant's research jobs, newest first. |
| `GET /api/v2/research/{job_id_or_slug}/timeline` | A job's execution events, newest first.    |
| `GET /api/v2/billing/transactions`               | Credit ledger entries, newest first.       |

## CursorPage\[T] shape

Every paged response is a CursorPage\[T] envelope: the page items, an opaque next\_cursor token, and a has\_more flag.

json

```
{
  "items": [
    { "job_id": "...", "status": "completed", "created_at": "..." },
    ...
  ],
  "next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wNC0yN1QxMDoxNTowMFoiLCJpZCI6IjU1MGU4NDAwLi4uIn0",
  "has_more": true
}
```

## First page

Omit the cursor parameter. Pass limit if you want a smaller page; each endpoint documents its maximum.

curl

```
curl "https://api.grep.ai/api/v2/research?limit=50" \
  -H "Authorization: Bearer $GREP_API_KEY"
```

## Next page

Pass next\_cursor from the previous response as cursor. Stop when has\_more is false.

curl

```
curl "https://api.grep.ai/api/v2/research?limit=50&cursor=$NEXT_CURSOR" \
  -H "Authorization: Bearer $GREP_API_KEY"
```

## Why keyset, not offset?

Cursors encode a (created\_at, id) pair as opaque base64. This is keyset pagination — and it's stable in ways offset pagination is not.

- Inserts at the head of the list don't shift later pages. With offset, page 2 would suddenly include the row that page 1 just showed you.
- Performance is constant regardless of how deep you walk. Offset pagination scans every skipped row.
- Cursors are opaque — never parse them client-side. The encoding may change between API versions.

## Walking all pages

A simple generator that yields every job across the full history.

python

```
import os
import httpx

API_KEY = os.environ["GREP_API_KEY"]
BASE = "https://api.grep.ai/api/v2"

def all_jobs():
    cursor = None
    while True:
        params = {"limit": 100}
        if cursor:
            params["cursor"] = cursor
        r = httpx.get(
            f"{BASE}/research",
            params=params,
            headers={"Authorization": f"Bearer {API_KEY}"},
            timeout=30,
        )
        r.raise_for_status()
        page = r.json()
        for job in page["items"]:
            yield job
        if not page["has_more"]:
            return
        cursor = page["next_cursor"]

for job in all_jobs():
    print(job["job_id"], job["status"])
```
