Predictu
API Reference

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.

Operator-scoped. Every endpoint under /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 StatusCodeMeaning
400BAD_REQUESTInvalid parameters or request body
401UNAUTHORIZEDMissing or expired operator token
403FORBIDDENValid token but insufficient permissions for this action
404NOT_FOUNDResource does not exist or belongs to another operator
409CONFLICTDuplicate resource (e.g. duplicate embed origin)
422VALIDATION_ERRORSchema validation failed
429RATE_LIMITEDToo many requests (120 req/min per operator)
500INTERNAL_ERRORUnexpected 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

GET/api/operator/dashboard

Query Parameters

ParamTypeDefaultDescription
periodstring"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

GET/api/operator/users

Query Parameters

ParamTypeDefaultDescription
pagenumber1Page number (1-indexed).
limitnumber50Results per page. Max 200.
searchstringnullSearch by username, email, external ID, or internal user ID.
tierstringnullFilter by risk tier: normal, sharp, restricted, banned.
statusstringnullFilter by account status: active, inactive, suspended.
sortstring"created_at"Sort: created_at, last_active, balance, trade_count, pnl, volume.
orderstring"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

GET/api/operator/users/[id]

Returns detailed profile for a specific user belonging to this operator. Includes full metrics, scoring summary, active restrictions, and recent activity.

Path Parameters

ParamTypeDescription
idstringUser 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

POST/api/operator/users/[id]/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.

Ledger entry created. Every balance adjustment creates an immutable ledger entry with the operator admin's ID and the provided reason. These entries are visible to Predictu admins in the God Mode dashboard for audit purposes.

Path Parameters

ParamTypeDescription
idstringUser 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"
  }
}
FieldTypeRequiredDescription
actionstringYesAction type: deposit (add funds) or withdraw (remove funds).
amount_usdnumberYesAmount in USD. Must be positive. For withdrawals, cannot exceed user's available balance.
reasonstringYesHuman-readable reason for the adjustment. Stored in ledger and audit log.
idempotency_keystringYesUnique key to prevent duplicate adjustments. If a request with the same key was already processed, the original result is returned.
metadataobjectNoArbitrary 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

GET/api/operator/trades

Query Parameters

ParamTypeDefaultDescription
pagenumber1Page number (1-indexed).
limitnumber50Results per page. Max 200.
user_idstringnullFilter by user ID.
market_idstringnullFilter by market ID.
sidestringnullFilter: buy or sell.
outcomestringnullFilter: yes or no.
statusstringnullFilter: filled, settled, cancelled.
min_amountnumbernullMinimum trade amount in USD.
max_amountnumbernullMaximum trade amount in USD.
fromstringnullISO 8601 start date.
tostringnullISO 8601 end date.
sortstring"created_at"Sort: created_at, amount, price.
orderstring"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

GET/api/operator/positions

Query Parameters

ParamTypeDefaultDescription
pagenumber1Page number.
limitnumber50Results per page. Max 200.
user_idstringnullFilter by user ID.
market_idstringnullFilter by market ID.
statusstringnullFilter: open, closed, settled.
outcomestringnullFilter: yes or no.
sortstring"opened_at"Sort: opened_at, market_value, pnl, quantity.
orderstring"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

GET/api/operator/markets

Query Parameters

ParamTypeDefaultDescription
pagenumber1Page number.
limitnumber50Results per page. Max 200.
searchstringnullFull-text search on market title.
categorystringnullFilter by category (must be in operator's allowed list).
statusstringnullFilter: active, paused, resolved.
sortstring"volume"Sort: volume, created_at, end_date, trade_count.
orderstring"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

GET/api/operator/markets/[id]

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

ParamTypeDescription
idstringMarket 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

GET/api/operator/revenue

Query Parameters

ParamTypeDefaultDescription
fromstring30 days agoISO 8601 start date.
tostringnowISO 8601 end date.
granularitystring"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

GET/api/operator/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

GET/api/operator/risk

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

GET/api/operator/settlements

Query Parameters

ParamTypeDefaultDescription
pagenumber1Page number.
limitnumber50Results per page.
statusstringnullFilter: pending, processing, completed, failed.
market_idstringnullFilter by market.
fromstringnullISO 8601 start date.
tostringnullISO 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

GET/api/operator/invoices

Query Parameters

ParamTypeDefaultDescription
statusstringnullFilter: 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

GET/api/operator/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

PATCH/api/operator/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"
  }
}
Editable fields. Only the following sections can be updated by operators: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

GET/api/operator/config

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

POST/api/operator/config

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

GET/api/operator/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
}
Key prefixes only. For security, the full API key and secret are never returned after initial creation. Only the key prefix (first 12 characters) is shown for identification.

Create API Key

POST/api/operator/api-keys

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
}
FieldTypeRequiredDescription
namestringYesHuman-readable label for the key.
permissionsstring[]NoPermission scopes: s2s_callback, server_api, read_only. Defaults to all.
expires_atstringNoISO 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
  }
}
Store the secret immediately. The 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

DELETE/api/operator/api-keys

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"
  }
}
Cannot undo. Key deletion is permanent. Ensure no production systems are using this key before revoking. Consider creating a replacement key first, updating your systems, then deleting the old key.

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

GET/api/operator/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

POST/api/operator/embed/origins

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"
}
FieldTypeRequiredDescription
originstringYesThe 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"
  }
}
DNS verification. New origins require DNS TXT record verification before they become active. Add the provided token as a TXT record on the domain. Predictu checks verification status automatically every 5 minutes. Once verified, the origin's status changes to verified and the embed will accept PostMessage communication from it.

Remove Embed Origin

DELETE/api/operator/embed/origins

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

GET/api/operator/embed/code

Query Parameters

ParamTypeDefaultDescription
formatstring"html"Output format: html (iframe snippet), react (React component), vanilla (JS SDK).
themestring"dark"Theme: dark or light.
widthstring"100%"Embed width.
heightstring"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

POST/api/operator/reports/export

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"]
}
FieldTypeRequiredDescription
typestringYesReport type: trades, users, positions, revenue, settlements.
formatstringYesOutput format: csv, xlsx, pdf.
filtersobjectNoFilter criteria (varies by report type). All include from/to date range.
columnsstring[]NoSpecific 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"
  }
}
Download expiry. Report download links expire after 2 hours. If the link expires before you download the file, you must generate a new report. Large reports (over 100k rows) are processed asynchronously - poll the returned 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

POST/api/operator/upload

Send the file as multipart/form-data. The Content-Type must bemultipart/form-data.

Form Fields

FieldTypeRequiredDescription
fileFileYesThe file to upload. Max 2MB. Accepted: png, jpg, jpeg, svg, webp.
categorystringYesUpload 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"
  }
}
Auto-apply. When you upload a 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:

HeaderDescription
X-RateLimit-LimitMaximum requests per minute (120).
X-RateLimit-RemainingRemaining requests in the current window.
X-RateLimit-ResetUnix timestamp when the rate limit window resets.
// Example rate limit headers
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 98
X-RateLimit-Reset: 1742392860
429 responses. When the rate limit is exceeded, the API returns 429 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.

LanguagePackageStatus
JavaScript / TypeScript@predictu/operator-sdkStable
Pythonpredictu-operatorBeta
Gogithub.com/predictu/operator-goPlanned
PHPpredictu/operator-phpPlanned
// 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',
});