SDK package and version
polymarket-client==0.7.1 (same code in 0.7.0)
Runtime
Python 3.14.5, uv 0.12.6
What happened?
/v1/market-positions returns one entry per outcome token with the rows nested inside, and the SDK models it that way — items are MetaMarketPosition (_internal/actions/data.py:330-359).
The generic offset paginator then decides whether a page is full by counting top-level items:
# _internal/pagination.py:104-129
has_more = len(items) >= page_size
next_cursor = encode_offset_cursor(...) if has_more else None
But len(items) here is the number of outcomes, not the number of rows: 2 for a binary market. At the endpoint maximum page_size=500 the check is 2 >= 500, so the first page always reports has_more=False and next_cursor=None.
The rows are reachable: limit and offset do apply inside each group. But with next_cursor=None there is no second page to ask for, so iter_items() and to_pandas(limit=None) return the first page and report no truncation. Async behaves the same.
Among offset-paginated specs this is the only grouped response; the flat ones are unaffected. It looks like a leftover from #192/#205: len(items) >= page_size is correct for flat responses, but here the item count is decoupled from the row count.
Minimal reproduction
from unittest.mock import patch
from polymarket._internal.actions.data import list_market_positions_spec
from polymarket._internal.dispatch import sync_paginate_offset
TOTAL = 1200 # positions per outcome, above the endpoint maximum of 500
class GroupedTransport:
def __init__(self) -> None:
self.calls = []
def get_json(self, path, params=None):
self.calls.append(dict(params or {}))
offset, limit = int(params["offset"]), int(params["limit"])
window = range(offset, min(offset + limit, TOTAL))
return [
{"token": token, "positions": [{"size": "1"} for _ in window]}
for token in ("111", "222")
]
transport = GroupedTransport()
spec = list_market_positions_spec(market="0x" + "ab" * 32, status="OPEN")
with patch("polymarket._internal.dispatch._sync_transport_for", return_value=transport):
groups = list(sync_paginate_offset(object(), spec, page_size=500).iter_items())
print(len(transport.calls)) # 1, expected 3
print([len(g.positions) for g in groups]) # [500, 500], expected [1200, 1200]
Expected behavior, actual behavior, or logs
Expected: three requests (offset=0, 500, 1000) and all 1200 positions per outcome.
Actual: one request, 700 rows per outcome dropped silently, no truncation marker.
Possible fix: let OffsetPaginatedSpec carry a row-count function for the fullness check — len(items) by default, sum(len(g.positions or ()) for g in items) for this endpoint.
SDK package and version
polymarket-client==0.7.1(same code in0.7.0)Runtime
Python 3.14.5, uv 0.12.6
What happened?
/v1/market-positionsreturns one entry per outcome token with the rows nested inside, and the SDK models it that way — items areMetaMarketPosition(_internal/actions/data.py:330-359).The generic offset paginator then decides whether a page is full by counting top-level items:
But
len(items)here is the number of outcomes, not the number of rows: 2 for a binary market. At the endpoint maximumpage_size=500the check is2 >= 500, so the first page always reportshas_more=Falseandnext_cursor=None.The rows are reachable:
limitandoffsetdo apply inside each group. But withnext_cursor=Nonethere is no second page to ask for, soiter_items()andto_pandas(limit=None)return the first page and report no truncation. Async behaves the same.Among offset-paginated specs this is the only grouped response; the flat ones are unaffected. It looks like a leftover from #192/#205:
len(items) >= page_sizeis correct for flat responses, but here the item count is decoupled from the row count.Minimal reproduction
Expected behavior, actual behavior, or logs
Expected: three requests (
offset=0, 500, 1000) and all 1200 positions per outcome.Actual: one request, 700 rows per outcome dropped silently, no truncation marker.
Possible fix: let
OffsetPaginatedSpeccarry a row-count function for the fullness check —len(items)by default,sum(len(g.positions or ()) for g in items)for this endpoint.