Skip to content

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.