groupItemTitle.
Every event response carries the event’s markets in markets[], but
not always the same market row: the list (GET /api/events) carries a
slimmer list row per market, while GET /api/events/{slug} and
GET /api/events/featured carry the full MarketSummaryDto — see
List rows vs detail. Most clients query
GET /api/events for the home grid, GET /api/events/{slug} for an
event page and GET /api/markets/{slug} for the trading detail page.
Event shape
One row ofGET /api/events (the event fields are the same on
GET /api/events/{slug} and GET /api/events/featured). Every
event-level key is always present; nullable ones are null, never
missing. For the keys of each markets[] row, see List rows vs
detail.
Football metadata fields
Other event fields
Crypto events
An event in thecrypto vertical carries a cryptoSeries block when
its markets belong to a recurring Up/Down
series; every other event carries
null. The factory creates one event per series per UTC day, so an
Up/Down event is a day of windows (~96 at the 15-minute cadence) —
navigate it through GET /api/crypto-series/{id}/windows rather than
through markets[], which nests only the windows around now: the
tradeable ones on the list, plus the windows awaiting settlement and the
three latest-ending settled (RESOLVED or VOIDED) windows on
GET /api/events/{slug}. A finished day on the list’s ?status=closed
view nests the same: every window still awaiting settlement and at most
three settled ones. The one exception is a day whose windows were all
VOIDED before they ended: the list nests all of them, and
GET /api/events/{slug} returns 404 for it. For the whole day, settled
windows included, call
GET /api/crypto-series/{id}/windows?state=all&from=2026-07-29&to=2026-07-30.
totalMarketCount counts every window of the day. Check each market’s
status before trading.
cryptoSeries: null — their markets carry the rule in
crypto, and an event
that is one period of a recurring long-dated series carries a
cryptoLongSeries block.
Per-market fields driven by the event grouping
List rows vs detail
The event fields are the same on all three. Because
markets[]
depends on the view, render counts from openMarketCount /
totalMarketCount, never from markets.length. The order of
markets[] is stable but not part of the contract.
Fields the list row leaves out
Slimmer list rows. A market row on
GET /api/events is the full MarketSummaryDto without the fields below.
Everything else — ids and token ids, outcomes, outcomePrices,
chances, status, tradable, tradingWindow, lifecycle,
resolution, crypto, cryptoWindow, marketType, vertical,
volumes — is the same as on the full row. Migration notes:
Changelog.
Two blocks are trimmed rather than removed:
predictionisnullexcept on markets listed from a vendor prediction question (for example politics).probabilityChangeis the 24-hour move of each outcome’s implied probability, in percentage points (0.27→0.32is5), rounded to two decimals.changeisnullwhen there is no price to compare with; the block itself can benullor absent.
GET /api/markets?limit=500&offset=… (up
to 500 full rows per request, with hasMore; PRE_MARKET, OPEN,
PAUSED and POSTPONED by default, closed=true or status= for the
others) instead of calling
GET /api/events/{slug} once per event — detail calls share the
/api/events* rate-limit bucket with your list walk. For one event’s
markets in any status, call GET /api/events/{slug}; for one market,
GET /api/markets/{slug} (no probabilityChange).
Filtering
Filters AND across fields and OR within a field. The
multi-value fields (
stage, group, team, tag, vertical,
competition, subcategory, scope, asset, cadence, frequency,
predictionType) accept repeated keys (?tag=a&tag=b) or CSV
(?tag=a,b) — both forms can be mixed, up to 20 distinct values per
field, duplicates collapsed. More is 400 too_many_<field>, for
example too_many_tag (predictionType uses too_many_prediction_type).
By default the list holds events with at least one tradeable market
(PRE_MARKET / OPEN / PAUSED / POSTPONED), plus fixtures
currently in play, whose markets may already be CLOSED.
?status=closed (or the legacy ?closed=true) returns the settled /
finished set instead: events with no tradeable market and at least one
closed market, excluding fixtures in play.
Paging
GET /api/events returns at most 100 events per response. Page
with page / limit: a limit above 100 is served as 100, not
rejected, and without page / limit you get the first 100 events.
Walk page=1, 2, … with limit=100 and stop at the first empty page.
The first page (page=1, from=0, or no page and no digits-only
from) also carries total, the number of events matching every
filter, independent of paging, and openMarketCount /
totalMarketCount, the per-event counters summed over those events —
so on status=closed the open sum is 0; show totalMarketCount.
With scope, both hold the same number instead: the markets of those
scopes that the filter admits on the view, not the sum of the rows’
counters. Later pages carry none of the three unless the request has
q. Treat them as optional: when they cannot be computed in time, the
first page comes without them. Full
rules, including the numeric from / to positions:
Pagination.
List data can be up to a minute or two old. Which events a page
holds, their order, volumes and each market’s
status / tradable
come from a list slice shared by every caller that can be up to about
20 seconds old. Anonymous requests — API-key requests included, since
the key is ignored on this endpoint — are then served from a short
cache on top: usually a few seconds, up to about 30 seconds for a full
page of 100 events, 60 seconds for a full status=closed page, and 2 minutes on a
closed page where every market is settled. A first page’s total,
openMarketCount and totalMarketCount are cached with it. Do not use
GET /api/events as a price feed; read the
orderbook or the
WebSocket instead.Tags
The tag taxonomy is admin-curated rather than free-form. UseGET /api/tags for the full active list and the slug as the value
for ?tag=.
isFeatured tags are surfaced in the primary navigation strip;
non-featured tags remain selectable through filter UI but are not
pinned.