Authentication

Three schemes. Pick by use case. Sandbox and production share the same schemes, the same sk_live_* prefix, and the same host — capability is differentiated by the key's tier flag.

Overview

Scheme Use case Header(s) Prefix
API Key Machine-to-machine, creator profiles, aggregators X-Api-Key sk_live_* (tier-flagged)
HMAC (5-header) High-security trading bots, request-signing partners 4 × PAX-* + Content-Type Same as API Key
Session Bearer Browser / mobile client flows Authorization: Bearer <token> Session-scoped

1. API Key (simplest)

Send the key in the X-Api-Key header. No signing. Rate-limited per key.

curl https://api.predictasiax.com/v1/account \
  -H "X-Api-Key: sk_live_YOUR_KEY_HERE"

Minting a key

Via the dashboard (Settings → API Keys → Mint new key) or via REST:

curl -X POST https://api.predictasiax.com/v1/keys \
  -H "Authorization: Bearer <session_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "my-arb-bot",
    "permissions": ["read", "trade"]
  }'

Response (shown once — cannot be retrieved again):

{
  "ok": true,
  "data": {
    "key_id":     "sk_live_ABC123",
    "secret":     "<32-bytes-hex>",
    "passphrase": "<human-readable>",
    "permissions": ["read", "trade"],
    "created_at_ms": 1786190400000
  }
}

Environments & tiers

Every sk_live_* key carries a tier flag that controls its capabilities and rate limits — enforced server-side on every request:

TierCapabilityRate multiplier
self_serve SANDBOXSimulated fills at mid-price, $10 per-order + $100 daily notional caps. Zero real-money risk.1× baseline
read_live LIVEAuto-graduated production tier — reached at $100 30-day attributed volume. Real-money trading with tier-native limits.1× baseline
trade_capped LIVEReal-money trading with per-order + daily notional ceilings.1× baseline
trade_full LIVEUnrestricted real-money trading, MM-tier order sizes.1× baseline
genesis LIVEInaugural cohort — early access to unreleased endpoints, custom bursts.10× baseline
partner / institutionalSigned agreement — dedicated infra, co-marketing, negotiated fee splits.up to 20× (negotiable per contract)

Mismatched credentials return 401 WRONG_ENV_KEY.

2. HMAC (5-header)

For higher-security trading bots. Every request is signed with your secret. Server verifies signature + timestamp within 30-second skew window.

Required headers (all 5)

HeaderValue
PAX-ADDRESSYour key_id (e.g., sk_live_ABC123)
PAX-TIMESTAMPUNIX ms epoch, must be within 30s of server time
PAX-NONCEYour passphrase from key mint
PAX-SIGNATUREbase64(HMAC-SHA256(secret, message))
Content-Typeapplication/json for POST/PUT/PATCH

Message construction

message = timestamp + method + path + body

# Examples:
# GET  /v1/markets?category=crypto (no body)  → "1786190400000GET/v1/markets?category=crypto"
# POST /v1/orders {"market_id":"m_abc"}       → "1786190400000POST/v1/orders{\"market_id\":\"m_abc\"}"

Signing code

import hmac, hashlib, base64, time, json, requests

KEY_ID     = "sk_live_ABC123"
SECRET     = "<32-bytes-hex>"
PASSPHRASE = "correct-horse-battery-staple"
BASE       = "https://api.predictasiax.com"

def sign(method, path, body=""):
    ts = str(int(time.time() * 1000))
    message = ts + method + path + body
    sig = base64.b64encode(
        hmac.new(SECRET.encode(), message.encode(), hashlib.sha256).digest()
    ).decode()
    return {
        "PAX-ADDRESS": KEY_ID,
        "PAX-TIMESTAMP":  ts,
        "PAX-NONCE": PASSPHRASE,
        "PAX-SIGNATURE":  sig,
        "Content-Type":    "application/json",
    }

# GET
r = requests.get(BASE + "/v1/account", headers=sign("GET", "/v1/account"))

# POST
body = json.dumps({"market_id": "m_abc", "outcome_id": "yes",
                   "side": "buy", "order_type": "market", "size": "100"})
r = requests.post(BASE + "/v1/orders", headers=sign("POST", "/v1/orders", body), data=body)
print(r.json())
import crypto from "node:crypto";

const KEY_ID     = "sk_live_ABC123";
const SECRET     = "<32-bytes-hex>";
const PASSPHRASE = "correct-horse-battery-staple";
const BASE       = "https://api.predictasiax.com";

function sign(method, path, body = "") {
  const ts = Date.now().toString();
  const message = ts + method + path + body;
  const sig = crypto.createHmac("sha256", SECRET).update(message).digest("base64");
  return {
    "PAX-ADDRESS": KEY_ID,
    "PAX-TIMESTAMP":  ts,
    "PAX-NONCE": PASSPHRASE,
    "PAX-SIGNATURE":  sig,
    "Content-Type":    "application/json",
  };
}

// GET
const r1 = await fetch(BASE + "/v1/account", { headers: sign("GET", "/v1/account") });

// POST
const body = JSON.stringify({ market_id: "m_abc", outcome_id: "yes",
                              side: "buy", order_type: "market", size: "100" });
const r2 = await fetch(BASE + "/v1/orders", {
  method: "POST", body, headers: sign("POST", "/v1/orders", body),
});
console.log(await r2.json());
#!/bin/bash
KEY_ID="sk_live_ABC123"
SECRET="<32-bytes-hex>"
PASSPHRASE="correct-horse-battery-staple"
BASE="https://api.predictasiax.com"

sign() {
  local method="$1" path="$2" body="$3"
  local ts=$(date +%s%3N)
  local message="${ts}${method}${path}${body}"
  local sig=$(printf '%s' "$message" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64)
  echo "-H PAX-ADDRESS:${KEY_ID} -H PAX-TIMESTAMP:${ts} -H PAX-NONCE:${PASSPHRASE} -H PAX-SIGNATURE:${sig}"
}

# GET
curl "${BASE}/v1/account" $(sign GET /v1/account)

# POST
body='{"market_id":"m_abc","outcome_id":"yes","side":"buy","order_type":"market","size":"100"}'
curl -X POST "${BASE}/v1/orders" -H "Content-Type:application/json" $(sign POST /v1/orders "$body") -d "$body"

Server-side verification (for reference)

Server rejects with 401 INVALID_SIGNATURE if any of:

Post-trade verification. HMAC authenticates the request; the operational audit chain lets anyone independently confirm that a fill was recorded. See /verify for the public Merkle verifier, six no-auth /v1/audit/* endpoints, and client-side inclusion-proof reconstruction.

3. Session Bearer (browser / mobile only)

Not intended for machine partners. Browser client obtains a bearer token from POST /api/auth/login, sends on every request:

curl https://api.predictasiax.com/v1/account \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Session tokens expire after 7 days idle (24h if refresh not performed). Machine partners should always prefer API Key + HMAC.

WebSocket authentication

The WS connection accepts anonymous connections for public channels (fast_tick, ticker, orderbook, markets_snapshot). To subscribe to private channels (account, deposit, etc.), send an AUTH message after connect:

wscat -c wss://predictasiax.com/ws

> {"method":"AUTH","token":"sk_live_ABC123"}
< {"type":"authenticated","email":"[email protected]"}

> {"method":"SUBSCRIBE","params":["account","fast_round_settled"]}

You can pass either the API key or a session bearer as token — server handles both.

Revoking a key

curl -X DELETE https://api.predictasiax.com/v1/keys/sk_live_ABC123 \
  -H "Authorization: Bearer <session_token>"

Effective within 5 seconds across our backend fleet. All subsequent requests using the revoked key return 401 KEY_REVOKED.

Never commit secrets to git. Store SECRET and PASSPHRASE in a secret manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault). Rotate on any suspicion of leak — old key can be revoked via DELETE /v1/keys/{id}.