Phoenix Prediction Docs

Overview

The signed Operator API - routes, authentication, stats, and errors

Most operators run Phoenix Prediction through the hosted iframe and the back office. The Operator API is for your backend: read your stats, settings and categories, and run the wallet test.

The base path is:

https://prediction.phoenixverse.io/operator/api

Every request is scoped to the operator and environment you sign for. You cannot read another operator's data through this API.

Routes

Method and pathPurpose
GET /statsSummary statistics for your operator in the signed environment, one entry per currency (below)
GET /settingsYour operator code, name, per-currency order limits and fee schedule, read-only (see Settings and Categories)
GET /categoriesThe active categories your events can use (see Settings and Categories)
POST /wallet-testRun the wallet test against the signed environment's wallet (see Testing)
GET /wallet-test/runsYour recent wallet test runs and whether the latest cleared

A correctly signed request to any other path answers 404 with { "error": "not_found" } (authentication runs first, so an unsigned one answers 401).

OpenAPI is available from https://prediction.phoenixverse.io/openapi.json, with a Redoc UI at https://prediction.phoenixverse.io/docs. It lists every route on this page, Phoenix's public signing key (GET /.well-known/signing-key.pem) and the embed's boot configuration call.

Authentication

The Operator API uses Ed25519 request signatures, verified with the public key registered for the environment, or with either of its two keys during a rotation. It does not use shared secrets.

Required headers:

X-Operator-Code: acme
X-Operator-Environment: sandbox
X-Signature-Timestamp: 1779100000
X-Signature-Nonce: Zr4kP0w1xQ7mV2nB9cLdHg
X-Signature: base64url-ed25519-signature

The signature covers the query string and a once-only nonce. See Request Signing for the canonical payload. The older signature without a nonce is deprecated and is accepted only for requests with no query string.

Stats

GET /operator/api/stats?from=2026-10-01T00:00:00Z&to=2026-10-08T00:00:00Z
Query paramNotes
from, toISO 8601 times with a time zone. Each defaults to the last 24 hours when absent; an unreadable one answers 400 with { "error": "invalid_from" } or { "error": "invalid_to" }
category_idOnly events in this category
market_idOnly this market
player_idOnly this player, by Phoenix's internal player id (not your sub)

The response has one entry per currency, covering the signed environment. It lists the currencies your operator offers, in your order, then any other currency your books hold money in. Money is never added up across currencies. Each entry has the currency and its figures; money values are decimal strings:

{
  "operator_id": "…",
  "environment": "sandbox",
  "from": "2026-10-01T00:00:00Z",
  "to": "2026-10-08T00:00:00Z",
  "currencies": [
    {
      "currency": "KES",
      "total_volume": "152340.00",
      "ggr": "4120.55",
      "fee_revenue": "3980.10",
      "mm_pnl": "140.45",
      "realized_pnl": "140.45",
      "unrealized_pnl": "0",
      "active_markets": 42,
      "active_players": 318,
      "order_count": 2210,
      "avg_bet_size": "68.93",
      "settled_order_count": 1890,
      "settled_payout": "48211.00"
    }
  ]
}

from and to echo the window used, including the 24-hour default.

For figures per event or per day, use the back office's Dashboard, Trades and Finance pages.

Request Bodies

Only POST /wallet-test takes a body. A body states only the fields the endpoint takes: a field it does not take is refused, never ignored, and nothing runs. Phoenix answers 422 with error: "forbidden" and names each refused field in details.fields:

{
  "error": "forbidden",
  "message": "fields not accepted: player_idx is not a field of this command",
  "details": {
    "fields": { "player_idx": ["is not a field of this command"] }
  }
}

A field of the wrong type answers 400 with error: "validation" and the same details.fields shape. Query parameters on this POST count as body fields.

Errors

HTTPBodyMeaning
400{ "error": "validation", ... }, invalid_from, invalid_to, player_id is required, invalid_amountThe request is malformed
401{ "error": "unauthorized" }Signature, timestamp, nonce, operator code, environment or key verification failed, or the operator or environment is not active (see Failure Behavior)
404{ "error": "not_found" }No such route
422{ "error": "forbidden", ... }The body states a field the endpoint does not take
422currency_not_supported, no_wallet_base_url, wallet_url_not_public, operator_or_environment_not_foundThe wallet test cannot start