API reference
Every Forex Investment endpoint — customer and admin — with the permission each admin route gates on, the KYC feature each money route asserts, the tables behind them and the enums they accept.
Two API surfaces. Everything under /api/forex is scoped to the calling
customer and carries no permission — it is gated by KYC features instead.
Everything under /api/admin/forex carries an explicit permission.
Conventions
The platform pins the HTTP status at 200 and puts the real outcome in the body. Read the body, always. A withdrawal approval that was refused because it would breach the customer's cap comes back with a 200 and a message saying so; a client branching on the status code renders it as a green toast over money that never moved.
Two more things that catch integrators:
- Amounts are
DOUBLE, not decimal. Do not use them for exact accounting arithmetic without rounding deliberately. - An investment carries no currency. The unit of account is on the plan it
was bought against. Any sum over
forex_investment.amountwithout a group by the plan's currency is a number that does not exist. - Profit is signed at settlement, but only since v6.1.2. Rows settled
before that hold a loss as a positive number beside a
LOSSresult. ApplyCASE WHEN result = 'LOSS' THEN -ABS(profit) ELSE profit ENDto anything you sum.
Customer endpoints
Public
Both are unauthenticated. Money totals on each are grouped by the plan's currency with the largest pool headlined and named; the average return is a mean of per-row ratios, so it stays valid on a mixed-currency install.
Plans and durations
The plan list accepts activeTab (all or trending), search,
minProfit, maxInvestment and sortBy (popularity, profit or
minInvestment). It excludes defaultProfit, defaultResult and status
from every response, so the configured outcome is never visible to the buyer,
and it computes an invested total per plan which is what popularity sorts
by.
GET /api/forex/duration returns every duration on the platform. Only the ones
returned by GET /api/forex/plan/{id}/duration can actually be used against
that plan — anything else is refused at investment time.
Accounts
The account list is a read that writes: it provisions any missing account type
on first access, claiming an unowned row of that type if one exists and
creating an inactive empty one otherwise. LIVE provisioning is skipped —
silently, and without failing the request — for a customer who does not hold
create_forex_account.
Deposit body: type (wallet type), currency, amount, requestNonce
(8–128 characters, required), and optionally chain. Withdraw body is the
same without the nonce.
Both are rate-limited to five per minute.
Investments
Creation is rate-limited to ten per hour. acceptTerms must be true; the
acceptance time and terms version are stored on the investment.
Everything else
KYC gates on customer routes
| Feature | Guards |
|---|---|
deposit_forex |
POST /api/forex/account/{id}/deposit |
withdraw_forex |
POST /api/forex/account/{id}/withdraw |
invest_forex |
POST /api/forex/investment |
create_forex_account |
Provisioning a LIVE account on GET /api/forex/account |
Admin endpoints
Dashboard
Accepts timeframe of 1m, 3m or 1y, which scopes the volume chart only.
Accounts
Plans
Both write routes reject negative figures, inverted amount limits, inverted
profit ranges, and a profit percentage outside the plan's own advertised band.
Add ?force=true to a delete for a permanent removal — which is refused while
any investment references the plan, settled ones included.
Durations
Investments
Signals
Deposits
Withdrawals
Tables
| Table | Holds | Notes |
|---|---|---|
forex_plan |
Plans | Soft-deleted (paranoid) — filter deletedAt |
forex_duration |
Terms | Soft-deleted |
forex_plan_duration |
Plan-to-duration links | The join that scopes a duration to a plan |
forex_investment |
Investments | Soft-deleted. Cascades from forex_plan and forex_duration at the database level |
forex_account |
DEMO and LIVE accounts | userId is nullable — an unowned row is a claimable pre-provisioned account |
forex_signal |
Signals | Soft-deleted |
forex_account_signal |
Account-to-signal links | |
transaction |
Deposits and withdrawals | Core table. Filter on type |
Transaction types written by this addon
| Type | Written when | Status |
|---|---|---|
FOREX_DEPOSIT |
A customer funds a forex account | COMPLETED immediately; REJECTED after a reversal |
FOREX_WITHDRAW |
A customer requests a withdrawal | PENDING, then COMPLETED or REJECTED by an admin |
FOREX_INVESTMENT |
An investment is opened | COMPLETED — an audit row, not a money movement of its own |
Enums
| Column | Values |
|---|---|
forex_account.type |
DEMO, LIVE |
forex_duration.timeframe |
HOUR, DAY, WEEK, MONTH |
forex_investment.status |
ACTIVE, COMPLETED, CANCELLED, REJECTED |
forex_investment.result |
WIN, LOSS, DRAW |
forex_plan.defaultResult |
WIN, LOSS, DRAW |
forex_plan.walletType |
FIAT or SPOT — anything else cannot be funded |
Key columns
Worth knowing if you query directly:
| Column | Means |
|---|---|
forex_plan.profitPercentage |
What settlement actually pays |
forex_plan.defaultProfit |
A fallback percentage, used only when profitPercentage is unusable |
forex_plan.minProfit / maxProfit |
The advertised band. Validation only — not paid |
forex_plan.status |
false means invisible to customers and closed to new money |
forex_account.currency / walletType |
The binding, set by the account's first movement. null means unbound |
forex_account.dailyWithdrawn / monthlyWithdrawn |
Counters, advanced on approval |
forex_account.lastWithdrawReset / lastMonthlyWithdrawReset |
Independent window anchors |
forex_investment.roiPercentage |
Written at settlement. Preferred over the plan when present |
forex_investment.profit |
The signed effect on the principal |
forex_investment.metadata |
On a cancelled investment, carries principalRefunded |
forex_investment.termsAcceptedAt / termsVersion |
Stored at creation |
The cron
| Job | Category | Every |
|---|---|---|
processForexInvestments |
forex |
1 hour |
It can be triggered manually from /admin/system/cron. Doing so while the
scheduled tick is running is safe — every settlement is a conditional update on
a locked row, so a duplicate run pays nothing.