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/apiEvery request is scoped to the operator and environment you sign for. You cannot read another operator's data through this API.
Routes
| Method and path | Purpose |
|---|---|
GET /stats | Summary statistics for your operator in the signed environment, one entry per currency (below) |
GET /settings | Your operator code, name, per-currency order limits and fee schedule, read-only (see Settings and Categories) |
GET /categories | The active categories your events can use (see Settings and Categories) |
POST /wallet-test | Run the wallet test against the signed environment's wallet (see Testing) |
GET /wallet-test/runs | Your 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-signatureThe 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 param | Notes |
|---|---|
from, to | ISO 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_id | Only events in this category |
market_id | Only this market |
player_id | Only 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
| HTTP | Body | Meaning |
|---|---|---|
400 | { "error": "validation", ... }, invalid_from, invalid_to, player_id is required, invalid_amount | The 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 |
422 | currency_not_supported, no_wallet_base_url, wallet_url_not_public, operator_or_environment_not_found | The wallet test cannot start |