SDK Reference
User Identity

User Identity

The Grounded Intelligence SDK manages user identity through anonymous and identified users, with automatic session migration.

Note: The package is installed as lumina-sdk, but we refer to it as the Grounded Intelligence SDK.

Automatic Anonymous Tracking

When the SDK is initialized, it automatically:

  1. Checks localStorage for lumina_id
  2. Checks cookie for lumina_id
  3. Generates new anonymous ID if not found: anon_<uuid>
  4. Persists to localStorage and cookie (1 year TTL)

No action needed — tracking starts immediately with an anonymous ID.

// After initialization
Lumina.init({ /* ... */ })
 
console.log(Lumina.getDistinctId()) // "anon_abc123..."
console.log(Lumina.isAnonymous())   // true
 
// Start capturing events immediately
const session = await Lumina.session.start()
// User is tracked as anon_abc123...

Identifying Users

When a user logs in or signs up, call identify() to merge their anonymous session into a known identity:

Basic Identification

await Lumina.identify('user_12345', {
  email: 'jane@example.com',
  name: 'Jane Doe',
  plan: 'pro'
})
 
console.log(Lumina.getDistinctId()) // "user_12345"
console.log(Lumina.isAnonymous())   // false

With Rich Properties

await Lumina.identify('user_12345', {
  // Contact info
  email: 'jane@example.com',
  name: 'Jane Doe',
  
  // Subscription
  plan: 'pro',
  mrr: 99,
  
  // Metadata
  signed_up_at: '2025-01-15T10:30:00Z',
  company: 'Acme Corp',
  role: 'Product Manager',
  
  // Segmentation
  cohort: 'q1_2025',
  experiments: ['new_ui', 'ai_suggestions'],
  
  // Location
  timezone: 'America/Los_Angeles',
  locale: 'en-US'
})

What Happens on Identify

  1. distinctId changes from anon_xyz to user_12345
  2. Identify event sent to server
  3. Server creates UserAlias to link anonymous → identified
  4. Past events migrated — All anonymous events now queryable under user_12345
  5. Future events tagged with new distinctId
  6. Persistence updated in cookie (primary) and localStorage (backup)
  7. Session cleared if opts.newSession is true or user changes
// Step 1: User arrives (anonymous)
Lumina.init({ /* ... */ })
console.log(Lumina.getDistinctId()) // "anon_xyz789"
console.log(Lumina.isAnonymous())   // true
 
// Capture some anonymous events
const session1 = await Lumina.session.start()
// ... user interacts anonymously
 
// Step 2: User signs up
await Lumina.identify('user_12345', {
  email: 'jane@example.com',
  name: 'Jane Doe',
  plan: 'free'
})
console.log(Lumina.getDistinctId()) // "user_12345"
console.log(Lumina.isAnonymous())   // false
 
// All past anonymous events now linked to user_12345
 
// Step 3: User upgrades plan (update properties)
await Lumina.identify('user_12345', {
  plan: 'pro', // Updates property
  upgraded_at: new Date().toISOString()
})
 
// Step 4: User logs out
Lumina.reset()
console.log(Lumina.getDistinctId()) // "anon_abc123" (new anonymous ID)
console.log(Lumina.isAnonymous())   // true

Updating User Properties

Call identify() again with the same user ID to update properties:

// Initial identification
await Lumina.identify('user_12345', {
  email: 'jane@example.com',
  plan: 'free'
})
 
// Later: User upgrades
await Lumina.identify('user_12345', {
  plan: 'pro',
  upgraded_at: '2025-02-01T14:20:00Z'
})
 
// Later: User changes email
await Lumina.identify('user_12345', {
  email: 'jane.doe@newcompany.com',
  company: 'New Company Inc'
})

Note: Properties are merged, not replaced. To clear a property, set it to null or undefined.

Resetting Identity

Call reset() when a user logs out to create a new anonymous identity:

// User logs out
Lumina.reset()

What Happens on Reset

  1. New anonymous ID generated: anon_<new_uuid>
  2. Previous identity cleared from cookie and localStorage
  3. Session trace ID cleared from sessionStorage
  4. Future events use new ID

Example Logout Flow

async function handleLogout() {
  // Clear application session
  await clearUserSession()
  
  // Reset Lumina identity
  Lumina.reset()
  
  console.log('Logged out')
  console.log('New distinct ID:', Lumina.getDistinctId()) // "anon_new123"
  console.log('Is anonymous:', Lumina.isAnonymous())     // true
  
  // Redirect to login
  router.push('/login')
}

API Reference

await Lumina.identify(userId: string, properties?: Record<string, any>, opts?: { newSession?: boolean })

Identify the current user. Switches from anonymous to known identity.

Parameters:

  • userId: Unique user identifier (e.g., database ID, email)
  • properties: Optional user properties (name, email, etc.)
  • opts.newSession: If true, clears current session and starts fresh

Example:

// After user login
await Lumina.identify('user_123', {
  email: 'user@example.com',
  name: 'John Doe',
})
 
// With new session option
await Lumina.identify('user_123', {
  email: 'user@example.com',
}, { newSession: true })

Behavior:

  • Persists userId to cookie and localStorage
  • Updates distinctId on all subsequent events
  • If user changes, clears session by default
  • If opts.newSession is true, clears current session trace ID

Lumina.reset()

Reset user identity (e.g., on logout). Generates new anonymous ID.

Returns: void

Example:

Lumina.reset()
 
console.log(Lumina.isAnonymous()) // true
console.log(Lumina.getDistinctId()) // "anon_abc123" (new)

Effects:

  • Generates new anonymous ID
  • Clears old ID from cookie and localStorage
  • Clears session trace ID from sessionStorage
  • Future events use new anonymous ID

Lumina.getDistinctId()

Get current distinct ID (user ID or anonymous ID).

Returns: string

Example:

const id = Lumina.getDistinctId()
// "user_12345" (identified) or "anon_abc123" (anonymous)

Lumina.isAnonymous()

Check if current user is anonymous.

Returns: boolean

Example:

if (Lumina.isAnonymous()) {
  // Show login prompt or guest checkout
  console.log('User is browsing anonymously')
} else {
  // Show personalized content
  console.log('User is logged in as:', Lumina.getDistinctId())
}

Best Practices

1. Identify Early

Call identify() as soon as the user logs in or signs up:

// ✅ Good: Identify immediately after login
async function handleLogin(userId: string, userInfo: any) {
  // Authenticate user
  await authenticateUser(userId)
  
  // Identify in Lumina
  await Lumina.identify(userId, {
    email: userInfo.email,
    name: userInfo.name,
    plan: userInfo.plan
  })
  
  // Redirect to dashboard
  router.push('/dashboard')
}
 
// ❌ Bad: Never identifying users
// Anonymous tracking is okay, but identified users provide richer analytics

2. Include Useful Properties

Add properties that help with segmentation and analysis:

// ✅ Good: Rich properties for segmentation
await Lumina.identify('user_12345', {
  email: 'jane@example.com',
  name: 'Jane Doe',
  plan: 'pro',
  company: 'Acme Corp',
  role: 'Product Manager',
  industry: 'SaaS',
  company_size: '50-200',
  signed_up_at: '2025-01-15T10:30:00Z',
  experiments: ['new_ui', 'ai_suggestions']
})
 
// ❌ Bad: Minimal properties
await Lumina.identify('user_12345', {
  email: 'jane@example.com'
})
// Missed opportunity for segmentation!

3. Protect PII

Be mindful of personally identifiable information:

// ✅ Good: Use references instead of raw PII
await Lumina.identify('user_12345', {
  email_hash: hashEmail(user.email), // Hash sensitive data
  name: user.name, // OK if needed
  plan: user.plan, // Not PII
  account_id: user.accountId // Reference
})
 
// ⚠️ Caution: Storing PII
await Lumina.identify('user_12345', {
  email: 'jane@example.com', // Consider if necessary
  phone: '+1-555-123-4567',  // Sensitive
  ssn: '123-45-6789'         // Never do this!
})

4. Reset on Logout

Always reset identity when user logs out:

// ✅ Good: Reset on logout
function handleLogout() {
  clearSession()
  Lumina.reset() // Generate new anonymous ID
  router.push('/login')
}
 
// ❌ Bad: Never resetting
// Old user identity persists for next visitor!

5. Check Identity State

Use isAnonymous() to conditionally show features:

// ✅ Good: Show appropriate UI based on identity
function ChatHeader() {
  const isAnon = Lumina.isAnonymous()
  
  return (
    <div>
      <h1>Chat</h1>
      {isAnon ? (
        <button onClick={showLoginPrompt}>
          Sign in to save history
        </button>
      ) : (
        <span>
          Logged in as {Lumina.getDistinctId()}
        </span>
      )}
    </div>
  )
}

Common Patterns

Login Flow

async function handleLogin(credentials: { email: string; password: string }) {
  try {
    // Authenticate user
    const user = await authenticateUser(credentials)
    
    // Identify in Lumina
    await Lumina.identify(user.id, {
      email: user.email,
      name: user.name,
      plan: user.plan,
      logged_in_at: new Date().toISOString()
    })
    
    console.log('User identified:', Lumina.getDistinctId())
    
    // Redirect
    router.push('/dashboard')
  } catch (error) {
    console.error('Login failed:', error)
  }
}

Signup Flow

async function handleSignup(data: { email: string; name: string; password: string }) {
  try {
    // Create user account
    const user = await createUser(data)
    
    // Identify immediately
    await Lumina.identify(user.id, {
      email: user.email,
      name: user.name,
      plan: 'free',
      signed_up_at: new Date().toISOString(),
      source: 'organic',
      cohort: 'q1_2025'
    })
    
    console.log('User signed up and identified:', Lumina.getDistinctId())
    
  // Start onboarding
    router.push('/onboarding')
  } catch (error) {
    console.error('Signup failed:', error)
  }
}

Logout Flow

async function handleLogout() {
  try {
    // Clear application session
    await clearUserSession()
    
    // Reset Lumina identity
    Lumina.reset()
    
    console.log('User logged out')
    console.log('New anonymous ID:', Lumina.getDistinctId())
    
    // Redirect to login
    router.push('/login')
  } catch (error) {
    console.error('Logout failed:', error)
  }
}

Check Auth on Mount

import { useEffect, useState } from 'react'
 
function App() {
  const [isAuthenticated, setIsAuthenticated] = useState(false)
  
  useEffect(() => {
    // Check if user is authenticated
    const checkAuth = async () => {
      const user = await getCurrentUser()
      
      if (user) {
        // User is logged in, identify them
        await Lumina.identify(user.id, {
          email: user.email,
          name: user.name,
          plan: user.plan
        })
        setIsAuthenticated(true)
      } else {
        // User is not logged in, ensure anonymous
        if (!Lumina.isAnonymous()) {
          Lumina.reset()
        }
        setIsAuthenticated(false)
      }
    }
    
    checkAuth()
  }, [])
  
  return <div>{/* App content */}</div>
}

Troubleshooting

Identity Not Persisting

Problem: Identity resets on page reload

Solution: Ensure cookies and localStorage are enabled:

// Check if storage is available
if (typeof window !== 'undefined' && window.localStorage) {
  console.log('localStorage available')
} else {
  console.warn('localStorage not available - identity may not persist')
}

Multiple Identities

Problem: Same user tracked under multiple IDs

Solution: Always call identify() with the same user ID:

// ✅ Good: Consistent user ID
await Lumina.identify('user_12345', { /* ... */ })
 
// ❌ Bad: Different IDs for same user
await Lumina.identify('user_12345', { /* ... */ })
await Lumina.identify('bob@example.com', { /* ... */ }) // Creates duplicate!

Old Identity After Logout

Problem: Previous user's identity persists

Solution: Always call reset() on logout:

// ✅ Good: Reset on logout
Lumina.reset()
 
// ❌ Bad: Forgot to reset
// Next user sees previous user's data!

Next Steps