Phoenix Prediction Docs

Request Signing

Sign Operator API requests with your Ed25519 key

Every Operator API request must be signed with your Ed25519 private key. Phoenix verifies the signature with the public key registered for your operator.

Headers

HeaderDescription
X-Operator-CodeYour Phoenix Prediction operator code
X-Signature-TimestampUnix seconds when the request was signed
X-SignatureBase64url Ed25519 signature

Requests outside the timestamp replay window are rejected.

Canonical Payload

Sign this exact string:

operator_code + "\n" +
timestamp + "\n" +
METHOD + "\n" +
path + "\n" +
sha256_hex(raw_body)

Example for a bodyless GET /operator/api/markets:

acme
1779100000
GET
/operator/api/markets
e3b0c44298fc1c149afbf4c8996fb924...

The path does not include scheme, host, or query string. Bodyless requests use the SHA-256 hash of an empty string.

Node.js Example

import { createPrivateKey, createHash, sign } from 'node:crypto';

function sha256Hex(input: string) {
  return createHash('sha256').update(input).digest('hex');
}

function signOperatorRequest(input: {
  operatorCode: string;
  method: string;
  path: string;
  body?: string;
  privateKeyPem: string;
}) {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const body = input.body ?? '';
  const payload = [
    input.operatorCode,
    timestamp,
    input.method.toUpperCase(),
    input.path,
    sha256Hex(body),
  ].join('\n');

  const key = createPrivateKey(input.privateKeyPem);
  const signature = sign(null, Buffer.from(payload), key).toString('base64url');

  return {
    'content-type': 'application/json',
    'x-operator-code': input.operatorCode,
    'x-signature-timestamp': timestamp,
    'x-signature': signature,
  };
}

For Ed25519, Node's sign algorithm argument is null.

Failure Behavior

Authentication failures return HTTP 401 with:

{ "error": "unauthorized" }

Phoenix intentionally does not reveal which verification step failed.

On this page