Skip to content

fix: polymarket deprecated endpoint update#233

Open
nikbpetrov wants to merge 1 commit into
forecastingresearch:mainfrom
nikbpetrov:polymarket-api-deprecation
Open

fix: polymarket deprecated endpoint update#233
nikbpetrov wants to merge 1 commit into
forecastingresearch:mainfrom
nikbpetrov:polymarket-api-deprecation

Conversation

@nikbpetrov

@nikbpetrov nikbpetrov commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

closes #194

No clue actually why the liquidity_num_min param was not used for server-side filtering; it results in more consistent behaviour and 10x fewer requests; i've tested and validated this param and it works as expected; the 5ff64b0 commit adds this (still keeping the client-side filtering for robustness)

fixes a dormant bug where the /markets endpoint can only offset up to 2500, while the qualifying markets are ~1000 more at the moment. Verified with:

> curl -sS -w '\nHTTP %{http_code}\n' \
  'https://gamma-api.polymarket.com/markets?limit=500&active=true&closed=false&archived=false&order=liquidity&ascending=false&offset=2500'
> {"type":"validation error","error":"offset too large, use /markets/keyset for deeper pagination"}

HTTP 422

Summary by CodeRabbit

  • New Features
    • Polymarket market loading now uses cursor-based pagination for smoother browsing of large result sets.
  • Bug Fixes
    • Improved duplicate handling when the same market appears across multiple pages.
    • More resilient market details parsing when API responses omit expected fields.
    • Added request timeouts and stronger request verification for price history retrieval.
  • Tests
    • Updated and expanded pagination and cursor-shape assertions, including deduplication and retry behavior.

@nikbpetrov nikbpetrov force-pushed the polymarket-api-deprecation branch from 19a9225 to f5a3f87 Compare June 30, 2026 11:51
@houtanb houtanb force-pushed the polymarket-api-deprecation branch from f5a3f87 to 5298f1b Compare July 9, 2026 19:42
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Migrates Polymarket market retrieval to the /markets/keyset endpoint and cursor-based pagination. The fetcher now applies liquidity filtering, deduplicates markets, uses request timeouts and TLS verification, tolerates missing market fields, and has updated pagination, retry, filtering, and response-shape tests.

Changes

Polymarket API pagination and request handling

Layer / File(s) Summary
Keyset pagination and market collection
src/sources/polymarket.py, src/tests/test_polymarket.py
Uses after_cursor/next_cursor pagination with liquidity and ordering parameters, deduplicates markets by conditionId, and stops when no cursor is returned. Tests verify cursor forwarding, omission of offset, liquidity filtering, deduplication, and updated response mocks.
HTTP hardening and response validation
src/sources/polymarket.py, src/tests/test_polymarket.py
Adds shared request timeouts and CLOB TLS verification, defaults missing markets fields to an empty list, and tests retry behavior, price-history filtering, and wrapped market responses.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: houtanb

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: updating Polymarket’s deprecated endpoint.
Linked Issues check ✅ Passed The PR switches Polymarket from /markets to /markets/keyset as required by issue #194.
Out of Scope Changes check ✅ Passed The changes stay aligned with the endpoint migration and related pagination/filtering adjustments.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sources/polymarket.py (1)

259-297: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

No retry/backoff for the pagination loop; transient errors silently truncate results.

_get_market and _fetch_price_history both use @backoff.on_exception for RequestException, but this loop just logs and breaks on the first failure, returning whatever pages were fetched so far without raising. Since fetch() (line 90-92) treats the returned list as the complete set of newly active markets, a single mid-pagination network blip can silently drop qualifying markets from the dataset with no error surfaced upstream.

Consider wrapping each page fetch with the same @backoff.on_exception pattern used elsewhere in this file, or at minimum re-raising after logging so callers are aware of a partial fetch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sources/polymarket.py` around lines 259 - 297, The pagination loop in the
Polymarket market fetch logic currently logs a RequestException and breaks,
which can silently return a partial market list. Update the page-fetch path in
the market retrieval method to use the same retry/backoff approach as
`_get_market` and `_fetch_price_history`, or re-raise the exception after
logging instead of breaking, so `fetch()` does not treat an incomplete result as
a successful full fetch.
🧹 Nitpick comments (2)
src/tests/test_polymarket.py (1)

390-412: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a test for the RequestException branch in the pagination loop.

_fetch_active_markets_from_api now has a try/except requests.exceptions.RequestException path that breaks and returns partial results (source lines 295-297), but no test in this class exercises it. Given the partial-results/data-loss concern raised on the source file, test coverage here would help lock in whatever behavior is decided.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tests/test_polymarket.py` around lines 390 - 412, Add a test for the
RequestException path in PolymarketSource._fetch_active_markets_from_api so the
pagination loop’s try/except branch is covered. Extend the existing pagination
tests in test_pagination or add a sibling test that patches
sources.polymarket.requests.get to succeed once and then raise
requests.exceptions.RequestException, then assert the method returns the partial
markets collected before the failure and stops making further requests.
src/sources/polymarket.py (1)

294-305: 🩺 Stability & Availability | 🔵 Trivial

Consider a safety cap on pagination iterations.

Termination relies entirely on the API omitting next_cursor on the last page. Given this is a newly-migrated endpoint, a defensive max-iteration/max-page guard would prevent a runaway loop if the API ever returns a stale/repeating cursor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sources/polymarket.py` around lines 294 - 305, The pagination loop in the
markets-fetching flow needs a defensive cap so it cannot run forever if the API
repeats a stale cursor. Update the logic around the `after_cursor`/`next_cursor`
handling in the markets retrieval method to track page or iteration count and
stop once a reasonable maximum is reached. Keep the existing `next_cursor`
termination path, but add a clear log and exit condition in the same loop that
fetches pages and appends to `all_markets`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/sources/polymarket.py`:
- Around line 256-266: The paginated Gamma API fetch in the market-loading loop
uses requests.get without a timeout, which can block the loop indefinitely.
Update the request in the market retrieval logic to pass an explicit timeout,
and keep it consistent across all iterations so a slow or hung API response
cannot stall the fetch process. Use the existing logger/info flow and the
requests.get call inside the market pagination loop as the place to apply the
fix.

---

Outside diff comments:
In `@src/sources/polymarket.py`:
- Around line 259-297: The pagination loop in the Polymarket market fetch logic
currently logs a RequestException and breaks, which can silently return a
partial market list. Update the page-fetch path in the market retrieval method
to use the same retry/backoff approach as `_get_market` and
`_fetch_price_history`, or re-raise the exception after logging instead of
breaking, so `fetch()` does not treat an incomplete result as a successful full
fetch.

---

Nitpick comments:
In `@src/sources/polymarket.py`:
- Around line 294-305: The pagination loop in the markets-fetching flow needs a
defensive cap so it cannot run forever if the API repeats a stale cursor. Update
the logic around the `after_cursor`/`next_cursor` handling in the markets
retrieval method to track page or iteration count and stop once a reasonable
maximum is reached. Keep the existing `next_cursor` termination path, but add a
clear log and exit condition in the same loop that fetches pages and appends to
`all_markets`.

In `@src/tests/test_polymarket.py`:
- Around line 390-412: Add a test for the RequestException path in
PolymarketSource._fetch_active_markets_from_api so the pagination loop’s
try/except branch is covered. Extend the existing pagination tests in
test_pagination or add a sibling test that patches
sources.polymarket.requests.get to succeed once and then raise
requests.exceptions.RequestException, then assert the method returns the partial
markets collected before the failure and stops making further requests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e0bd0532-d76b-41bc-b383-27fa364cb880

📥 Commits

Reviewing files that changed from the base of the PR and between d2b3364 and 5298f1b.

📒 Files selected for processing (2)
  • src/sources/polymarket.py
  • src/tests/test_polymarket.py

Comment thread src/sources/polymarket.py Outdated
Comment thread src/sources/polymarket.py
Comment thread src/sources/polymarket.py
Comment thread src/sources/polymarket.py Outdated
@nikbpetrov nikbpetrov force-pushed the polymarket-api-deprecation branch from 5298f1b to 1f9343f Compare July 10, 2026 09:23

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/sources/polymarket.py`:
- Around line 240-241: Ensure _fetch_price_history does not swallow
RequestException: re-raise a retryable exception so its backoff wrapper retries,
or make _fetch_active_markets_from_api treat a None result as a hard failure
instead of silently dropping the market. Update
test_skips_when_price_history_none to assert the revised failure and retry
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c56dcc66-16c1-4324-8dad-1592d6bae98d

📥 Commits

Reviewing files that changed from the base of the PR and between 5298f1b and 1f9343f.

📒 Files selected for processing (2)
  • src/sources/polymarket.py
  • src/tests/test_polymarket.py

Comment thread src/sources/polymarket.py
Comment on lines +240 to +241
and attaches each qualifying market's price history. A transient request failure retries
the whole paginated fetch via backoff rather than returning a truncated result.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== outline ==\n'
ast-grep outline src/sources/polymarket.py --view expanded || true

printf '\n== search symbols ==\n'
rg -n "_fetch_price_history|backoff|test_skips_when_price_history_none|RequestException|price history" src tests || true

printf '\n== line ranges ==\n'
nl -ba src/sources/polymarket.py | sed -n '210,330p'

Repository: forecastingresearch/forecastbench

Length of output: 11274


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== polymarket slices ==\n'
sed -n '229,310p' src/sources/polymarket.py
printf '\n== fetch_price_history slice ==\n'
sed -n '348,398p' src/sources/polymarket.py
printf '\n== relevant tests slice ==\n'
sed -n '430,530p' src/tests/test_polymarket.py

Repository: forecastingresearch/forecastbench

Length of output: 10022


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '130,175p' src/sources/polymarket.py
sed -n '175,235p' src/sources/polymarket.py

Repository: forecastingresearch/forecastbench

Length of output: 4660


Do not swallow price-history failures

_fetch_price_history catches RequestException and returns None, so its backoff wrapper never retries. In _fetch_active_markets_from_api, that None just drops the market, which leaves the fetched set incomplete instead of retrying the failing request. Re-raise a retryable exception here, or handle None as a hard failure, and update test_skips_when_price_history_none accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sources/polymarket.py` around lines 240 - 241, Ensure
_fetch_price_history does not swallow RequestException: re-raise a retryable
exception so its backoff wrapper retries, or make _fetch_active_markets_from_api
treat a None result as a hard failure instead of silently dropping the market.
Update test_skips_when_price_history_none to assert the revised failure and
retry behavior.

Comment thread src/sources/polymarket.py
Comment on lines +259 to 260
"order": "liquidityNum",
"ascending": False,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file around the cited lines and relevant helpers.
python3 - <<'PY'
from pathlib import Path
path = Path("src/sources/polymarket.py")
lines = path.read_text().splitlines()
for start, end in [(1, 360)]:
    for i in range(start-1, min(end, len(lines))):
        if 220 <= i+1 <= 340:
            print(f"{i+1:4d}: {lines[i]}")
PY

Repository: forecastingresearch/forecastbench

Length of output: 6231


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map nearby symbols to focus on pagination and retry behavior.
ast-grep outline src/sources/polymarket.py --view expanded

Repository: forecastingresearch/forecastbench

Length of output: 1368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect price-history handling and surrounding retry logic.
python3 - <<'PY'
from pathlib import Path
path = Path("src/sources/polymarket.py")
lines = path.read_text().splitlines()
for start, end in [(350, 390)]:
    for i in range(start-1, min(end, len(lines))):
        print(f"{i+1:4d}: {lines[i]}")
PY

Repository: forecastingresearch/forecastbench

Length of output: 1947


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Continue to the exception handling and any caller behavior.
python3 - <<'PY'
from pathlib import Path
path = Path("src/sources/polymarket.py")
lines = path.read_text().splitlines()
for start, end in [(389, 405)]:
    for i in range(start-1, min(end, len(lines))):
        print(f"{i+1:4d}: {lines[i]}")
PY

Repository: forecastingresearch/forecastbench

Length of output: 1035


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the rest of the price-history exception path.
python3 - <<'PY'
from pathlib import Path
path = Path("src/sources/polymarket.py")
lines = path.read_text().splitlines()
for start, end in [(389, 405)]:
    for i in range(start-1, min(end, len(lines))):
        print(f"{i+1:4d}: {lines[i]}")
PY

Repository: forecastingresearch/forecastbench

Length of output: 1035


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the remainder of the price-history exception path to see how failures are surfaced.
python3 - <<'PY'
from pathlib import Path
path = Path("src/sources/polymarket.py")
lines = path.read_text().splitlines()
for start, end in [(389, 410)]:
    for i in range(start-1, min(end, len(lines))):
        print(f"{i+1:4d}: {lines[i]}")
PY

Repository: forecastingresearch/forecastbench

Length of output: 1257


🌐 Web query:

Polymarket Gamma API markets keyset pagination order after_cursor liquidityNum docs

💡 Result:

The Polymarket Gamma API utilizes keyset (cursor-based) pagination for efficient data retrieval, explicitly rejecting offset-based pagination [1][2][3]. Keyset Pagination Mechanics: - Use the after_cursor query parameter to request subsequent pages [1][3]. - The API response contains a next_cursor field; pass this value into the after_cursor parameter of the next request [1][3]. - Note: Offset-based pagination is not supported and will return an HTTP 422 error [1][3]. - Technical reports as of April 2026 have noted potential issues where the keyset endpoint may fail to advance past the first page when the cursor parameter is provided [4]. Developers are encouraged to use the provided TypeScript or Python SDKs, which abstract these pagination patterns [5][6]. LiquidityNum: - liquidityNum represents a market's liquidity expressed as a numerical value [7][8][9]. - It is available as a filterable field in various endpoints (e.g., liquidity_num_min, liquidity_num_max) to refine market search results [10][9]. General API Tips: - The Gamma API base URL is https://gamma-api.polymarket.com [3]. - SDKs for TypeScript and Python are available to simplify handling of pagination, typing, and request flows [5][6].

Citations:


Avoid paginating on mutable liquidity. A market can move across the cursor between requests and be skipped entirely; the conditionId dedupe only removes repeats, not misses. Also, _fetch_price_history catches RequestException and returns None, so the backoff wrapper never retries and transient CLOB failures silently drop markets from the output.

@nikbpetrov

Copy link
Copy Markdown
Collaborator Author

tests pass (my old-new test harness)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Polymarket API endpoint is deprecated and has already been sunset

2 participants