Authentication
API keys
Before anything else, sign in to the Predictex web app and create an API key there. The key and its secret are the only credentials a client needs, and the secret is returned once at creation and cannot be retrieved afterwards.
| Permission | Endpoints granted |
|---|---|
read |
POST /order/rfq, GET /order, GET /orders, GET /orders/updates, GET /positions, GET /settlements |
trade |
POST /order |
GET /fixtures is public and needs no key.
Optional key attributes are ipRestrictions, a list of addresses or CIDR blocks permitted to use the key, and expiresAt, an expiry timestamp in milliseconds. Keys with the trade permission require an expiry and a single wallet signing approval performed in the web app.
Request signing
Every request to a read or trade endpoint carries four headers.
| Header | Value |
|---|---|
X-Api-Key |
The API key. |
X-Timestamp |
Current time in milliseconds. |
X-Recv-Window |
Accepted clock drift in milliseconds, between 100 and 60000. |
X-Signature |
Hex encoded HMAC SHA256, computed as follows. |
payload = rawBody when there is one, otherwise rawQueryString
signBase = timestamp + apiKey + recvWindow + METHOD + path + payload
signature = hex(hmac_sha256(apiSecret, signBase))
The path is signed without its query string. The body is signed byte for byte as transmitted. Each signature is accepted exactly once, so every request must be signed freshly. A timestamp outside the recv window returns -1021. Synchronize clocks against GET /time. Runnable Node, Python, and Go examples follow below and accompany each endpoint page.
Client examples
Set three environment variables. Node requires npm install axios. Python requires pip install requests. Go uses only the standard library.
export PREDICTEX_API=https://predictex-api-testnet.d8x.xyz
export PREDICTEX_KEY=pk_your_key
export PREDICTEX_SECRET=sk_your_secret
import axios from "axios";
import crypto from "crypto";
const API = process.env.PREDICTEX_API;
const KEY = process.env.PREDICTEX_KEY;
const SECRET = process.env.PREDICTEX_SECRET;
const RECV = "5000";
function sign(method, path, payload) {
const ts = Date.now().toString();
const base = ts + KEY + RECV + method + path + payload;
const sig = crypto.createHmac("sha256", SECRET).update(base).digest("hex");
return {
"X-Api-Key": KEY,
"X-Timestamp": ts,
"X-Recv-Window": RECV,
"X-Signature": sig,
};
}
async function signedGet(path, params = {}) {
const qs = new URLSearchParams(params).toString();
const res = await axios.get(`${API}${path}`, {
params,
headers: sign("GET", path, qs),
});
return res.data;
}
async function signedPost(path, body) {
const data = JSON.stringify(body);
const res = await axios.post(`${API}${path}`, data, {
headers: { "Content-Type": "application/json", ...sign("POST", path.split("?")[0], data) },
});
return res.data;
}
import hashlib
import hmac
import json
import os
import time
import requests
API = os.environ["PREDICTEX_API"]
KEY = os.environ["PREDICTEX_KEY"]
SECRET = os.environ["PREDICTEX_SECRET"]
RECV = "5000"
def sign(method: str, path: str, payload: str) -> dict:
ts = str(int(time.time() * 1000))
base = ts + KEY + RECV + method + path + payload
sig = hmac.new(SECRET.encode(), base.encode(), hashlib.sha256).hexdigest()
return {
"X-Api-Key": KEY,
"X-Timestamp": ts,
"X-Recv-Window": RECV,
"X-Signature": sig,
}
def signedGet(path: str, params: dict | None = None) -> dict:
qs = "&".join(f"{k}={v}" for k, v in (params or {}).items())
r = requests.get(f"{API}{path}", params=params, headers=sign("GET", path, qs), timeout=30)
r.raise_for_status()
return r.json()
def signedPost(path: str, body: dict) -> dict:
data = json.dumps(body, separators=(",", ":"))
headers = {"Content-Type": "application/json", **sign("POST", path.split("?")[0], data)}
r = requests.post(f"{API}{path}", data=data, headers=headers, timeout=45)
r.raise_for_status()
return r.json()
package predictex
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
var (
api = os.Getenv("PREDICTEX_API")
key = os.Getenv("PREDICTEX_KEY")
secret = os.Getenv("PREDICTEX_SECRET")
)
const recv = "5000"
func sign(method, path, payload string) map[string]string {
ts := strconv.FormatInt(time.Now().UnixMilli(), 10)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(ts + key + recv + method + path + payload))
return map[string]string{
"X-Api-Key": key,
"X-Timestamp": ts,
"X-Recv-Window": recv,
"X-Signature": hex.EncodeToString(mac.Sum(nil)),
}
}
func call(req *http.Request, out any) error {
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
if res.StatusCode >= 400 {
var e struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
json.Unmarshal(body, &e)
return fmt.Errorf("api error %d: %s", e.Code, e.Msg)
}
return json.Unmarshal(body, out)
}
func signedGet(path string, params url.Values, out any) error {
qs := params.Encode()
u := api + path
if qs != "" {
u += "?" + qs
}
req, _ := http.NewRequest("GET", u, nil)
for k, v := range sign("GET", path, qs) {
req.Header.Set(k, v)
}
return call(req, out)
}
func signedPost(path string, body any, out any) error {
data, _ := json.Marshal(body)
req, _ := http.NewRequest("POST", api+path, bytes.NewReader(data))
req.Header.Set("Content-Type", "application/json")
signPath, _, _ := strings.Cut(path, "?")
for k, v := range sign("POST", signPath, string(data)) {
req.Header.Set(k, v)
}
return call(req, out)
}
Info
The signed GET payload must byte match the query string the HTTP library sends, so build both from the same parameters in the same order.