Skip to Content

Markets

GET /api/v1/markets/:id

Returns full details for a single market, including best bid/ask.

Auth: None
Rate limit: v1: 60 req / 60 s per IP
Cache: s-maxage=10, stale-while-revalidate=30

Path parameters

ParamTypeDescription
idUUIDMarket identifier

Response

{ "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "title": "Will Reform Party win the most seats?", "description": "Resolves YES if Reform Party gets the most Riigikogu seats in the 2026 election.", "category": "estonian_politics", "status": "open", "eventId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "bestBid": 58, "bestAsk": 61, "lastTradePrice": 58, "yesVolume": 124500, "noVolume": 87300, "closeTime": "2026-03-01T20:00:00.000Z", "createdAt": "2025-11-15T09:00:00.000Z", "resolvedOutcome": null, "thumbnailId": "c3d4e5f6-a7b8-9012-cdef-123456789012", "thumbnailUrl": "https://cdn.juhus.ee/thumbnails/reform-2026.webp" }
FieldTypeDescription
idUUID stringMarket identifier
titlestringMarket title
descriptionstringMarket description
categorystringCategory slug
statusstringopen | paused | closed | resolved
eventIdUUID string | nullParent event, or null for standalone
bestBidinteger | nullHighest open YES buy order price (1–99)
bestAskinteger | nullLowest open YES sell order price (1–99), derived from best NO bid
lastTradePriceinteger | nullMost recent matched price (1–99)
yesVolumeintegerTotal YES-side volume in EUR-cents
noVolumeintegerTotal NO-side volume in EUR-cents
closeTimeISO 8601 | nullScheduled trading close time
createdAtISO 8601Creation timestamp
resolvedOutcome"yes" | "no" | "custom" | nullResolution outcome; null while unresolved
thumbnailIdUUID string | nullThumbnail image id; null if none set
thumbnailUrlstring | nullThumbnail image URL; null if none set

Errors

StatusBodyWhen
400{ "error": "Invalid id" }id is not a valid UUID
404{ "error": "Market not found" }No market with that id

GET /api/v1/markets/:id/orderbook

Returns the aggregated order book: all open YES buy orders (bids) and all open YES sell orders (asks, derived from NO buys).

Auth: None
Rate limit: v1: 60 req / 60 s per IP
Cache: s-maxage=5, stale-while-revalidate=10

Path parameters

ParamTypeDescription
idUUIDMarket identifier

Response

{ "bids": [ { "price": 58, "quantity": 120 }, { "price": 55, "quantity": 340 }, { "price": 50, "quantity": 500 } ], "asks": [ { "price": 61, "quantity": 80 }, { "price": 65, "quantity": 200 } ] }

bids are open YES buy orders, sorted descending by price (highest first).
asks are open YES sell orders (stored as NO buys), sorted ascending by price (lowest first). The ask price is already converted to YES-price space: 100 - stored_no_price.

FieldTypeDescription
bidsarrayYES buy orders aggregated by price level
asksarrayYES sell orders aggregated by price level
bids[].priceintegerPrice level (1–99)
bids[].quantityintegerTotal unfilled shares at this level
asks[].priceintegerPrice level in YES space (1–99)
asks[].quantityintegerTotal unfilled shares at this level

Errors

StatusBodyWhen
400{ "error": "Invalid id" }id is not a valid UUID
404{ "error": "Market not found" }No market exists with that id. An empty order book on an existing market returns 200 with empty bids and asks arrays.

GET /api/v1/markets/:id/trades

Returns paginated trade history for a market, most recent first.

Auth: None
Rate limit: v1: 60 req / 60 s per IP
Cache: s-maxage=5, stale-while-revalidate=10

Path parameters

ParamTypeDescription
idUUIDMarket identifier

Query parameters

ParamTypeDefaultDescription
pageinteger1Page number
limitinteger20Results per page (1–50)

Response

{ "data": [ { "id": "c3d4e5f6-a7b8-9012-cdef-123456789012", "price": 58, "quantity": 10, "createdAt": "2026-01-15T14:32:00.000Z" } ], "meta": { "total": 312, "page": 1, "limit": 20, "hasMore": true } }
FieldTypeDescription
idUUID stringTrade identifier
priceintegerMatched price (1–99)
quantityintegerNumber of shares traded
createdAtISO 8601When the trade occurred

Errors

StatusBodyWhen
400{ "error": "Invalid id" }id is not a valid UUID
404{ "error": "Market not found" }No market with that id

GET /api/v1/markets/:id/stream

Server-Sent Events stream. Sends an initial snapshot immediately on connect, then re-sends a full snapshot after every trade or order book change.

Auth: None
Rate limit: sse:market: 120 connections / 60 s per IP

The v1 path streams directly, public and unauthenticated, for server-side and API-key clients. From the browser, use the same-origin proxy at /api/stream/market/:id: EventSource can’t send custom headers, and the proxy avoids CORS.

A non-200 is returned as a real HTTP status before the stream opens: a malformed UUID returns 400, an unknown market returns 404.

Path parameters

ParamTypeDescription
idUUIDMarket identifier

Event frame shape

Each data: frame is a JSON snapshot:

{ "orderBook": { "yesBids": [ { "price": 58, "quantity": 120 }, { "price": 55, "quantity": 340 } ], "noBids": [ { "price": 42, "quantity": 80 }, { "price": 38, "quantity": 200 } ] }, "recentTrades": [ { "price": 58, "quantity": 10, "createdAt": "2026-01-15T14:32:00.000Z", "takerName": "Alice", "takerHandle": "alice", "takerPrivate": false, "side": "yes" } ], "tradersCount": 47 }

orderBook.noBids are raw NO buy orders sorted descending by stored price. To convert a NO bid to a YES ask price: 100 - noBid.price.

FieldTypeDescription
orderBook.yesBidsarrayOpen YES buy orders aggregated by price, sorted descending
orderBook.noBidsarrayOpen NO buy orders aggregated by stored price, sorted descending
recentTradesarrayUp to 50 most recent trades, newest first
recentTrades[].priceintegerMatched price (1–99)
recentTrades[].quantityintegerShares traded
recentTrades[].createdAtISO 8601Trade time
recentTrades[].takerNamestring | nullTaker display name; null if private profile
recentTrades[].takerHandlestring | nullTaker handle; null if private profile
recentTrades[].takerPrivatebooleantrue if taker has a private profile
recentTrades[].side"yes" | "no" | nullSide the taker bought; null for legacy trades
tradersCountintegerDistinct traders who have ever traded this market

Keep-alive

The server writes : ping comments every 25 seconds. Reconnect on any network error. The stream sends a fresh snapshot on each connection.

Errors

StatusBodyWhen
400{ "error": "INVALID_ID" }id is not a valid UUID
404{ "error": "Market not found" }No market with that id
429{ "error": "Too many requests" }sse:market rate limit exceeded