Users

This guide explains how to create and manage poker accounts linked to users on your platform. Every user in the poker system is identified by your external user ID — a string you control that maps to your platform’s user identifier.

Required scopes: user:create (provision and update), user:read (read user details)

How User Mapping Works

The Developer API uses an ExternalUserMapping to link your platform users to poker accounts:

Your Platform                     Poker Engine
┌──────────────┐                  ┌──────────────────┐
│ User: usr_42  │ ──── maps to ──► │ Poker User: abc-1 │
│ User: usr_99  │ ──── maps to ──► │ Poker User: def-2 │
│ User: usr_200 │ ──── maps to ──► │ Poker User: ghi-3 │
└──────────────┘                  └──────────────────┘
  • Each mapping is scoped to your partner account — two different partners can use the same externalUserId without conflict.
  • One external user ID maps to exactly one poker account per partner.
  • One poker account can only be mapped to one external user ID per partner.

Provisioning a User

Basic Provisioning

Create a poker account linked to a user on your platform:

const { apiRequest } = require('./hmac-client'); // See quickstart.md

const result = await apiRequest('POST', '/developer/users', {
  externalUserId: 'user_12345',
  username: 'john_doe',
  email: 'john@example.com',
  firstName: 'John',
  lastName: 'Doe',
});

console.log(result);

Request:

POST /api/v1/developer/users
{
  "externalUserId": "user_12345",
  "username": "john_doe",
  "email": "john@example.com",
  "firstName": "John",
  "lastName": "Doe"
}

Response (201 Created):

{
  "user": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "externalUserId": "user_12345",
    "username": "john_doe",
    "email": "john@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "balance": "0",
    "virtualBalance": "0",
    "createdAt": "2026-02-18T12:00:00.000Z"
  },
  "isNew": true
}

Minimal Provisioning

Only the externalUserId is strictly required. Other fields depend on your partner’s username policy (see below):

const result = await apiRequest('POST', '/developer/users', {
  externalUserId: 'user_12345',
});
// Works if the username policy is 'generate_random'

Request Body Fields

FieldTypeRequiredDescription
externalUserIdstringYesYour platform’s user ID (1-255 chars)
usernamestringNoPoker username (3-30 chars, alphanumeric + underscore; stored in lowercase). Required if policy is require
emailstringNoUser’s email address (stored in lowercase). Required if policy is generate_from_email and no username is sent. If omitted, a placeholder {username}@developer-provisioned.internal is stored and returned
firstNamestringNoFirst name (max 100 chars)
lastNamestringNoLast name (max 100 chars)
countrystringNoCountry (max 100 chars)
avatarstringNoAvatar URL (must be a valid URL)
metadataobjectNoArbitrary key-value data stored with the mapping

The response does not include country, avatar or metadata.

Idempotent Provisioning

Calling the provisioning endpoint with the same externalUserId is idempotent — it returns the existing user instead of creating a duplicate:

// First call — creates the user
const first = await apiRequest('POST', '/developer/users', {
  externalUserId: 'user_12345',
  username: 'john_doe',
  email: 'john@example.com',
});
console.log(first.isNew); // true

// Second call with same externalUserId — returns existing user
const second = await apiRequest('POST', '/developer/users', {
  externalUserId: 'user_12345',
  username: 'different_name', // Ignored — user already exists
  email: 'different@example.com', // Ignored
});
console.log(second.isNew); // false
console.log(second.user.username); // "john_doe" (original)

Response (200 OK) for existing user:

{
  "user": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "externalUserId": "user_12345",
    "username": "john_doe",
    "email": "john@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "balance": "100",
    "virtualBalance": "0",
    "createdAt": "2026-02-18T12:00:00.000Z"
  },
  "isNew": false
}

This means you can safely call the provisioning endpoint every time a user navigates to poker — it will not create duplicates.

Username Policies

The platform admin sets a username policy on your partner account that determines how poker usernames are assigned when a user is provisioned. New partner accounts start on generate_from_email. Ask the admin which policy applies to yours.

require — Username Must Be Provided

The username field is required in the provisioning request. If omitted, the API returns a 400 MISSING_USERNAME error.

Best for: Platforms where users already have unique display names.

// Username policy: require
const result = await apiRequest('POST', '/developer/users', {
  externalUserId: 'user_12345',
  username: 'john_doe', // Required
  email: 'john@example.com',
});
// Missing username — returns 400
const result = await apiRequest('POST', '/developer/users', {
  externalUserId: 'user_12345',
  email: 'john@example.com',
  // No username → 400 MISSING_USERNAME
});

generate_from_email — Derived from Email

The username is automatically generated from the email prefix. If the generated username is taken, a numeric suffix is appended.

Best for: Platforms where users have email addresses but no display names.

// Username policy: generate_from_email
const result = await apiRequest('POST', '/developer/users', {
  externalUserId: 'user_12345',
  email: 'john.smith@example.com', // Required (used to generate username)
});
console.log(result.user.username); // "john_smith" (or a suffixed variant if taken)

Generation rules:

  1. Take the email prefix (before @): john.smith
  2. Lowercase: john.smith
  3. Replace non-alphanumeric characters with _: john_smith
  4. If the result is shorter than 3 characters, append _user
  5. If that username is taken, append _ and a number, or a random hex string as a last resort. Don’t rely on the suffix being sequential.

If neither email nor username is sent, the API returns 400 MISSING_USERNAME.

You can still provide a username explicitly — if included, it overrides the auto-generation:

const result = await apiRequest('POST', '/developer/users', {
  externalUserId: 'user_12345',
  email: 'john.smith@example.com',
  username: 'poker_john', // This overrides the auto-generated username
});
console.log(result.user.username); // "poker_john"

generate_random — Random Username

A random username is generated in the format player_ followed by 8 hex characters.

Best for: Platforms where anonymity is preferred or usernames are not important.

// Username policy: generate_random
const result = await apiRequest('POST', '/developer/users', {
  externalUserId: 'user_12345',
  // No username or email needed
});
console.log(result.user.username); // "player_a1b2c3d4"

You can still provide a username explicitly — if included, it overrides the random generation.

Getting User Details

Retrieve a user’s current profile and balance by their external user ID.

const result = await apiRequest('GET', '/developer/users/user_12345');

console.log(result);

Request:

GET /api/v1/developer/users/user_12345

Response (200 OK):

{
  "user": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "externalUserId": "user_12345",
    "username": "john_doe",
    "email": "john@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "balance": "250",
    "virtualBalance": "0",
    "createdAt": "2026-02-18T12:00:00.000Z"
  }
}

URL-Encoding External User IDs

If your external user ID contains special characters (e.g., user/123 or user@abc), URL-encode it:

const externalId = 'user/123';
const result = await apiRequest('GET', `/developer/users/${encodeURIComponent(externalId)}`);

User Not Found

If no mapping exists for the given externalUserId and your partner:

{
  "error": "USER_NOT_FOUND",
  "message": "No user found with this external user ID"
}

HTTP status: 404.

Updating User Profiles

Update a user’s profile fields. You cannot change the username or email — only firstName, lastName, country, and avatar. Any other field in the body is ignored. Updating requires the user:create scope; the response does not include country or avatar.

const result = await apiRequest('PUT', '/developer/users/user_12345', {
  firstName: 'Jonathan',
  lastName: 'Smith',
  country: 'United States',
  avatar: 'https://cdn.example.com/avatars/john.jpg',
});

console.log(result.user.firstName); // "Jonathan"

Request:

PUT /api/v1/developer/users/user_12345
{
  "firstName": "Jonathan",
  "lastName": "Smith",
  "country": "United States",
  "avatar": "https://cdn.example.com/avatars/john.jpg"
}

Response (200 OK):

{
  "user": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "externalUserId": "user_12345",
    "username": "john_doe",
    "email": "john@example.com",
    "firstName": "Jonathan",
    "lastName": "Smith",
    "balance": "250",
    "virtualBalance": "0",
    "createdAt": "2026-02-18T12:00:00.000Z"
  }
}

Update Body Fields

FieldTypeDescription
firstNamestringFirst name (max 100 chars)
lastNamestringLast name (max 100 chars)
countrystringCountry (max 100 chars)
avatarstringAvatar URL (must be a valid URL)

All fields are optional. Only include the fields you want to change.

Handling Email Conflicts

If you provision a user with an email that already belongs to a different poker account (not mapped to your partner), the API returns a 409 EMAIL_CONFLICT error:

{
  "error": "EMAIL_CONFLICT",
  "message": "A user with this email already exists but is not linked to this partner"
}

What this means: Someone already has a poker account with that email address (either registered directly or provisioned by another partner).

How to handle it:

  1. Provision without email — Omit the email field. The system will assign an internal placeholder email. This needs a username, or a generate_random policy.
  2. Use a different email — If your user has multiple email addresses, try a different one.

Complete Code Examples

Node.js: Full User Lifecycle

const { apiRequest } = require('./hmac-client');

async function userLifecycle() {
  // 1. Provision a new user
  console.log('1. Provisioning user...');
  const provision = await apiRequest('POST', '/developer/users', {
    externalUserId: 'demo_user_001',
    username: 'demo_player',
    email: 'demo@example.com',
    firstName: 'Demo',
    lastName: 'Player',
  });
  console.log(`   Created: ${provision.user.username} (new: ${provision.isNew})`);

  // 2. Get user details
  console.log('\n2. Getting user details...');
  const user = await apiRequest('GET', '/developer/users/demo_user_001');
  console.log(`   Balance: $${user.user.balance}`);

  // 3. Update profile
  console.log('\n3. Updating profile...');
  const updated = await apiRequest('PUT', '/developer/users/demo_user_001', {
    firstName: 'Updated',
    country: 'Canada',
  });
  console.log(`   Name: ${updated.user.firstName} ${updated.user.lastName}`);
  console.log(`   Country: updated to Canada`);

  // 4. Re-provision (idempotent)
  console.log('\n4. Re-provisioning (idempotent)...');
  const reprovision = await apiRequest('POST', '/developer/users', {
    externalUserId: 'demo_user_001',
  });
  console.log(`   Same user? ${reprovision.user.id === provision.user.id}`);
  console.log(`   Is new? ${reprovision.isNew}`); // false
}

userLifecycle().catch(console.error);

Python: Provision and Get User

from hmac_client import api_request  # See authentication.md for hmac_client

# Provision a user
result = api_request('POST', '/developer/users', {
    'externalUserId': 'py_user_001',
    'username': 'python_player',
    'email': 'python@example.com',
})
print(f"User: {result['user']['username']} (new: {result['isNew']})")

# Get user details
user = api_request('GET', '/developer/users/py_user_001')
print(f"Balance: ${user['user']['balance']}")

# Update profile
updated = api_request('PUT', '/developer/users/py_user_001', {
    'firstName': 'Updated',
    'country': 'Germany',
})
print(f"Updated: {updated['user']['firstName']}")

PHP: Provision and Get User

<?php
require_once 'hmac-client.php'; // See authentication.md

// Provision a user
$result = apiRequest('POST', '/developer/users', [
    'externalUserId' => 'php_user_001',
    'username'       => 'php_player',
    'email'          => 'php@example.com',
]);
echo "User: {$result['user']['username']} (new: " . ($result['isNew'] ? 'yes' : 'no') . ")\n";

// Get user details
$user = apiRequest('GET', '/developer/users/php_user_001');
echo "Balance: \${$user['user']['balance']}\n";

// Update profile
$updated = apiRequest('PUT', '/developer/users/php_user_001', [
    'firstName' => 'Updated',
    'country'   => 'France',
]);
echo "Country: updated\n";

Error Reference

Error CodeHTTP StatusCauseResolution
VALIDATION_ERROR400A field failed validation (for example an invalid email or username format). Uses the nested error formatFix the fields listed in error.details
MISSING_USERNAME400Username policy is require but no username was provided; or policy is generate_from_email but neither a username nor an email was providedInclude the required field in the request body
EMAIL_CONFLICT409Email already belongs to a poker account not linked to your partnerOmit the email, use a different email, or contact the admin
USERNAME_CONFLICT409The provided username is already takenChoose a different username
USER_NOT_FOUND404No user mapping exists for this externalUserId and partnerProvision the user first with POST /developer/users

Next Steps

Try the live demo Pricing

Have questions about Poker Engine? We reply fast.