Tula SDK Documentation

Version 0.1.0

Overview

What is Tula SDK?

The Tula SDK is a powerful JavaScript library that automatically optimizes your in-game store prices and quantities through intelligent A/B testing. It uses advanced bandit algorithms to continuously learn which price points and quantities drive the highest revenue and conversion rates for your game.

Key Features

🎯 Automatic Optimization

Real-time price optimization using Thompson Sampling bandit algorithms

📊 Automatic Event Tracking

Framework-agnostic auto-tracking with DOM data attributes - works with any JavaScript environment

🏪 Universal Compatibility

Works with React, Unity, Cocos2d, vanilla JS, and any framework that renders DOM elements

🔒 Secure & Reliable

Server-side verification and secure API key authentication

Getting Started

Installation

Install the Tula SDK using npm or yarn:

npm install tula-js

Or using yarn:

yarn add tula-js

Initialization

Initialize the SDK with your API key and game configuration:

import TulaSDK from 'tula-js';

const tula = new TulaSDK({
  apiKey: 'your_api_key_here',
  debug: true,                      // Optional: enables debug logging
  batchSize: 10,                    // Optional: event batch size (default: 10)
  flushInterval: 5000,              // Optional: auto-flush interval in ms (default: 5000)
  platform: 'ios'                  // Optional: 'ios' | 'android' | 'both' (auto-detected)
});

// Initialize with player identification
await tula.initialize('player_123', {
  level: 42,
  vip: false,
  country: 'US'
});

// Or let SDK auto-generate player ID
await tula.initialize(); // Auto-generates and persists player ID

Automatic Variant Caching & Event Tracking

Zero-Delay Pricing: During initialization, the SDK automatically fetches and caches all item variants for instantgetItemVariant() responses. No delays when showing pricing to users!

Auto-Tracking: The SDK automatically starts tracking events once initialized. Simply adddata-tula-store anddata-tula-item attributes to your DOM elements for automatic view tracking.

If no player ID is provided, the SDK will automatically generate one and persist it locally. This is useful for games that don't have user authentication.

The SDK maintains both an external player ID (your identifier) and an internal UUID for backend operations.

Quick Auto-Tracking Setup

Add these attributes to your HTML for automatic event tracking:

<!-- Store page wrapper -->
<div data-tula-store data-tula-properties='{"section": "main"}'>
  <h1>Game Store</h1>
  <!-- Your store UI here -->
</div>

<!-- Individual item elements -->
<div data-tula-item data-tula-variant='{"itemId": "gold_coins", "variant": {...}}'>
  <img src="coins.png" alt="Gold Coins" />
  <p>$0.99 - 100 Coins</p>
  <button>Buy Now</button>
</div>

How Auto-Tracking Works

Store views are tracked immediately when elements with data-tula-store are added to DOM

Item views are tracked when elements with data-tula-item become 50% visible

• Works with any JavaScript framework: React, Unity, Cocos2d, vanilla JS, etc.

Authentication

Important: Secure Your API Key

Never expose your API key in client-side code for web games. For mobile games, use environment variables or secure storage mechanisms.

Your API key authenticates your game with the Tula backend. You can find it in your dashboard under API Key Management.

Core Concepts

Variant Selection

The SDK automatically selects the optimal price and quantity combination for each item based on real-time performance data. This process happens transparently when you request an item variant.

// Get the optimized variant for a virtual item
const variant = await tula.getItemVariant('coins_pack');

// The variant contains all necessary information
console.log(variant.itemId);                 // Developer-friendly ID: "coins_pack"
console.log(variant.variant.productId);      // App Store/Play Store product ID
console.log(variant.variant.price.formatted); // "$0.99"
console.log(variant.variant.quantity);       // 100
console.log(variant.experimentId);           // Current experiment ID if active
console.log(variant.experimentName);         // Human-readable experiment name
console.log(variant.isExperiment);           // true if part of A/B test
console.log(variant.isControl);              // true if this is the control group

// Experiment view is automatically tracked when getting a variant
// if it's part of an active experiment

A/B Testing

Tula automatically runs A/B tests on your virtual items, testing different price points and quantities to find the optimal combination. The SDK handles all the complexity:

  • Automatic user assignment to test groups
  • Consistent experience for each user
  • Real-time performance tracking
  • Statistical significance calculation
  • Automatic winner selection

Bandit Algorithms

Tula uses Thompson Sampling, a sophisticated bandit algorithm that balances exploration (trying new variants) with exploitation (using proven winners). This approach:

  • Reduces opportunity cost compared to traditional A/B testing
  • Adapts in real-time to user behavior
  • Automatically allocates more traffic to winning variants
  • Continues to explore new opportunities while exploiting winners

API Reference

Initialization Methods

new TulaSDK(config)

Creates a new instance of the SDK.

Parameters
ParameterTypeDescription
apiKeystringYour Tula API key for authentication
debug?booleanEnable debug logging for development (default: false)
batchSize?numberNumber of events to batch before sending (default: 10)
flushInterval?numberAuto-flush interval in milliseconds (default: 5000)
platform?'ios' | 'android' | 'both'Target platform (auto-detected if not specified)
Example
const tula = new TulaSDK({
  apiKey: 'your_api_key',
  debug: true,
  batchSize: 5,
  flushInterval: 3000
});

initialize(playerId, metadata?)

Initializes the SDK for a specific player.

Parameters
ParameterTypeDescription
playerIdstringUnique player identifier
metadata?objectOptional player metadata
Returns

Promise<void>

Resolves when initialization is complete

Example
await tula.initialize('player_123', {
  level: 42,
  vip: false,
  country: 'US'
});

Getting Variants

getItemVariant(itemId, platform?)

Returns the optimal variant for a virtual item instantly from cache. During initialization, all variants are pre-fetched and cached for zero-delay responses.

Parameters
ParameterTypeDescription
itemIdstringDeveloper-friendly item ID
platform?'ios' | 'android' | 'both'Target platform (auto-detected by default)
Returns

Promise<ItemVariantResponse>

The optimal variant with pricing and experiment context (returns instantly from cache)

{
  virtualItemId: string,       // Internal UUID - not exposed to developer
  itemId: string,              // Developer-friendly ID
  experimentId: string | null,
  experimentName?: string,
  experimentArmId: string | null,
  experimentArmName?: string,
  variant: {
    skuVariantId: string,
    productId: string,         // App/Play Store ID
    price: {
      cents: number,
      dollars: number,
      currency: string,
      formatted: string        // e.g., "$0.99"
    },
    quantity: number,
    platform: 'ios' | 'android' | 'both',
    productType: 'consumable' | 'non_consumable' | 'auto_renewable_subscription' | 'non_renewing_subscription'
  },
  platformSpecific: IOSPlatformData | AndroidPlatformData | null,
  isExperiment: boolean,
  isControl: boolean,
  trafficWeight?: number,
  metadata?: {
    selectionReason: string,
    algorithmUsed?: string,
    userId?: string,
    platform?: string,
    timestamp: string
  }
}
Example
// Returns instantly from cache (no network delay!)
const variant = await tula.getItemVariant('coins_pack');
console.log(variant.itemId);                 // "coins_pack"
console.log(variant.variant.price.formatted); // "$0.99"
console.log(variant.variant.quantity);        // 100
console.log(variant.isExperiment);            // true if A/B testing

// Works for any platform
const iosVariant = await tula.getItemVariant('coins_pack', 'ios');
const androidVariant = await tula.getItemVariant('coins_pack', 'android');

Variant Caching

refreshVariantCache()

Manually refreshes the variant cache by fetching all variants from the server. Useful if you want to update cached data during the session.

Returns

Promise<void>

Resolves when cache is refreshed with latest variants

Example
// Refresh cache to get latest experiment assignments
await tula.refreshVariantCache();

// Now getItemVariant() will use updated data
const variant = await tula.getItemVariant('coins_pack');

getCachedVariantCount()

Returns the number of variants currently cached. Useful for debugging or displaying cache status.

Returns

number

Number of variants in the cache

Example
const count = tula.getCachedVariantCount();
console.log(`${count} variants cached`); // "5 variants cached"

Event Tracking

logEvent(eventType, properties?)

Logs a custom event with experiment context.

Parameters
ParameterTypeDescription
eventType'store_view' | 'item_click' | 'purchase_start' | 'purchase_complete' | 'purchase_fail' | 'experiment_view'Type of event to log
properties?objectAdditional event properties
Returns

Promise<LogEventResponse>

Confirmation of logged event with metadata

{
  success: boolean,
  eventId: string,
  playerId: string,
  timestamp: string,
  message: string,
  metadata?: {
    eventType: string,
    experimentId?: string,
    experimentArmId?: string,
    processingTime: number
  }
}
Example
await tula.logEvent('store_view', {
  source: 'main_menu',
  items_shown: 5
});

trackStoreView(properties?)

Convenience method for tracking store views.

Parameters
ParameterTypeDescription
properties?objectAdditional event properties
Returns

void

Synchronous method that queues the event

Example
tula.trackStoreView({
  source: 'level_complete'
});

trackItemClick(variant, properties?)

Convenience method for tracking item clicks with experiment context.

Parameters
ParameterTypeDescription
variantItemVariantResponseThe variant being clicked
properties?objectAdditional click properties
Returns

Promise<LogEventResponse>

Confirmation of logged event

Example
await tula.trackItemClick(variant, {
  ui_element: 'store_button',
  screen: 'main_store'
});

Purchase Tracking

trackPurchaseStart(variant, properties?)

Tracks when a user initiates a purchase.

Parameters
ParameterTypeDescription
variantItemVariantResponseThe variant being purchased
properties?objectAdditional purchase properties
Example
await tula.trackPurchaseStart(variant, {
  source: 'store_page',
  user_action: 'button_click'
});

trackPurchaseComplete(variant, transactionId, properties?)

Tracks successful purchases with transaction details.

Parameters
ParameterTypeDescription
variantItemVariantResponseThe variant that was purchased
transactionIdstringPlatform transaction ID
properties?objectAdditional purchase properties
Example
await tula.trackPurchaseComplete(variant, transaction.id, {
  receipt: transaction.receipt,
  verified: true
});

trackPurchaseFail(variant, error, properties?)

Tracks failed or cancelled purchases.

Parameters
ParameterTypeDescription
variantItemVariantResponseThe variant that failed to purchase
errorstringError message or reason
properties?objectAdditional error properties
Example
await tula.trackPurchaseFail(variant, error.message, {
  error_code: 'USER_CANCELLED',
  step: 'payment_authorization'
});

Auto-Tracking Methods

trackElementAsStore(element, properties?)

Manually mark a DOM element for automatic store view tracking.

Parameters
ParameterTypeDescription
elementElementDOM element to track
properties?objectAdditional event properties
Returns

void

Element will be tracked when added to DOM

Example
const storeElement = document.createElement('div');
tula.trackElementAsStore(storeElement, { 
  section: 'weapons' 
});

trackElementAsItem(element, itemVariant, properties?)

Manually mark a DOM element for automatic item view tracking.

Parameters
ParameterTypeDescription
elementElementDOM element to track
itemVariantItemVariantResponseItem variant data
properties?objectAdditional event properties
Returns

void

Element will be tracked when 50% visible

Example
const itemElement = document.createElement('div');
tula.trackElementAsItem(itemElement, variant, { 
  position: 1 
});

enableAutoTracking()

Enables automatic tracking of elements with data-tula-* attributes.

Returns

void

Auto-tracking starts immediately if SDK is initialized

Example
tula.enableAutoTracking(); // Resume auto-tracking

disableAutoTracking()

Disables automatic tracking of DOM elements.

Returns

void

Auto-tracking stops immediately

Example
tula.disableAutoTracking(); // Switch to manual tracking

Utility Methods

trackCustomEvent(eventType, properties?)

Tracks a custom event with a user-defined event type.

Parameters
ParameterTypeDescription
eventTypestringCustom event type name
properties?objectAdditional event properties
Returns

void

Synchronous method that queues the event

Example
tula.trackCustomEvent('level_complete', {
  level: 5,
  time_taken: 120,
  score: 1500
});

getPlayerId()

Returns the external player ID (developer-friendly ID).

Returns

string | null

The external player ID or null if not initialized

Example
const playerId = tula.getPlayerId(); // "player_123"

getInternalPlayerId()

Returns the internal player UUID used by Tula backend.

Returns

string | null

The internal player UUID or null if not initialized

Example
const internalId = tula.getInternalPlayerId(); // "abc-123-def-456"

isInitialized()

Checks if the SDK has been initialized.

Returns

boolean

True if initialized, false otherwise

Example
if (tula.isInitialized()) {
  // SDK is ready to use
}

flush()

Manually flushes all queued events to the server.

Returns

Promise<void>

Resolves when all events are sent

Example
await tula.flush(); // Force send all queued events

destroy()

Cleans up the SDK, ends the session, flushes remaining events, and clears the variant cache.

Returns

Promise<void>

Resolves when cleanup is complete

Example
await tula.destroy(); // Clean shutdown - clears cache and ends session

Integration Guide

Auto-Tracking Integration (Recommended)

The easiest way to integrate Tula SDK is using automatic event tracking with data attributes:

import TulaSDK from 'tula-js';

class GameStore {
  constructor() {
    this.tula = new TulaSDK({
      apiKey: process.env.TULA_API_KEY
    });
  }

  async initialize(playerId) {
    await this.tula.initialize(playerId, {
      level: this.getPlayerLevel(),
      vip: this.isVIPPlayer()
    });
    // Auto-tracking starts automatically after initialization
  }

  async loadStoreItems() {
    // Fetch optimized variants for all items
    const variants = [];
    for (const item of this.items) {
      const variant = await this.tula.getItemVariant(item.id);
      variants.push({ ...item, variant });
    }

    // Render store with data attributes for auto-tracking
    this.renderStoreWithAutoTracking(variants);
    return variants;
  }

  renderStoreWithAutoTracking(variants) {
    // Create store container with auto-tracking
    const storeContainer = document.createElement('div');
    storeContainer.setAttribute('data-tula-store', '');
    storeContainer.setAttribute('data-tula-properties', JSON.stringify({
      source: 'main_menu',
      items_count: variants.length
    }));

    variants.forEach((item, index) => {
      const itemElement = document.createElement('div');
      
      // Auto-track item views when element becomes visible
      itemElement.setAttribute('data-tula-item', '');
      itemElement.setAttribute('data-tula-variant', JSON.stringify(item.variant));
      itemElement.setAttribute('data-tula-properties', JSON.stringify({
        position: index + 1,
        category: item.category
      }));

      // Add your item UI
      itemElement.innerHTML = `
        <img src="${item.icon}" alt="${item.name}" />
        <h3>${item.name}</h3>
        <p>${item.variant.variant.price.formatted} - ${item.variant.variant.quantity} ${item.unit}</p>
        <button onclick="purchaseItem('${item.id}')">Buy Now</button>
      `;

      storeContainer.appendChild(itemElement);
    });

    // Store view and item views are automatically tracked!
    document.getElementById('store-root').appendChild(storeContainer);
  }

  async purchaseItem(itemId) {
    const variant = this.getVariantById(itemId);
    
    try {
      // Track purchase start
      await this.tula.trackPurchaseStart(variant);

      // Process purchase...
      const transaction = await this.processPlatformPurchase(variant.variant.productId);

      // Track completion
      await this.tula.trackPurchaseComplete(variant, transaction.id);
      
      return { success: true, transaction };
    } catch (error) {
      // Track failure
      await this.tula.trackPurchaseFail(variant, error.message);
      return { success: false, error };
    }
  }
}

Alternative: Manual Tracking Control

For games that need manual control over tracking, you can disable auto-tracking and use the utility methods:

tula.disableAutoTracking() andtula.trackElementAsStore(element)

Manual Integration (Advanced)

For advanced use cases or canvas-only games, you can manually track events:

import TulaSDK from 'tula-js';

class GameStore {
  constructor() {
    this.tula = new TulaSDK({
      apiKey: process.env.TULA_API_KEY
    });
    
    // Disable auto-tracking for manual control
    this.tula.disableAutoTracking();
  }

  async initialize(playerId) {
    await this.tula.initialize(playerId, {
      level: this.getPlayerLevel(),
      vip: this.isVIPPlayer(),
      totalSpent: this.getPlayerLifetimeValue()
    });
  }

  async loadStoreItems() {
    // Manually track store view
    await this.tula.logEvent('store_view', {
      source: 'main_menu',
      items_count: this.items.length
    });

    // Get optimized variants for each item
    const variants = [];
    for (const item of this.items) {
      const variant = await this.tula.getItemVariant(item.id);
      variants.push({ ...item, variant });

      // Manually track item view
      await this.tula.logEvent('experiment_view', {
        itemId: variant.itemId,
        skuVariantId: variant.variant.skuVariantId,
        experimentId: variant.experimentId,
        experimentArmId: variant.experimentArmId,
        position: variants.length
      });
    }

    // Track custom events for game-specific metrics
    this.tula.trackCustomEvent('store_loaded', {
      load_time: Date.now() - startTime,
      items_count: variants.length,
      player_level: this.getPlayerLevel()
    });

    return variants;
  }

  async purchaseItem(variant) {
    try {
      // Track purchase start
      await this.tula.trackPurchaseStart(variant);

      // Platform-specific purchase flow
      const transaction = await this.processPlatformPurchase(
        variant.variant.productId
      );

      // Track successful purchase
      await this.tula.trackPurchaseComplete(
        variant, 
        transaction.id,
        {
          receipt: transaction.receipt,
          verified: await this.verifyReceipt(transaction)
        }
      );

      // Grant items to player
      this.grantItems(variant.variant.quantity);

      return { success: true, transaction };

    } catch (error) {
      // Track failed purchase
      await this.tula.trackPurchaseFail(
        variant,
        error.message,
        {
          error_code: error.code,
          step: this.getPurchaseStep(error)
        }
      );

      return { success: false, error };
    }
  }

  async processPlatformPurchase(productId) {
    // Platform-specific implementation
    if (this.platform === 'ios') {
      return await this.processStoreKitPurchase(productId);
    } else if (this.platform === 'android') {
      return await this.processPlayBillingPurchase(productId);
    }
  }
}

Best Practices

1. Initialize Early

Initialize the SDK as early as possible in your game's lifecycle, ideally right after player authentication. This ensures all events are properly tracked.

2. Use Auto-Tracking When Possible

Use data-tula-store and data-tula-item attributes for automatic tracking. This reduces integration complexity and ensures consistent event tracking across your game.

3. Framework-Agnostic Integration

The SDK works with React, Unity WebGL, Cocos2d, vanilla JS, and any framework that renders DOM elements. Choose the integration method that fits your tech stack.

4. Include Player Context

Pass relevant player metadata during initialization (level, VIP status, spending history). This helps Tula segment players and optimize prices for different player types.

5. Handle Errors Gracefully

Always wrap SDK calls in try-catch blocks and have fallback pricing ready in case of network issues. Never let SDK errors break your purchase flow.

6. Fetch Variants Early, Track Views Late

Fetch item variants during app startup to prevent UI flicker, but only track experiment views when items are actually displayed to users. Auto-tracking handles this timing automatically.

7. Test in Sandbox

Always test your integration in sandbox environments before going live. Verify that purchases are tracked correctly and receipts are validated.

8. Monitor Performance

Regularly check your dashboard to monitor experiment performance. Look for significant changes in conversion rates and revenue to ensure everything is working correctly.

9. Server-Side Verification

Always verify purchases server-side using receipt validation. Never trust client-side purchase data alone for granting items or currency.

Troubleshooting

API Key Invalid

Error: "Invalid API key" or 401 Unauthorized

Solution: Verify your API key in the dashboard. Make sure you're using the correct key for your environment (development vs. production).

Virtual Item Not Found

Error: "Virtual item 'item_name' not found"

Solution: Ensure the virtual item exists in your dashboard and is active. Check that you're using the correct item ID or name.

No SKU Variants Available

Error: "No SKU variants found for this item"

Solution: Create SKU variants for your virtual items in the dashboard. Make sure they're properly configured for your target platform.

SDK Not Initialized

Error: "SDK must be initialized before tracking events"

Solution: Call tula.initialize() before making any other SDK calls. Wait for the promise to resolve before proceeding.

Network Timeout

Error: Network request timeout or connection errors

Solution: Implement retry logic and fallback pricing. Cache variant data locally to handle temporary network issues.

Platform Detection Issues

Error: Incorrect platform detection or "both" platform returned

Solution: Explicitly specify the platform in SDK config: { platform: "ios" } or { platform: "android" }. Check console logs for platform detection details.

Event Batching Problems

Error: Events not being sent or delayed sending

Solution: Adjust batchSize and flushInterval settings, or call tula.flush() manually to force immediate sending.

Debug Mode

Enable debug mode to see detailed logs: new TulaSDK({ debug: true })

This will log all SDK operations, API calls, and responses to the console.

Need More Help?

If you're still experiencing issues, please contact our support team: