Session Error Troubleshooting

Specific troubleshooting for SCORM session creation and management errors.

Table of Contents

Creation Errors

Error: "Package not found"

Cause: Package ID doesn't exist or belongs to different tenant.

Solutions:

  1. Verify Package ID:

    curl -X GET "https://app.allureconnect.com/api/v1/packages/pkg_abc123" \
      -H "X-API-Key: your-api-key"
    
  2. Check Tenant:

    • Ensure package belongs to your tenant
    • Verify API key tenant matches
    • Check package hasn't been deleted
  3. Verify Format:

    • Package ID must be UUID format
    • Check for typos
    • Verify ID is complete

Error: "Invalid user_id"

Cause: User ID format is invalid.

Solutions:

  1. Check Format:

    • User ID can be any string (up to 255 chars)
    • Must be non-empty
    • Avoid special characters that break URLs
  2. Recommended Format:

    // Use UUID or your system's user identifier
    const userId = user.id; // e.g., "user_123" or UUID
    
  3. URL Encoding:

    • Encode user ID if contains special chars
    • Use encodeURIComponent() for URLs
    • Avoid spaces and special characters

Error: "Session already exists"

Cause: Session with same ID already exists.

Solutions:

  1. Use Unique Session IDs:

    // Generate unique session ID
    const sessionId = crypto.randomUUID();
    
  2. Check Existing Sessions:

    curl -X GET "https://app.allureconnect.com/api/v1/sessions?user_id=user_123&package_id=pkg_abc123" \
      -H "X-API-Key: your-api-key"
    
  3. Resume Existing Session:

    • Check for incomplete sessions
    • Resume instead of creating new
    • Reuse existing session ID

Update Errors

Error: "Version conflict" (409)

Cause: Session was updated by another process.

Solutions:

  1. Implement Retry Logic:

    async function updateSessionWithRetry(
      sessionId: string,
      updates: any,
      maxRetries = 3
    ) {
      for (let attempt = 0; attempt < maxRetries; attempt++) {
        // Get current session
        const session = await fetch(
          `/api/v1/sessions/${sessionId}`,
          { headers: { 'X-API-Key': apiKey } }
        ).then(r => r.json());
    
        // Merge updates
        const payload = {
          version: session.version, // Use current version
          ...updates
        };
    
        // Attempt update
        const response = await fetch(
          `/api/v1/sessions/${sessionId}`,
          {
            method: 'PUT',
            headers: {
              'X-API-Key': apiKey,
              'Content-Type': 'application/json'
            },
            body: JSON.stringify(payload)
          }
        );
    
        if (response.ok) {
          return await response.json();
        }
    
        if (response.status === 409 && attempt < maxRetries - 1) {
          await delay(100 * (attempt + 1));
          continue;
        }
    
        throw new Error(`Update failed: ${response.status}`);
      }
    }
    
  2. Always Include Version:

    • Never update without version
    • Always fetch latest before update
    • Use optimistic locking correctly
  3. Handle Concurrent Updates:

    • Merge changes intelligently
    • Don't overwrite other updates
    • Preserve important data

Error: "Session not found"

Cause: Session doesn't exist or was deleted.

Solutions:

  1. Verify Session ID:

    • Check session ID is correct
    • Verify session exists
    • Check session hasn't expired
  2. Check Session Status:

    curl -X GET "https://app.allureconnect.com/api/v1/sessions/session_123" \
      -H "X-API-Key: your-api-key"
    
  3. Handle Expiration:

    • Sessions expire after inactivity
    • Create new session if expired
    • Check expiration time

Launch Errors

401 from learner browser

When a learner opens the Connect player (/player/:sessionId) in an LMS iframe, the browser calls GET /api/v1/sessions/:sessionId without a Connect API key. Access is granted only when the launch URL includes a signed session JWT (?token=…) or the learner arrived via a dispatch URL (/player/dispatch/:token).

This is not a Connect outage. An anonymous learner opening a tokenless player URL will always receive 401 with code: "AUTH_REQUIRED". That response is correct and by design.

Valid launch URL shapes

URL pattern Token required? Notes
/player/{sessionId}?token={jwt} Yes (in query) From launch_url in POST /api/v1/packages/{id}/launch
/player/dispatch/{jwt} Yes (in path) Server redirects to a fresh tokenized session URL
/player/{sessionId} (no query) Missing 401 AUTH_REQUIRED — integration bug upstream

Never construct /player/{sessionId} manually. Always embed the full launch_url returned by the launch API.

Structural validation (no auth)

Use the built-in validator before blaming Connect:

curl -s "https://app.allureconnect.com/api/v1/launch-links/validate?url=$(python3 -c 'import urllib.parse; print(urllib.parse.quote("PASTE_LMS_URL_HERE"))')"

Example for a tokenless URL:

{
  "status": "missing_token",
  "message": "Session launch URL has no `?token=` query parameter.",
  "remediation": "Use the full `launch_url` returned by POST /api/v1/packages/{packageId}/launch."
}

See GET /api/v1/launch-links/validate in the API reference for full response shapes.

Diagnostic curl matrix

Replace placeholders with the incident session ID and your server-side API key.

1. Confirm unauthenticated browser behavior (expected 401):

curl -s "https://app.allureconnect.com/api/v1/sessions/a5ca9bf9-c725-40f5-91fe-63bb93beade1"
# → {"code":"AUTH_REQUIRED", ...}

2. Validate the LMS-embedded URL structure:

curl -s "https://app.allureconnect.com/api/v1/launch-links/validate?url=$(python3 -c 'import urllib.parse; print(urllib.parse.quote("PASTE_LMS_URL_HERE"))')"

3. Confirm session exists (server-to-server, API key only):

curl -s -H "Authorization: Bearer $CONNECT_API_KEY" \
  "https://app.allureconnect.com/api/v1/sessions/a5ca9bf9-c725-40f5-91fe-63bb93beade1"
# 200 = session OK; learner 401 is URL/auth only
# 404 = wrong tenant, deleted session, or bad ID

4. Mint a fresh launch URL and retest in incognito:

curl -s -X POST "https://app.allureconnect.com/api/v1/packages/{packageId}/launch" \
  -H "Authorization: Bearer $CONNECT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"user_id":"test-learner"}'
# Open returned launch_url in incognito — should load

401 response code → owner

Inspect the failed request in DevTools → Network → Response body:

code Meaning Owner
AUTH_REQUIRED No launch token reached Connect LMS integrator — tokenless or stripped URL
EXPIRED_SESSION_TOKEN URL was correct but JWT expired or missed its refresh window LMS integrator — refresh before expiry or re-mint on Play click
INVALID_SESSION_TOKEN Truncated token or env secret mismatch Either — inspect URL length and SCORM_SESSION_TOKEN_SECRET across envs
SESSION_NOT_FOUND Token valid but session/tenant mismatch Either — wrong session ID or cross-tenant (returns 404, not 401)

Client/LMS integration

All LMS/client integrations use the same Connect contract: use full launch_url, prefer durable dispatch URLs for LMS-safe packaging, mint on Play (not on course card render), and never strip ?token=.

For a shareable incident handoff, see LMS client 401 handoff.

Error: "Session expired"

Cause: Session has expired.

Solutions:

  1. Check Expiration:

    const session = await getSession(sessionId);
    const expiresAt = new Date(session.expires_at);
    
    if (expiresAt < new Date()) {
      // Session expired, create new one
      const newSession = await launchSession(packageId, userId);
    }
    
  2. Refresh Session Token:

    • Call POST /api/v1/sessions/{sessionId}/refresh-token before expiry
    • Retry the failed commit once with the fresh bearer token
    • Re-mint a launch only when the original session cannot be refreshed
  3. Extend Expiration:

    • Some plans allow longer sessions
    • Contact support for custom expiration
    • Consider session renewal

Error: "Invalid launch token"

Cause: Launch token is invalid or expired.

Solutions:

  1. Regenerate Launch:

    const launch = await fetch(
      `/api/v1/packages/${packageId}/launch`,
      {
        method: 'POST',
        headers: {
          'X-API-Key': apiKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          user_id: userId,
          session_id: sessionId
        })
      }
    ).then(r => r.json());
    
  2. Check Token Expiration:

    • Tokens expire after expires_in_seconds
    • Default: 14,400 seconds
    • Refresh before expiry or re-mint if the refresh window has closed
  3. Verify Token Format:

    • Token must be valid JWT
    • Check token hasn't been modified
    • Verify token signature

Progress Tracking Errors

Error: "CMI data update failed"

Cause: Failed to update CMI data.

Solutions:

  1. Check CMI Data Format:

    const cmiData = {
      "cmi.core.lesson_status": "completed",
      "cmi.core.score.raw": "85",
      "cmi.core.score.max": "100"
    };
    
  2. Validate Data Types:

    • Strings for text fields
    • Numbers for score fields
    • Valid SCORM data model values
  3. Handle Version Conflicts:

    • Implement retry logic
    • Merge updates correctly
    • Use optimistic locking

Error: "Progress not updating"

Cause: Progress updates not being saved.

Solutions:

  1. Check Update Frequency:

    • Don't update too frequently
    • Batch updates when possible
    • Use appropriate intervals
  2. Verify Update Success:

    const response = await updateSession(sessionId, updates);
    if (!response.ok) {
      console.error('Update failed:', await response.text());
    }
    
  3. Check Session Status:

    • Verify session is active
    • Check session hasn't expired
    • Ensure session is accessible

Best Practices

1. Always Handle Version Conflicts

async function safeUpdateSession(sessionId: string, updates: any) {
  try {
    return await updateSessionWithRetry(sessionId, updates);
  } catch (error) {
    if (error.message.includes('409')) {
      // Version conflict - retry
      return await safeUpdateSession(sessionId, updates);
    }
    throw error;
  }
}

2. Check Session Before Operations

async function ensureSession(sessionId: string) {
  const session = await getSession(sessionId);
  
  if (!session) {
    throw new Error('Session not found');
  }
  
  if (new Date(session.expires_at) < new Date()) {
    throw new Error('Session expired');
  }
  
  return session;
}

3. Implement Proper Error Handling

async function launchCourse(packageId: string, userId: string) {
  try {
    const launch = await launchSession(packageId, userId);
    return launch;
  } catch (error) {
    if (error.status === 404) {
      // Package not found
      throw new Error('Course not available');
    } else if (error.status === 403) {
      // Access denied
      throw new Error('Access denied');
    } else {
      // Other error
      console.error('Launch failed:', error);
      throw new Error('Failed to launch course');
    }
  }
}

Related Documentation


Last Updated: 2026-05-24