Api pagination
29 free, verified agents, skills & packs for Claude Code - install with 'npx vanara install <name>'. Apache-2.0. From the Vanara catalog (206 items).
npx -y skills add vanara-agents/skills --skill api-paginationAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 22 days oldThe repository was created 22 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 7 stars7 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Implement correct, fast API pagination — cursor vs offset trade-offs, opaque cursor encoding, stable sort keys, page-size limits, total-count costs, and consumer-side iteration that survives inserts, deletes, and retries. With runnable cursor checks.
SKILL.md
3.9 KB, as published. Nobody here has run it
API Pagination
Pagination looks trivial until data changes underneath the reader. Offset pagination silently
skips or duplicates rows when inserts land mid-iteration; cursor pagination survives churn but
demands a stable sort key and an opaque token contract. This skill covers both sides: designing the
API and consuming one correctly. Deep detail lives in references/; copy-paste material in
examples/; a runnable cursor round-trip check in scripts/.
Decision: offset vs cursor
| Situation | Use |
|---|---|
| Admin tables, small datasets, "jump to page 7" required | Offset (LIMIT/OFFSET) |
| Feeds, sync endpoints, anything users scroll | Cursor (keyset) |
| Data changes while clients iterate | Cursor — offset will skip/duplicate |
| Deep pages (offset > ~10k rows) | Cursor — OFFSET n scans and discards n rows |
Offset's failure mode is correctness, not just speed: a row inserted before your current position shifts everything, so page 3 re-shows an item from page 2 (duplicate) or swallows one (skip).
Keyset mechanics (the part people get wrong)
The sort key must be unique and immutable. created_at alone is not unique — two rows with the
same timestamp make the cursor ambiguous and rows vanish. Always add a tiebreaker:
-- WHERE clause for "next page after (2026-07-01T12:00:00Z, id 4711)" descending
SELECT * FROM items
WHERE (created_at, id) < ('2026-07-01T12:00:00Z', 4711)
ORDER BY created_at DESC, id DESC
LIMIT 51; -- page_size + 1 to detect has_more without COUNT(*)
Fetch page_size + 1 rows: if you get 51, there is a next page and the 51st row's key becomes the
next cursor. This avoids COUNT(*), which on large tables costs more than the page itself
(see references/counting-and-totals.md).
Cursor token contract
Cursors are opaque to clients — never let them parse or build one. Encode the keyset values plus
the sort direction, base64url it, and (for public APIs) HMAC it so tampering is detectable. Include a
version byte so you can change the encoding later. Full recipe: references/cursor-encoding.md;
runnable round-trip: scripts/check-cursor.mjs.
Response envelope
{
"data": [ ... ],
"page_info": { "has_more": true, "next_cursor": "djEuMj..." }
}
Rules: next_cursor is absent (not empty-string) on the last page; page size is capped server-side
(a limit=10000 request gets the cap, documented, not an error); the default page size appears in
the docs and never changes silently. Consumer-side iteration rules — retries, cursor expiry,
resumption — live in references/consuming-pages.md.
Pitfalls
- Mutable sort keys (
updated_at) — rows teleport across pages as they update. Sort on immutable columns or accept re-delivery and dedupe client-side. - Cursor built from row position instead of row values — it is offset with extra steps and inherits every offset bug.
COUNT(*)on every page — the count is stale by the time the client renders it; provide totals on a separate, cacheable endpoint if the product truly needs them.- Breaking cursor encoding on deploy — clients hold cursors across releases; version the token.
- Missing composite index — keyset needs an index on exactly
(created_at, id)in sort order, or every page is a scan.
Verification
Run node scripts/check-cursor.mjs — encodes, decodes, tampers, and versions a cursor and asserts
every property above. For the SQL side, examples/keyset-queries.sql includes an EXPLAIN you can
run to confirm the index is used. Pairs with rest-api-design (envelopes, limits) and
sql-index-tuning (composite indexes).