Authentication Methods
The extended API supports two authentication methods:
- Signature authentication: the request parameters are signed with the
SecretKey, which itself never travels over the network. Programs should use this method. - Direct verification: the
SecretKeyis put into the request URL itself; meant for webhooks such as TradingView that accept only a single URL.
Signature Authentication
Request format
Send a POST request to https://www.fmz.com/api/v1 with the parameters as a form (application/x-www-form-urlencoded). The server also accepts the same parameters in the URL query string of a GET request, but then they end up in access logs along the way, so POST is recommended.
| Parameter | Description |
|---|---|
| version | Version, always 1.0. |
| access_key | The AccessKey of the API KEY. |
| method | Method name, e.g. GetNodeList. |
| args | Method parameters as a JSON string: an array in parameter order (e.g. [], [123, "ok"]), or an object keyed by parameter name (e.g. {"robotId": 123}), see Extended API Interface Details. Treated as [] when omitted. |
| nonce | Timestamp in milliseconds. It must be within 1 hour of server time and greater than the nonce of this API KEY's previous request. |
| sign | Signature, computed as described below. |
The request does not contain the SecretKey.
Signature
Concatenate the string below, where args is the exact JSON string being submitted:
plaintext
version + "|" + method + "|" + args + "|" + nonce + "|" + secretKey
Compute the MD5 of the result and use its 32-character lowercase hexadecimal form as sign.
Python example
python
import hashlib
import json
import time
import urllib.parse
import urllib.request
ACCESS_KEY = '' # AccessKey of the API KEY
SECRET_KEY = '' # SecretKey of the API KEY
def api(method, *args, **kwargs):
d = {
'version': '1.0',
'access_key': ACCESS_KEY,
'method': method,
# Positional arguments are sent as an array, keyword arguments as an object (by name)
'args': json.dumps(kwargs if kwargs else list(args)),
'nonce': int(time.time() * 1000),
}
s = '%s|%s|%s|%d|%s' % (d['version'], d['method'], d['args'], d['nonce'], SECRET_KEY)
d['sign'] = hashlib.md5(s.encode('utf-8')).hexdigest()
body = urllib.parse.urlencode(d).encode('utf-8')
with urllib.request.urlopen('https://www.fmz.com/api/v1', body, timeout=10) as resp:
return json.loads(resp.read().decode('utf-8'))
print(api('GetNodeList')) # Docker list
print(api('GetRobotList', appId='member2')) # By name: bots labeled member2
print(api('CommandRobot', 123, 'ok')) # Send an interactive command to bot 123
print(api('GetRobotDetail', 123)) # Details of bot 123
Go example
mylang
package main
import (
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"time"
)
const (
accessKey = "" // AccessKey of the API KEY
secretKey = "" // SecretKey of the API KEY
baseAPI = "https://www.fmz.com/api/v1"
)
var client = &http.Client{Timeout: 10 * time.Second}
func api(method string, args ...interface{}) (string, error) {
if args == nil {
args = []interface{}{}
}
b, err := json.Marshal(args)
if err != nil {
return "", err
}
nonce := strconv.FormatInt(time.Now().UnixMilli(), 10)
sum := md5.Sum([]byte("1.0|" + method + "|" + string(b) + "|" + nonce + "|" + secretKey))
form := url.Values{
"version": {"1.0"},
"access_key": {accessKey},
"method": {method},
"args": {string(b)},
"nonce": {nonce},
"sign": {hex.EncodeToString(sum[:])},
}
resp, err := client.PostForm(baseAPI, form)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
return string(body), err
}
func main() {
ret, err := api("GetNodeList")
fmt.Println(ret, err)
// Restart bot 123 with a new configuration; settings fields: see the bot configuration section in Extended API Interface Details
settings := map[string]interface{}{
"name": "hedge test",
"strategy": 456,
"period": 60,
"node": 789,
"exchanges": []interface{}{
map[string]interface{}{"pid": 1001, "pair": "BTC_USDT"},
},
}
ret, err = api("RestartRobot", 123, settings)
fmt.Println(ret, err)
}
Direct Verification
Direct verification computes no signature; the secret_key is put into the request parameters instead. This produces a fixed URL that can be entered into webhooks such as TradingView that accept only a single URL.
Security note: a
secret_keyin a URL ends up in browser history, proxy and server access logs, and the webhook provider's configuration; anyone who obtains the URL can call the API with this key's permissions. Use direct verification only forCommandRobotwebhooks, and create a dedicated API KEY for it that is grantedCommandRobotonly (see Create ApiKey). If it leaks, delete that API KEY immediately.
The request parameters are access_key, secret_key, method and args (a JSON array, URL-encoded); version, nonce and sign are not needed. CommandRobot skips the nonce check; other methods are still checked: without a nonce the server uses the current time (to the second), so a second call within the same second returns a nonce error (code 3).
For example, with an API KEY whose AccessKey is xxx and SecretKey is yyy, opening the URL below sends the interactive command ok12345 to the live trading bot with ID 186515:
plaintext
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyy&method=CommandRobot&args=%5B186515%2C%22ok12345%22%5D
Receiving a webhook body
When the command argument of CommandRobot is an empty string and the request is a POST, the server sends the request body to the bot as the interactive command. For example, set the TradingView webhook URL to:
plaintext
https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyy&method=CommandRobot&args=%5B186515%2C+%22%22%5D
The args value %5B186515%2C+%22%22%5D decodes to [186515, ""] (+ is a URL-encoded space): 186515 is the bot ID and the command is an empty string.
Simulating a TradingView webhook alert:
javascript
function main() {
var options = {
method: "POST",
body: `{"test": 123}`,
headers: {"Content-Type": "application/json"}
}
// A webhook alert sends a POST request with the required headers automatically
return HttpQuery("https://www.fmz.com/api/v1?access_key=xxx&secret_key=yyy&method=CommandRobot&args=%5B186515%2C+%22%22%5D", options)
}
The content of the TradingView alert message box is the request body:
-
JSON format:
plaintext{"close": {{close}}, "name": "aaa"}The bot with ID
186515receives the interactive command{"close": 39773.75, "name": "aaa"}. -
Text format:
plaintextBTCUSDTPERP Crossing 39700.00 close: {{close}}The bot with ID
186515receives the interactive commandBTCUSDTPERP Crossing 39700.00 close: 39739.4.
Python and Go examples
python
import json
import urllib.parse
import urllib.request
ACCESS_KEY = '' # AccessKey of an API KEY granted CommandRobot only
SECRET_KEY = '' # SecretKey
def api(method, *args):
query = urllib.parse.urlencode({
'access_key': ACCESS_KEY,
'secret_key': SECRET_KEY,
'method': method,
'args': json.dumps(list(args)),
})
with urllib.request.urlopen('https://www.fmz.com/api/v1?' + query, timeout=10) as resp:
return json.loads(resp.read().decode('utf-8'))
# Without permission for the method the result is {'code': 4, 'data': None}
print(api('CommandRobot', 186515, 'ok12345'))
mylang
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
)
const (
accessKey = "" // AccessKey of an API KEY granted CommandRobot only
secretKey = "" // SecretKey
baseAPI = "https://www.fmz.com/api/v1"
)
var client = &http.Client{Timeout: 10 * time.Second}
func api(method string, args ...interface{}) (string, error) {
if args == nil {
args = []interface{}{}
}
b, err := json.Marshal(args)
if err != nil {
return "", err
}
q := url.Values{
"access_key": {accessKey},
"secret_key": {secretKey},
"method": {method},
"args": {string(b)},
}
resp, err := client.Get(baseAPI + "?" + q.Encode())
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
return string(body), err
}
func main() {
ret, err := api("CommandRobot", 186515, "ok12345")
fmt.Println(ret, err)
}
References: