Guides

User-level Attribution

Overview

User-level attribution allows you to associate AI costs and usage with specific users of your application. By adding user identifiers to your GenAI calls, you can:

  • Track costs by user: Understand which users or customer segments drive your AI costs
  • Monitor usage patterns: Analyze how different users interact with your AI features
  • Apply user-specific limits: Implement budget constraints for individual users
  • Troubleshoot issues: Quickly identify problems related to specific users
  • Meet compliance requirements: Support audit trails and user-level accountability

Setting User IDs in Pay-i

Pay-i provides two primary methods for associating user IDs with your GenAI requests:

1. Using the @track Decorator and track_context() Context Manager

Pay-i provides two complementary approaches for adding user attribution to your GenAI calls, each designed for specific scenarios:

When to Use track_context() (For Runtime/Dynamic Data)

The track_context() context manager should be used when your user ID is only available at runtime:

from payi.lib.instrument import track_context

# RECOMMENDED: For user IDs from function parameters or request context
def get_ai_response(prompt, user_id):
    # Use track_context to capture runtime values
    with track_context(user_id=user_id):
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[{"role": "user", "content": prompt}]
        )
    return response.choices[0].message.content

When to Use @track Decorator (For Static Metadata)

The @track decorator should only be used with truly static values, not with functions that should run per-request:

from payi.lib.instrument import track

# IMPORTANT: Do NOT use functions like get_current_user_id() with @track
# as they'll only run once at module load time, not per request

# CORRECT: Only use @track with truly static values
@track(user_id="system_bot")  # Static literal value
def get_system_response(prompt):
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content

2. Using HTTP Headers

For direct API calls or non-Python implementations, you can include the user ID in the request header:

xProxy-User-ID: user_123

This works with all Pay-i's proxy endpoints for supported GenAI providers.

User ID Format Requirements

User IDs in Pay-i:

  • Must be strings (automatically converted if not)
  • Can contain alphanumeric characters, periods (.), hyphens (-), and underscores (_)
  • Cannot contain spaces
  • Have no fixed length limit, but keep them reasonably short for efficiency

Implementation Strategies

Web Applications

For web applications, you typically have user authentication systems that provide user identifiers:

# Flask example using session data
from flask import Flask, session, request
from payi.lib.instrument import track_context

app = Flask(__name__)

@app.route('/ai-chat')
def ai_chat():
    # Get user ID from session and pass it to the function
    user_id = session.get('user_id')
    return get_ai_response(request.args.get('prompt'), user_id)

def get_ai_response(prompt, user_id):
    # CORRECT: Use track_context for runtime values like session data
    with track_context(user_id=user_id):
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[{"role": "user", "content": prompt}]
        )
    return response.choices[0].message.content

B2B Applications

For B2B applications, you might want to track by organization and user:

# Composite ID approach
def get_enterprise_ai_response(prompt, org_id, user_id):
    # Create composite ID inside the function
    composite_id = f"{org_id}:{user_id}"
    
    with track_context(user_id=composite_id):
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[{"role": "user", "content": prompt}]
        )
    return response.choices[0].message.content

Anonymous Users

For applications with unauthenticated users, consider device or session-based identification:

# Using browser fingerprint or session ID
def get_anonymous_response(prompt, session_id):
    with track_context(user_id=session_id):
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[{"role": "user", "content": prompt}]
        )
    return response.choices[0].message.content

Best Practices

  1. Use track_context() for user IDs: Always use track_context() for user attribution since user IDs are typically request-specific values that change at runtime
  2. Capture user IDs at runtime: Get the ID when processing the request, not when defining functions
  3. Never use runtime functions with @track: Functions like get_current_user_id() should never be used with the @track decorator as they'll only execute once at module load time
  4. Reserve @track for static values: The @track decorator is designed for static metadata like use case names, not for dynamic values like user IDs
  5. Consistency: Use the same user ID format across your entire application
  6. Privacy considerations: Avoid using personally identifiable information (PII)
  7. Granularity: Choose an identification level that matches your analytics needs
  8. Fallbacks: Have a strategy for cases where user ID might not be available

Viewing User-level Data in Pay-i

Once you've implemented user-level attribution:

  1. Log in to developer.pay-i.com
  2. Navigate to your application dashboard
  3. Click on the Users tab
  4. Use the user_id column to filter, sort, and analyze requests by user
  5. Generate reports by user to understand cost distribution

Related Resources

  • payi_instrument() - Set a default user_id globally and initialize the SDK.
  • Track Context - Use the track_context() function for dynamic runtime values like user IDs.
  • Track Decorator - Use the @track decorator for static metadata like use case names.
  • Custom Headers - Use the xProxy-User-ID header for per-request attribution with proxied requests or the REST ingest API.
  • Manual Event Submission - Pass the user_id when submitting events via the SDK or REST API.