Skip to main content

Offering: Events, Markets and Outcomes

The offering is what a bettor can bet on. An Event is a real-world happening. It holds one or more Markets, which are the questions. Each Market holds two or more Outcomes, which are the things a bettor stakes on.

Every endpoint on this page is public. None of them needs the platform JWT.

Build navigation from categories​

curl "https://<your predictions host>/api/v1/categories?locale=en"
{
"categories": [
{
"id": "0f2b...",
"name": "Politics",
"slug": "politics",
"display_order": 1,
"icon": "landmark",
"color_class": null,
"tags": [
{ "id": "7a1c...", "name": "US Election", "slug": "us-election" }
]
}
]
}

Categories arrive ordered by display_order. icon is a Lucide icon name. Use slug to filter the event list, and name for display.

List events​

curl "https://<your predictions host>/api/v1/events?category=politics&sort=volume&order=desc&limit=20&locale=en"
ParameterTypeDescription
statusenumactive, suspended, settled, or voided
categorystringCategory slug
tagsstringComma-separated tag slugs
volume_mindecimal stringMinimum volume
liquidity_mindecimal stringMinimum liquidity
qstringFull-text search over event titles
sortenumvolume, liquidity, or end_date. Default volume
orderenumasc or desc. Default desc
cursorstringOpaque pagination cursor
limitinteger1 to 100. Default 20
localestringContent language

The response holds the events and one pagination object.

{
"data": [ { "id": "550e...", "title": "2024 US Presidential Election", "markets": [] } ],
"pagination": { "next_cursor": "eyJpZCI6...", "has_more": true }
}

Page through a large catalog​

Pagination is keyset-based, not offset-based. Read pagination.next_cursor and send it back as cursor on the next request. Stop when pagination.has_more is false.

Treat the cursor as opaque. Do not parse it, and do not build one yourself.

Keep every filter and sort parameter identical across the pages of one listing. A cursor is valid only for the query that produced it.

Read one event with its markets and outcomes​

curl "https://<your predictions host>/api/v1/events/<event id>?locale=en"
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "2024 US Presidential Election",
"status": "active",
"end_date": "2024-11-05T23:59:00Z",
"market_count": 1,
"markets": [
{
"id": "def67890-e89b-41d4-a716-446655440002",
"title": "Who will win the 2024 US presidential election?",
"status": "active",
"close_time": "2024-11-05T23:59:00Z",
"outcomes": [
{
"id": "abc12345-e89b-41d4-a716-446655440001",
"name": "Donald Trump",
"display_probability": "52.0",
"display_odds": "1.92",
"odds_display": "1.92",
"status": "active"
}
]
}
]
}

Render the display values as they arrive​

Every Outcome carries four fields you render.

FieldMeaning
display_probabilityMargin-adjusted probability, as a percentage with 1 decimal place
display_oddsMargin-adjusted decimal odds, 2 decimal places
odds_displayThe same odds, already formatted as a string
statusactive or locked

You MUST NOT round these values again. They arrive display-ready. Re-rounding them shows a bettor a price the platform does not hold. The one value you compute yourself is the payout, and Placing Bets covers it.

RECOMMENDED: do not assert that display_probability sums to 100 across a Market's Outcomes. Each value is rounded for display, so the set need not total exactly 100.

Render event and market images​

The event list gives image_url on each event. The event detail gives image_url on the event and on each of its markets. An empty string means that the source has no image. When the project disables images, no response has an image_url key.

You MUST select an image in this order: the market image_url, the event image_url, the category icon, your placeholder. Use the first value that is not empty. The event list has no market images, so a lobby card starts at the event image_url.

You MUST show market images only when they tell markets apart. Show them when the event has two or more markets and two or more different market image_url values. In all other events, show the event image.

You MUST NOT let an image block a bet. The source hosts the images without a CDN, and some files are large. Load them lazily and keep the stake control ready while they load.

Show a locked outcome, do not hide it​

An Outcome with status: "locked" is not bettable right now.

Render it visible, with its odds still shown, and disable its stake control. Hiding it changes the shape of an N-way market in front of the bettor. Blanking its odds looks like a broken page.

You MUST NOT add client-side odds bounds. The server owns lock authority and publishes it as status. A client rule that locks an outcome the server calls active hides a bet the bettor can legitimately place.

Always send ?locale= explicitly

?locale= works on five operations: GET /events, GET /events/{id}, GET /categories, GET /bettor/bets, and GET /bettor/bets/{id}.

Send it on every one of them, every time. If you omit it, the browser's own Accept-Language header decides the content language, because that header is CORS-safelisted and a browser attaches it to every request without asking you. A bettor whose browser is configured for Dutch then reads Dutch event titles on your English page.

?locale= beats Accept-Language in every resolver, so sending it is a complete defence.

Design for mixed-language content​

The platform fetches translations per language. Supported values are en, es, nl, fr, de, and ru. The set grows over time, and no language is ever removed.

The platform answers in the base language in two cases:

  • The locale value is outside the supported set.
  • The requested translation is not available for that text.

Both return 200, with the same response shape as a translated one. Design for that: a page can mix languages, so a Russian page can contain English market questions. Lay out your text so a longer or shorter string in another language does not break the page.