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.

The user stream does not currently emit a general per-fill event after an order has entered the book. If a released order rests as open or partially_filled, use the public market stream to trigger reconciliation and fetch the authoritative order with GET /api/v1/orders.

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)