CursorProof
CursorProof is a command-line tool for checking cursor pagination in REST APIs. It traverses an endpoint at several page sizes, checks duplicates, ordering and termination, and saves a trace that can be replayed without the API.
An API can return 200 OK on every page while skipping records or repeating a
page. An independent oracle, such as an unpaginated endpoint or database query,
lets CursorProof compare the returned stream with the expected records.
Install
Requires Python 3.10 or newer.
pipx install cursorproof
# or
python -m pip install cursorproof
Check your API
Save a configuration as cursorproof.yml. This example expects results and
next_cursor in each response and records identified by id:
url: http://localhost:8000/api/orders
response:
items: $.results
next_cursor: $.next_cursor
id: $.id
ordering:
- field: id
type: number
limits: [10, 50, 100]
Validate the configuration, run the checks, and replay the saved trace:
cursorproof check cursorproof.yml
cursorproof run cursorproof.yml
cursorproof replay cursorproof-repro.json
Token pagination is the default: the next request sends the returned token in
the cursor query parameter and the selected page size in limit.
For APIs that return a continuation URL, including Django REST framework, add:
pagination:
mode: url
cursor_param: cursor
limit_param: page_size
response:
items: $.results
next_cursor: $.next
id: $.id
URL mode requires CursorProof 0.2.0 or newer. To install the version from a
checkout, run uv tool install . from the repository root.
URL mode follows server-provided links after checking the origin, endpoint, scope parameters and page size. It does not follow redirects. The user guide explains authentication, response paths, ordering, oracles and the continuation URL contract.
Try the local demo
Clone the repository and run these commands from its root directory.
Install from the checkout with uv:
uv sync --locked
To install the checkout as an isolated CLI tool instead, run uv tool install ..
Start the demo API in one terminal:
uv run python examples/demo_api.py
In another terminal, run the broken example:
uv run cursorproof run examples/broken.yml
Replay its saved trace offline with:
uv run cursorproof replay cursorproof-repro.json
The demo has 40 records with repeated sort values. /broken continues using only
the timestamp and skips records at page boundaries despite successful responses.
CursorProof reports CP003 MISSING_ITEMS and differences between limits. The
/correct endpoint also uses the unique ID; try it with:
uv run cursorproof run examples/correct.yml --format json --repro correct-repro.json
Understand the result
- Exit
0: all executed checks passed. - Exit
1: CursorProof found a pagination invariant violation. - Exit
2: configuration or execution failed, or a traversal could not finish.
Use --format json for a machine-readable report and --repro PATH to choose
the trace file. A passing run describes the tested data and configuration.
Without an oracle, it cannot establish completeness: every tested page size
could omit the same records. See the guide for
oracle configuration,
finding codes
and trace privacy.
Django REST framework example
The DRF configuration
uses standard CursorPagination links. The
Django REST framework example
shows how a large group of equal sort values can repeat a page. It includes a
local HTTP/SQLite setup, a unique-ordering control and traces you can replay
without Django.
EmDash plugin storage example
The EmDash example
is a historical check against a TypeScript target. It runs EmDash's real
PluginStorageRepository before and after the fix for
issue #2691 behind a thin
test wrapper, compares the stream with an independent database oracle and saves
traces you can replay without Node.js. It is not a claim about current EmDash.
Additional checks
CursorProof also supports snapshot consistency, live keyset checks, mutation hooks, cardinality boundary tests and cursor binding probes. Mutation hooks and boundary tests execute commands supplied in your configuration and can change data. Use controlled test environments and read the consistency and mutation documentation before enabling them. URL mode does not support boundary tests or cursor binding probes.
Run cursorproof --help or cursorproof COMMAND --help for command options.
Development
uv sync --locked
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv build
The analyzer checks recorded observations without network I/O. The runner makes requests, and the CLI displays reports. Tests use HTTPX transports, Hypothesis and the local demo API.
The optional locked integration group adds Django/DRF for tests against a real
HTTP endpoint. Follow the
integration instructions
to use a separate environment and the built wheel. CI runs the core checks
on Python 3.10 through 3.14. The Django/DRF integration tests require
Python 3.12 or newer and run on Python 3.12 through 3.14.
Contributing and support
See Contributing for setup, checks and pull request guidance. Use issues for bugs and feature requests, and follow the code of conduct. Report vulnerabilities privately using the security policy.
Release history is in the changelog. CursorProof is licensed under Apache-2.0.
Metadata
Release files for cursorproof 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| cursorproof-0.2.1.tar.gz | 119.0 kB | With provenance | Details for cursorproof-0.2.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| cursorproof-0.2.1-py3-none-any.whl | Python 3 | none | any | Details for cursorproof-0.2.1-py3-none-any.whl |
Total release size: 155.1 kB
Release files / cursorproof-0.2.1.tar.gz
| Download URL | cursorproof-0.2.1.tar.gz |
|---|---|
| Size | 119.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1e09283e52b4e3b3c4b1715850d96d43fabd47a979eceda0a7c7d674de4cdc25
|
|
BLAKE2b-256 checksum How to use checksums |
62b5336f5842cf1686b57bfaa0f590b2b10befe43cec7f9ef7016959102b3e4b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logRelease files / cursorproof-0.2.1-py3-none-any.whl
| Download URL | cursorproof-0.2.1-py3-none-any.whl |
|---|---|
| Size | 36.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8b80c7b0e4e2f19c73687a98c444916cb488bf0818b5c4a8ada125757e2ffdf4
|
|
BLAKE2b-256 checksum How to use checksums |
d9452791b98d6c95a2ebf721af728424e0aba2b987464ab95f9ddc79afda358d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logSecurity for cursorproof 0.2.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for cursorproof-0.2.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.