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:
Verify Package ID:
curl -X GET "https://app.allureconnect.com/api/v1/packages/pkg_abc123" \ -H "X-API-Key: your-api-key"Check Tenant:
- Ensure package belongs to your tenant
- Verify API key tenant matches
- Check package hasn't been deleted
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:
Check Format:
- User ID can be any string (up to 255 chars)
- Must be non-empty
- Avoid special characters that break URLs
Recommended Format:
// Use UUID or your system's user identifier const userId = user.id; // e.g., "user_123" or UUIDURL 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:
Use Unique Session IDs:
// Generate unique session ID const sessionId = crypto.randomUUID();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"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:
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}`); } }Always Include Version:
- Never update without version
- Always fetch latest before update
- Use optimistic locking correctly
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:
Verify Session ID:
- Check session ID is correct
- Verify session exists
- Check session hasn't expired
Check Session Status:
curl -X GET "https://app.allureconnect.com/api/v1/sessions/session_123" \ -H "X-API-Key: your-api-key"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:
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); }Refresh Session Token:
- Call
POST /api/v1/sessions/{sessionId}/refresh-tokenbefore expiry - Retry the failed commit once with the fresh bearer token
- Re-mint a launch only when the original session cannot be refreshed
- Call
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:
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());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
- Tokens expire after
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:
Check CMI Data Format:
const cmiData = { "cmi.core.lesson_status": "completed", "cmi.core.score.raw": "85", "cmi.core.score.max": "100" };Validate Data Types:
- Strings for text fields
- Numbers for score fields
- Valid SCORM data model values
Handle Version Conflicts:
- Implement retry logic
- Merge updates correctly
- Use optimistic locking
Error: "Progress not updating"
Cause: Progress updates not being saved.
Solutions:
Check Update Frequency:
- Don't update too frequently
- Batch updates when possible
- Use appropriate intervals
Verify Update Success:
const response = await updateSession(sessionId, updates); if (!response.ok) { console.error('Update failed:', await response.text()); }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
- Common Issues - General troubleshooting
- API Errors - API-specific issues
- CMI Data Guide - Understanding CMI data
Last Updated: 2026-05-24