API Reference

Complete reference for all Developer API endpoints. Every endpoint follows a consistent format for machine parseability.

Base URL: https://your-poker-platform.com/api/v1

Authentication: Unless marked as "Public", all endpoints require HMAC-signed authentication headers. See Authentication.

Registration

POST /developer/register

Register as a new developer partner.

Authentication: Public (no authentication required)

Rate Limit: 5 requests per hour per IP

Required Scopes: None

Prerequisite: Self-registration must be enabled in platform settings.

Request Headers:

HeaderTypeRequiredDescription
Content-TypestringYesapplication/json

Request Body:

FieldTypeRequiredValidationDescription
namestringYes3-100 charactersPartner organization name (must be unique)
contactEmailstringYesValid emailContact email address
websiteUrlstringNoValid URLPartner website
descriptionstringNoMax 500 charactersDescription of integration purpose

Example Request:

curl -X POST https://your-poker-platform.com/api/v1/developer/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Gaming",
    "contactEmail": "dev@acme.com",
    "websiteUrl": "https://acme.com",
    "description": "Online gaming platform integration"
  }'

Response (201 Created — Auto-Approved):

{
  "message": "Partner registration approved. Store your API credentials securely — the secret will not be shown again.",
  "partner": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Acme Gaming",
    "status": "approved"
  },
  "apiKey": {
    "rawKey": "pk_live_YOUR_KEY_HERE",
    "rawSecret": "sk_live_YOUR_SECRET_HERE"
  }
}

The auto-approved key gets the platform’s default scopes and rate limit. Default scopes are empty until an admin configures them, so the key may need scopes added before it can call anything.

Response (201 Created — Pending Approval):

{
  "message": "Partner registration submitted. An administrator will review your application.",
  "partner": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Acme Gaming",
    "status": "pending_approval"
  }
}

Error Responses:

StatusError CodeCondition
400VALIDATION_ERRORA body field failed validation
403SELF_REGISTRATION_DISABLEDSelf-registration is turned off
409NAME_CONFLICTPartner name already taken
429RATE_LIMIT_EXCEEDEDRate limit exceeded

GET /developer/status

Check the registration status of a partner by email.

Authentication: Public (no authentication required)

Rate Limit: 3 requests per minute per IP

Required Scopes: None

Query Parameters:

ParameterTypeRequiredDescription
emailstringYesContact email to look up

Example Request:

curl "https://your-poker-platform.com/api/v1/developer/status?email=dev@acme.com"

Response (200 OK):

{
  "status": "approved"
}

Possible status values: approved, pending, not_found. A suspended partner also returns not_found.

Error Responses:

StatusError CodeCondition
400MISSING_EMAILemail query parameter is missing
429RATE_LIMIT_EXCEEDEDRate limit exceeded

Users

POST /developer/users

Provision a new user or retrieve an existing one by external user ID.

Authentication: HMAC-signed

Required Scopes: user:create

Request Headers:

HeaderTypeRequiredDescription
Content-TypestringYesapplication/json
X-API-KeystringYesYour API key
X-SignaturestringYesHMAC-SHA256 signature
X-TimestampstringYesUnix timestamp in milliseconds
X-Request-IdstringYesUnique request ID for deduplication and signature nonce

Request Body:

FieldTypeRequiredValidationDescription
externalUserIdstringYes1-255 charactersYour platform’s user identifier
usernamestringNo3-30 chars, alphanumeric + underscorePoker username (required if username policy is require)
emailstringNoValid emailUser’s email address
firstNamestringNoMax 100 charactersFirst name
lastNamestringNoMax 100 charactersLast name
countrystringNoMax 100 charactersCountry
avatarstringNoValid URLAvatar image URL
metadataobjectNoJSON objectArbitrary metadata stored with the mapping

Example Request:

curl -X POST https://your-poker-platform.com/api/v1/developer/users \
  -H "Content-Type: application/json" \
  -H "X-API-Key: pk_live_YOUR_KEY_HERE" \
  -H "X-Signature: COMPUTED_HMAC_SIGNATURE" \
  -H "X-Timestamp: 1708272000000" \
  -H "X-Request-Id: UNIQUE_UUID" \
  -d '{
    "externalUserId": "user_42",
    "username": "john_doe",
    "email": "john@example.com",
    "firstName": "John",
    "lastName": "Doe"
  }'

Response (201 Created — New User):

{
  "user": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "externalUserId": "user_42",
    "username": "john_doe",
    "email": "john@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "balance": "0",
    "virtualBalance": "0",
    "createdAt": "2026-02-18T12:00:00.000Z"
  },
  "isNew": true
}

Response (200 OK — Existing User):

Same shape as above, with isNew: false. This endpoint is idempotent — calling it again with the same externalUserId returns the existing user without modification.

Error Responses:

StatusError CodeCondition
400VALIDATION_ERRORA body field failed validation
400MISSING_USERNAMEPolicy needs a value that wasn’t sent: username under require, username or email under generate_from_email
409EMAIL_CONFLICTEmail already in use by unlinked account
409USERNAME_CONFLICTRequested username already taken

GET /developer/users/:externalUserId

Retrieve a provisioned user by their external user ID.

Authentication: HMAC-signed

Required Scopes: user:read

Path Parameters:

ParameterTypeDescription
externalUserIdstringURL-encoded external user ID

Example Request:

curl https://your-poker-platform.com/api/v1/developer/users/user_42 \
  -H "X-API-Key: pk_live_YOUR_KEY_HERE" \
  -H "X-Signature: COMPUTED_HMAC_SIGNATURE" \
  -H "X-Timestamp: 1708272000000" \
  -H "X-Request-Id: UNIQUE_UUID"

Response (200 OK):

{
  "user": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "externalUserId": "user_42",
    "username": "john_doe",
    "email": "john@example.com",
    "firstName": "John",
    "lastName": "Doe",
    "balance": "100",
    "virtualBalance": "0",
    "createdAt": "2026-02-18T12:00:00.000Z"
  }
}

Error Responses:

StatusError CodeCondition
404USER_NOT_FOUNDNo mapping exists for this partner + external user ID

PUT /developer/users/:externalUserId

Update a provisioned user’s profile fields. Cannot change username or email; other fields in the body are ignored. The response does not include country or avatar.

Authentication: HMAC-signed

Required Scopes: user:create

Path Parameters:

ParameterTypeDescription
externalUserIdstringURL-encoded external user ID

Request Body:

FieldTypeRequiredValidationDescription
firstNamestringNoMax 100 charactersFirst name
lastNamestringNoMax 100 charactersLast name
countrystringNoMax 100 charactersCountry
avatarstringNoValid URLAvatar image URL

Example Request:

curl -X PUT https://your-poker-platform.com/api/v1/developer/users/user_42 \
  -H "Content-Type: application/json" \
  -H "X-API-Key: pk_live_YOUR_KEY_HERE" \
  -H "X-Signature: COMPUTED_HMAC_SIGNATURE" \
  -H "X-Timestamp: 1708272000000" \
  -H "X-Request-Id: UNIQUE_UUID" \
  -d '{
    "firstName": "Jonathan",
    "country": "Canada"
  }'

Response (200 OK):

Same shape as GET response with updated fields.

Error Responses:

StatusError CodeCondition
404USER_NOT_FOUNDNo mapping exists for this partner + external user ID

Authentication

POST /developer/auth/code

Generate a single-use login code for a user. The code can be exchanged by the user’s browser for a full session.

Authentication: HMAC-signed

Required Scopes: auth:code

Request Body:

FieldTypeRequiredValidationDescription
externalUserIdstringYes1-255 charactersExternal user ID
modestringNocookie or token (default: cookie)Authentication mode
usernamestringNo3-30 chars, alphanumeric + underscoreUsername (for auto-provisioning)
emailstringNoValid emailEmail (for auto-provisioning)
firstNamestringNoMax 100 charactersFirst name (for auto-provisioning)
lastNamestringNoMax 100 charactersLast name (for auto-provisioning)

If the user does not yet have an account, they will be auto-provisioned using the provided fields.

Example Request:

curl -X POST https://your-poker-platform.com/api/v1/developer/auth/code \
  -H "Content-Type: application/json" \
  -H "X-API-Key: pk_live_YOUR_KEY_HERE" \
  -H "X-Signature: COMPUTED_HMAC_SIGNATURE" \
  -H "X-Timestamp: 1708272000000" \
  -H "X-Request-Id: UNIQUE_UUID" \
  -d '{
    "externalUserId": "user_42",
    "mode": "cookie",
    "email": "john@example.com"
  }'

Response (201 Created):

{
  "code": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
  "expiresIn": 60,
  "mode": "cookie"
}

The code is a 64-character hex string. It expires in 60 seconds and can only be used once.

Error Responses:

StatusError CodeCondition
400VALIDATION_ERRORA body field failed validation
400MODE_NOT_ALLOWEDRequested mode is disabled in settings
400MISSING_USERNAMEAuto-provisioning a new user needs a value that wasn’t sent (see POST /developer/users)
409EMAIL_CONFLICTAuto-provisioning: email already in use by unlinked account
409USERNAME_CONFLICTAuto-provisioning: requested username already taken

POST /auth/exchange

Exchange a login code for a full authenticated session. This endpoint is called by the user’s browser, not the partner’s backend.

Authentication: Public (the code is the credential)

Feature Toggle: Requires developerApi to be enabled

Rate Limit: 10 requests per minute per IP

Required Scopes: None

Request Headers:

HeaderTypeRequiredDescription
Content-TypestringYesapplication/json

Request Body:

FieldTypeRequiredValidationDescription
codestringYesExactly 64 lowercase hex charactersThe login code from POST /developer/auth/code

Example Request:

curl -X POST https://your-poker-platform.com/api/v1/auth/exchange \
  -H "Content-Type: application/json" \
  -d '{
    "code": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
  }'

The response depends on the mode the code was generated with. In both, user is the user’s profile record without credential fields, plus their group; the examples are abridged.

Response (200 OK — Cookie Mode):

Sets access_token and refresh_token as HttpOnly, SameSite=Strict cookies.

{
  "success": true,
  "data": {
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "username": "john_doe",
      "email": "john@example.com",
      "firstName": "John",
      "lastName": "Doe",
      "balance": "100",
      "virtualBalance": "0"
    },
    "expiresIn": 900
  }
}

Response (200 OK — Token Mode):

No cookies are set. Tokens are returned in the response body.

{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expiresIn": 900,
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "username": "john_doe",
      "email": "john@example.com",
      "firstName": "John",
      "lastName": "Doe",
      "balance": "100",
      "virtualBalance": "0"
    }
  }
}

Error Responses:

StatusError CodeCondition
400VALIDATION_ERRORCode is not 64 lowercase hex characters
401INVALID_CODECode does not exist
401CODE_EXPIREDCode has expired (60-second lifetime)
401CODE_ALREADY_USEDCode has already been exchanged
403ACCOUNT_BLOCKEDUser’s account is blocked
403ACCOUNT_FROZENUser’s account is frozen
403AUTHORIZATION_ERRORdeveloperApi feature toggle is disabled
403FEATURE_TIER_RESTRICTEDLicense tier does not include the Developer API
429RATE_LIMIT_EXCEEDEDRate limit exceeded

All errors from this endpoint use the nested format: { "success": false, "error": { "code": "...", "message": "..." } }.

Balance

GET /developer/users/:externalUserId/balance

Read a user’s current balance.

Authentication: HMAC-signed

Required Scopes: balance:read

Path Parameters:

ParameterTypeDescription
externalUserIdstringURL-encoded external user ID

Example Request:

curl https://your-poker-platform.com/api/v1/developer/users/user_42/balance \
  -H "X-API-Key: pk_live_YOUR_KEY_HERE" \
  -H "X-Signature: COMPUTED_HMAC_SIGNATURE" \
  -H "X-Timestamp: 1708272000000" \
  -H "X-Request-Id: UNIQUE_UUID"

Response (200 OK):

{
  "balance": "150",
  "virtualBalance": "0",
  "tournamentTickets": "0"
}

All values are strings. Trailing zeros are not kept, so 150.00 is returned as "150". tournamentTickets is a count.

Error Responses:

StatusError CodeCondition
404USER_NOT_FOUNDNo mapping exists for this partner + external user ID

POST /developer/users/:externalUserId/balance/credit

Add funds to a user’s balance.

Authentication: HMAC-signed

Required Scopes: balance:write

Path Parameters:

ParameterTypeDescription
externalUserIdstringURL-encoded external user ID

Request Body:

FieldTypeRequiredValidationDescription
amountstringYesPositive decimal, max 2 decimal places (^\d+(\.\d{1,2})?$)Amount to credit
currencystringNoMax 10 charactersAccepted but not used or stored
referencestringNoMax 255 charactersIdempotency key — same reference returns original transaction
descriptionstringNoMax 500 charactersTransaction description (default: Developer API credit)

Example Request:

curl -X POST https://your-poker-platform.com/api/v1/developer/users/user_42/balance/credit \
  -H "Content-Type: application/json" \
  -H "X-API-Key: pk_live_YOUR_KEY_HERE" \
  -H "X-Signature: COMPUTED_HMAC_SIGNATURE" \
  -H "X-Timestamp: 1708272000000" \
  -H "X-Request-Id: UNIQUE_UUID" \
  -d '{
    "amount": "50.00",
    "reference": "purchase_12345",
    "description": "Chip purchase from Acme Gaming"
  }'

Response (201 Created — New Transaction):

{
  "transaction": {
    "id": "txn_abc123",
    "type": "DEPOSIT",
    "amount": "50",
    "balanceBefore": "100",
    "balanceAfter": "150",
    "reference": "purchase_12345",
    "description": "Chip purchase from Acme Gaming",
    "createdAt": "2026-02-18T12:00:00.000Z"
  },
  "isNew": true
}

Response (200 OK — Idempotent Match):

Same shape as above with isNew: false. Returned when a transaction with the same reference and partner ID already exists for the same user, amount and operation.

Error Responses:

StatusError CodeCondition
400VALIDATION_ERRORamount is missing or malformed, or another field is too long
400INVALID_AMOUNTAmount is zero
403ACCOUNT_BLOCKEDUser’s account is blocked
403ACCOUNT_FROZENUser’s account is frozen
404USER_NOT_FOUNDNo mapping exists for this partner + external user ID
409IDEMPOTENCY_CONFLICTreference was already used for a different user, amount, or operation

POST /developer/users/:externalUserId/balance/debit

Remove funds from a user’s balance.

Authentication: HMAC-signed

Required Scopes: balance:write

Path Parameters:

ParameterTypeDescription
externalUserIdstringURL-encoded external user ID

Request Body:

FieldTypeRequiredValidationDescription
amountstringYesPositive decimal, max 2 decimal places (^\d+(\.\d{1,2})?$)Amount to debit
currencystringNoMax 10 charactersAccepted but not used or stored
referencestringNoMax 255 charactersIdempotency key — same reference returns original transaction
descriptionstringNoMax 500 charactersTransaction description (default: Developer API debit)

Example Request:

curl -X POST https://your-poker-platform.com/api/v1/developer/users/user_42/balance/debit \
  -H "Content-Type: application/json" \
  -H "X-API-Key: pk_live_YOUR_KEY_HERE" \
  -H "X-Signature: COMPUTED_HMAC_SIGNATURE" \
  -H "X-Timestamp: 1708272000000" \
  -H "X-Request-Id: UNIQUE_UUID" \
  -d '{
    "amount": "25.00",
    "reference": "cashout_789",
    "description": "Cashout to Acme Gaming"
  }'

Response (201 Created — New Transaction):

{
  "transaction": {
    "id": "txn_def456",
    "type": "WITHDRAWAL",
    "amount": "25",
    "balanceBefore": "150",
    "balanceAfter": "125",
    "reference": "cashout_789",
    "description": "Cashout to Acme Gaming",
    "createdAt": "2026-02-18T12:00:00.000Z"
  },
  "isNew": true
}

Response (200 OK — Idempotent Match):

Same shape as above with isNew: false.

Error Responses:

StatusError CodeCondition
400VALIDATION_ERRORamount is missing or malformed, or another field is too long
400INVALID_AMOUNTAmount is zero
400INSUFFICIENT_BALANCEUser balance is less than the debit amount
403ACCOUNT_BLOCKEDUser’s account is blocked
403ACCOUNT_FROZENUser’s account is frozen
404USER_NOT_FOUNDNo mapping exists for this partner + external user ID
409IDEMPOTENCY_CONFLICTreference was already used for a different user, amount, or operation

Webhooks

POST /developer/webhooks

Subscribe to a webhook event type. One subscription per event type per partner (upserts if subscription already exists).

Authentication: HMAC-signed

Required Scopes: webhooks:manage

Request Body:

FieldTypeRequiredValidationDescription
eventTypestringYesMust be a supported event typeEvent type to subscribe to
urlstringYesValid URL. In production: HTTPS, no embedded credentials, no local hostnames or private IPsEndpoint to receive webhook deliveries

Supported event types: user.created, user.updated, deposit.confirmed, deposit.failed, withdrawal.completed, withdrawal.failed, balance.credited, balance.debited

Example Request:

curl -X POST https://your-poker-platform.com/api/v1/developer/webhooks \
  -H "Content-Type: application/json" \
  -H "X-API-Key: pk_live_YOUR_KEY_HERE" \
  -H "X-Signature: COMPUTED_HMAC_SIGNATURE" \
  -H "X-Timestamp: 1708272000000" \
  -H "X-Request-Id: UNIQUE_UUID" \
  -d '{
    "eventType": "balance.credited",
    "url": "https://your-platform.com/webhooks/poker"
  }'

Response (201 Created):

{
  "subscription": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "partnerId": "6fa459ea-ee8a-3ca4-894e-db77e160355e",
    "eventType": "balance.credited",
    "url": "https://your-platform.com/webhooks/poker",
    "isActive": true,
    "createdAt": "2026-02-18T12:00:00.000Z"
  }
}

Error Responses:

StatusError CodeCondition
400VALIDATION_ERROReventType is missing, or url is not a valid URL
400INVALID_EVENT_TYPEEvent type not in supported list
400INVALID_URLURL breaks one of the rules above

GET /developer/webhooks

List all webhook subscriptions for this partner.

Authentication: HMAC-signed

Required Scopes: webhooks:manage

Example Request:

curl https://your-poker-platform.com/api/v1/developer/webhooks \
  -H "X-API-Key: pk_live_YOUR_KEY_HERE" \
  -H "X-Signature: COMPUTED_HMAC_SIGNATURE" \
  -H "X-Timestamp: 1708272000000" \
  -H "X-Request-Id: UNIQUE_UUID"

Response (200 OK):

{
  "subscriptions": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "eventType": "balance.credited",
      "url": "https://your-platform.com/webhooks/poker",
      "isActive": true,
      "createdAt": "2026-02-18T12:00:00.000Z",
      "stats": {
        "totalDeliveries": 142,
        "failedDeliveries": 3,
        "lastDelivery": "2026-02-18T11:55:00.000Z"
      }
    }
  ]
}

DELETE /developer/webhooks/:subscriptionId

Delete a webhook subscription.

Authentication: HMAC-signed

Required Scopes: webhooks:manage

Path Parameters:

ParameterTypeDescription
subscriptionIdstringUUID of the subscription to delete

Example Request:

curl -X DELETE https://your-poker-platform.com/api/v1/developer/webhooks/550e8400-e29b-41d4-a716-446655440000 \
  -H "X-API-Key: pk_live_YOUR_KEY_HERE" \
  -H "X-Signature: COMPUTED_HMAC_SIGNATURE" \
  -H "X-Timestamp: 1708272000000" \
  -H "X-Request-Id: UNIQUE_UUID"

Response: 204 No Content

Error Responses:

StatusError CodeCondition
403FORBIDDENSubscription belongs to a different partner
404SUBSCRIPTION_NOT_FOUNDSubscription not found

Documentation

These endpoints are public and stay available when the developerApi feature toggle is off.

EndpointReturns
GET /developer/docsDocumentation home as HTML. Add ?format=json for { title, content, pages }
GET /developer/docs/:pageA page as HTML or, with ?format=json, JSON. Pages: quickstart, authentication, sso, users, balance, webhooks, errors, security, api-reference
GET /developer/docs/openapi.yamlOpenAPI specification as YAML
GET /developer/docs/openapi.jsonOpenAPI specification as JSON

Common Request Headers

All HMAC-authenticated requests require these headers:

HeaderTypeRequiredDescription
Content-TypestringWith a bodyapplication/json
X-API-KeystringYesYour API key (starts with pk_live_)
X-SignaturestringYesHMAC-SHA256 of the canonical signing string using your API secret
X-TimestampstringYesCurrent Unix timestamp in milliseconds
X-Request-IdstringYesUnique UUID for request deduplication and signature nonce

Signature computation:

canonical_path_query = request path as sent, with any query parameters sorted by key, then value, and URL-encoded
signing_string = [
  "v1",
  HTTP_METHOD_UPPERCASE,
  canonical_path_query,
  X-Timestamp,
  X-Request-Id,
  raw_request_body
].join("\n")
signature = HMAC-SHA256(signing_string, api_secret)
X-Signature = hex(signature)

For GET and DELETE requests with no body, use an empty string for the final raw body field, so the signing string ends with a newline. X-Request-Id is still required.

Common Response Format

Successful responses return the requested data directly:

{
  "user": { ... },
  "isNew": true
}

Most error responses follow this format:

{
  "error": "ERROR_CODE",
  "message": "Human-readable description"
}

POST /auth/exchange wraps its responses, success or error. Body validation and feature-toggle errors on any endpoint use the same nested error format. See Errors.

{
  "success": true,
  "data": { ... }
}
{
  "success": false,
  "error": { "code": "ERROR_CODE", "message": "Human-readable description" }
}

Rate Limits

EndpointLimitWindowScope
All authenticated endpointsConfigurable per key (default: 100)1 minutePer API key
POST /developer/register51 hourPer IP
GET /developer/status31 minutePer IP
POST /auth/exchange101 minutePer IP

Responses to the per-key limit include a Retry-After header with seconds to wait. Every request made with a valid key counts toward it, including rejected ones.

  • Quickstart — Get started in 5 minutes
  • Authentication — HMAC signing details and code examples
  • SSO — Single sign-on integration guide
  • Users — User provisioning guide
  • Balance — Balance operations guide
  • Webhooks — Webhook integration guide
  • Errors — Complete error code reference
  • Security — Security best practices
Try the live demo Pricing

Have questions about Poker Engine? We reply fast.