Note: You must have PostgreSQL and Redis running locally (e.g., via docker compose up -d db redis) before starting the Spring Boot application using Maven.
mvn spring-boot:runIf you want one command for backend dependencies during frontend work, run:
docker compose up --buildThat starts:
- the Spring Boot backend on
http://localhost:8080 - PostgreSQL on
localhost:5433 - Redis on
localhost:6379
The compose setup defaults to the demo Spring profile so you can use demo auth
without wiring Google OAuth during local frontend work.
To stop everything:
docker compose downTo enable the username and password authentication flow (Demo Mode), run the application with the demo profile:
mvn spring-boot:run -Dspring-boot.run.profiles=demoThis document outlines the API endpoints for the Predictor Backend service.
Most endpoints require a valid JWT. To obtain one, use the Google Auth endpoint.
- Header:
Authorization: Bearer <your_jwt_token>
| Endpoint | Authentication |
|---|---|
POST /v1/auth/google |
Public |
POST /v1/auth/demo/register |
Public (Demo Mode Only) |
POST /v1/auth/demo/login |
Public (Demo Mode Only) |
POST /v1/auth/refresh |
Public |
POST /v1/auth/logout |
Public |
GET /v1/auth/me |
Required |
GET /v1/markets |
Public |
GET /v1/markets/{id} |
Public |
GET /v1/markets/{id}/me |
Required |
POST /v1/markets |
Required (ADMIN) |
POST /v1/markets/{id}/resolve |
Required (ADMIN) |
POST /v1/markets/{id}/trades |
Required |
GET /v1/users |
Required |
GET /v1/users/me/summary |
Required |
GET /v1/stream/events |
Public / Optional |
Exchanges a Google ID Token for an application access and refresh token.
- URL:
/v1/auth/google - Method:
POST - Auth Required: No
- Request Body:
{ "tokenId": "string" } - Response Structure:
{ "accessToken": "string", "refreshToken": "string", "expiresInSeconds": 900 }
Obtain a new access token using a refresh token.
- URL:
/v1/auth/refresh - Method:
POST - Auth Required: No
- Request Body:
{ "refreshToken": "string" }
Revokes the provided refresh token.
- URL:
/v1/auth/logout - Method:
POST - Auth Required: No (Token passed in body)
- Request Body:
{ "refreshToken": "string" }
Registers a new user with a username and password. Only active in demo profile.
- URL:
/v1/auth/demo/register - Method:
POST - Auth Required: No
- Request Body:
{ "username": "string", "password": "string", "email": "string" }
Authenticates a user with a username and password. Only active in demo profile.
- URL:
/v1/auth/demo/login - Method:
POST - Auth Required: No
- Request Body:
{ "username": "string", "password": "string" }
Retrieves the profile of the currently authenticated user.
- URL:
/v1/auth/me - Method:
GET - Auth Required: Yes
- Response Structure:
{ "userId": "string", "email": "string", "name": "string", "pictureUrl": "string", "balance": 1000.00, "role": "USER" }
Creates a new prediction market. Only accessible to users with the ADMIN role.
- URL:
/v1/markets - Method:
POST - Auth Required: Yes (ADMIN)
- Request Body:
| Field | Type | Description | Required | Constraints |
|---|---|---|---|---|
name |
String | Name of the market | Yes | Unique, Cannot be empty |
description |
String | Description of the market | No | |
liquidity |
Double | Initial liquidity | No | Default: 50.0, Must be > 0 |
category |
String | Category of the market | No | Default: "General" |
yesLabel |
String | Custom label for YES outcome | No | Default: "Yes" |
noLabel |
String | Custom label for NO outcome | No | Default: "No" |
Example Response:
{
"status": "success",
"message": "Market created successfully.",
"marketId": "uuid-string"
}Retrieves a list of all markets, optionally filtered by status.
- URL:
/v1/markets - Method:
GET - Auth Required: No
- Query Parameters:
status(Optional): Filter markets by status (OPEN,RESOLVED).
Response Object (Array):
[
{
"marketId": "string",
"marketName": "string",
"status": "OPEN",
"resolvedOutcome": null,
"category": "string",
"outcomes": [
{
"outcomeId": "YES",
"label": "string",
"probability": 0.5
},
{
"outcomeId": "NO",
"label": "string",
"probability": 0.5
}
],
"totalValue": 0.0
}
]Retrieves details of a specific market.
- URL:
/v1/markets/{marketId} - Method:
GET - Auth Required: No
Returns the authenticated user's investment summary and trade history for a specific market.
- URL:
/v1/markets/{marketId}/me - Method:
GET - Auth Required: Yes
Response Structure:
{
"userId": "string",
"marketId": "string",
"marketName": "string",
"marketStatus": "OPEN",
"resolvedOutcome": null,
"currentYesChance": 0.58,
"currentNoChance": 0.42,
"yesSharesHeld": 3.5,
"noSharesHeld": 1.0,
"totalInvested": 12.50,
"totalYesInvested": 8.00,
"totalNoInvested": 4.50,
"firstTradeAt": "2026-03-27T10:00:00Z",
"lastTradeAt": "2026-03-27T12:00:00Z",
"projectedPayoutIfYes": 3.5,
"projectedPayoutIfNo": 1.0,
"realizedPayout": null,
"realizedNetPnl": null,
"tradeCount": 2,
"trades": [
{
"tradeId": "string",
"outcome": "NO",
"sharesBought": 1.0,
"cost": 4.50,
"tradedAt": "2026-03-27T12:00:00Z"
}
]
}Notes:
- if the user has never traded that market, this still returns
200with zeroed holdings andtrades: [] - projected payout fields are outcome-based settlement payouts, not current cash-out estimates
- when the market is resolved,
realizedPayoutandrealizedNetPnlare populated - trade history is returned newest first
Resolves a market with a specific outcome (YES or NO). Only accessible to users with the ADMIN role.
- URL:
/v1/markets/{marketId}/resolve - Method:
POST - Auth Required: Yes (ADMIN)
- Request Body:
{ "outcomeId": "YES" }
Executes a trade to buy shares in a market.
- URL:
/v1/markets/{marketId}/trades - Method:
POST - Auth Required: Yes
- Request Body:
| Field | Type | Description | Required | Constraints |
|---|---|---|---|---|
outcome |
String | Outcome to buy ("YES" or "NO") | Yes | Case-insensitive |
amount |
Double | Investment amount (cost) | Yes | Must be > 0 |
Example Response:
{
"status": "success",
"message": "Trade executed successfully.",
"tradeId": "string",
"sharesBought": 15.4,
"cost": 10.0,
"outcome": "YES"
}Retrieves a list of all registered users.
- URL:
/v1/users - Method:
GET - Auth Required: Yes
Response Object (Array):
[
{
"userId": "string"
}
]Returns the authenticated user's available balance and summaries for the 3 most recently traded unique markets.
- URL:
/v1/users/me/summary - Method:
GET - Auth Required: Yes
Response Structure:
{
"userId": "string",
"availableBalance": 1000.00,
"recentMarkets": [
{
"marketId": "string",
"marketName": "string",
"marketStatus": "OPEN",
"lastTradedAt": "2026-03-27T12:00:00Z",
"resolvedOutcome": null,
"userYesShares": 3.5,
"userNoShares": 1.0,
"currentYesChance": 0.58,
"currentNoChance": 0.42,
"projectedPayoutIfYes": 3.5,
"projectedPayoutIfNo": 1.0
}
]
}Notes:
recentMarketscontains at most 3 unique markets- markets are ordered by the user's most recent trade time, newest first
- projected payouts are settlement-style payouts, not current cash-out estimates
availableBalanceis cash balance only
Provides a Server-Sent Events (SSE) stream for real-time updates on market changes and trades.
- URL:
/v1/stream/events - Method:
GET - Auth Required: Optional (Uses token for rate limiting if provided)
- Headers:
Accept: text/event-stream
- Query Parameters:
marketId(Optional): Filter events for a specific market.
All error responses (4xx and 5xx) follow a consistent structure.
Response Structure:
{
"timestamp": "2026-03-15T18:44:36",
"status": 401,
"error": "Unauthorized",
"message": "Invalid access token"
}To run the automated tests:
mvn testTo run a specific test class:
mvn test -Dtest=AuthIntegrationTestTo run demo authentication tests:
mvn test -Dtest=DemoAuthIntegrationTest