Skip to content

gamma-api keyset endpoints: cursor parameter ignored, returns same first page #227

@punksterlabs

Description

@punksterlabs

Summary

Both gamma-api.polymarket.com/markets/keyset and gamma-api.polymarket.com/events/keyset return HTTP 200 with a populated next_cursor, but passing that next_cursor back as cursor=... on the next request returns the same first-page items. The cursor parameter appears to be ignored server-side.

This blocks every client preparing for the May 1 2026 sunset of the legacy /markets, /events list endpoints, since the keyset endpoints are the documented replacement.

Reproducer

# Page 1, no cursor.
curl -s "https://gamma-api.polymarket.com/markets/keyset?limit=2" \
  | python -c "import json,sys; d=json.load(sys.stdin); print('ids:', [m['id'] for m in d['markets']]); print('next_cursor:', d['next_cursor'][:40] + '...')"

# Page 2, using the next_cursor from page 1.
NXT=$(curl -s "https://gamma-api.polymarket.com/markets/keyset?limit=2" \
  | python -c "import json,sys; print(json.load(sys.stdin)['next_cursor'])")
curl -s "https://gamma-api.polymarket.com/markets/keyset?limit=2&cursor=${NXT}" \
  | python -c "import json,sys; d=json.load(sys.stdin); print('ids:', [m['id'] for m in d['markets']]); print('next_cursor:', d['next_cursor'][:40] + '...')"

Expected: page 2 returns ids strictly different from page 1 (advancing the keyset).
Observed: page 2 returns the identical ids and the identical next_cursor.

Same behavior on /events/keyset. Reproducible from any IP at the time of writing (2026-04-27).

What I've ruled out

  • URL encoding of the cursor (with and without urlencode)
  • Cursor parameter name (cursor, next_cursor, after all behave identically)
  • Transport (POST body and X-Cursor header don't help)
  • Cloudflare cache (cache-buster _=$RANDOM produces cf-cache-status: MISS but the server still returns the same first page)
  • Filter combinations (with and without closed=false, order=id, ascending=false — all loop)
  • Decoding the cursor confirms it correctly encodes the last-seen id, e.g.:
    {"v":1,"k":"markets","oh":"...","keys":[{"t":"string","v":"540817"}]}
    so the server is correctly producing the cursor — it just isn't honoring the cursor on the next request.

A multi-page walk against /markets/keyset exhausts at 200 pages, returning ~1,000 unique markets and ~199,000 duplicates (every page is page 1).

Cross-check: CLOB cursor works

For comparison, clob.polymarket.com/markets cursor pagination works correctly:

NXT=$(curl -s "https://clob.polymarket.com/markets" | python -c "import json,sys; print(json.load(sys.stdin)['next_cursor'])")
# NXT = "MTAwMA==" (decodes to "1000")
curl -s "https://clob.polymarket.com/markets?next_cursor=${NXT}" | python -c "import json,sys; print(json.load(sys.stdin)['next_cursor'])"
# Returns "MjAwMA==" (decodes to "2000") — cursor advances correctly.

So this is gamma-keyset-specific; not a general issue with cursor handling at Polymarket.

Asks

  1. Is this a known issue? I haven't found a tracking item.
  2. ETA on a fix relative to the May 1 2026 sunset of /markets and /events? If the cursor stays broken when legacy sunsets, every keyset client loses pagination simultaneously.
  3. Any documented workaround on the keyset endpoints? (Alternative pagination parameter name, or a documented requirement I've missed.)
  4. If no near-term fix is planned, is clob.polymarket.com/markets (whose cursor works) a supported alternative source for the full market universe? It returns a different shape so clients would need to map.

Happy to provide more diagnostic data — full request/response captures, cursor base64 decodes, etc. — if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions