Skip to content

keys.json fields

{
  "version": 2,
  "keys": [
    { /* KeyRecord */ }
  ]
}

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.