# Pagination

Traverse stable result sets with opaque, signed keyset cursors.

## Cursor contract

| Property | Behaviour |
| --- | --- |
| Default page size | 20 resources |
| Maximum page size | 100 resources |
| Lifetime | 24 hours |
| Filter changes | Rejected explicitly |
| Page-size changes | Allowed |
| Tampering | Rejected explicitly |

## Pagination loop

1. Send the initial filters and `limit`.
2. Read `has_more` and `next_cursor`.
3. Send the same filters with that cursor.
4. Stop when `has_more` is false.

Official SDK iterators enforce the same loop and stop on a repeated cursor.

> **Treat cursors as opaque:** Do not decode, persist beyond their lifetime, modify or reuse them with another operation or channel.
