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"
}| Field | Type | Description |
|---|---|---|
id | UUID string | Your user id |
displayName | string | Display name |
email | string | Account email |
balance | integer | EUR-cent balance |
createdAt | ISO 8601 | Account creation time |
GET /api/v1/me/orders
Returns your paginated order history.
Auth: Required
Query parameters
| Param | Type | Default | Description |
|---|---|---|---|
status | "open" | "filled" | "all" | "open" | "open" returns open + partially_filled; "filled" returns fully filled only; "all" returns all |
page | integer | 1 | Page number |
limit | integer | 20 | Results 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
| Field | Type | Description |
|---|---|---|
balance | integer | EUR-cent balance |
positions | array | All positions with quantity > 0, ordered by market close time |
openOrders | array | All resting orders (status open or partially_filled), newest first; capped at 200 |
Position fields
| Field | Type | Description |
|---|---|---|
id | UUID string | Position id |
marketId | UUID string | Market |
marketTitle | string | Market title |
marketStatus | string | Current market status |
lastTradePrice | integer | null | Last traded price (1–99) |
side | "yes" | "no" | Which side you hold |
quantity | integer | Shares held |
avgPrice | integer | Weighted average cost (1–99) |
realisedPnl | integer | EUR-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
| Status | Body | When |
|---|---|---|
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) |