Make an API safe to call again
Picture
Define its resource, input, validation, response, and errors. For writes, also define what a repeated request means.
Predict
Is a timestamp alone always enough for gap-free keyset pagination?
Walk through the small example
No, ties need a stable unique key. A composite timestamp-and-ID order distinguishes tied records.
Mental model and runnable Python model
State endpoint contracts before implementation: for example POST /notes with validation and an idempotency key, GET /notes/{id} with ownership checks, and GET /notes?cursor=… with a documented order and limit. Keyset pagination resumes from a stable ordered key rather than skipping an offset that shifts under inserts. Version resource representations deliberately, bound page sizes, and use consistent error bodies without exposing secrets. Authentication establishes the principal; authorization must be checked on each operation.
What the function promises: The model pages an immutable snapshot of (timestamp,id) pairs in descending order. Real opaque cursors need encoding/validation and authorization; no network API is implemented.
def page_after(records, cursor=None, limit=2):
if not 1 <= limit <= 100:
raise ValueError("Invalid page size")
ordered = sorted(records, reverse=True)
if cursor is not None:
ordered = [record for record in ordered if record < cursor]
page = ordered[:limit]
following = page[-1] if len(ordered) > limit else None
return page, following
records = [(10, "c"), (10, "b"), (9, "a")]
first, cursor = page_after(records)
second, end = page_after(records, cursor)
assert first + second == sorted(records, reverse=True)
assert end is None
Work and storage: This illustrative implementation sorts O(n log n) and scans O(n). A matching database index can seek by cursor and read a bounded page without sorting the whole dataset.
When to recognize it: A cursor locates the next page. The server must still enforce the caller’s access rights and decide snapshot behavior under concurrent changes.
Check before applying it: Pagination, authorization, and idempotency are different API promises; one mechanism does not supply the others.
Change one rule
A POST response is lost and the client retries. What should a retry-safe write record?
See what changes
A stable request key, its validated payload fingerprint, and the committed result together with the state change. Reject conflicting payload reuse instead of executing a different operation under the same key.
Connect
Follow one request across the network: Network timeouts make the outcome of a write uncertain.
Make failure and retries part of the design: Carry the stable request identity across the server’s atomic write boundary.