Skip to Content
API ReferencePortfolio

Portfolio

All endpoints in this section require authentication. See Authentication.

Rate limit: v1-auth: 120 req / 60 s per IP


GET /api/v1/me

Returns your account information and balance.

Auth: Required

Response

{ "id": "e5f6a7b8-c9d0-1234-ef01-345678901234", "displayName": "Alice", "email": "[email protected]", "balance": 987600, "createdAt": "2025-06-01T12:00:00.000Z" }
FieldTypeDescription
idUUID stringYour user id
displayNamestringDisplay name
emailstringAccount email
balanceintegerEUR-cent balance
createdAtISO 8601Account creation time

GET /api/v1/me/orders

Returns your paginated order history.

Auth: Required

Query parameters

ParamTypeDefaultDescription
status"open" | "filled" | "all""open""open" returns open + partially_filled; "filled" returns fully filled only; "all" returns all
pageinteger1Page number
limitinteger20Results per page (1–50)

Unlike GET /orders, this endpoint does not accept "cancelled" as a status filter. Use GET /orders?status=cancelled if you need cancelled orders.

Response

{ "data": [ { "id": "d4e5f6a7-b8c9-0123-def0-234567890123", "userId": "e5f6a7b8-c9d0-1234-ef01-345678901234", "marketId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "marketTitle": "Will Reform Party win the most seats?", "side": "yes", "price": 55, "quantity": 100, "filledQuantity": 40, "status": "partially_filled", "orderType": "buy", "createdAt": "2026-01-10T09:00:00.000Z", "expiresAt": null } ], "meta": { "total": 87, "page": 1, "limit": 20, "hasMore": true } }

GET /api/v1/me/portfolio

Returns your balance, all open positions (quantity > 0), and all currently resting orders in a single call. Designed for portfolio page polling.

Auth: Required

Response

{ "balance": 987600, "positions": [ { "id": "f6a7b8c9-d0e1-2345-f012-456789012345", "marketId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "marketTitle": "Will Reform Party win the most seats?", "marketStatus": "open", "lastTradePrice": 58, "side": "yes", "quantity": 60, "avgPrice": 54, "realisedPnl": 0 } ], "openOrders": [ { "id": "d4e5f6a7-b8c9-0123-def0-234567890123", "marketId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "marketTitle": "Will Reform Party win the most seats?", "side": "yes", "price": 55, "quantity": 100, "filledQuantity": 40, "status": "partially_filled", "orderType": "buy", "createdAt": "2026-01-10T09:00:00.000Z" } ] }

Portfolio fields

FieldTypeDescription
balanceintegerEUR-cent balance
positionsarrayAll positions with quantity > 0, ordered by market close time
openOrdersarrayActive orders (status scheduled, open, or partially_filled), newest first; capped at 200. Scheduled rows include releaseAt and budgetCents

Position fields

FieldTypeDescription
idUUID stringPosition id
marketIdUUID stringMarket
marketTitlestringMarket title
marketStatusstringCurrent market status
lastTradePriceinteger | nullLast traded price (1–99)
side"yes" | "no"Which side you hold
quantityintegerShares held
avgPriceintegerWeighted average cost (1–99)
realisedPnlintegerEUR-cents realised after resolution

GET /api/v1/user/stream

Server-Sent Events stream for live-order lifecycle and matching-side-effect notifications.

Auth: Required
Rate limit: sse:user: 30 / 60 s per user (120 / 60 s per IP)

Server-side and API-key clients connect to the v1 path with their jk_ key as Authorization: Bearer. From the browser, use the same-origin proxy at /api/stream/user, which authenticates with the cookie session: EventSource can’t send an Authorization header.

Auth and rate-limit errors are returned as real HTTP statuses before the stream opens, as JSON, not as SSE frames. A missing or invalid key returns 401; exceeding either sse:user tier (30 / 60 s per user or 120 / 60 s per IP) returns 429 with a Retry-After header.

Connection frame

On connect the server immediately sends:

{ "type": "connected" }

Event types

All subsequent frames have a type field:

order_pending

Sent once when a new live-market order is accepted into the delay window. Idempotent POST retries do not emit it again.

{ "type": "order_pending", "orderId": "d4e5f6a7-b8c9-0123-def0-234567890123", "marketId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "side": "yes", "price": 55, "quantity": 100, "releaseAt": "2026-01-15T14:32:03.000Z", "budgetCents": null }

order_released

Sent once after the delayed order is revalidated and matched against the current book. Inspect status to determine the outcome; a release-time validation failure is represented by status: "cancelled" on this event, not by order_cancelled.

{ "type": "order_released", "orderId": "d4e5f6a7-b8c9-0123-def0-234567890123", "marketId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "status": "partially_filled", "side": "yes", "price": 55, "quantity": 100, "filledQuantity": 40, "cancelReason": null, "fills": 2, "balance": 976400, "feeCents": 18 }

status is open | partially_filled | filled | cancelled. fills is the number of trades generated during release, not an array. balance and feeCents may be null when no value was produced.

maker_fill

Published after one or more of your resting maker orders fill. One completed match emits at most one event to each distinct maker, aggregating that maker’s unique resting order IDs. It is an invalidation signal: fetch authoritative balance, position, and order state after receiving it.

{ "type": "maker_fill", "marketId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "orderIds": [ "d4e5f6a7-b8c9-0123-def0-234567890123", "e5f6a7b8-c9d0-1234-ef01-345678901234" ], "fillCount": 3 }

The event deliberately carries no balance. The incoming match result’s balance belongs to the taker, and one maker event can cover several resting orders. Redis Pub/Sub does not replay events missed during a reconnect gap, so clients should retain a slow reconciliation poll or re-fetch on reconnect rather than relying on pure push.

order_cancelled

Sent when matching cancels one of your resting maker orders as a side effect, for example because it is underfunded or the position is no longer sufficient. Direct DELETE responses are authoritative for user-initiated cancellation.

{ "type": "order_cancelled", "orderId": "d4e5f6a7-b8c9-0123-def0-234567890123", "marketId": "b2c3d4e5-f6a7-8901-bcde-f12345678901", "marketTitle": "Will Reform Party win the most seats?", "side": "yes", "price": 55, "quantity": 100, "filledQuantity": 40, "cancelReason": "market_closed" }

Possible cancelReason values across cancellation and release events: user_cancelled | user_amended | insufficient_funds | market_closed | position_insufficient | expired | trading_paused | self_trade | post_only_would_cross | insufficient_liquidity | self_excluded | loss_limit

Keep-alive

The server writes : ping comments every 25 seconds. Reconnect on any network error.

Errors

StatusBodyWhen
401{ "error": "Unauthorized" }Missing or invalid token
429{ "error": "Too many requests" }sse:user limit exceeded: per user (30 / 60 s) or per IP (120 / 60 s)