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"
| Parameter | Type | Description |
|---|---|---|
status | enum | active, suspended, settled, or voided |
category | string | Category slug |
tags | string | Comma-separated tag slugs |
volume_min | decimal string | Minimum volume |
liquidity_min | decimal string | Minimum liquidity |
q | string | Full-text search over event titles |
sort | enum | volume, liquidity, or end_date. Default volume |
order | enum | asc or desc. Default desc |
cursor | string | Opaque pagination cursor |
limit | integer | 1 to 100. Default 20 |
locale | string | Content 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.
| Field | Meaning |
|---|---|
display_probability | Margin-adjusted probability, as a percentage with 1 decimal place |
display_odds | Margin-adjusted decimal odds, 2 decimal places |
odds_display | The same odds, already formatted as a string |
status | active 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.
?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
localevalue 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.