Operator Dashboard API
Complete REST API reference for the per-operator dashboard at predictu-operator.vercel.app. Each casino operator integrated with Predictu gets access to their own scoped dashboard for managing users, monitoring trades, tracking revenue, configuring embed settings, and managing risk - all scoped to their own player base and data.
/api/operator/* automatically scopes all data to the authenticated operator. An operator can only see their own users, trades, revenue, and configuration. Cross-operator data is never exposed.Authentication
All requests must include the operator session JWT in the Authorization header. Operator tokens are issued via the /api/auth/operator/login endpoint and carry therole: "operator_admin" claim along with the operator_slug scope. Tokens expire after 8 hours.
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// Decoded token payload
{
"sub": "opu_001",
"role": "operator_admin",
"operator_slug": "example-casino",
"operator_id": "op_abc123",
"permissions": ["read", "write", "users", "config", "billing"],
"exp": 1742428800
}Standard Error Format
All error responses follow a consistent JSON envelope:
{
"error": {
"code": "UNAUTHORIZED",
"message": "Operator authentication required",
"details": null
}
}| HTTP Status | Code | Meaning |
|---|---|---|
400 | BAD_REQUEST | Invalid parameters or request body |
401 | UNAUTHORIZED | Missing or expired operator token |
403 | FORBIDDEN | Valid token but insufficient permissions for this action |
404 | NOT_FOUND | Resource does not exist or belongs to another operator |
409 | CONFLICT | Duplicate resource (e.g. duplicate embed origin) |
422 | VALIDATION_ERROR | Schema validation failed |
429 | RATE_LIMITED | Too many requests (120 req/min per operator) |
500 | INTERNAL_ERROR | Unexpected server failure |
Dashboard Overview
Returns the operator's home dashboard summary. Aggregates all key metrics for the operator's player base including user counts, trade volume, revenue, exposure, and trend deltas compared to the previous period.
Get Dashboard
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
period | string | "7d" | Time window for trend deltas. One of 1d, 7d, 30d, 90d, all. |
Response - 200 OK
{
"data": {
"operator": {
"slug": "example-casino",
"name": "Example Casino",
"status": "live",
"s2s_healthy": true
},
"users": {
"total": 8420,
"active_24h": 642,
"active_7d": 2840,
"new_7d": 380,
"delta_pct": 14.2
},
"trades": {
"total": 214800,
"volume_usd": 4280000.00,
"count_24h": 3420,
"volume_24h_usd": 68400.00,
"delta_pct": 11.3
},
"revenue": {
"total_usd": 128400.00,
"period_usd": 18200.00,
"delta_pct": 8.9,
"operator_share_usd": 12740.00,
"predictu_share_usd": 5460.00,
"breakdown": {
"spread_usd": 14200.00,
"fees_usd": 4000.00
}
},
"exposure": {
"current_usd": 42000.00,
"limit_usd": 100000.00,
"utilization_pct": 42.0
},
"positions": {
"open_count": 4200,
"total_value_usd": 84000.00,
"avg_position_usd": 20.00
},
"risk": {
"risk_level": "low",
"circuit_breakers_active": 0,
"sharp_bettors": 3,
"recent_events": 2
},
"settlements": {
"pending_count": 12,
"pending_usd": 4200.00,
"last_settled_at": "2026-03-19T08:12:00Z"
},
"chart_data": [
{ "date": "2026-03-12", "revenue_usd": 2400.00, "trades": 480, "users": 420 },
{ "date": "2026-03-13", "revenue_usd": 2680.00, "trades": 520, "users": 448 }
]
}
}Users
Manage the operator's player base. Users are created when a player first interacts with the Predictu embed on the operator's site. Each user has an internal Predictu ID and anexternal_id from the operator's system (passed via the embed init or S2S registration).
List Users
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number (1-indexed). |
limit | number | 50 | Results per page. Max 200. |
search | string | null | Search by username, email, external ID, or internal user ID. |
tier | string | null | Filter by risk tier: normal, sharp, restricted, banned. |
status | string | null | Filter by account status: active, inactive, suspended. |
sort | string | "created_at" | Sort: created_at, last_active, balance, trade_count, pnl, volume. |
order | string | "desc" | Sort order: asc or desc. |
Response - 200 OK
{
"data": [
{
"id": "usr_xyz",
"external_id": "gbn_player_42",
"username": "player_42",
"email": "player42@email.com",
"tier": "normal",
"status": "active",
"balance_usd": 142.50,
"trade_count": 84,
"volume_usd": 2480.00,
"pnl_usd": 38.20,
"win_rate": 0.54,
"open_positions": 5,
"last_trade_at": "2026-03-19T10:30:00Z",
"last_active_at": "2026-03-19T10:15:00Z",
"created_at": "2026-01-08T18:30:00Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 8420,
"total_pages": 169
}
}Get User Detail
Returns detailed profile for a specific user belonging to this operator. Includes full metrics, scoring summary, active restrictions, and recent activity.
Path Parameters
| Param | Type | Description |
|---|---|---|
id | string | User ID (e.g. usr_xyz). Must belong to the authenticated operator. |
Response - 200 OK
{
"data": {
"id": "usr_xyz",
"external_id": "gbn_player_42",
"username": "player_42",
"email": "player42@email.com",
"tier": "normal",
"status": "active",
"balance_usd": 142.50,
"metrics": {
"trade_count": 84,
"volume_usd": 2480.00,
"pnl_usd": 38.20,
"win_rate": 0.54,
"avg_trade_usd": 29.52,
"largest_trade_usd": 200.00,
"open_positions": 5,
"total_deposited_usd": 500.00,
"total_withdrawn_usd": 300.00,
"exposure_usd": 280.00
},
"scoring": {
"score": 42,
"tier": "normal",
"last_evaluated_at": "2026-03-19T08:00:00Z"
},
"restrictions": {
"max_bet_usd": 1000,
"allowed_categories": null,
"blocked_markets": [],
"cooldown_until": null
},
"recent_trades": [
{
"id": "trd_abc123",
"market_title": "Will BTC exceed $150k by April 2026?",
"outcome": "yes",
"side": "buy",
"price": 0.62,
"quantity": 50.00,
"cost_usd": 31.00,
"created_at": "2026-03-19T10:30:00Z"
}
],
"recent_ledger": [
{
"id": "ldg_001",
"type": "trade_debit",
"amount_usd": -31.00,
"balance_after_usd": 142.50,
"description": "Buy 50 YES @ $0.62",
"created_at": "2026-03-19T10:30:00Z"
}
],
"last_active_at": "2026-03-19T10:15:00Z",
"created_at": "2026-01-08T18:30:00Z"
}
}Adjust User Balance
Deposit or withdraw funds from a user's Predictu balance. This is the operator-initiated balance adjustment endpoint. For S2S-integrated operators, this supplements the automated callback flow - useful for manual corrections, promotional credits, or dispute resolutions.
Path Parameters
| Param | Type | Description |
|---|---|---|
id | string | User ID. Must belong to the authenticated operator. |
Request Body
{
"action": "deposit",
"amount_usd": 50.00,
"reason": "Promotional credit for new user welcome bonus",
"idempotency_key": "promo_usr_xyz_20260319",
"metadata": {
"promotion_id": "promo_welcome_2026",
"source": "operator_dashboard"
}
}| Field | Type | Required | Description |
|---|---|---|---|
action | string | Yes | Action type: deposit (add funds) or withdraw (remove funds). |
amount_usd | number | Yes | Amount in USD. Must be positive. For withdrawals, cannot exceed user's available balance. |
reason | string | Yes | Human-readable reason for the adjustment. Stored in ledger and audit log. |
idempotency_key | string | Yes | Unique key to prevent duplicate adjustments. If a request with the same key was already processed, the original result is returned. |
metadata | object | No | Arbitrary metadata to attach to the ledger entry. |
Response - 200 OK
{
"data": {
"ledger_entry_id": "ldg_adj_001",
"user_id": "usr_xyz",
"action": "deposit",
"amount_usd": 50.00,
"balance_before_usd": 142.50,
"balance_after_usd": 192.50,
"reason": "Promotional credit for new user welcome bonus",
"idempotency_key": "promo_usr_xyz_20260319",
"metadata": {
"promotion_id": "promo_welcome_2026",
"source": "operator_dashboard"
},
"operator_admin_id": "opu_001",
"created_at": "2026-03-19T12:00:00Z"
}
}Error - 422 Insufficient Balance
{
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "User balance ($142.50) is less than withdrawal amount ($200.00)",
"details": {
"current_balance_usd": 142.50,
"requested_amount_usd": 200.00,
"available_usd": 142.50
}
}
}Trades
View all trades executed by users belonging to this operator. Each trade represents an atomic buy or sell of an outcome share. Trades are immutable after creation.
List Trades
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number (1-indexed). |
limit | number | 50 | Results per page. Max 200. |
user_id | string | null | Filter by user ID. |
market_id | string | null | Filter by market ID. |
side | string | null | Filter: buy or sell. |
outcome | string | null | Filter: yes or no. |
status | string | null | Filter: filled, settled, cancelled. |
min_amount | number | null | Minimum trade amount in USD. |
max_amount | number | null | Maximum trade amount in USD. |
from | string | null | ISO 8601 start date. |
to | string | null | ISO 8601 end date. |
sort | string | "created_at" | Sort: created_at, amount, price. |
order | string | "desc" | Sort order. |
Response - 200 OK
{
"data": [
{
"id": "trd_abc123",
"user_id": "usr_xyz",
"user_name": "player_42",
"external_user_id": "gbn_player_42",
"market_id": "mkt_001",
"market_title": "Will BTC exceed $150k by April 2026?",
"category": "crypto",
"outcome": "yes",
"side": "buy",
"price": 0.62,
"quantity": 50.00,
"cost_usd": 31.00,
"fee_usd": 0.47,
"spread_usd": 0.93,
"pnl_usd": null,
"status": "filled",
"s2s_transaction_id": "txn_gbn_abc123",
"s2s_callback_status": "confirmed",
"created_at": "2026-03-19T10:30:00Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 214800,
"total_pages": 4296
}
}Positions
View all open and historical positions held by the operator's users. A position is the aggregate holding of a specific outcome in a specific market by a user.
List Positions
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number. |
limit | number | 50 | Results per page. Max 200. |
user_id | string | null | Filter by user ID. |
market_id | string | null | Filter by market ID. |
status | string | null | Filter: open, closed, settled. |
outcome | string | null | Filter: yes or no. |
sort | string | "opened_at" | Sort: opened_at, market_value, pnl, quantity. |
order | string | "desc" | Sort order. |
Response - 200 OK
{
"data": [
{
"id": "pos_001",
"user_id": "usr_xyz",
"user_name": "player_42",
"external_user_id": "gbn_player_42",
"market_id": "mkt_001",
"market_title": "Will BTC exceed $150k by April 2026?",
"category": "crypto",
"outcome": "yes",
"quantity": 50.00,
"avg_price": 0.62,
"cost_basis_usd": 31.00,
"current_price": 0.68,
"market_value_usd": 34.00,
"unrealized_pnl_usd": 3.00,
"unrealized_pnl_pct": 9.68,
"realized_pnl_usd": 0.00,
"status": "open",
"trade_count": 1,
"opened_at": "2026-03-19T10:30:00Z",
"closed_at": null,
"settled_at": null
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 4200,
"total_pages": 84
}
}Markets
Browse prediction markets available to the operator's users. Markets are filtered by the operator's allowed_categories configuration. Includes operator-specific metrics like local volume and user engagement.
List Markets
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number. |
limit | number | 50 | Results per page. Max 200. |
search | string | null | Full-text search on market title. |
category | string | null | Filter by category (must be in operator's allowed list). |
status | string | null | Filter: active, paused, resolved. |
sort | string | "volume" | Sort: volume, created_at, end_date, trade_count. |
order | string | "desc" | Sort order. |
Response - 200 OK
{
"data": [
{
"id": "mkt_001",
"title": "Will BTC exceed $150k by April 2026?",
"category": "crypto",
"status": "active",
"image_url": "https://cdn.predictu.com/markets/mkt_001.png",
"yes_price": 0.62,
"no_price": 0.38,
"spread": 0.04,
"global_volume_usd": 482000.00,
"operator_volume_usd": 148000.00,
"operator_trade_count": 3800,
"operator_user_count": 920,
"operator_exposure_usd": 8400.00,
"end_date": "2026-04-30T23:59:59Z",
"resolution": null,
"created_at": "2026-01-15T00:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 648,
"total_pages": 13
}
}Get Market Detail
Returns detailed market information with operator-scoped metrics. Includes price history, local volume breakdown, top traders in this market from the operator's user base, and exposure analysis.
Path Parameters
| Param | Type | Description |
|---|---|---|
id | string | Market ID (e.g. mkt_001). |
Response - 200 OK
{
"data": {
"id": "mkt_001",
"title": "Will BTC exceed $150k by April 2026?",
"description": "Resolves YES if Bitcoin price exceeds $150,000 USD on any major exchange before April 30, 2026 23:59 UTC.",
"category": "crypto",
"tags": ["bitcoin", "price", "crypto"],
"status": "active",
"image_url": "https://cdn.predictu.com/markets/mkt_001.png",
"pricing": {
"yes_price": 0.62,
"no_price": 0.38,
"spread": 0.04,
"last_updated": "2026-03-19T11:58:00Z"
},
"global_metrics": {
"volume_usd": 482000.00,
"trade_count": 12400,
"unique_traders": 2840
},
"operator_metrics": {
"volume_usd": 148000.00,
"trade_count": 3800,
"unique_traders": 920,
"exposure_usd": 8400.00,
"yes_exposure_usd": 5200.00,
"no_exposure_usd": 3200.00,
"position_count": 680,
"avg_trade_usd": 38.95
},
"top_traders": [
{
"user_id": "usr_top1",
"username": "btc_believer",
"volume_usd": 4200.00,
"trade_count": 28,
"pnl_usd": 320.00
}
],
"price_history": [
{ "timestamp": "2026-03-19T11:00:00Z", "yes_price": 0.61, "volume_usd": 480.00 },
{ "timestamp": "2026-03-19T12:00:00Z", "yes_price": 0.62, "volume_usd": 520.00 }
],
"end_date": "2026-04-30T23:59:59Z",
"resolution": null,
"resolved_at": null,
"created_at": "2026-01-15T00:00:00Z"
}
}Revenue
Track the operator's revenue share. The casino operator is the house - revenue represents net player losses (spread income + settlement profit + fees). Predictu takes an agreed percentage of player losses as revenue share (configured via revenue_share_pct, default 30%). The operator keeps the remainder (typically 70%).
Get Revenue
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
from | string | 30 days ago | ISO 8601 start date. |
to | string | now | ISO 8601 end date. |
granularity | string | "day" | Aggregation: hour, day, week, month. |
Response - 200 OK
{
"data": {
"period": { "from": "2026-02-17T00:00:00Z", "to": "2026-03-19T23:59:59Z" },
"revenue_share_pct": 70,
"totals": {
"gross_revenue_usd": 18200.00,
"spread_revenue_usd": 14200.00,
"fee_revenue_usd": 4000.00,
"operator_share_usd": 12740.00,
"predictu_share_usd": 5460.00,
"trade_volume_usd": 364000.00,
"trade_count": 16800,
"unique_traders": 3200,
"revenue_per_trade_usd": 1.08,
"revenue_per_user_usd": 5.69
},
"by_category": [
{ "category": "sports", "gross_usd": 7800.00, "operator_share_usd": 5460.00, "trade_count": 7200 },
{ "category": "crypto", "gross_usd": 5400.00, "operator_share_usd": 3780.00, "trade_count": 4800 },
{ "category": "politics", "gross_usd": 3200.00, "operator_share_usd": 2240.00, "trade_count": 3000 },
{ "category": "entertainment", "gross_usd": 1800.00, "operator_share_usd": 1260.00, "trade_count": 1800 }
],
"series": [
{
"date": "2026-03-18",
"gross_usd": 620.00,
"spread_usd": 480.00,
"fees_usd": 140.00,
"operator_share_usd": 434.00,
"trade_count": 890,
"volume_usd": 17800.00,
"unique_traders": 280
}
]
}
}Exposure
View the operator's current risk exposure. Exposure is the maximum potential payout across all open positions from the operator's users. The operator's exposure limit is set by Predictu during onboarding and can be adjusted via the God Mode dashboard.
Get Exposure
Response - 200 OK
{
"data": {
"total_exposure_usd": 42000.00,
"exposure_limit_usd": 100000.00,
"utilization_pct": 42.0,
"by_category": [
{ "category": "sports", "exposure_usd": 22000.00, "limit_usd": 50000.00, "utilization_pct": 44.0 },
{ "category": "crypto", "exposure_usd": 12000.00, "limit_usd": 30000.00, "utilization_pct": 40.0 },
{ "category": "politics", "exposure_usd": 8000.00, "limit_usd": 20000.00, "utilization_pct": 40.0 }
],
"by_market": [
{
"market_id": "mkt_001",
"title": "Will BTC exceed $150k by April 2026?",
"exposure_usd": 8400.00,
"yes_exposure_usd": 5200.00,
"no_exposure_usd": 3200.00,
"position_count": 142,
"volume_usd": 28400.00
}
],
"high_exposure_users": [
{
"user_id": "usr_high1",
"username": "highroller_99",
"exposure_usd": 4800.00,
"position_count": 12,
"tier": "sharp"
}
],
"snapshot_at": "2026-03-19T12:00:00Z"
}
}Risk
Operator-scoped risk overview. Shows the current risk level, active circuit breakers affecting the operator's markets, recent risk events, and a summary of sharp bettors within the operator's user base.
Get Risk Overview
Response - 200 OK
{
"data": {
"risk_level": "low",
"risk_score": 28,
"exposure": {
"total_usd": 42000.00,
"limit_usd": 100000.00,
"utilization_pct": 42.0
},
"circuit_breakers": {
"active_count": 0,
"breakers": []
},
"sharp_bettors": {
"sharp_count": 3,
"restricted_count": 1,
"banned_count": 0,
"top_sharps": [
{
"user_id": "usr_sharp1",
"username": "edge_hunter",
"score": 72,
"tier": "sharp",
"pnl_usd": 4820.00,
"win_rate": 0.64
}
]
},
"recent_events": [
{
"id": "evt_op_001",
"type": "wall_violation",
"severity": "info",
"message": "Per-trade limit reached for user edge_hunter ($500 max)",
"user_id": "usr_sharp1",
"created_at": "2026-03-19T09:30:00Z"
}
],
"walls_status": {
"wall_1_per_trade": { "status": "green", "violations_24h": 4 },
"wall_2_per_market": { "status": "green", "violations_24h": 1 },
"wall_3_per_category": { "status": "green", "violations_24h": 0 },
"wall_4_per_operator": { "status": "green", "violations_24h": 0 }
}
}
}Settlements
View settlement history for markets that have resolved. Shows which markets were settled, how many of the operator's users were affected, and the net financial impact.
List Settlements
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
page | number | 1 | Page number. |
limit | number | 50 | Results per page. |
status | string | null | Filter: pending, processing, completed, failed. |
market_id | string | null | Filter by market. |
from | string | null | ISO 8601 start date. |
to | string | null | ISO 8601 end date. |
Response - 200 OK
{
"data": [
{
"id": "stl_op_001",
"market_id": "mkt_042",
"market_title": "Will the Fed cut rates in March 2026?",
"resolution": "yes",
"status": "completed",
"operator_positions_settled": 180,
"operator_winners": 112,
"operator_losers": 68,
"operator_payout_usd": 3200.00,
"operator_loss_usd": 2100.00,
"operator_net_pnl_usd": -1100.00,
"s2s_callbacks": {
"sent": 180,
"confirmed": 180,
"failed": 0
},
"resolved_at": "2026-03-18T20:00:00Z",
"settled_at": "2026-03-18T20:00:03Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 62,
"total_pages": 2
}
}Invoices
View the operator's billing invoices from Predictu. Invoices are generated monthly on the 1st and represent Predictu's share of the operator's net player losses for the prior period.
List Invoices
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
status | string | null | Filter: draft, sent, paid, overdue. |
Response - 200 OK
{
"data": [
{
"id": "inv_001",
"period": { "from": "2026-02-01", "to": "2026-02-28" },
"status": "paid",
"amount_usd": 5460.00,
"line_items": [
{ "description": "Predictu share of spread player losses (30%)", "gross_usd": 14200.00, "amount_usd": 4260.00 },
{ "description": "Predictu share of fee player losses (30%)", "gross_usd": 4000.00, "amount_usd": 1200.00 }
],
"trade_volume_usd": 182000.00,
"trade_count": 8420,
"revenue_share_pct": 30,
"issued_at": "2026-03-01T00:00:00Z",
"due_at": "2026-03-15T00:00:00Z",
"paid_at": "2026-03-05T14:20:00Z",
"pdf_url": "/api/operator/invoices/inv_001/pdf"
}
],
"total": 4
}Settings
View and update the operator's profile settings. Operators can update their contact information, branding, and certain configuration values. Some settings (like risk limits and revenue share) are read-only and can only be changed by Predictu admins.
Get Settings
Response - 200 OK
{
"data": {
"profile": {
"name": "Example Casino",
"slug": "example-casino",
"contact_email": "tech@example-casino.com",
"contact_name": "Jordan Lee",
"website_url": "https://example-casino.com",
"logo_url": "https://cdn.predictu.com/operators/example-casino/logo.png",
"favicon_url": "https://cdn.predictu.com/operators/example-casino/favicon.png"
},
"branding": {
"primary_color": "#00ff88",
"secondary_color": "#0a1a0a",
"display_name": "Example Casino Predictions",
"custom_css": null
},
"s2s": {
"enabled": true,
"callback_url": "https://api.example-casino.com/predictu/callback",
"signing_key_fingerprint": "sha256:abc123...",
"healthy": true,
"last_ping": "2026-03-19T11:58:00Z"
},
"limits": {
"max_bet_usd": 1000,
"min_bet_usd": 1,
"exposure_limit_usd": 100000,
"revenue_share_pct": 70,
"custom_fees_bps": 150,
"risk_tier": "premium"
},
"categories": {
"allowed": ["sports", "politics", "entertainment", "crypto"],
"blocked": []
},
"status": "live",
"onboarded_at": "2025-11-22T14:30:00Z",
"updated_at": "2026-03-18T09:00:00Z"
}
}Update Settings
Update operator profile and branding settings. Only the fields included in the request body are updated. Risk limits, revenue share, and other Predictu-managed fields cannot be changed through this endpoint.
Request Body
{
"profile": {
"contact_email": "newtech@example-casino.com",
"contact_name": "Alex Chen"
},
"branding": {
"primary_color": "#00e676",
"display_name": "Example Casino Bets"
},
"s2s": {
"callback_url": "https://api-v2.example-casino.com/predictu/callback"
}
}profile (contact info), branding (colors, display name, custom CSS), and s2s.callback_url. Attempting to update read-only fields returns a 403.Response - 200 OK
{
"data": {
"updated_fields": [
"profile.contact_email",
"profile.contact_name",
"branding.primary_color",
"branding.display_name",
"s2s.callback_url"
],
"previous_values": {
"profile.contact_email": "tech@example-casino.com",
"profile.contact_name": "Jordan Lee",
"branding.primary_color": "#00ff88",
"branding.display_name": "Example Casino Predictions",
"s2s.callback_url": "https://api.example-casino.com/predictu/callback"
},
"updated_at": "2026-03-19T12:00:00Z"
}
}Configuration
Operator-level configuration for trading behavior, UI preferences, and feature toggles. Unlike settings (which are profile/branding), config controls how the prediction market embed behaves for this operator's users.
Get Configuration
Response - 200 OK
{
"data": {
"trading": {
"default_trade_amount_usd": 10,
"quick_amounts": [5, 10, 25, 50, 100],
"show_advanced_trading": false,
"allow_limit_orders": false,
"confirm_trades_above_usd": 100,
"auto_close_positions_on_settlement": true
},
"ui": {
"show_global_volume": true,
"show_market_charts": true,
"show_orderbook": false,
"show_related_markets": true,
"show_user_pnl": true,
"default_market_sort": "volume",
"markets_per_page": 20,
"theme_override": null,
"custom_footer_html": null
},
"notifications": {
"trade_confirmation": true,
"settlement_notification": true,
"price_alert_enabled": true,
"daily_digest": false
},
"categories": {
"enabled": ["sports", "politics", "entertainment", "crypto"],
"featured": ["sports", "crypto"],
"hidden": []
},
"features": {
"batch_trading": true,
"watchlist": true,
"price_alerts": true,
"social_feed": false,
"leaderboard": true,
"referral_program": false
},
"updated_at": "2026-03-18T09:00:00Z"
}
}Update Configuration
Update operator configuration. Supports partial updates - only include the keys you want to change. Changes take effect immediately for all users on the operator's embed.
Request Body
{
"trading": {
"default_trade_amount_usd": 5,
"quick_amounts": [1, 5, 10, 25, 50],
"confirm_trades_above_usd": 50
},
"ui": {
"show_orderbook": true,
"default_market_sort": "trade_count"
},
"features": {
"social_feed": true,
"leaderboard": true
}
}Response - 200 OK
{
"data": {
"updated_keys": [
"trading.default_trade_amount_usd",
"trading.quick_amounts",
"trading.confirm_trades_above_usd",
"ui.show_orderbook",
"ui.default_market_sort",
"features.social_feed",
"features.leaderboard"
],
"previous_values": {
"trading.default_trade_amount_usd": 10,
"trading.quick_amounts": [5, 10, 25, 50, 100],
"trading.confirm_trades_above_usd": 100,
"ui.show_orderbook": false,
"ui.default_market_sort": "volume",
"features.social_feed": false,
"features.leaderboard": true
},
"updated_at": "2026-03-19T12:00:00Z"
}
}API Keys
Manage API key pairs used for S2S authentication and server-side API access. Each operator can have multiple active key pairs, enabling key rotation without downtime. Keys are used to sign S2S callback requests and authenticate server-to-server API calls.
List API Keys
Response - 200 OK
{
"data": [
{
"id": "key_001",
"name": "Production Key",
"prefix": "pk_live_abc1",
"status": "active",
"permissions": ["s2s_callback", "server_api"],
"last_used_at": "2026-03-19T11:58:00Z",
"requests_24h": 4280,
"created_at": "2025-11-22T14:30:00Z",
"expires_at": null
},
{
"id": "key_002",
"name": "Staging Key",
"prefix": "pk_test_xyz9",
"status": "active",
"permissions": ["s2s_callback", "server_api"],
"last_used_at": "2026-03-18T16:00:00Z",
"requests_24h": 120,
"created_at": "2026-01-10T10:00:00Z",
"expires_at": null
}
],
"total": 2
}Create API Key
Generate a new API key pair. The full secret is returned only once in the response - store it securely immediately.
Request Body
{
"name": "New Production Key",
"permissions": ["s2s_callback", "server_api"],
"expires_at": null
}| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-readable label for the key. |
permissions | string[] | No | Permission scopes: s2s_callback, server_api, read_only. Defaults to all. |
expires_at | string | No | ISO 8601 expiration date. null for non-expiring. |
Response - 201 Created
{
"data": {
"id": "key_003",
"name": "New Production Key",
"api_key": "pk_live_def456...",
"api_secret": "sk_live_ghi789...",
"prefix": "pk_live_def4",
"status": "active",
"permissions": ["s2s_callback", "server_api"],
"created_at": "2026-03-19T12:00:00Z",
"expires_at": null
}
}api_secret value is displayed only in this response. It cannot be retrieved later. If lost, you must delete this key and create a new one.Delete API Key
Revoke and permanently delete an API key. Any requests using this key will immediately start receiving 401 Unauthorized responses. This action is irreversible.
Request Body
{
"key_id": "key_002"
}Response - 200 OK
{
"data": {
"id": "key_002",
"name": "Staging Key",
"status": "revoked",
"revoked_at": "2026-03-19T12:00:00Z"
}
}Embed Origins
Manage the allowed origins for the Predictu iframe embed. The embed will only load and communicate via PostMessage with origins in this allowlist. This is a critical security control - only add domains you control.
List Embed Origins
Response - 200 OK
{
"data": [
{
"id": "org_001",
"origin": "https://example-casino.com",
"status": "verified",
"verified_at": "2025-11-22T14:30:00Z",
"last_seen_at": "2026-03-19T11:58:00Z",
"request_count_24h": 14200,
"created_at": "2025-11-22T14:30:00Z"
},
{
"id": "org_002",
"origin": "https://www.example-casino.com",
"status": "verified",
"verified_at": "2025-11-22T14:35:00Z",
"last_seen_at": "2026-03-19T11:42:00Z",
"request_count_24h": 2800,
"created_at": "2025-11-22T14:35:00Z"
}
],
"total": 2
}Add Embed Origin
Add a new allowed origin for the iframe embed. The origin must be a valid URL with thehttps:// protocol (HTTP is not allowed in production). The origin undergoes an automated verification check.
Request Body
{
"origin": "https://staging.example-casino.com"
}| Field | Type | Required | Description |
|---|---|---|---|
origin | string | Yes | The origin URL. Must be https://. Do not include paths or trailing slashes (e.g. https://example-casino.com, not https://example-casino.com/). |
Response - 201 Created
{
"data": {
"id": "org_003",
"origin": "https://staging.example-casino.com",
"status": "pending_verification",
"verification_token": "pred_verify_abc123",
"verification_instructions": "Add a DNS TXT record: predictu-verify=pred_verify_abc123",
"created_at": "2026-03-19T12:00:00Z"
}
}verified and the embed will accept PostMessage communication from it.Remove Embed Origin
Remove an allowed origin. The iframe embed will immediately stop accepting communication from this origin. Users currently viewing the embed from this origin will see an error on their next interaction.
Request Body
{
"origin_id": "org_003"
}Response - 200 OK
{
"data": {
"id": "org_003",
"origin": "https://staging.example-casino.com",
"status": "removed",
"removed_at": "2026-03-19T12:00:00Z"
}
}Embed Code
Retrieve the ready-to-use embed code snippet for integrating the Predictu prediction market widget into the operator's site. The generated code includes the operator's API key, branding configuration, and PostMessage bridge setup.
Get Embed Code
Query Parameters
| Param | Type | Default | Description |
|---|---|---|---|
format | string | "html" | Output format: html (iframe snippet), react (React component), vanilla (JS SDK). |
theme | string | "dark" | Theme: dark or light. |
width | string | "100%" | Embed width. |
height | string | "600px" | Embed height. |
Response - 200 OK
{
"data": {
"format": "html",
"code": "<iframe\n id=\"predictu-embed\"\n src=\"https://app.predictu.com/embed?operator=example-casino&theme=dark\"\n width=\"100%\"\n height=\"600px\"\n frameborder=\"0\"\n allow=\"clipboard-write\"\n style=\"border: none; border-radius: 12px;\"\n></iframe>\n<script src=\"https://cdn.predictu.com/embed/bridge.js\"></script>\n<script>\n PredictuBridge.init({\n apiKey: 'pk_live_abc123...',\n operatorSlug: 'example-casino',\n onTradeComplete: (trade) => console.log('Trade:', trade),\n onBalanceChange: (balance) => console.log('Balance:', balance)\n });\n</script>",
"api_key": "pk_live_abc123...",
"embed_url": "https://app.predictu.com/embed?operator=example-casino&theme=dark",
"bridge_url": "https://cdn.predictu.com/embed/bridge.js"
}
}Reports
Export operator data as downloadable reports. Reports are generated on-demand and include only the authenticated operator's data.
Export Report
Request Body
{
"type": "trades",
"format": "csv",
"filters": {
"from": "2026-03-01T00:00:00Z",
"to": "2026-03-19T23:59:59Z",
"user_id": null,
"market_id": null,
"side": null
},
"columns": ["id", "user_name", "external_user_id", "market_title", "outcome", "side", "price", "quantity", "cost_usd", "fee_usd", "status", "created_at"]
}| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Report type: trades, users, positions, revenue, settlements. |
format | string | Yes | Output format: csv, xlsx, pdf. |
filters | object | No | Filter criteria (varies by report type). All include from/to date range. |
columns | string[] | No | Specific columns to include. Defaults to all available columns for the report type. |
Response - 200 OK
{
"data": {
"report_id": "rpt_op_001",
"type": "trades",
"format": "csv",
"status": "ready",
"download_url": "/api/operator/reports/rpt_op_001/download",
"expires_at": "2026-03-19T14:00:00Z",
"row_count": 4280,
"file_size_bytes": 620000,
"generated_at": "2026-03-19T12:00:03Z"
}
}report_id for status updates.File Upload
Upload operator branding assets (logos, favicons) to the Predictu CDN. Uploaded files are served via a global CDN for fast loading in the iframe embed.
Upload File
Send the file as multipart/form-data. The Content-Type must bemultipart/form-data.
Form Fields
| Field | Type | Required | Description |
|---|---|---|---|
file | File | Yes | The file to upload. Max 2MB. Accepted: png, jpg, jpeg, svg, webp. |
category | string | Yes | Upload category: logo, favicon, banner, custom_asset. |
Response - 200 OK
{
"data": {
"id": "file_op_001",
"url": "https://cdn.predictu.com/operators/example-casino/logo-v2.png",
"category": "logo",
"content_type": "image/png",
"size_bytes": 18400,
"width": 512,
"height": 512,
"uploaded_at": "2026-03-19T12:00:00Z"
}
}logo or favicon, the URL is automatically updated in your operator branding settings. The change propagates to all active embed instances within 60 seconds via the PostMessage bridge refresh cycle.Pagination & Rate Limits
All list endpoints use offset-based pagination with a consistent response envelope.
Pagination Format
// Request
GET /api/operator/trades?page=2&limit=50
// Response envelope
{
"data": [...],
"pagination": {
"page": 2,
"limit": 50,
"total": 214800,
"total_pages": 4296
}
}Rate Limits
Operator API endpoints are rate-limited to 120 requests per minute per operator account. Rate limit headers are included in every response:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests per minute (120). |
X-RateLimit-Remaining | Remaining requests in the current window. |
X-RateLimit-Reset | Unix timestamp when the rate limit window resets. |
// Example rate limit headers
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 98
X-RateLimit-Reset: 1742392860429 Too Many Requests with a Retry-After header indicating seconds until the next available request window. Implement exponential backoff in your integration to handle rate limits gracefully.SDK & Client Libraries
Official client libraries are available for common languages. These handle authentication, rate limiting, pagination, and error handling automatically.
| Language | Package | Status |
|---|---|---|
| JavaScript / TypeScript | @predictu/operator-sdk | Stable |
| Python | predictu-operator | Beta |
| Go | github.com/predictu/operator-go | Planned |
| PHP | predictu/operator-php | Planned |
// TypeScript SDK example
import { PredictuOperator } from '@predictu/operator-sdk';
const client = new PredictuOperator({
apiKey: 'pk_live_abc123...',
apiSecret: 'sk_live_xyz789...',
});
// List users with pagination
const users = await client.users.list({
page: 1,
limit: 50,
tier: 'normal',
sort: 'last_active',
});
// Deposit funds
const result = await client.users.adjustBalance('usr_xyz', {
action: 'deposit',
amount_usd: 50,
reason: 'Welcome bonus',
idempotency_key: 'welcome_usr_xyz',
});
// Get revenue
const revenue = await client.revenue.get({
from: '2026-03-01',
to: '2026-03-19',
granularity: 'day',
});