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
externalUserIdwithout 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
| Field | Type | Required | Description |
|---|---|---|---|
externalUserId | string | Yes | Your platform’s user ID (1-255 chars) |
username | string | No | Poker username (3-30 chars, alphanumeric + underscore; stored in lowercase). Required if policy is require |
email | string | No | User’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 |
firstName | string | No | First name (max 100 chars) |
lastName | string | No | Last name (max 100 chars) |
country | string | No | Country (max 100 chars) |
avatar | string | No | Avatar URL (must be a valid URL) |
metadata | object | No | Arbitrary 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:
- Take the email prefix (before
@):john.smith - Lowercase:
john.smith - Replace non-alphanumeric characters with
_:john_smith - If the result is shorter than 3 characters, append
_user - 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
| Field | Type | Description |
|---|---|---|
firstName | string | First name (max 100 chars) |
lastName | string | Last name (max 100 chars) |
country | string | Country (max 100 chars) |
avatar | string | Avatar 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:
- Provision without email — Omit the
emailfield. The system will assign an internal placeholder email. This needs ausername, or agenerate_randompolicy. - 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 Code | HTTP Status | Cause | Resolution |
|---|---|---|---|
VALIDATION_ERROR | 400 | A field failed validation (for example an invalid email or username format). Uses the nested error format | Fix the fields listed in error.details |
MISSING_USERNAME | 400 | Username policy is require but no username was provided; or policy is generate_from_email but neither a username nor an email was provided | Include the required field in the request body |
EMAIL_CONFLICT | 409 | Email already belongs to a poker account not linked to your partner | Omit the email, use a different email, or contact the admin |
USERNAME_CONFLICT | 409 | The provided username is already taken | Choose a different username |
USER_NOT_FOUND | 404 | No user mapping exists for this externalUserId and partner | Provision the user first with POST /developer/users |
Next Steps
- SSO Integration — Authenticate provisioned users with single sign-on
- Balance Operations — Credit and debit user balances
- Webhooks — Get notified when users are created or updated
- API Reference — Complete endpoint specifications