Pagination¶
Every "list" endpoint that can return more than a server-imposed cap returns a
Page[T]. A page is iterable, knows whether there's a next page, and carries
the opaque cursor you'd hand back to fetch it.
Anatomy¶
page = client.markets.list(status="open", limit=200)
for market in page: # Page is iterable
print(market.ticker)
len(page) # items on this page
page.items # the underlying list[Market]
page.cursor # next-page cursor (None or "" if you're on the last page)
page.has_next # bool — True iff cursor is set and non-empty
Page is a Pydantic model with extra="allow", so any envelope fields the
server attaches (totals, debug keys) survive on the instance and you can read
them with normal attribute access.
Walking pages manually¶
cursor = None
while True:
page = client.orders.list(status="resting", cursor=cursor, limit=200)
for order in page:
process(order)
if not page.has_next:
break
cursor = page.cursor
list_all() — the easy way¶
Every resource that has a list() also has a list_all() that walks cursors
for you:
# Sync: returns an Iterator[T]
for market in client.markets.list_all(status="open"):
...
# Async: returns an AsyncIterator[T] — async for works directly
async for market in async_client.markets.list_all(status="open"):
...
list_all() accepts the same kwargs as list() (minus cursor) plus an
optional max_pages cap. It walks until the server returns no more pages.
Cursor-loop safety¶
list_all() keeps a set of cursors it has already used. If the server returns
a cursor that already appeared, it raises KalshiError("Cursor loop detected
on …"). This catches server-side pagination bugs that would otherwise spin
forever.
There is no built-in 1000-page cap. If you want one, pass
max_pages=1000.
Endpoints that don't paginate¶
A few list endpoints return a plain list[T], not Page[T]:
client.series.list(...)— flat list of series.client.exchange.announcements()— a flat list.client.order_groups.list()— no cursor.client.account.limits()/client.exchange.status()— single objects.client.markets.bulk_orderbooks(...)/bulk_candlesticks(...)— flat list of up to 100 items.
These don't need pagination; they're sized at most by the API's natural caps.
Endpoints with a non-Page shape¶
client.portfolio.positions() and client.fcm.positions() return
PositionsResponse, not Page. It carries two parallel lists
(market_positions and event_positions) plus its own cursor /
has_next. There is no positions_all() helper — walk it manually:
cursor = None
while True:
resp = client.portfolio.positions(cursor=cursor)
for pos in resp.market_positions:
...
if not resp.has_next:
break
cursor = resp.cursor
client.incentive_programs.list() uses the server-side cursor key
next_cursor instead of cursor. The SDK normalizes this — pass cursor=
and read .cursor exactly like any other list call.
DataFrames¶
Page[T] carries .to_dataframe() and .to_polars(). They convert only the
current page — not all pages. See DataFrames.
Reference¶
kalshi.models.common.Page ¶
Bases: BaseModel, Generic[T]
A page of results from a list endpoint.
Iterable over items. Also exposes cursor metadata for manual pagination.
Usage
page = client.markets.list(limit=50) for item in page: # iterate items process(item) print(page.cursor) # access cursor print(page.has_next) # check if more pages exist