# Pagination

Hooksbase uses opaque cursor pagination for list endpoints. You ask for one page at a time, receive a `nextCursor` when more results exist, and send that cursor back with the exact same filter set to fetch the next page.

## Cursor model

Pagination rules are consistent across the supported list routes:

- `limit` defaults to `20`
- `limit` is clamped to `1..100`
- malformed non-integer `limit` values return `400`
- cursors are opaque; do not parse or construct them yourself
- foreign or malformed cursors return `400`
- cursors are scoped to the exact active filter set

Example request:

**Request one page**

```bash
curl -G https://api.hooksbase.com/v1/deliveries \
  -H "Authorization: Bearer swk_..." \
  -d webhookId=wh_... \
  -d limit=20
```

Example response shape:

**Paged list response**

```json
{
  "items": [
    { "id": "del_...", "status": "succeeded" }
  ],
  "nextCursor": "eyJ..."
}
```

When `nextCursor` is present, pass it back as the `cursor` query parameter on the next request (keeping every other filter identical). When it is `null` or absent, you have reached the end.

## Supported endpoints

These Public API routes use cursor pagination:

- `GET /v1/webhooks`
- `GET /v1/project/api-keys`
- `GET /v1/project/audit-logs`
- `GET /v1/deliveries`
- `GET /v1/webhooks/{id}/deliveries`
- `GET /v1/deliveries/{id}/replay-jobs`
- `GET /v1/dlq`
- `GET /v1/dlq/export`
- `GET /v1/webhooks/{id}/schedules`
- operator alerting list and dispatch-history routes under `/v1/project`

Some cursors are scoped even more tightly:

- webhook cursors include the authenticated project and webhook status filter
- project delivery cursors include the full filter set
- replay-job cursors are scoped to one source delivery
- DLQ cursors are scoped to the active DLQ filter set
- DLQ export accepts cursor pagination but caps each export page at 20 rows

## Using the SDK helpers

The TypeScript SDK includes `paginateCursor()` and `takePages()` helpers when you want to consume multiple server pages without hand-rolling the loop.

**Iterate deliveries one page at a time**

```ts
import { createHooksbaseClient, paginateCursor, takePages } from '@hooksbase/sdk'

const client = createHooksbaseClient({ apiKey: process.env.HOOKSBASE_API_KEY })

const iterator = paginateCursor((cursor) =>
  client.deliveries.list({
    webhookId: 'wh_123',
    limit: 20,
    cursor: cursor ?? undefined,
  }),
)

for await (const delivery of iterator) {
  console.log(delivery.id, delivery.status)
}

// Or stop after the first N items:
const first100 = await takePages(iterator, 100)
```

Each callback receives the latest `cursor` (`null` on the first call) and returns the raw `{ items, nextCursor }` response. The helper keeps requesting pages until `nextCursor` is `null` or absent.

## Common mistakes

- Reusing a cursor with a different filter set.
- Assuming cursor pagination supports arbitrary jump-to-page behavior.
- Treating the cursor as stable forever. Always read and use the latest `nextCursor` from the response you just received.
