Casino API
The Casino API is the primary interface for interacting with Predictu’s prediction market platform. It handles trading, balance management, position tracking, and market configuration. All endpoints require authentication via a session token (for user-facing requests) or an operator API key (for server-to-server requests).
https://app.predictu.com/api/casinoAuthentication: Bearer token in
Authorization header, or operator API key in X-Operator-Key header for S2S calls.Content-Type: All requests and responses use
application/json.Error Handling
All endpoints return errors in a consistent format. HTTP status codes follow REST conventions.
// Error response format:
{
"error": "Insufficient balance"
}Error responses always contain an error string. Some endpoints return structured error codes for programmatic handling:
| HTTP Status | Meaning | Example Error |
|---|---|---|
| 400 | Validation / business logic error | Missing required fields, Invalid side, Amount must be positive |
| 400 | Market not tradeable | Market not tradeable, Market exposure cap reached |
| 400 | Insufficient balance | Insufficient balance |
| 400 | Risk engine rejection | Trade rejected by risk engine |
| 401 | Missing or invalid authentication | Unauthorized |
| 403 | Authenticated but not authorized | Admin access required |
| 500 | Unexpected server error | Failed to fetch positions |
Trade (Buy/Sell)
Place a buy or sell trade on a prediction market. This is the core trading endpoint. It runs through a 12-step execution pipeline: validate inputs, run risk assessment (5 walls of defence), calculate spread-adjusted price, resolve operator, get wallet adapter, check balance, debit/credit via adapter, record trade, upsert position, update exposure, update wagering stats, and return the result (or rollback via compensating transaction on failure).
Request Body
{
"marketId": "0x1234abcd...",
"side": "buy",
"outcome": "yes",
"amount": 25.00,
"midPrice": 62,
"marketTitle": "Will Bitcoin hit $100k by June?",
"category": "crypto",
"liquidity": 150000
}| Field | Type | Required | Description |
|---|---|---|---|
marketId | string | Yes | The market to trade on (Polymarket condition ID). |
side | string | Yes | "buy" or "sell" |
outcome | string | Yes | "yes" or "no" (lowercase) |
amount | number | Yes | USD amount to spend (buy) or number of shares to sell (sell). Must be > 0. |
midPrice | number | Yes | Current market mid price in cents (0–100). The spread is applied on top of this. |
marketTitle | string | No | Human-readable market title. Stored on the trade and position records. |
category | string | No | Market category (e.g. "crypto", "politics"). Used for tradeability checks. |
liquidity | number | No | Market liquidity value. Used for tradeability checks (defaults to 100000 if omitted). |
Response (200 OK)
{
"success": true,
"tradeId": "uuid-string",
"positionId": "uuid-string",
"executionPrice": 64,
"shares": 39.0625,
"cost": 25.00,
"newBalance": 75.00
}| Field | Type | Description |
|---|---|---|
success | boolean | Always true on 200 responses. |
tradeId | string | UUID of the recorded trade. |
positionId | string | UUID of the upserted position. |
executionPrice | number | Spread-adjusted execution price in cents. |
shares | number | Number of shares bought or sold. |
cost | number | USD cost of the trade (buy) or proceeds received (sell). |
newBalance | number | User’s balance after the trade. |
Selling Positions
When side is "sell", the amount field represents the number ofshares to sell (not a dollar amount). The user must hold at least this many shares in the specified outcome. Sell responses include an additional realizedPnl field.
// Sell request:
{
"marketId": "0x1234abcd...",
"side": "sell",
"outcome": "yes",
"amount": 20.00,
"midPrice": 68
}
// Sell response includes realizedPnl:
{
"success": true,
"tradeId": "uuid-string",
"positionId": "uuid-string",
"executionPrice": 66,
"shares": 20.00,
"cost": 13.20,
"newBalance": 88.20,
"realizedPnl": 0.60
}Trade Executor Pipeline
Every trade passes through a 12-step pipeline inside the Trade Executor:
- Validate inputs - check required fields, side, outcome, positive amount.
- Check market tradeability - blocked categories, disabled markets, price/liquidity criteria.
- Risk assessment - 5 walls of defence (velocity, tier limits, exposure caps, sharp detection, operator limits).
- Calculate spread-adjusted price - apply base spread + per-market overrides + per-user sharp adjustments.
- Resolve operator - look up the user’s
operator_idfor multi-tenancy. - Get wallet adapter - resolve the S2S callback adapter for the user’s operator.
- Check balance - verify sufficient funds via the adapter.
- Debit/credit balance - execute the balance change via the wallet adapter.
- Record trade - insert into
casino_tradeswith full audit fields. - Upsert position - create or average into
casino_positions(with optimistic concurrency). - Update exposure - recalculate
casino_exposure(net exposure, max loss). - Update wagering stats - increment
total_wageredon the user record (best-effort).
On failure after step 8, a compensating transaction reverses the balance change viaadapter.rollback().
Quote
Get a price quote for a potential trade without executing it. Quotes include the spread and are calculated synchronously against the default spread configuration. No authentication is required.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
side | string | Yes | "buy" or "sell" |
outcome | string | Yes | "yes" or "no" (lowercase) |
amount | number | Yes | USD amount (buy) or number of shares (sell) |
midPrice | number | Yes | Current market mid price in cents (0–100) |
Response (200 OK)
{
"executionPrice": 64,
"shares": 39.0625,
"cost": 25.00,
"spreadPct": 4,
"payout": 39.06,
"midPrice": 62
}| Field | Type | Description |
|---|---|---|
executionPrice | number | Spread-adjusted execution price in cents. Ask price for buys, bid price for sells. |
shares | number | Number of shares the user would receive (buy) or is selling (sell). |
cost | number | USD cost of the trade. |
spreadPct | number | Effective spread percentage applied. |
payout | number | Potential payout if the outcome is correct ($1 per share). |
midPrice | number | The mid price that was passed in, echoed back. |
Balance
Get Balance
Returns the authenticated user’s current balance. The balance is resolved through the wallet adapter, which sends a BALANCE callback to the operator’s S2S server.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
ledger | string | No | Set to "true" to include the 50 most recent ledger entries. |
Response (200 OK)
{
"balance": 100.00,
"ledger": null
}When ledger=true is passed, the ledger field contains an array of the 50 most recent s2s_transactions entries (most recent first). When omitted or false,ledger is null.
// With ledger=true:
{
"balance": 100.00,
"ledger": [
{
"id": "uuid",
"user_id": "uuid",
"amount": 25.00,
"type": "trade_debit",
"description": "Buy 39 shares YES @ 64c",
"created_at": "2025-03-18T14:32:01.000Z"
}
]
}Admin Balance Adjustment
Admin-only endpoint to deposit or withdraw funds from a user’s balance. Used for promotional credits, refunds, and manual corrections. Requires admin role.
Request Body
{
"targetUserId": "uuid-string",
"amount": 50.00,
"action": "deposit",
"description": "Promotional welcome bonus"
}| Field | Type | Required | Description |
|---|---|---|---|
targetUserId | string | Yes | UUID of the target user. |
amount | number | Yes | Amount to deposit or withdraw. Must be > 0. |
action | string | Yes | "deposit" or "withdraw" |
description | string | No | Reason for the adjustment (stored in the ledger). Defaults to "Admin deposit" or "Admin withdrawal". |
Response (200 OK)
{
"success": true,
"newBalance": 150.00
}debitBalance engine will return an error if the amount exceeds the user’s current balance.Deposit
Deposit funds into the authenticated user’s account. This is the user-facing deposit endpoint (as opposed to the admin balance adjustment above). A maximum balance cap of $100,000 is enforced.
Request Body
{
"amount": 100.00
}| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Deposit amount. Must be > 0 and ≤ 100,000. The deposit will be rejected if the resulting balance would exceed $100,000. |
Response (200 OK)
{
"balance": 200.00
}| Field | Type | Description |
|---|---|---|
balance | number | The user’s new balance after the deposit. |
Withdraw
Withdraw funds from the authenticated user’s Predictu balance. A maximum withdrawal of $100,000 per request is enforced, and the user must have sufficient balance.
Request Body
{
"amount": 50.00
}| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Withdrawal amount. Must be > 0, ≤ 100,000, and ≤ the user’s current balance. |
Response (200 OK)
{
"success": true,
"newBalance": 150.00,
"amount": 50.00
}| Field | Type | Description |
|---|---|---|
success | boolean | Always true on 200 responses. |
newBalance | number | The user’s balance after the withdrawal. |
amount | number | The amount that was withdrawn (echoed back). |
Positions
Returns all positions for the authenticated user, filtered by status. Results are ordered byupdated_at descending (most recently updated first).
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | No | Position status filter. Default: "open". Other values: "closed", "resolved", "voided". |
Response (200 OK)
{
"positions": [
{
"id": "uuid-string",
"user_id": "uuid-string",
"market_id": "0x1234abcd...",
"market_title": "Will Bitcoin hit $100k by June?",
"outcome": "yes",
"shares": 39.0625,
"avg_price": 64,
"status": "open",
"realized_pnl": 0,
"operator_id": "uuid-string",
"created_at": "2025-03-18T14:32:01.000Z",
"updated_at": "2025-03-18T15:00:00.000Z"
}
]
}| Field | Type | Description |
|---|---|---|
positions | array | Array of position objects. Empty array if no positions match the filter. |
Trades
Returns the authenticated user’s trade history, ordered by created_at descending (most recent first).
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | No | Maximum number of trades to return. Default: 50, max: 200. |
marketId | string | No | Filter trades to a specific market. |
Response (200 OK)
{
"trades": [
{
"id": "uuid-string",
"user_id": "uuid-string",
"market_id": "0x1234abcd...",
"market_title": "Will Bitcoin hit $100k by June?",
"side": "buy",
"outcome": "yes",
"shares": 39.0625,
"price_mid": 62,
"price_executed": 64,
"spread_applied": 2,
"cost": 25.00,
"balance_before": 100.00,
"balance_after": 75.00,
"operator_id": "uuid-string",
"s2s_transaction_id": null,
"created_at": "2025-03-18T14:32:01.000Z"
}
]
}| Field | Type | Description |
|---|---|---|
trades | array | Array of trade objects. Empty array if no trades match. |
Markets Configuration
Returns the casino’s market configuration: blocked categories, individually disabled markets, market selection criteria, and per-market custom spreads. This is not a market listing endpoint -markets are sourced from Polymarket and filtered client-side using these rules. No authentication is required.
Response (200 OK)
{
"blockedCategories": ["adult", "drugs"],
"disabledMarketIds": ["0xabc123...", "0xdef456..."],
"criteria": {
"min_liquidity": 5000,
"min_price": 5,
"max_price": 95
},
"customSpreads": {
"0x1234abcd...": 6,
"0x5678efgh...": 2.5
}
}| Field | Type | Description |
|---|---|---|
blockedCategories | string[] | Categories that are blocked from trading. |
disabledMarketIds | string[] | Individual market IDs that have been manually disabled. |
criteria | object | Market selection criteria. Contains min_liquidity, min_price, and max_price thresholds. |
customSpreads | object | Map of market ID to custom spread percentage override. Markets not in this map use the default spread. |
Resolve Market
Admin-only endpoint to resolve or void a prediction market. Resolving triggers settlement: winning positions are credited $1 per share via the wallet adapter, losing positions are marked as resolved. Voiding refunds all positions at their original cost basis.
Request Body
// Resolve a market:
{
"marketId": "0x1234abcd...",
"outcome": "yes",
"action": "resolve"
}
// Void a market:
{
"marketId": "0x1234abcd...",
"action": "void",
"reason": "Resolution criteria became ambiguous"
}| Field | Type | Required | Description |
|---|---|---|---|
marketId | string | Yes | The market to resolve or void. |
outcome | string | Yes (resolve) | "yes" or "no" (lowercase). Required when action is "resolve". Ignored for voids. |
action | string | No | "resolve" (default) or "void". When "void", all positions are refunded at cost basis. |
reason | string | No | Reason for voiding. Stored on the settlement record. Defaults to "Voided by admin" when omitted. |
Response (200 OK) - Resolve
{
"success": true,
"marketId": "0x1234abcd...",
"outcome": "yes",
"totalPositions": 142,
"totalWinners": 89,
"totalPayout": 4250.00,
"casinoProfit": -1340.00
}| Field | Type | Description |
|---|---|---|
success | boolean | Whether the resolution completed successfully. |
marketId | string | The market that was resolved. |
outcome | string | The winning outcome ("yes" or "no"). |
totalPositions | number | Total number of open positions that were settled. |
totalWinners | number | Number of winning positions. |
totalPayout | number | Total USD paid out to winners ($1 per share). |
casinoProfit | number | Casino profit: total cost basis collected minus total payouts. Negative means the casino lost money. |
Response (200 OK) - Void
{
"success": true,
"marketId": "0x1234abcd...",
"totalPositions": 142,
"totalPayout": 3500.00,
"casinoProfit": 0
}When voiding, all positions are refunded at their original cost basis. casinoProfit is always 0 for voids. The totalPayout represents the total amount refunded.
Rate Limits
The Casino API enforces rate limits to prevent abuse and ensure fair access.
| Endpoint | Limit | Window |
|---|---|---|
POST /trade | 20 requests | Per minute per user |
GET /quote | 60 requests | Per minute per user |
GET /balance | 60 requests | Per minute per user |
GET /positions | 30 requests | Per minute per user |
GET /markets | 30 requests | Per minute per user |
GET /trades | 30 requests | Per minute per user |
POST /deposit | 5 requests | Per minute per user |
POST /withdraw | 5 requests | Per minute per user |
POST /resolve | 10 requests | Per minute per admin |
Rate limit headers are included in every response:
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 18
X-RateLimit-Reset: 1710772321