API reference

Every Forex & Multi-Asset Trading endpoint — 65 admin routes with the permission each gates on, 18 customer routes, three WebSocket streams, the enums, the tables and the compliance gates.

6 min readUpdated 6 August 2026api, endpoints, websocket, permissions, tables, enums

Two API surfaces. Everything under /api/admin/forex-trading carries an explicit permission and writes an audit entry under the ADMIN_FX module. Everything under /api/forex-trading is scoped to the calling user and carries no permission at all — it is gated by ownership, by the geo-block list, by the trade_forex KYC feature and by fxTradingEnabled instead.

Conventions that will catch you out

The platform pins the HTTP status at 200 and puts the real outcome in the body. CORS handling in the uWebSockets layer forces it; the code you need is in the response payload. A client branching on the status code will read a geo-blocked account creation, a rejected order and a refused withdrawal as successes.

Money columns on these tables are DOUBLE, but anything that reaches you through a DECIMAL column elsewhere in the platform arrives as a string. Coerce before arithmetic.

Sizes are base units, not lots. amount on an order, a position and a trade deal is in base units; lots are amount / metadata.contractSize. A 1-lot EUR/USD order is amount: 100000.

Symbols are CURRENCY/PAIR. EUR/USD, XAU/USD, AAPL/USD. There is no separate symbol column — it is composed from fx_instrument.currency and .pair, which are unique together.

Admin endpoints

65 routes. Grant the permissions under Users → Roles & Permissions; nothing is granted by default and Super Admin bypasses the check.

Risk desk

Net exposure per symbol, accounts at risk, balance vs equity, quoting health, hedge monitor

Accounts

Paginated browser over every trading account with owner and tier context
Value/label pairs for the account form's tier picker
Kill-switch, leverage, tier, swap-free flag, withdraw limits. Balance is NOT editable
Books a reason-coded ADJUSTMENT deal through the ledger

Positions, orders and the ledger

All positions across every account
Force-closes a position at the current market. Stamped ADMIN
All orders across every account
The immutable money ledger — every balance-affecting event

Withdrawals

The withdrawal queue — FX_TRADING_WITHDRAW wallet transactions
Approve (credits the wallet) or reject (refunds the fx account)

Market-data providers

Lists market-data vendors, self-healing the four seeded rows
Updates a provider's configuration
Activates or deactivates a provider. Only one may be active
Verifies the configured API credentials
Tests an unsaved credential without persisting it

Instruments

Paginated catalog, sorted A→Z by base currency
Symbol-group options for the instrument form
Edits status, group, swaps, provider symbols, contract spec, display flags
Moves one instrument through its lifecycle
Seeds the curated catalog and links provider symbols

There is no create and no delete for instruments — see Adding a symbol.

Symbol groups

Lists dealing-desk symbol groups
Session-calendar options for the symbol-group form
Creates a symbol group
Updates a symbol group
Deletes a symbol group

Session calendars

Lists trading-hours calendars
Creates a trading-hours calendar
Updates a trading-hours calendar
Deletes a calendar. Refused while a symbol group references it
Economic-calendar and news feed status — see the permission note below

Economic calendar and market news

Lists economic calendar rows, PROVIDER and MANUAL
Creates a MANUAL calendar row
Updates a calendar row
Deletes a calendar row
Lists news rows, PROVIDER and MANUAL
Creates a MANUAL news row
Updates a news row
Deletes a news row

The durability rules on those eight routes are in The economic calendar and news feeds.

A-book execution

Every route here is view / create / edit / delete on forex_trading.execution. Treat edit.forex_trading.execution as Super-Admin adjacent: it enables a hedge venue, flattens an orphan and books a late fill, all of which move real money at a real broker.

External-execution status and live counts
Lists hedge venues, self-healing the two seeded rows
Updates a hedge venue's configuration
Enables or disables a hedge venue
Verifies the venue connection
Imports and links venue symbols
In-flight external execution state
The operator alert inbox
Acknowledges one alert
Flattens an ORPHAN broker position at market. Confirm-gated
Books a late-discovered venue execution to the client. Confirm-gated
A-book revenue per provider per month
The A-book operator runbook
Lists placement-time routing rules
Creates a routing rule
Updates a routing rule
Enables or disables a routing rule
Deletes a routing rule
Dry-runs the routing resolver against a hypothetical order
Instrument options for the routing-rule form
Symbol-group options for the routing-rule form
Account-group options for the routing-rule form
Execution-provider options for the routing-rule form
Account options for the routing test-order widget

Settings

Reads the extension settings with defaults applied
Writes changed keys and clears the settings cache

fxTradingExternalRoutingEnabled is rejected with a 403 by the PUT no matter what permissions the caller holds — it is writable only through the core system settings page, where the protected-keys check enforces Super Admin.

Three screens — Instruments, Economic Calendar and Market News — are gated in the frontend permission manifest on view.forex_trading.instrument, while the routes behind two of them check view.forex_trading.economic_event and view.forex_trading.news. A role granted only the finer data key can be refused the page; a role granted only the instrument key can open the page and get an empty table. Grant both.

Customer endpoints

18 REST routes under /api/forex-trading, plus three WebSocket routes. None carries a permission. Five are public — the market data ones — and the rest require a session and verify that the caller owns the account in the request.

Accounts and money

The caller's accounts with live equity and margin. Auto-provisions one DEMO account on first call
Creates a DEMO or LIVE account. Currency is fixed for its lifetime
Instant transfer from the platform wallet into the account. LIVE only
Withdrawal request under the free-margin gate. Debits now, credits the wallet only on approval
Stamps the leveraged-trading risk acknowledgment. Idempotent, per account, server-side
The account statement — the caller's own deal ledger. Requires accountId

Deposit and withdraw both take amount in the account currency and a walletType. The withdrawable amount is max(0, min(balance, equity − usedMargin)), computed inside the same row lock as the debit, and is refused outright below the tier's margin-call level or while the account's metrics are stale.

Orders

The caller's orders for one account, newest first. Requires accountId
Places a MARKET, LIMIT, STOP or STOP_LIMIT order
Modifies a pending order's prices, SL/TP/trailing and expiry while OPEN or TRIGGERED
Cancels an OPEN or TRIGGERED pending order. No funds move

A MARKET order fills instantly against the desk at the marked-up price. A pending order rests until the risk engine triggers it on a later tick; new levels on a modify must not be instantly triggerable at the current mark. Pending orders never reserve margin, which is why cancelling one moves nothing.

Positions

OPEN positions with live floating P&L, or closed history. Requires accountId
Closes fully, or partially when amount is passed
Updates stop loss, take profit and the server-side trailing distance. Pass null to clear

Market data — public

Tradable instruments (ACTIVE, CLOSE_ONLY, HALTED) with contract specs, desk parameters and session state
Snapshot quote for one instrument — bid/ask/last, day OHLC, quoting and session state
Historical OHLCV candles as time/open/high/low/close/volume tuples, ascending
Economic calendar in a time window. Hidden rows are never returned
Market news, newest first. Hidden rows are never returned

chart takes symbol and interval (required), plus from/to or duration. It is cache-first with session-aware back-fill — a closed-session range is left empty rather than fabricated.

calendar takes from, to, currency (comma-separated) and impact (LOW,MEDIUM,HIGH); rows with no currency are always included. news takes limit, category and symbol, where symbol matches either leg or the closed-up pair (EURUSD) against headline and summary text, or an operator's relatedSymbols tag — and never falls back to the unfiltered feed, so an empty array is a legitimate answer.

WebSocket

Three routes, all behind the same reverse-proxy /api path as the REST API. If your proxy does not upgrade WebSocket connections the terminal renders once and then stops moving.

Route Auth Subscribe payload Carries
/api/forex-trading/ticker none {type: "tickers"} Pins every visible ACTIVE instrument on the tick engine, returns a full snapshot, then per-symbol deltas
/api/forex-trading/market none the selected symbol Ticker, OHLCV and quoting/session status frames for one instrument, with an immediate snapshot on subscribe
/api/forex-trading/order required {type: "orders"|"positions", accountId, userId} The caller's own order and position updates, plus engine sweep updates

The ticker snapshot fills gaps with a TTL-cached REST quote for instruments the tick engine has never seen — every US stock for about 17 hours a day and the whole weekend — so a closed market shows a price instead of a dash. Those snapshot values are display-only and are never fed into the tick engine.

On /api/forex-trading/order the server enforces that the cookie-authenticated user owns both the userId in the payload and the account. Client-supplied ids are never trusted.

Enums an integrator must branch on

Column Values
fx_account.type DEMO, LIVE
fx_account.marginMode HEDGING, NETTING
fx_order.side, fx_position.side BUY, SELL
fx_order.type MARKET, LIMIT, STOP, STOP_LIMIT
fx_order.timeInForce GTC, GTD, DAY
fx_order.status OPEN, TRIGGERED, ROUTING, FILLED, CANCELLED, REJECTED, EXPIRED
fx_order.routing, fx_position.routing INTERNAL, EXTERNAL
fx_position.status OPEN, CLOSED, LIQUIDATED
fx_position.closeReason MANUAL, SL, TP, TRAILING, STOP_OUT, GAP, ADMIN, DELISTED, CORPORATE_ACTION
fx_deal.kind OPEN, CLOSE, PARTIAL_CLOSE, SWAP, COMMISSION, DIVIDEND, ADJUSTMENT, NBP_CORRECTION, DEPOSIT, WITHDRAW, WITHDRAW_REVERSAL
fx_instrument.status INACTIVE, ACTIVE, CLOSE_ONLY, HALTED, DELISTED
fx_instrument.assetClass FOREX, STOCK, COMMODITY, INDEX, CRYPTO
fx_economic_event.source, fx_market_news.source PROVIDER, MANUAL
fx_economic_event.impact LOW, MEDIUM, HIGH

ROUTING only appears on A-book installs: the order intent is journaled and the broker confirmation has not arrived. LIQUIDATED is a stop-out; the position also carries closeReason: STOP_OUT.

closeReason is the one enum where the column accepts more than the backend writes. Only MANUAL, SL, TP, TRAILING, STOP_OUT and ADMIN are ever produced; GAP, DELISTED and CORPORATE_ACTION are validator members with no writer, so a branch on them is dead code. See Running the desk.

Tables and who writes them

Fourteen tables, all MySQL. ScyllaDB is not used by this extension.

Table Written by
fx_provider Provider PUT and status PUT; self-healed on the provider list GET
fx_execution_provider Execution provider PUT, status PUT, symbols POST, verify POST; self-healed on the execution provider list GET; the hedge monitor cron writes venue balance, NAV and margin onto the row, and the reconciler advances its sync cursor
fx_instrument Instrument PUT, status PUT, import POST. Nothing else — no create, no delete
fx_symbol_group Group POST/PUT/DELETE; created by import
fx_session_calendar Calendar POST/PUT/DELETE; created by import
fx_account_group No route writes this table. SQL only — see Account tiers
fx_account Customer account POST, deposit, withdraw, risk-ack; admin account PUT and adjust; the risk engine and the reconciler refresh equity and used margin
fx_order Customer order POST/PUT/DELETE; the risk engine on trigger, fill, reject and expiry; the reconciler's expiry sweep
fx_position Customer position close and PUT; admin force-close; the risk engine on SL/TP/trailing/stop-out; the swap cron's accrual
fx_deal Only the account service, through applyBalanceChange. Append-only, and every write carries an idempotency key
fx_routing_rule Routing rule POST/PUT/status/DELETE
fx_execution_alert The alert dispatcher (any severity, from any engine path) and the ack POST
fx_economic_event Economic-event POST/PUT/DELETE and the 15-minute sync (PROVIDER rows only)
fx_market_news News POST/PUT/DELETE and the 15-minute sync (PROVIDER rows only)

Withdrawals are not an fx table. A request writes a PENDING row on the core transaction table with type = 'FX_TRADING_WITHDRAW'; the admin queue is a filtered view of it. Deposits are recorded as FX_TRADING_DEPOSIT.

Processing that request does not write a second row of some refund type. Approving flips the PENDING row to COMPLETED and credits the wallet through the wallet service, which books its own COMPLETED FX_TRADING_WITHDRAW row for the credit. Rejecting flips the row to REJECTED and writes nothing new on transaction at all — the money goes back as a WITHDRAW_REVERSAL deal on fx_deal, and the request's consumed daily and monthly withdrawal allowance is credited back on fx_account. FX_TRADING_WITHDRAW_REFUND exists only as a wallet-service operation type, is not a value the transaction.type ENUM accepts, and would persist as FX_TRADING_DEPOSIT if anything called it — nothing does. Do not match on it.

The balance is a denormalisation of fx_deal. The reconciler verifies that Σ pnl == balance every five minutes and raises a critical ledger drift alert by email to every Super Admin when it does not. A direct UPDATE fx_account SET balance sets that alarm off within five minutes and leaves you unable to distinguish a real accounting fault from your own edit.

Compliance gates, and where they are deliberately absent

Three independent gates run on the customer surface. Knowing which routes are not gated is the point of this section.

Gate Applied at
Geo block (fxTradingGeoBlockList) GET /account (a blocked user is not auto-provisioned and gets an empty list), POST /account, POST /account/{id}/deposit, POST /order, PUT /order/{id}
KYC feature (trade_forex) POST /order only, and only for LIVE accounts. DEMO is exempt — it is the funnel
fxTradingEnabled POST /account (LIVE only), POST /account/{id}/deposit, POST /account/{id}/withdraw, POST /order (LIVE accounts only)

POST /position/{id}/close, PUT /position/{id}, DELETE /order/{id} and POST /account/{id}/withdraw do not run the geo gate, deliberately. Adding a country to the block list must not trap an existing customer's funds — they can still flatten their book and take their money out. If you need to stop that too, block at your edge or CDN; this gate is application-level.

Two more properties of the geo gate worth knowing before you rely on it. It resolves the country from approved KYC data, the self-declared profile country and the CDN cf-ipcountry header, and a match on any signal blocks. And a user with no resolvable country is allowed — the platform has no mandatory country capture before KYC, so blocking unknowns would lock out every pre-KYC demo user.

The KYC gate and the geo gate both fail open when their own lookups fail: settings unavailable, KYC tables absent, user row unreadable. Geo data being unavailable does not take trading down.

Next