keys.json fields¶
The reader still accepts legacy version: 1 files with flat limit fields and
the nested limits shape shown by earlier documentation. Any create, bind,
update, or revoke operation performed by futucli atomically upgrades the file
to version: 2 and writes only nested limits. Unknown fields, or the same
limit present both at the key root and under limits, are rejected instead of
being silently interpreted as unlimited.
KeyRecord fields¶
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | ✓ | Human-readable name, unique within keys.json; shown as key_id in audit |
hash |
string | ✓ | SHA-256 hex of the plaintext key |
scopes |
["qot:read", "acc:read", ...] |
✓ | What this key can do |
limits |
object | — (empty = unlimited) | Limits, see table below |
allowed_machines |
[fp, ...] / null |
— | null = no binding; [] = frozen; [fp1,fp2] = whitelist |
created_at |
RFC3339 timestamp | ✓ | Creation time |
expires_at |
RFC3339 / null | — | null = never expires |
note |
string | — | Free-form note |
limits fields¶
| Field | Type | Description |
|---|---|---|
allowed_markets |
["HK","US","CN","HKCC",...] / null |
Market whitelist |
allowed_symbols |
["HK.00700","US.AAPL"] / null |
Symbol whitelist |
allowed_trd_sides |
["BUY","SELL","SELL_SHORT","BUY_BACK"] / null |
Direction whitelist |
max_order_value |
float / null | Per-order cap (qty × price, in local currency) |
max_daily_value |
float / null | Daily rolling cap (resets at UTC midnight) |
max_orders_per_minute |
u32 / null | Rate limit (60-second sliding window) |
hours_window |
"HH:MM-HH:MM" / null | Time window (local timezone; cross-midnight written as 22:00-04:00) |
allowed_acc_ids |
[u64, ...] / null |
Internal account-ID whitelist |
allowed_card_nums |
[string, ...] / null |
Account card-number or 4-digit suffix whitelist; resolved fail-closed at runtime |
null / omitted = unlimited.
Scope strings¶
| Scope | What it unlocks |
|---|---|
qot:read |
Quote read + subscribe |
acc:read |
Account read-only |
trade:simulate |
Simulated trading writes |
trade:real |
Real trading writes |
trade:unlock |
Used by the MCP futu_unlock_trade tool (v1.4+) |
Examples¶
Minimal read-only¶
{
"id": "research",
"hash": "a1b2...",
"scopes": ["qot:read"],
"created_at": "2026-04-15T10:00:00Z"
}
Tightly-limited simulation bot¶
{
"id": "sim-bot",
"hash": "c4d5...",
"scopes": ["qot:read", "acc:read", "trade:simulate"],
"limits": {
"allowed_markets": ["HK"],
"allowed_symbols": ["HK.00700", "HK.09988"],
"max_order_value": 100000,
"max_daily_value": 500000,
"max_orders_per_minute": 3,
"hours_window": "09:30-16:00",
"allowed_trd_sides": ["SELL"]
},
"allowed_machines": ["fp_bot_host_abc123"],
"created_at": "2026-04-15T10:00:00Z",
"expires_at": "2026-05-15T10:00:00Z",
"note": "simulated take-profit sell bot"
}
Operations¶
All mutations go through futucli — do not edit the file by hand
(atomic writes avoid concurrent corruption; hand-writing SHA-256 hashes
is error-prone):
futucli gen-key ... # add
futucli list-keys # list
futucli revoke-key <id> # remove
futucli bind-key <id> ... # edit allowed_machines
After editing, kill -HUP the gateway / MCP to hot-reload.