Predictu
API Reference

S2S Operator API

The Server-to-Server (S2S) Operator API is used by Predictu administrators to manage casino operators programmatically. It provides endpoints for creating, updating, and configuring operators, managing their API keys, and testing callback connectivity. All endpoints require God Mode authentication unless otherwise noted.

Base URL: https://app.predictu.com/api/s2s
Authentication: God Mode session token in Authorization: Bearer <token> header.
Content-Type: All requests and responses use application/json.
Admin-only API: Every endpoint in this section requires the god_mode role. Operator admin users cannot access these endpoints. This API is for Predictu internal use to manage the B2B operator lifecycle.

Operator Data Model

Before diving into the endpoints, here is the complete operator data model that is returned and accepted across the API:

{
  "id": "op_abc123",
  "name": "Example Casino",
  "domain": "example-casino.com",
  "status": "active",
  "contact_email": "admin@example-casino.com",
  "billing_email": "billing@example-casino.com",
  "callback_url": "https://api.example-casino.com/callbacks/predictu",
  "callback_status": "healthy",
  "callback_last_success": "2025-03-18T14:30:00.000Z",
  "callback_failure_count": 0,
  "revenue_share_predictu": 30,
  "revenue_share_operator": 70,
  "allowed_categories": ["politics", "sports", "crypto", "entertainment"],
  "branding": {
    "primary_color": "#1a7a3a",
    "secondary_color": "#0d5a25",
    "bg_primary": "#0a120a",
    "bg_secondary": "#0f1a0f",
    "logo_url": "https://cdn.predictu.com/operators/op_abc123/logo.png",
    "icon_url": "https://cdn.predictu.com/operators/op_abc123/icon.png"
  },
  "config": {
    "new_tier_limit": 10,
    "regular_tier_limit": 100,
    "vip_tier_limit": 1000,
    "auto_promote_enabled": true,
    "auto_promote_days": 7,
    "auto_promote_trades": 5,
    "daily_loss_limit_enabled": false,
    "can_promote_vip": false
  },
  "onboarding_step": 8,
  "onboarding_complete": true,
  "allowed_origins": [
    "https://www.example-casino.com",
    "https://staging.example-casino.com"
  ],
  "user_count": 1247,
  "active_user_count_24h": 89,
  "total_volume": 245000.00,
  "mtd_revenue": 12400.00,
  "created_at": "2025-01-10T09:00:00.000Z",
  "updated_at": "2025-03-18T14:30:00.000Z"
}

List All Operators

GET/api/s2s/operators

Returns a paginated list of all operators on the platform. Supports filtering by status and searching by name or domain.

Query Parameters

ParameterTypeRequiredDescription
statusstringNoFilter by status: "active", "inactive", "onboarding", or "all" (default: "all")
searchstringNoSearch by operator name or domain (partial match)
sortstringNoSort field: "name", "created", "volume", "users" (default: "created")
orderstringNo"asc" or "desc" (default: "desc")
limitnumberNoPagination limit (default: 20, max: 100)
offsetnumberNoPagination offset (default: 0)

Response (200 OK)

{
  "operators": [
    {
      "id": "op_abc123",
      "name": "Example Casino",
      "domain": "example-casino.com",
      "status": "active",
      "user_count": 1247,
      "active_user_count_24h": 89,
      "total_volume": 245000.00,
      "mtd_revenue": 12400.00,
      "revenue_share_predictu": 30,
      "onboarding_complete": true,
      "created_at": "2025-01-10T09:00:00.000Z"
    },
    {
      "id": "op_def456",
      "name": "LuckySpin",
      "domain": "luckyspin.io",
      "status": "onboarding",
      "user_count": 0,
      "active_user_count_24h": 0,
      "total_volume": 0.00,
      "mtd_revenue": 0.00,
      "revenue_share_predictu": 25,
      "onboarding_complete": false,
      "created_at": "2025-03-15T11:00:00.000Z"
    }
  ],
  "total": 2,
  "limit": 20,
  "offset": 0
}

Create Operator

POST/api/s2s/operators

Creates a new operator record and initializes the onboarding process. The operator starts in"onboarding" status and must complete the 8-step checklist before being activated.

Request Body

{
  "name": "LuckySpin Casino",
  "domain": "luckyspin.io",
  "contact_email": "admin@luckyspin.io",
  "billing_email": "billing@luckyspin.io",
  "revenue_share_predictu": 25,
  "allowed_categories": ["politics", "sports", "crypto", "entertainment"],
  "config": {
    "new_tier_limit": 15,
    "regular_tier_limit": 200,
    "vip_tier_limit": 2000,
    "auto_promote_enabled": true,
    "auto_promote_days": 5,
    "auto_promote_trades": 3
  }
}
FieldTypeRequiredDescription
namestringYesOperator display name. Must be unique. 3–100 characters.
domainstringYesPrimary domain. Used for display and default origin whitelisting.
contact_emailstringYesPrimary contact email for the operator.
billing_emailstringNoBilling email. Defaults to contact_email if not provided.
revenue_share_predictunumberYesPredictu’s revenue share percentage (1–99). Operator gets the remainder.
allowed_categoriesstring[]NoMarket categories this operator can access. Defaults to all.
configobjectNoOperator-specific configuration overrides. See Operator Config.

Response (201 Created)

{
  "success": true,
  "operator": {
    "id": "op_new789",
    "name": "LuckySpin Casino",
    "domain": "luckyspin.io",
    "status": "onboarding",
    "contact_email": "admin@luckyspin.io",
    "billing_email": "billing@luckyspin.io",
    "revenue_share_predictu": 25,
    "revenue_share_operator": 75,
    "onboarding_step": 1,
    "onboarding_complete": false,
    "created_at": "2025-03-18T15:00:00.000Z"
  }
}

Error Responses

StatusCodeDescription
400VALIDATION_ERRORMissing or invalid fields
409NAME_EXISTSAn operator with this name already exists
409DOMAIN_EXISTSAn operator with this domain already exists

Side Effects

  • An operator_onboarding record is created with step 1 marked as complete
  • The audit log records the creation with the God Mode admin’s user ID
  • Default branding values are initialized (Predictu green theme)
  • An empty allowed_origins list is created

Get Operator Detail

GET/api/s2s/operators/[id]

Returns the complete detail of a single operator, including all configuration, branding, onboarding status, and summary statistics.

Path Parameters

ParameterTypeDescription
idstringOperator ID (e.g., op_abc123)

Response (200 OK)

Returns the full operator object as described in the Operator Data Model section above, plus additional computed fields:

{
  // ... all fields from the operator data model, plus:

  "api_keys": [
    {
      "key_id": "key_abc123",
      "public_key": "pk_live_abc123...",
      "status": "active",
      "created_at": "2025-01-10T09:30:00.000Z",
      "last_used_at": "2025-03-18T14:30:00.000Z"
    }
  ],
  "onboarding_checklist": {
    "create_record": { "complete": true, "completed_at": "2025-01-10T09:00:00.000Z" },
    "configure_revenue": { "complete": true, "completed_at": "2025-01-10T09:05:00.000Z" },
    "generate_keys": { "complete": true, "completed_at": "2025-01-10T09:10:00.000Z" },
    "set_callback": { "complete": true, "completed_at": "2025-01-10T09:15:00.000Z" },
    "test_ping": { "complete": true, "completed_at": "2025-01-10T09:20:00.000Z" },
    "configure_branding": { "complete": true, "completed_at": "2025-01-10T09:30:00.000Z" },
    "whitelist_origins": { "complete": true, "completed_at": "2025-01-10T09:35:00.000Z" },
    "activate": { "complete": true, "completed_at": "2025-01-10T10:00:00.000Z" }
  },
  "recent_invoices": [
    {
      "invoice_id": "inv_abc",
      "period": "2025-02",
      "gross_revenue": 8500.00,
      "predictu_share": 2550.00,
      "operator_share": 5950.00,
      "status": "paid"
    }
  ]
}

Error Responses

StatusCodeDescription
404NOT_FOUNDNo operator found with this ID

Update Operator

PATCH/api/s2s/operators/[id]

Updates an existing operator’s configuration, contact details, branding, or status. Only the fields included in the request body are updated; all other fields remain unchanged.

Request Body

Any combination of the following fields. All are optional -include only what you want to change.

{
  "name": "Example Casino (Updated)",
  "contact_email": "new-admin@example-casino.com",
  "billing_email": "new-billing@example-casino.com",
  "status": "active",
  "callback_url": "https://api-v2.example-casino.com/callbacks/predictu",
  "revenue_share_predictu": 35,
  "allowed_categories": ["politics", "sports"],
  "allowed_origins": [
    "https://www.example-casino.com",
    "https://app.example-casino.com"
  ],
  "branding": {
    "primary_color": "#2ecc71"
  },
  "config": {
    "new_tier_limit": 20,
    "can_promote_vip": true
  }
}
FieldTypeDescription
namestringOperator display name
contact_emailstringPrimary contact email
billing_emailstringBilling email
statusstring"active", "inactive", or "onboarding"
callback_urlstringS2S callback endpoint (must be HTTPS)
revenue_share_predictunumberPredictu’s revenue share % (1–99)
allowed_categoriesstring[]Market categories accessible to this operator
allowed_originsstring[]Whitelisted iframe origins (replaces entire list)
brandingobjectPartial branding update (merged with existing)
configobjectPartial config update (merged with existing)

Response (200 OK)

{
  "success": true,
  "operator": {
    // Full updated operator object
    "id": "op_abc123",
    "name": "Example Casino (Updated)",
    "status": "active",
    "revenue_share_predictu": 35,
    "revenue_share_operator": 65,
    "updated_at": "2025-03-18T16:00:00.000Z",
    // ... all other fields with current values
  },
  "changes": {
    "name": { "from": "Example Casino", "to": "Example Casino (Updated)" },
    "revenue_share_predictu": { "from": 30, "to": 35 },
    "config.can_promote_vip": { "from": false, "to": true }
  }
}

The changes object in the response provides a diff of what actually changed, making it easy to verify the update and for audit purposes.

Error Responses

StatusCodeDescription
400VALIDATION_ERRORInvalid field values
404NOT_FOUNDNo operator found with this ID
409NAME_EXISTSNew name conflicts with another operator
Status changes: Changing an operator’s status to "inactive"immediately blocks all new trades for that operator’s users. Existing open positions remain until resolved. Changing back to "active" restores trading. All status changes are logged and trigger an S2S callback to the operator (if their callback URL is configured).

Audit Trail

Every field change is recorded in the audit log with the admin who made the change, the timestamp, and the before/after values. Sensitive changes (status, revenue share, config) also generate a notification to all God Mode admins.

Generate API Keys

POST/api/s2s/operators/[id]/keys

Generates a new API key pair (public key + secret key) for the operator. If the operator already has active keys, the old keys enter a 24-hour grace period before expiring.

Request Body

{
  "label": "Production keys v2",
  "grace_period_hours": 24
}
FieldTypeRequiredDescription
labelstringNoHuman-readable label for the key pair. Defaults to "API Key".
grace_period_hoursnumberNoHours before old keys expire (default: 24, min: 1, max: 72).

Response (201 Created)

{
  "success": true,
  "key_pair": {
    "key_id": "key_new456",
    "public_key": "pk_live_xyz789abc...",
    "secret_key": "sk_live_secret123...",
    "label": "Production keys v2",
    "status": "active",
    "created_at": "2025-03-18T16:00:00.000Z"
  },
  "previous_keys": {
    "key_id": "key_abc123",
    "status": "expiring",
    "expires_at": "2025-03-19T16:00:00.000Z"
  }
}
Secret key visibility: The secret_key is returned only oncein this response. It is never stored in plaintext and cannot be retrieved again. If lost, the operator must generate a new key pair. Copy the secret key immediately and store it securely.

Grace Period Behavior

When new keys are generated while old keys are active:

  • The new keys become active immediately
  • Old keys are marked as "expiring" with an expires_at timestamp
  • Both old and new keys are accepted during the grace period
  • After the grace period, old keys are permanently deactivated
  • A KEY_ROTATED event is logged in the audit trail

Get Public Key

GET/api/s2s/operators/[id]/public-key

Returns the operator’s current active public key. This is a convenience endpoint used during embed integration setup and debugging. Unlike other S2S endpoints, this one does notrequire God Mode authentication -it returns only public information.

Response (200 OK)

{
  "operator_id": "op_abc123",
  "public_key": "pk_live_xyz789abc...",
  "key_id": "key_new456",
  "status": "active",
  "created_at": "2025-03-18T16:00:00.000Z"
}

Error Responses

StatusCodeDescription
404NOT_FOUNDNo operator found with this ID
404NO_ACTIVE_KEYOperator exists but has no active API key
No auth required: This endpoint is intentionally public. The public key is, by definition, safe to expose. It is used in the embed code snippet and client-side integrations.

Test Callback (PING)

POST/api/s2s/operators/[id]/test

Sends a test PING callback to the operator’s configured callback URL and reports the result. This is used during onboarding to verify that the operator’s server can receive and respond to Predictu callbacks.

Request Body

{
  "callback_url": "https://api.example-casino.com/callbacks/predictu",
  "timeout_ms": 10000
}
FieldTypeRequiredDescription
callback_urlstringNoURL to test. Defaults to the operator’s currently configured callback_url. Useful for testing a new URL before saving it.
timeout_msnumberNoTimeout in milliseconds (default: 10000, max: 30000).

PING Callback Payload

The following payload is sent to the operator’s callback URL as a POST request:

// Request sent TO the operator's callback URL:
POST https://api.example-casino.com/callbacks/predictu
Content-Type: application/json
X-Predictu-Signature: sha256=abc123...
X-Predictu-Event: PING
X-Predictu-Delivery: dlv_test_789

{
  "event": "PING",
  "operator_id": "op_abc123",
  "timestamp": "2025-03-18T16:05:00.000Z",
  "test": true,
  "payload": {
    "message": "This is a test callback from Predictu. Respond with HTTP 200 to confirm receipt."
  }
}

Expected Operator Response

The operator’s server must respond with HTTP 200 and the following body:

{
  "status": "ok",
  "received_event": "PING"
}

API Response (200 OK)

{
  "success": true,
  "test_result": {
    "callback_url": "https://api.example-casino.com/callbacks/predictu",
    "http_status": 200,
    "response_body": {
      "status": "ok",
      "received_event": "PING"
    },
    "latency_ms": 142,
    "signature_sent": "sha256=abc123...",
    "tls_version": "TLSv1.3",
    "ip_resolved": "203.0.113.42"
  }
}

Failure Response (200 OK, but test failed)

Note: Even when the test fails, the API itself returns 200. The failure details are in the response body.

{
  "success": false,
  "test_result": {
    "callback_url": "https://api.example-casino.com/callbacks/predictu",
    "error": "TIMEOUT",
    "error_message": "Callback did not respond within 10000ms",
    "latency_ms": 10000,
    "suggestion": "Ensure the callback URL is accessible and the server responds within the timeout period. Check firewall rules and DNS resolution."
  }
}

Possible Test Errors

ErrorDescriptionSuggestion
TIMEOUTServer did not respond within the timeoutCheck server availability and network connectivity
CONNECTION_REFUSEDTCP connection was refusedVerify the server is running and the port is correct
DNS_RESOLUTION_FAILEDCould not resolve the hostnameCheck the domain name in the callback URL
TLS_ERRORSSL/TLS handshake failedEnsure valid SSL certificate (self-signed not accepted)
HTTP_ERRORServer returned a non-200 status codeCheck server logs for the callback handler
INVALID_RESPONSEServer returned 200 but response body was not valid JSON or missing required fieldsEnsure response includes status and received_event
NOT_HTTPSCallback URL does not use HTTPSHTTPS is required for all callback URLs

Callback Signature Verification

All callbacks sent by Predictu include a signature header that operators should verify to ensure the callback is authentic.

Signature Format

X-Predictu-Signature: sha256=<hex-encoded HMAC-SHA256>

The signature is computed over the raw JSON request body using the operator’s secret key:

// Verification pseudocode:
const signature = hmac_sha256(operator_secret_key, request_body_string);
const expected = request.headers['X-Predictu-Signature'];
const is_valid = timing_safe_equals(
  'sha256=' + hex_encode(signature),
  expected
);

Callback Event Types

Beyond PING, the following callback events are sent during normal operation:

EventDescriptionPayload
PINGTest/health checkMessage string
USER_REGISTEREDNew user created via embedUser ID, external player ID, timestamp
TRADE_PLACEDUser placed a tradeTrade details (market, side, amount, price)
POSITION_RESOLVEDA user’s position was settledPosition details, outcome, payout
USER_RESTRICTEDUser was auto-restricted by scoringUser ID, score, classification
BALANCE_CHANGEDUser balance changed (deposit/withdrawal/payout)User ID, previous balance, new balance, reason
MARKET_RESOLVEDA market was resolvedMarket ID, outcome, affected user count

Rate Limits

EndpointLimitWindow
GET /operators60 requestsPer minute
POST /operators10 requestsPer minute
GET /operators/[id]60 requestsPer minute
PATCH /operators/[id]30 requestsPer minute
POST /operators/[id]/keys5 requestsPer hour
GET /operators/[id]/public-key120 requestsPer minute
POST /operators/[id]/test10 requestsPer minute