Auth API
The Auth API handles user registration, login, session management, and embedded iframe authentication. It supports two authentication flows: direct auth (for standalone usage) andembed auth (for casino iframe integrations where the operator manages the user session).
https://app.predictu.com/api/authContent-Type: All requests and responses use
application/json.Sessions: Successful authentication returns a JWT token with a 24-hour expiry. The token should be passed in the
Authorization: Bearer <token> header for all subsequent API calls.Authentication Flows
Predictu supports two distinct authentication flows depending on how the user accesses the platform.
Direct Authentication
Used when users access Predictu directly (not through a casino embed). Users register with an email and password, then log in to receive a session token.
POST /registerPOST /loginGET /meEmbed Authentication
Used when users access Predictu through a casino operator’s iframe embed. The operator passes a signed token from their system, and Predictu either finds an existing user or creates one automatically.
POST /embed-init verifies the operator token, finds/creates the user, and returns a Predictu session tokenLogin
Authenticates a user with email and password. Returns a JWT session token on success. This endpoint is rate-limited to prevent brute-force attacks.
Request Body
{
"email": "alice@example.com",
"password": "s3cur3P@ssw0rd"
}| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User’s email address. Case-insensitive. |
password | string | Yes | User’s password. Minimum 8 characters. |
Response (200 OK)
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2025-03-19T14:32:00.000Z",
"user": {
"id": "usr_def456",
"email": "alice@example.com",
"username": "alice",
"tier": "regular",
"operator_id": "op_abc123",
"created_at": "2025-01-15T10:00:00.000Z"
}
}Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing or invalid email/password format |
| 401 | INVALID_CREDENTIALS | Email not found or password incorrect |
| 403 | ACCOUNT_BANNED | User account has been banned by an admin |
| 429 | RATE_LIMITED | Too many login attempts. Try again after cooldown. |
retry_after field (in seconds).Security Notes
- Passwords are hashed with bcrypt (cost factor 12) and never stored in plaintext
- Failed login attempts are logged with IP address and user agent for security auditing
- The error message for wrong email vs. wrong password is intentionally identical (
INVALID_CREDENTIALS) to prevent email enumeration - Successful login clears the failed attempt counter for that email
Register
Creates a new user account. The user is assigned to the new tier by default and receives an initial balance of $0. If an operator_id is provided, the user is associated with that operator.
Request Body
{
"email": "alice@example.com",
"password": "s3cur3P@ssw0rd",
"username": "alice",
"operator_id": "op_abc123"
}| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Valid email address. Must be unique across the platform. |
password | string | Yes | Minimum 8 characters, must contain at least one uppercase letter, one lowercase letter, and one number. |
username | string | Yes | Display name. 3–30 characters, alphanumeric and underscores only. Must be unique. |
operator_id | string | No | Operator to associate the user with. If omitted, the user is a “direct” user. |
Response (201 Created)
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2025-03-19T14:32:00.000Z",
"user": {
"id": "usr_new789",
"email": "alice@example.com",
"username": "alice",
"tier": "new",
"operator_id": "op_abc123",
"balance": 0.00,
"created_at": "2025-03-18T14:32:00.000Z"
}
}Error Responses
| Status | Code | Description |
|---|---|---|
| 400 | VALIDATION_ERROR | Missing or invalid fields (details in error.details) |
| 409 | EMAIL_EXISTS | An account with this email already exists |
| 409 | USERNAME_EXISTS | This username is already taken |
| 404 | OPERATOR_NOT_FOUND | The specified operator_id does not exist |
| 403 | OPERATOR_INACTIVE | The specified operator is not active (still onboarding or disabled) |
Side Effects
When a user registers successfully, the following side effects occur:
- A row is inserted into the
userstable with tiernew - A default
user_scoresrow is created with neutral metric values - If
operator_idis provided, the operator receives aUSER_REGISTEREDS2S callback - The registration event is logged in the audit trail
- A session token is automatically generated (the user is logged in immediately)
Get Current User
Returns the profile of the currently authenticated user. This endpoint is used to verify that a session token is still valid and to fetch the latest user state (tier, balance, etc.).
Headers
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Response (200 OK)
{
"user": {
"id": "usr_def456",
"email": "alice@example.com",
"username": "alice",
"tier": "regular",
"classification": "moderate",
"operator_id": "op_abc123",
"balance": 150.00,
"open_positions_count": 3,
"total_trades": 42,
"created_at": "2025-01-15T10:00:00.000Z",
"last_trade_at": "2025-03-18T14:32:01.000Z"
}
}Error Responses
| Status | Code | Description |
|---|---|---|
| 401 | UNAUTHORIZED | No token provided or token is malformed |
| 401 | TOKEN_EXPIRED | Token has expired (24-hour lifetime exceeded) |
| 403 | ACCOUNT_BANNED | User account was banned after the token was issued |
/me endpoint does not refresh the token. When a token is nearing expiry, the client should call POST /login again to obtain a fresh token. Embed sessions use a different mechanism (see POST /embed-init).Embed Init
Initializes an authenticated session for a user within a casino operator’s embedded iframe. This is the primary authentication mechanism for the B2B casino integration. The operator signs a JWT with their secret key containing the player’s identity, and Predictu verifies it and creates/finds the corresponding Predictu user.
Request Body
{
"operator_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyYXRvcl9pZCI6Im9wX2FiYzEyMyIsInBsYXllcl9pZCI6InBsYXllcl83ODkiLCJ1c2VybmFtZSI6ImFsaWNlIiwiZXhwIjoxNzEwNzc1OTIxfQ.signature"
}| Field | Type | Required | Description |
|---|---|---|---|
operator_token | string | Yes | A JWT signed by the operator’s secret key containing the player identity. |
Operator Token Format
The operator JWT must contain the following claims:
{
"operator_id": "op_abc123", // Required: the operator's ID in Predictu
"player_id": "player_789", // Required: the player's ID in the operator's system
"username": "alice", // Optional: display name (used if creating new user)
"email": "alice@example.com", // Optional: email (used if creating new user)
"exp": 1710775921 // Required: expiry timestamp (max 5 minutes from now)
}exp more than 5 minutes in the future are rejected.Response (200 OK)
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2025-03-19T14:32:00.000Z",
"user": {
"id": "usr_embed_abc",
"username": "alice",
"tier": "new",
"operator_id": "op_abc123",
"external_player_id": "player_789",
"balance": 0.00,
"is_new": true,
"created_at": "2025-03-18T14:32:00.000Z"
}
}The is_new field indicates whether this call created a new user (true) or found an existing one (false). This is useful for the operator to know when to trigger onboarding flows or welcome bonuses.
User Matching Logic
When /embed-init is called, the system uses the following logic to find or create the user:
operator_id + external_player_idis_new: falseis_new: trueError Responses
| Status | Code | Description |
|---|---|---|
| 400 | INVALID_TOKEN | Token is malformed or cannot be decoded |
| 401 | SIGNATURE_INVALID | Token signature does not match the operator’s secret key |
| 401 | TOKEN_EXPIRED | Operator token has expired |
| 400 | MISSING_CLAIMS | Required claims (operator_id, player_id) missing from token |
| 404 | OPERATOR_NOT_FOUND | Operator ID in token does not match any active operator |
| 403 | OPERATOR_INACTIVE | Operator exists but is not active |
| 403 | ORIGIN_NOT_ALLOWED | Request origin does not match operator’s whitelisted origins |
Security Considerations
- Signature verification: The operator token is verified against the operator’s secret key stored in Predictu’s database. HMAC-SHA256 is the required algorithm.
- Origin check: In addition to token verification, the request’s
OriginorRefererheader is checked against the operator’s whitelisted origins. This prevents a stolen operator token from being used on an unauthorized domain. - Idempotency: Calling
/embed-initmultiple times with the sameoperator_id+player_idis safe. It always returns the same user (with a fresh Predictu token). - No password: Embed users do not have passwords. They can only authenticate through the operator’s embed flow. They cannot use
POST /login.
JWT Token Format
Predictu session tokens are standard JWTs with the following claims:
// Header:
{
"alg": "HS256",
"typ": "JWT"
}
// Payload:
{
"sub": "usr_def456", // User ID
"email": "alice@example.com", // User email (if available)
"tier": "regular", // User tier at time of token issuance
"operator_id": "op_abc123", // Operator (null for direct users)
"role": "user", // "user", "operator_admin", or "god_mode"
"iat": 1710772321, // Issued at
"exp": 1710858721 // Expires at (24 hours later)
}localStorage in production environments as they are vulnerable to XSS attacks. The embed integration uses postMessage to pass the token to the iframe, where it is held in memory only.Rate Limits
| Endpoint | Limit | Window | Scope |
|---|---|---|---|
POST /login | 5 requests | 15 minutes | Per email |
POST /login | 20 requests | 15 minutes | Per IP |
POST /register | 3 requests | 15 minutes | Per IP |
GET /me | 60 requests | 1 minute | Per user |
POST /embed-init | 30 requests | 1 minute | Per operator |
