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
openOrdersarrayAll resting orders (status open or partially_filled), newest first; capped at 200

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 real-time account events: order fills, order cancellations, and balance changes.

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_fill

Sent when one of your orders is partially or fully matched.

{ "type": "order_fill", "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": 20, "filledQuantity": 20, "balance": 976400 }

order_cancelled

Sent when one of your resting orders is cancelled (by you, expiry, market close, or insufficient position during a counterparty fill).

{ "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" }

cancelReason values: user_cancelled | user_amended | insufficient_funds | market_closed | position_insufficient | expired

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)