Errors
Common public error codes and meanings
Wallet-Facing Errors
How your wallet should answer Phoenix. See Errors on the wallet operations page for how each answer is read.
| Situation | Recommended response |
|---|---|
Player does not have funds for reserve_cash | HTTP 422 with code: "insufficient_funds" |
| Your wallet will not take a bet for another reason (a limit, a closed account) | HTTP 422 with a code of your choice. Only reserve_cash may be declined |
| Phoenix signature is invalid | HTTP 401 |
| Request body is malformed | HTTP 400 |
| Same idempotency key is still processing | Return the stored result once ready, or HTTP 409 or 503. Phoenix sends the move again either way |
| Same idempotency key is reused with a different payload | HTTP 422 |
| Temporary outage | HTTP 503 |
Launch Refusals
A launch token Phoenix refuses is answered by the embed's boot configuration call with a body of { "error", "message", "details" }, one code per reason (malformed_token, invalid_signature, unknown_key, token_expired, environment_not_active, unsupported_currency and the rest). The full list, with each code's HTTP status and details, is under Refusals You Can Read. The embed also reports the code to your page as an error event (see Parent Bridge).
Player-Facing Phoenix Reasons
When a player command is rejected, the command reply carries a structured reason:
{
"kind": "command",
"outcome": "rejected",
"reason": {
"code": "validation",
"message": "insufficient funds",
"details": { "reason": "insufficient_funds" }
}
}reason.code is a closed set. Branch on it in client code; never parse reason.message, which is a human-readable string for display and logs. A reason can also carry details, and on internal reasons retriable (true when trying again may work) and sometimes retry_after_ms.
Every refused bet names what happened in reason.details.reason: the order's own reason (for example insufficient_funds, wallet_declined, wallet_not_called, expired, market_closed, market_paused, insufficient_position), or the one Phoenix refused it with before it reached the market (for example below_min_order_size, above_max_order_size, player_banned, operator_not_active). The coarse reason.code puts several of these under validation, so word a refusal from details.reason, never from code alone: a wallet that declined a bet is not the player's order being invalid. The embed does this for you (see Player Protection).
When your wallet declines a reservation with any code other than insufficient_funds, the reply also names your code:
{
"kind": "command",
"outcome": "rejected",
"reason": {
"code": "validation",
"message": "your wallet declined this bet",
"details": { "reason": "wallet_declined", "wallet_code": "limit_exceeded" }
}
}The embed shows the player what your code means, which you set under Player protection in the back office; Phoenix gives limit_exceeded, self_excluded, cooling_off, verification_required, account_blocked and player_not_found a meaning already (see Declining a Reservation).
When Phoenix did not send the reservation to your wallet at all, because one of its own rules refused it (for example wallet_url_not_public), the reply says so and names Phoenix's code instead:
{
"kind": "command",
"outcome": "rejected",
"reason": {
"code": "internal",
"message": "this bet could not be sent to your wallet; nothing was taken",
"details": { "reason": "wallet_not_called", "phoenix_code": "wallet_url_not_public" }
}
}reason.code | Meaning |
|---|---|
validation | The request was well-formed but invalid for the market or order (amount, price, size off the market's grid, or the wallet declined the reservation) |
not_found | The market does not exist, or is not offered to this operator |
forbidden | The action is not allowed (operator not active, player blocked, currency not supported, not the order owner, a field the command does not take) |
conflict | The request conflicts with the current state (market closed, paused, resolved, not yet open, order already finished, idempotency key reused with different parameters) |
rate_limited | Phoenix is busy; try again shortly |
internal | Phoenix could not complete the command; see the message, and retriable |
A bet sent without an idempotency key is refused with idempotency_required. One more code is reserved but not sent today: unknown_mutation. A command the embed's connection does not know is answered unknown_request, and a player-only command from a visitor login_required.
The reason.message strings, by code:
reason.message | code | When |
|---|---|---|
insufficient funds | validation | Your wallet declined the reservation with insufficient_funds |
your wallet declined this bet | validation | Your wallet declined the reservation with another code, carried in reason.details.wallet_code |
this bet could not be sent to your wallet; nothing was taken | internal | Phoenix did not send the reservation: one of its own rules refused it before any request. Phoenix's code is in reason.details.phoenix_code |
invalid order | validation | The order failed validation (quantity, price, or budget) |
invalid order: <field> | validation | A named field of the order is invalid |
a market order cannot carry a limit price | validation | A market order included a limit price |
insufficient position to sell | validation | The sell exceeds the player's position |
order is below the minimum order size | validation | Under your minimum for the currency |
order exceeds the maximum order size | validation | Over your maximum for the currency |
amount has more decimals than the currency allows | validation | The cash amount has more decimals than the currency, or the share quantity is not on the market's grid (see Order size precision) |
no fee schedule configured for this market | validation | Your operator has no fee schedule yet; Phoenix sets it |
invalid environment | validation | The session's environment is not valid |
invalid payload: ... | validation | A field of the command has the wrong type |
market not found | not_found | The market does not exist, is not offered to this operator, or is hidden by its Curation |
operator is not active for trading | forbidden | Your operator is suspended or not enabled |
player is banned from trading | forbidden | Your support team blocked this player in the back office |
currency not supported by this operator | forbidden | The currency is not one of yours |
not the order owner | forbidden | A cancel targeted another player's order |
fields not accepted: ... | forbidden | The command states a field it does not take |
market is closed | conflict | The market no longer accepts orders: it is closed or on hold, or the order reached the book after its close time |
trading is paused | conflict | Your operator's book on this market is suspended |
market is not yet open | conflict | The market is not published yet |
market is closed for new positions | conflict | The market is resolved or void |
order already filled or cancelled | conflict | A cancel arrived after the order finished |
idempotency key reused with different parameters | conflict | The same idempotency key was sent with a different order |
market is opening - retry shortly | rate_limited | Your operator's book on this market is being opened by its first order |
order intake is at capacity - retry shortly | rate_limited | Phoenix has too many orders in flight, or too many of yours are waiting on your wallet. A new order waits at most about a second for room, then is refused instead of queuing; nothing was taken. Try again shortly |
the wallet did not confirm the bet in time; any money held for it is returned | internal | Your wallet did not fund the buy within 60 seconds. Any money your wallet holds for it is released. retriable is true: the player may place it again |
the order could not be funded | internal | The reservation could not be matched to the order |
order service temporarily unavailable | internal | A temporary problem; try again |
market temporarily unavailable | internal | The market's book could not be opened; try again |
A retry under the same idempotency key with identical parameters is safely de-duplicated: it returns the original order with no error. Only a key reused with different parameters conflicts. A resend of a bet Phoenix already took is answered with that bet's first outcome, even if the market has closed since.
When Phoenix cannot tell whether a bet was taken (it could not confirm that it stored the order), it answers with an error reply unconfirmed, not a refused command: the bet may exist. Send the same bet again under the same idempotency key, and do not mint a new key, which would be a second bet. The reply to the resend says what happened, and Phoenix takes the bet once either way.
A buy reply waits up to 5 seconds for the order's outcome. If your wallet has not answered by then, the reply is accepted, and a later refusal shows on the order itself (below).
Show friendly copy to players. Keep reason.code and reason.message in logs for support.
Rejected After Acceptance
An order can be rejected after Phoenix accepted it, for example when your wallet answers after the reply. The player then sees the order end as rejected, and your support team reads why on the order in the back office (Trades and the player's page). The stored reason is one of:
| Stored reason | Meaning |
|---|---|
insufficient_funds | Your wallet declined the reservation with insufficient_funds |
wallet_declined:<code> | Your wallet declined the reservation with <code> (older orders may show wallet_declined alone) |
wallet_not_called:<code> | Phoenix did not send the reservation: one of its own rules refused it, with Phoenix's <code> (for example wallet_url_not_public). Your wallet was never called |
expired | Your wallet did not fund the order within 60 seconds. Any money it holds for the order is released |
stake_precision | The amount or quantity is not on the market's grid |
market_paused | Your operator's book on the market was suspended when the order reached it |
market_closed | The market was closed or on hold, or the order reached it after its close time |
invalid_order | The order failed validation |
market_order_with_limit | A market order carried a limit price |
insufficient_position | A sell exceeded the player's position when it reached the book |
unfunded_order, reserve_mismatch, not_fundable | The reservation could not be matched to the order |
When the book refuses an order your wallet had funded, the reservation is released with a release_cash (reason ORDER_REJECTED). When your wallet declined the reservation, nothing was held and nothing is released.
Operator API Errors
See Operator API errors.