Authentication

Three schemes. Pick by use case. Sandbox and production use the same schemes but different key prefixes.

Overview

Scheme Use case Header(s) Prefix
API Key Machine-to-machine, creator profiles, aggregators X-Api-Key sk_live_* / sk_test_*
HMAC (Polymarket-compatible) High-security trading bots, request-signing partners 5 × POLY_* 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://predictasiax.com/api/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://predictasiax.com/api/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
  }
}

Environment separation

Key prefixEnvironmentWrong-env behavior
sk_live_* LIVE Production only (predictasiax.com) 401 WRONG_ENV_KEY if used on sandbox
sk_test_* TEST Sandbox only (sandbox.predictasiax.com) 401 WRONG_ENV_KEY if used on production

2. HMAC (Polymarket-compatible)

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
POLY_ACCESS_KEYYour key_id (e.g., sk_live_ABC123)
POLY_TIMESTAMPUNIX ms epoch, must be within 30s of server time
POLY_PASSPHRASEYour passphrase from key mint
POLY_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://predictasiax.com/api"

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 {
        "POLY_ACCESS_KEY": KEY_ID,
        "POLY_TIMESTAMP":  ts,
        "POLY_PASSPHRASE": PASSPHRASE,
        "POLY_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://predictasiax.com/api";

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 {
    "POLY_ACCESS_KEY": KEY_ID,
    "POLY_TIMESTAMP":  ts,
    "POLY_PASSPHRASE": PASSPHRASE,
    "POLY_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://predictasiax.com/api"

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 POLY_ACCESS_KEY:${KEY_ID} -H POLY_TIMESTAMP:${ts} -H POLY_PASSPHRASE:${PASSPHRASE} -H POLY_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:

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://predictasiax.com/api/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":"partner@example.com"}

> {"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://predictasiax.com/api/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}.