The module manifest
How GET /api/user/modules decides, for one signed-in customer, which modules the app shows — the checks in the order the server makes them, the reason codes, what the app shows for each, when the app asks again, and how to find out why a screen is missing.
The app does not decide for itself which of its screens a customer may use. It asks your server once, right after sign-in, and draws the Futures tab, the Trading Tools tiles, the Help Centre row and the spot order ticket from the answer. That answer is the module manifest: one entry per module, each with a yes or no and, for a no, the reason.
Everything on this page happens on your server. The app has no setting that shows a module the server refuses, and none that hides one it allows — apart from the build flag for Merchant, AI Investment and Affiliate, which decides whether their screens exist at all (see Merchant, AI Investment and Affiliate).
The request
It needs a signed-in session and answers 401 without one. It answers for the customer who is asking, so two customers on the same install can get different answers. Nothing in it depends on whether the caller is the app or a browser.
A shortened answer:
{
"user": {
"roleName": "User",
"isSuperAdmin": false,
"kycLevel": 1,
"kycEnforced": true,
"residence": "GB",
"attestationEnforced": false
},
"modules": [
{
"id": "futures",
"title": "Futures",
"extension": "futures",
"installed": true,
"enabled": true,
"licensed": true,
"kycRequired": true,
"kycSatisfied": false,
"attestationRequired": true,
"attested": true,
"visible": false,
"reason": "KYC_REQUIRED"
}
]
}| Field | Meaning |
|---|---|
user.kycLevel |
The customer's KYC level (0 when none) |
user.kycEnforced |
True only when KYC Verification and Enforce KYC Feature Access are both on |
user.residence |
The two-letter country the server decided this customer lives in, or null when it could not tell |
user.attestationEnforced |
Whether Enforce Licence Attestations (Mobile App) is on |
id |
The module id the app uses. It is not always the addon's name: faq is the knowledge_base addon, copy-trading is copy_trading |
title |
The name the app puts in its messages — "Spot trading", "Token offerings", "Store", "Bot console" |
extension |
The addon the module needs, or null for a core module |
installed |
The addon is installed. False as well when an addon it depends on is missing |
enabled |
The addon is switched on and no switch of yours turns the module off |
licensed |
The addon's licence is valid |
kycRequired, kycSatisfied |
Whether the module names a KYC feature, and whether this customer's level has it. kycSatisfied is always true while KYC feature enforcement is off |
attestationRequired, attested |
Whether the module is one of the nine licence-gated ones, and whether this customer passes. attested is always true while attestation enforcement is off |
visible |
The one answer the app branches on. True only when reason is OK |
reason |
Why, in one word — see below |
mode |
On the staking entry only: SYNTHETIC (fixed-rate staking) or REAL (on-chain staking) |
The list always carries the same 21 modules, including four the app has no
screens for (nft, copy-trading, ai-support, ecosystem), because the
server answers for the website's addons as well. Binary options, the core
Investment plans and forex are not in it and cannot be added, so no answer can
ever tell the app to show them.
How each module is decided
The server runs these checks in order and stops at the first one that fails.
The reason names the check that failed.
| Order | Reason | What failed |
|---|---|---|
| 1 | NOT_INSTALLED |
The module's addon is not installed, or an addon it depends on is missing |
| 2 | DISABLED |
The addon is switched off, a switch of yours turns the module off, or an addon it depends on is installed but switched off |
| 3 | UNLICENSED |
The addon's licence is not valid — or the licence check itself failed |
| 4 | NOT_ATTESTED |
Attestation enforcement is on, the customer's country is known, and there is no live licence row for this module in that country |
| 4 | RESIDENCE_UNKNOWN |
Attestation enforcement is on and the server does not know the customer's country |
| 5 | KYC_REQUIRED |
KYC feature enforcement is on and the customer's level does not include the module's feature |
| — | OK |
Everything passed |
Core modules (wallet, markets, trade, account, support, blog,
news) have no addon, so they always count as installed and licensed. Only
checks 2, 4 and 5 can stop them, and only two core modules have anything for
those checks to look at: trade (its switch, attestation and KYC) and
wallet (KYC).
Attestation is checked before KYC on purpose. If you are not licensed to serve a customer's country, telling them to complete identity verification would cost them their documents and still show them nothing.
1. Installed
An addon is installed when it has a row in your extension list, whether or not it is switched on. Two modules also need a second addon:
- Futures needs the Exchange Engine addon (
ecosystem). Without it, Futures reportsNOT_INSTALLEDhowever healthy the Futures addon is. - Staking in on-chain mode needs Exchange Engine and Web3 Wallet &
On-Chain Trading (
dex). Fixed-rate staking — the default — needs only the Staking addon.
2. Switched on
The addon's own on/off state in System → Extension Manager
(/admin/system/extension) is the first half. The second half is a switch of
yours, for four modules:
| Module | The switch | Turns the module off when |
|---|---|---|
trade |
Enable Spot Trading (spotWallets), Finance → Trading Infrastructure → Trading Settings (/admin/trading/settings) |
It is off. A setting that was never saved counts as on |
ico |
Maintenance Mode (icoMaintenanceMode) in the ICO settings (/admin/ico/settings) |
It is on |
ai-investment |
Allow AI Investment & MLM in the Mobile App (mobileAllowRestrictedProducts) |
It is off — which is the default |
mlm |
The same switch | It is off — which is the default |
An addon that another module depends on counts here too: Futures with Exchange
Engine installed but switched off reports DISABLED, and so does on-chain
staking when Exchange Engine or Web3 Wallet & On-Chain Trading is switched off
or unlicensed.
When staking runs on-chain and one of its two extra addons is missing,
switched off or unlicensed, the server does not hide Staking for that reason
from a customer who holds an on-chain position that is pending delegation,
active, being unstaked, unbonding or ready to withdraw. Hiding it would leave
them no way to get their coins out. The Staking addon's own checks, and
attestation, still apply to them. Everyone else gets NOT_INSTALLED or
DISABLED as usual.
3. Licensed
The addon's licence must be valid. When the licence check throws — an
unreachable licence file, say — that one module reports UNLICENSED rather
than the whole request failing, and the backend log records
Licence check failed for "<addon>". Hiding a module whose licence cannot be
confirmed is the safe direction: a tab that then refuses every request is worse
than no tab. See Licences and the extension manager.
4. Licence attestation
Only when Enforce Licence Attestations (Mobile App) is on — Admin →
System → Platform Settings, tab Features, group Verification, off by
default — and only for nine modules: trade, p2p, staking, ico,
futures, ecosystem, copy-trading, gateway and ai-investment.
The server works out where the customer lives from their approved KYC applications, oldest first, and falls back to the country on their profile. It never uses the IP address or the phone's settings. Then:
- a known country with a live (unexpired) licence row for the module passes;
- a known country without one gets
NOT_ATTESTED; - no known country gets
RESIDENCE_UNKNOWN.
With enforcement on and no rows recorded, all nine modules are closed to every
app user: their tiles, the Futures tab and the spot ticket stay on screen,
locked, and say why (see the warning under
Finding out why a screen is missing).
The rows live under System → Compliance → Licence Attestations
(/admin/system/attestation) — see Licence attestations.
The website is never gated by any of this.
5. KYC feature
Only when KYC Verification (kycStatus) and Enforce KYC Feature Access
(kycFeatureEnforcement) are both on — same tab and group as above. A module
that names a KYC feature then needs the customer's level to include it:
| Module | KYC feature |
|---|---|
wallet |
view_wallets |
trade |
trade |
ico |
purchase_ico |
ecommerce |
view_ecommerce |
futures |
futures_trading |
gateway |
use_gateway |
ai-investment |
invest_ai |
The features are set per level in the KYC level builder — see KYC. That page also lists the individual actions your server refuses for a missing feature, which go further than this list: a module can be shown while an action inside it is refused.
Every module at a glance
| Module | Title | Addon | Also needs | Your switch | KYC feature | Attested |
|---|---|---|---|---|---|---|
wallet |
Wallet | core | — | — | view_wallets |
No |
markets |
Markets | core | — | — | — | No |
trade |
Spot trading | core | — | Enable Spot Trading | trade |
Yes |
account |
Account | core | — | — | — | No |
support |
Support | core | — | — | — | No |
blog |
Blog | core | — | — | — | No |
news |
Market news | core | — | — | — | No |
p2p |
P2P trading | p2p |
— | — | — | Yes |
staking |
Staking | staking |
On-chain mode: ecosystem and dex |
— | — | Yes |
ico |
Token offerings | ico |
— | ICO Maintenance Mode | purchase_ico |
Yes |
ecommerce |
Store | ecommerce |
— | — | view_ecommerce |
No |
nft |
NFT marketplace | nft |
— | — | — | No |
futures |
Futures | futures |
ecosystem |
— | futures_trading |
Yes |
ecosystem |
Ecosystem wallets | ecosystem |
— | — | — | Yes |
copy-trading |
Copy trading | copy_trading |
— | — | — | Yes |
faq |
Help centre | knowledge_base |
— | — | — | No |
ai-support |
Support assistant | ai_support |
— | — | — | No |
hummingbot |
Bot console | hummingbot |
— | — | — | No |
gateway |
Merchant | gateway |
— | — | use_gateway |
Yes |
ai-investment |
AI Investment | ai_investment |
— | Allow AI Investment & MLM in the Mobile App | invest_ai |
Yes |
mlm |
Affiliate | mlm |
— | Allow AI Investment & MLM in the Mobile App | — | No |
Which screen each module opens is on What is in the app.
What the app shows for each reason
The app splits the reasons in two. KYC_REQUIRED, NOT_ATTESTED and
RESIDENCE_UNKNOWN mean the product exists on your platform but this customer
may not start anything in it — they may still hold a futures position or a
P2P escrow there, so the way in stays on screen. NOT_INSTALLED, DISABLED
and UNLICENSED mean the product is not on your platform at all, so nobody can
hold anything in it and the way in disappears.
| Reason | Trading Tools tile | Futures tab | Spot order ticket |
|---|---|---|---|
OK |
Shown, opens | Shown, order form works | Shown |
KYC_REQUIRED |
Dimmed; a tap shows the message for five seconds | Shown, order form locked | Locked, with a Verify identity button |
RESIDENCE_UNKNOWN |
Dimmed, as above | Shown, order form locked | Locked, with an Add your country button |
NOT_ATTESTED |
Dimmed, as above | Shown, order form locked | Locked, with "Registered country: |
NOT_INSTALLED, DISABLED, UNLICENSED |
Absent | Absent | Locked |
| Any reason this build does not know | Absent | Absent | Locked |
| Module missing from the answer | Absent | Absent | Locked |
The locked Futures order form adds a second line under the message: "Positions you already hold can still be closed." The spot ticket on the full-screen chart shows the same messages without the buttons.
The Help Centre row in Profile appears only when faq is OK. When it is
not, the help-centre shortcut in the support chat answers "This platform has
not published a help centre."
The messages
The app writes these itself, with the module's title from the answer in place
of title:
| Reason | The customer sees |
|---|---|
KYC_REQUIRED |
"Complete identity verification to use title." |
RESIDENCE_UNKNOWN |
"Tell us your country of residence to use title. Add it to your profile, or complete identity verification." |
NOT_ATTESTED |
"title is not offered in your registered country, because this platform is not licensed to provide it there. The rest of your account is unaffected." |
DISABLED, NOT_INSTALLED, UNLICENSED |
"title is not available on this platform." |
| Anything else | "title is not available right now." |
So a customer without the trade feature reads "Complete identity verification
to use Spot trading." When trade is missing from the answer altogether, the
spot ticket says "Trading is not available right now."
Only the first two tell the customer to do something, because only those two
are theirs to fix. NOT_ATTESTED deliberately does not suggest the website or
another device.
When the answer cannot be read
The app fails closed: until it has an answer, every module counts as hidden.
- While the first request is in flight, the Trading Tools row and the Futures tab are simply not there yet, and the spot ticket shows a spinner.
- If no answer has ever arrived, Trading Tools shows "We couldn't check which tools are available on your account." with Retry, and the spot ticket shows "Couldn't check what's available on your account." with Retry.
- If an earlier answer arrived and a later request fails, the app keeps the earlier answer. A dropped connection does not empty somebody's dashboard.
- A 401 or other refusal from your server counts as no answer, not as an empty list, so an expired session never looks like an unlicensed install.
Hiding is the deliberate direction. Showing a module by default would put something you may not be licensed for in front of a store reviewer, under your developer account.
When the app asks again
The app reads the manifest:
- at every sign-in — password, Google, and after the two-factor code;
- every time the app starts with a saved sign-in;
- when the app comes back to the foreground, only if no answer has loaded yet;
- on pull-to-refresh on Home, only if no answer has loaded yet;
- from the Retry buttons above;
- when the customer comes back from Add your country or Verify identity on the locked spot ticket.
It throws the answer away at sign-out, so the next person to sign in on the phone never sees the previous account's modules.
It does not ask on a timer, and your server does not tell it when something changes. A change you make — an addon switched on, a licence row recorded, a customer's KYC approved — reaches a customer who is already signed in the next time they sign in or fully close and reopen the app.
Taking something away works the other way round. A licence row withdrawn or a KYC feature removed from a level is enforced by your server on the next request, so the order or offer is refused even while an app that read an older answer still shows the screen.
Finding out why a screen is missing
-
Check the build. Merchant, AI Investment and Affiliate exist only in a build made with
--dart-define=STORE_RESTRICTED_MODULES=true. NFT, copy trading and the support assistant have no screens in any build. -
Read the answer the customer gets. Sign in to your website as that customer — or as a test account with the same KYC level and country — and, in the same browser, open
<your baseUrl>/api/user/modules. The browser sends the website session, and the server answers exactly as it answers the app. Find the module's entry and readreason, thenuser.residenceanduser.attestationEnforced. -
Act on the reason.
Reason Where to look NOT_INSTALLEDThe addon — and for Futures, Exchange Engine — in System → Extension Manager DISABLEDThe addon's on/off state, then the switch for that module in the table above UNLICENSEDThe addon's licence; the backend log for Licence check failedNOT_ATTESTEDA live row for this module and user.residenceunder Licence Attestations — or whether enforcement should be on at allRESIDENCE_UNKNOWNThe customer adds a country to their profile, or you approve a KYC application that carries one KYC_REQUIREDThe customer's KYC level, and whether that level lists the module's feature OKThe server allows it. Have the customer sign out and in again, so the app reads the answer afresh -
Have the customer sign in again after you change anything. The app keeps the answer it read at sign-in.
If the spot ticket, the futures order form and the Futures, P2P, Staking and
Launchpad tiles locked for everybody at once — tiles dimmed, order forms showing
a message instead — while the website still works, Enforce Licence
Attestations (Mobile App) was switched on with no licence rows recorded. The
entries read NOT_ATTESTED or RESIDENCE_UNKNOWN. Record your countries or
switch it off.
What it deliberately does not do
- It does not read your website's navigation settings. The app's public
settings request (
/api/settings) lists which addons are switched on, but carries nothing about licences, KYC or countries, so the app does not use it to decide what to show. - It does not hide the core tabs. Nothing in the app reads the
wallet,markets,accountorsupportentries: the Wallet and Market tabs, Profile and support tickets are always there. A KYC level withoutview_walletshides no tab. What your server refuses inside the Wallet tab — a deposit, a withdrawal, a transfer — follows the other features on the customer's level, action by action. - It does not refresh itself. See When the app asks again.
- It does not show what the server does not list. A module this build knows but the server's answer does not contain is hidden — which is also how an app build newer than your server behaves.