Operator Integration API

One integration.
Every game we make.

A seamless-wallet REST API. You keep the player's balance; PlayEola calls your wallet for every bet, win, and rollback. HMAC-signed, idempotent, and typed end to end.

Base URLhttps://api.playeola.com
Version3.1
ModelSeamless wallet
Surface25 endpoints

Overview

The Operator Integration API connects your platform to the PlayEola game runtime. It runs in two directions — the calls you make to us, and the wallet calls we make to you.

Quickstart

Authenticate

Call POST /integration/auth with a signed empty body to confirm your API key and secret resolve to your entity.

Launch a game

Call POST /games/game-launch with a player context. You get a single-use launch_url — open it exactly as returned.

Serve wallet calls

Implement /balance, /debit, /credit, and /rollback. We call them during play; you settle against the player's real balance.

Environments. Test keys are prefixed pe_test_, production keys pe_live_. The base URL https://api.playeola.com is the same for both — the key selects the environment.

Authentication

Requests are signed with HMAC-SHA256. Every request except GET /health carries three headers.

Required headers
HeaderReqDescription
X-API-KeyyesYour API key — pe_live_… (prod) or pe_test_… (test)
X-PlayEola-SignatureyesHMAC-SHA256 signature, hex-encoded
X-PlayEola-TimestampyesISO-8601 timestamp
Content-TypePOSTapplication/json
Signing

Operator → PlayEola. Concatenate the timestamp and the compact JSON body, then HMAC with your secret:

HMAC-SHA256(timestamp + JSON.stringify(body), api_secret)

PlayEola → your wallet. Wallet calls also fold the endpoint path into the signed payload:

HMAC-SHA256(timestamp + endpoint_path + body, wallet_api_secret)
  • Sign compact JSON — no extra whitespace. For GET requests, sign an empty body {}.
  • The timestamp must be within 5 minutes of server time.
  • The server verifies against JSON.stringify(parsedBody) — number formatting must match JS: 0.500.5, 500.00500.
Canonical numbers. Serialize numbers the way JSON.stringify does or the signature won't match. Never pad decimals or send trailing zeros in a signed body.
signing helper
# sign timestamp + compact-JSON body with your secret
TS=$(date -u +%Y-%m-%dT%H:%M:%S.000Z)
BODY='{"brand_code":"MAIN","game_code":"Clockwork_Tyrant","context":{"player_id":"player_12345","currency":"USD"}}'
SIG=$(printf '%s' "$TS$BODY" \
  | openssl dgst -sha256 -hmac "$PE_API_SECRET" \
  | sed 's/^.* //')

curl -X POST https://api.playeola.com/games/game-launch \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $PE_API_KEY" \
  -H "X-PlayEola-Signature: $SIG" \
  -H "X-PlayEola-Timestamp: $TS" \
  -d "$BODY"
// sign.js — reusable signed client (Node 18+)
import crypto from "node:crypto";

const API_KEY    = process.env.PE_API_KEY;
const API_SECRET = process.env.PE_API_SECRET;
const BASE_URL   = "https://api.playeola.com";

export async function signedFetch(method, path, body) {
  const timestamp = new Date().toISOString();
  const payload   = body ? JSON.stringify(body) : "{}";
  const signature = crypto
    .createHmac("sha256", API_SECRET)
    .update(timestamp + payload)
    .digest("hex");

  const res = await fetch(BASE_URL + path, {
    method,
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": API_KEY,
      "X-PlayEola-Signature": signature,
      "X-PlayEola-Timestamp": timestamp
    },
    body: method === "GET" ? undefined : payload
  });
  return res.json();
}
// sign.ts — typed signed client (Node 18+)
import crypto from "node:crypto";

const API_KEY    = process.env.PE_API_KEY!;
const API_SECRET = process.env.PE_API_SECRET!;
const BASE_URL   = "https://api.playeola.com";

export async function signedFetch<T>(
  method: "GET" | "POST",
  path: string,
  body?: unknown
): Promise<T> {
  const timestamp = new Date().toISOString();
  const payload   = body ? JSON.stringify(body) : "{}";
  const signature = crypto
    .createHmac("sha256", API_SECRET)
    .update(timestamp + payload)
    .digest("hex");

  const res = await fetch(BASE_URL + path, {
    method,
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": API_KEY,
      "X-PlayEola-Signature": signature,
      "X-PlayEola-Timestamp": timestamp
    },
    body: method === "GET" ? undefined : payload
  });
  return res.json() as Promise<T>;
}

Response conventions

Successful responses carry "status": "success" alongside the endpoint's data. Errors arrive in one of two envelope shapes — integrations must handle both and ignore unknown fields.

Status codes
CodeMeaning
200OK
202Accepted
400Bad Request
401Unauthorized
403Forbidden
404Not Found
409Conflict
429Too Many Requests
500Internal Error
Error types
VALIDATION_ERRORAUTHENTICATION_ERROR AUTHORIZATION_ERRORNOT_FOUND BAD_REQUESTRATE_LIMIT_ERROR INTERNAL_ERROR
success200 OK
{
  "status": "success"
  // …endpoint data
}
error · flat401 / 403
{
  "status": "error",
  "type": "AUTHENTICATION_ERROR",
  "message": "Invalid signature",
  "context": {}
}
error · nested400 / 404 / 500
{
  "status": "error",
  "error": {
    "type": "VALIDATION_ERROR",
    "message": "brand_code is required"
  }
}

Errors

Two envelopes, one contract: read status first, then find type and message either at the top level (flat) or inside error (nested).

ShapeUsed byWhere to read type / message
Flat401 auth · 403 "Integration is not active"type + message at top level, optional context
Nested400 · 404 · other 403 · 500type + message inside an error object
Forward-compatible. Always ignore fields you don't recognize. New fields may be added to any response without a version bump.

Rate limits

Limits are applied per IP. Authenticated endpoints return standard RateLimit-* headers so you can pace yourself.

CategoryLimitWindow
/integration/auth100 req1 min
Bet processing (game client)600 req1 min
General (game-launch, games/list, …)1000 req1 min
Headers
RateLimit-PolicyRateLimit-Limit RateLimit-RemainingRateLimit-Reset
rate limited429
{
  "status": "error",
  "error": {
    "type": "RATE_LIMIT_ERROR",
    "message": "Too many requests, please try again later"
  }
}
PlayEola Operator Integration API · v3.1 Seamless wallet · HMAC-SHA256