API reference
Every AI Market Maker endpoint with the permission it gates on, the WebSocket subscription contract and its event streams, the tables the addon writes, and the response conventions that catch integrators out.
One API surface. Everything lives under /api/admin/ai/market-maker and every
route carries an explicit permission — there is no user-facing endpoint in this
addon.
Conventions that will catch you out
The platform pins the HTTP status at 200 and puts the real outcome in the body. Read the body, always. A client that branches on the status code will render a green toast over a failed deposit — which is exactly what the admin screens themselves did before v6.1.5, on pool deposits, withdrawals, rebalance calculations, configuration saves and deletes.
Amounts are DECIMAL(30,18) columns and arrive from mysql2 as strings.
Coerce before arithmetic and before comparison: "9" >= "10" is true as text,
which is how a market with a 9–10 price range became uneditable and how a market
could be reported as over its volume budget.
priceEngineState is a JSON column. Production MySQL returns it already parsed;
local MariaDB returns a string. The model handles both, but a direct database
consumer must too.
entropySeed is the secret 64-bit seed that generates a market's entire future
price path — anyone holding it can compute where the price will be, which on a
market that settles binary options is the whole game. priceEngineState reveals
the engine's internal position.
Both are removed by the model's serialiser and excluded at the query on the list endpoint, because the list route returns model instances that the response encoder walks itself, so the serialiser never ran there. If you build your own query, exclude them yourself.
Markets
Creation requires marketId, targetPrice, priceRangeLow and
priceRangeHigh. Everything else falls back: aggressionLevel to MODERATE,
maxDailyVolume to 1000000, volatilityThreshold to 5,
pauseOnHighVolatility to true and realLiquidityPercent to 20. Note that
the create wizard sends its own values for all of those, so the API defaults only
apply to a direct caller.
Valid status transitions are STOPPED to START, ACTIVE to PAUSE or STOP, and
PAUSED to RESUME or STOP. Anything else is refused with the valid actions
listed.
engineAttached is false when this process is not the one holding engine
leadership. In that case a START is recorded and the message says the engine
will begin on the next cycle, rather than claiming success.
Every one of the configuration routes applies its change to the running market, not just to the database row. Before v6.1.5 they wrote the row, reported success, and left the running market behaving exactly as before until the next full reload — up to 24 hours.
Bots
config accepts riskTolerance (0.1–1), tradeFrequency
(HIGH/MEDIUM/LOW), avgOrderSize (above 0), orderSizeVariance
(0.1–0.5), preferredSpread (above 0) and maxDailyTrades (above 0). Every
out-of-range value comes back as a 400 naming the range it needed to be in, and
the response reports changesApplied — a count of the fields that actually
differed.
status accepts cooldownMinutes alongside COOLDOWN, defaulting to 15. Any
other status clears the expiry. Activating a bot while its market maker is not
ACTIVE is refused.
There is no create-bot or delete-bot endpoint. The roster of six is fixed at market creation.
Pool
deposit and withdraw take currency (BASE or QUOTE) and amount. Both
accept an optional idempotencyKey so a retried submit settles once; without one
the ledger key is unique per request, so two identical calls are two movements.
rebalance takes targetRatio (0–1, default 0.5), mode (REPORT by default,
or EXECUTE) and maxSlippagePercent (EXECUTE only, default 2). It refuses an
ACTIVE market in both modes. EXECUTE answers 409 rather than partially
filling when the real book cannot cover the move within the slippage limit.
Analytics
The overview returns quoting — five counts that add up to the active market
count — plus blockers per market naming the closed gate, band and
bandPosition, inventory skew, and an engine block carrying isLeader and
leaderArbitratedBy. quoteCurrency is null when your markets do not share a
quote asset, and the client must then print the totals without a unit.
total24hVolume sums ACTIVE markets only; volumeToday sums every market
whatever its status. Neither is a rolling day. recentTradeCount genuinely is.
performance returns truncated when the period held more rows than it will
read, and its currentPrice is the live price — the running instance first, then
the persisted checkpoint, and only then the target.
pnl returns a ledger block stating which table the period figures come from
and when that ledger begins, because the all-time figure covers a longer span.
Emergency
stop accepts reason and cancelOpenOrders (default true). It returns
marketsStopped, botsStopped, ordersCancelled, the reason it recorded and a
timestamp. Send a real reason — it is written onto every market's history entry
and cannot be reconstructed afterwards.
reset-breaker returns engineRunning, wasTripped and tripReason rather
than a bare success, so a "cleared" response on a stopped engine cannot be
mistaken for a fix.
WebSocket
Event-driven, not polled. Subscribing sends a full snapshot once; after that, frames arrive only when something happens.
{ "action": "SUBSCRIBE", "payload": { "marketMakerId": "…" } }UNSUBSCRIBE takes the identical payload. Disconnecting removes every
subscription the client held.
| Stream | Carries |
|---|---|
ai-market-maker-data |
The initial snapshot: configuration, market, pool, every bot, the last 20 bot trades and the last 20 history entries |
ai-market-maker-event |
One event, typed TRADE, ORDER, STATUS_CHANGE, BOT_UPDATE, POOL_UPDATE, BOT_ACTIVITY or ERROR |
BOT_ACTIVITY carries what a bot is doing right now — AI_TRADE,
REAL_ORDER_PLACED, ORDER_CANCELLED, ANALYZING, WAITING or COOLDOWN —
with the side, price, amount and counterparty bot where they apply.
The snapshot degrades gracefully: if Scylla or the history query fails, the
frame still arrives with the parts that succeeded and an errors array naming
what did not. Only a missing market maker aborts it.
Events are raised by the engine, which runs in exactly one process. Treat the socket as an accelerator rather than the source of truth — the admin screens poll alongside it for the same reason.
Tables
MySQL:
| Table | Holds |
|---|---|
ai_market_maker |
The configuration rows. marketId is unique, so one per ecosystem market |
ai_market_maker_pool |
One pool per market maker: balances, capital basis, TVL and both P&L figures |
ai_bot |
The six bots per market, with their configuration and lifetime real-fill statistics |
ai_market_maker_history |
The immutable trail. Per-trade rows are pruned on the retention setting; lifecycle rows never are |
ai_market_maker_engine_lease |
The database arbiter for engine leadership |
ScyllaDB, in the Ecosystem keyspace:
| Table | Holds |
|---|---|
ai_bot_orders |
Orders the bots placed, keyed by market and creation time |
ai_bot_trades |
The bot tape, in 1e18 fixed point |
ai_bot_real_trades |
Per-fill P&L against real users, in plain decimals. The source of the daily, weekly and monthly figures |
Deleting a market maker cascades to its pool, its bots and its entire history.