A key-gated, queryable feed of settled conditions read from the Polygon blockchain: one row per ConditionResolution event this project has captured on the shared ConditionalTokens contract, keyed by condition_id, carrying the on-chain payout vector, the winning outcome index derived from it, the reporting oracle address, the question_id to join your own market metadata on, and the settlement block.
Two properties do the most work. First, it is a contract-wide read, not a venue read: every row comes from one shared contract, 0x4d97dcd97ec945f40cf65f87097ace5ea0476045, any address may report a payout vector to it, and the store held 77 distinct reporting addresses when that was measured on 2026-08-25. The feed publishes oracle and oracle_class on every row because attributing a settlement to any particular venue is a judgement about the reporting address, and the feed will not make it for you. Second, the default page is chain-only: without include_metadata=true the request addresses no venue-derived table at all.
Get a page and read the envelope before the rows: universe, coverage and freshness tell you what span the answer is drawn from and how far behind the chain it is.
curl -sS -H "X-API-Key: $CONVEXLY_API_KEY" \
"https://www.convexly.app/api/engine/public/resolved-markets?limit=5"freshness.newest_settlement_at currently reports a date in October 2020 and is not a feed-recency signal (see deviation 1 below). Read freshness.age_seconds beside age_measured_from, and coverage.swept_through; those are the fields that answer how current the feed is.Look up conditions you already care about. Ids the feed does not hold come back by name in requested_not_found:
curl -sS -H "X-API-Key: $CONVEXLY_API_KEY" \
"https://www.convexly.app/api/engine/public/resolved-markets?condition_id=0xAAA...&condition_id=0xBBB...&limit=200"Walk the whole store forward with cursor, not offset: an offset's cost grows with depth on a store this size, a cursor is a range boundary the index starts at, so its cost does not. Resend with the cursor from the last response until has_more is false.
# first page
curl -sS -H "X-API-Key: $CONVEXLY_API_KEY" \
"https://www.convexly.app/api/engine/public/resolved-markets?order=block_asc&since_block=6205069&limit=200"
# then, until has_more is false
curl -sS -H "X-API-Key: $CONVEXLY_API_KEY" \
"https://www.convexly.app/api/engine/public/resolved-markets?order=block_asc&since_block=6205069&limit=200&cursor=$NEXT"Every response carries a coverage block: floor_block, swept_through, head_lag_blocks, rewind_count, no_authorised_rewinds, source. Each field is stated here with its own boundary, because every one of them has a natural over-reading.
Absence is not a negative result. rows_matched counts rows this lane has captured, never markets that settled. Nothing in this feed supports the inference that a missing market did not settle.
The sweep walks contiguously forward from a declared floor. floor_block (6,205,069) is the lowest block the sweep may ever be positioned at, and the cursor only ever advances to a block it has read. The sub-floor claim carries a measured receipt: two independent rescans on unaffiliated RPC endpoints each walked block 0 to 6,205,068 in 621 windows and found every window empty with zero errored reads. The scan artifacts and the standalone checker are linked from the data page.
no_authorised_rewinds is not a contiguity claim. It reports that rewind_count is zero, meaning no span was re-read under a recorded authorisation. It does not say every block in the span produced a row, and it says nothing about blocks below the floor. It is named for what it proves.
capture_completeness describes a block span and nothing else. Every row currently reports windowed, meaning the read that produced it examined the span from capture_from_block to capture_to_block. The stronger value in the schema has no writer, deliberately: coverage is a property of the sweep, so the lane-level statement lives in coverage and never in a per-row stamp.
oracle says who reported, never who was right. The event carries nothing about proposal, dispute, bond, or how a dispute ended. oracle_class is a statement about the address, never about the settlement: unclassified is the default and covers the majority of addresses, and eoa means an externally owned account reported the payout directly, which is a structural fact worth knowing and not an allegation.
settlement_timestamp is null on effectively every row, by design. A Polygon log carries no timestamp of its own, and resolving one costs an extra RPC per distinct block, which the sweep does not spend. Measured 2026-08-25 16:08 UTC: 3 of 2,838,273 rows carry a settlement time. Derive settlement time from settlement_block against a Polygon node you control.
One row per condition is a measurement, not yet a constraint. Measured 2026-08-25 16:08 UTC: 2,838,273 rows, 2,838,273 distinct condition ids, zero nulls, held by writer behaviour rather than a unique index. If your pipeline keys on condition_id, key defensively.
The census above is not the receipt, and they do not agree. The published coverage manifest is an earlier read of the same store: 2,835,501 ConditionResolution events swept through block 92,639,309, generated 2026-08-25 15:45 UTC. The sweep runs every three hours, so the store gained 2,772 rows in the interval between the two reads. The manifest is the hashed, re-derivable figure and is the one to quote back at us; the census is the later count and the only source for the per-column coverage on this page.
question_text and resolution_rule_text are venue-published strings, not chain reads, and they are off by default. Measured 2026-08-25 16:08 UTC: 15,217 of 2,838,273 rows carry a non-empty question text. Do not build on this field being present. Every row reports a metadata_state:
There is no uptime commitment on this feed, and there will not be one. The path is Vercel to Railway to Supabase, three providers whose targets Convexly does not control, and the operator is one person on active military duty. A number that implied a night rota would be a fabrication. What exists instead is a measured freshness objective, republished with its measurement, and failure semantics designed so a broken read is distinguishable from an empty one: an unreadable store returns 502 and never an empty page.
The freshness objective: a settlement becomes readable within one sweep interval after it is head_lag_blocks deep (300 blocks, about 7.5 minutes at current Polygon block time). The sweep runs every three hours; target p50 under 3 hours and p95 under 5 hours, measured monthly. Nothing is owed if the objective is missed; what is owed is that the measurement is published and honest.
Read freshness rather than trusting any table. Every response carries age_seconds plus age_measured_from, which says whether that age is the age of the job or the age of the data. One caveat that favours you: stale_after_seconds is 172,800 (48 hours), roughly eleven times the measured p95, so state: "current" is a weak signal. If you need a sharp staleness signal, apply your own rule to age_seconds; six hours is a reasonable line.
Corrections: Convexly publishes corrections to its own figures unprompted: any published figure about this feed that a later measurement contradicts is recorded, key holders whose served rows were affected are notified within 2 business days of confirmation, and a published correction naming the wrong figure, the corrected figure, and the method follows within 5. Support objective: a first substantive reply within one business day, at support@convexly.app. Include the schema_version, the exact request URL, and if you can the freshness.last_run_at value from the response you are asking about.
Deprecation commitment: any breaking change will be announced ahead of time with a minimum of fourteen days between announcement and sunset. The response-header mechanics for that announcement (RFC 9745 Deprecation, RFC 8594 Sunset) are a commitment being built to, not yet emitted on this path; until they are, notice is delivered directly to each key holder.
schema_version is the pin. It bumps when a field is removed, renamed, or changes meaning. Additive fields do not bump it, so parse permissively: an unknown field is expected and is not an error. Version 2.0.0 (2026-08-25) was a major bump for three meaning changes: the default became chain-only (text fields null unless include_metadata is set), metadata_state gained the fourth value suppressed, and rows_matched narrowed to condition-id lookups carrying no other filter and no cursor. feed_id is stable and independent of the URL.
A contract that describes intent rather than behaviour is worse than no contract, so the gaps between the documentation and the code are published, dated, and re-checked before every revision of this page. The consumer-facing ones, as of 2026-08-27:
Back to the API reference · Coverage receipt package · Pricing
Feed id: convexly.polymarket.resolved-markets